Merge branch 'master' into cs-cam

pull/359/head
Miloslav Číž 7 years ago committed by GitHub
commit 199e41833f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -153,6 +153,7 @@ Programmers
Sylvain Thesnieres (Garvek)
t6
terrorfisch
thegriglat
Thomas Luppi (Digmaster)
Will Herrmann (Thunderforge)
Tom Mason (wheybags)

@ -1,3 +1,140 @@
0.43.0
------
Bug #815: Different settings cause inconsistent underwater visibility
Bug #1452: autosave is not executed when waiting
Bug #1555: Closing containers with spacebar doesn't work after touching an item
Bug #1692: Can't close container when item is "held"
Bug #2405: Maximum distance for guards attacking hostile creatures is incorrect
Bug #2445: Spellcasting can be interrupted
Bug #2489: Keeping map open not persisted between saves
Bug #2594: 1st person view uses wrong body texture with Better bodies
Bug #2628: enablestatreviewmenu command doen't read race, class and sign values from current game
Bug #2639: Attacking flag isn't reset upon reloading
Bug #2698: Snow and rain VFX move with the player
Bug #2704: Some creature swim animations not being used
Bug #2789: Potential risk of misunderstanding using the colored "owned" crosshair feature
Bug #3045: Settings containing '#' cannot be loaded
Bug #3097: Drop() doesn't work when an item is held (with the mouse)
Bug #3110: GetDetected doesn't work without a reference
Bug #3126: Framerate nosedives when adjusting dialogue window size
Bug #3243: Ampersand in configuration files isn't escaped automatically
Bug #3365: Wrong water reflection along banks
Bug #3441: Golden saint always dispelling soul trap / spell priority issue
Bug #3528: Disposing of corpses breaks quests
Bug #3531: No FPS limit when playing bink videos even though "framerate limit" is set in settings.cfg
Bug #3647: Multi-effect spells play audio louder than in Vanilla
Bug #3656: NPCs forget where their place in the world is
Bug #3665: Music transitions are too abrupt
Bug #3679: Spell cast effect should disappear after using rest command
Bug #3684: Merchants do not restock empty soul gems if they acquire filled ones.
Bug #3694: Wrong magicka bonus applied on character creation
Bug #3706: Guards don't try to arrest the player if attacked
Bug #3709: Editor: Camera is not positioned correctly on mode switches related to orbital mode
Bug #3720: Death counter not cleaned of non-existing IDs when loading a game
Bug #3744: "Greater/lesser or equal" operators are not parsed when their signs are swapped
Bug #3749: Yagrum Bagarn moves to different position on encountering
Bug #3766: DisableLevitation does not remove visuals of preexisting effect
Bug #3787: Script commands in result box for voiced dialogue are ignored
Bug #3793: OpenMW tries to animate animated references even when they are disabled
Bug #3794: Default sound buffer size is too small for mods
Bug #3796: Mod 'Undress for me' doesn't work: NPCs re-equip everything
Bug #3798: tgm command behaviour differs from vanilla
Bug #3804: [Mod] Animated Morrowind: some animations do not loop correctly
Bug #3805: Slight enchant miscalculation
Bug #3826: Rendering problems with an image in a letter
Bug #3833: [Mod] Windows Glow: windows textures are much darker than in original game
Bug #3835: Bodyparts with multiple NiTriShapes are not handled correctly
Bug #3839: InventoryStore::purgeEffect() removes only first effect with argument ID
Bug #3843: Wrong jumping fatigue loss calculations
Bug #3850: Boethiah's voice is distorted underwater
Bug #3851: NPCs and player say things while underwater
Bug #3864: Crash when exiting to Khartag point from Ilunibi
Bug #3878: Swapping soul gems while enchanting allows constant effect enchantments using any soul gem
Bug #3879: Dialogue option: Go to jail, persists beyond quickload
Bug #3891: Journal displays empty entries
Bug #3892: Empty space before dialogue entry display
Bug #3898: (mod) PositionCell in dialogue results closes dialogue window
Bug #3906: "Could not find Data Files location" dialog can appear multiple times
Bug #3908: [Wizard] User gets stuck if they cancel out of installing from a CD
Bug #3909: Morrowind Content Language dropdown is the only element on the right half of the Settings window
Bug #3910: Launcher window can be resized so that it cuts off the scroll
Bug #3915: NC text key on nifs doesn't work
Bug #3919: Closing inventory while cursor hovers over spell (or other magic menu item) produces left click sound
Bug #3922: Combat AI should avoid enemy hits when casts Self-ranged spells
Bug #3934: [macOS] Copy/Paste from system clipboard uses Control key instead of Command key
Bug #3935: Incorrect attack strength for AI actors
Bug #3937: Combat AI: enchanted weapons have too high rating
Bug #3942: UI sounds are distorted underwater
Bug #3943: CPU/GPU usage should stop when the game is minimised
Bug #3944: Attempting to sell stolen items back to their owner does not remove them from your inventory
Bug #3955: Player's avatar rendering issues
Bug #3956: EditEffectDialog: Cancel button does not update a Range button and an Area slider properly
Bug #3957: Weird bodypart rendering if a node has reserved name
Bug #3960: Clothes with high cost (> 32768) are not handled properly
Bug #3963: When on edge of being burdened the condition doesn't lower as you run.
Bug #3971: Editor: Incorrect colour field in cell table
Bug #3974: Journal page turning doesn't produce sounds
Bug #3978: Instant opening and closing happens when using a Controller with Menus/Containers
Bug #3981: Lagging when spells are cast, especially noticeable on new landmasses such as Tamriel Rebuilt
Bug #3982: Down sounds instead of Up ones are played when trading
Bug #3987: NPCs attack after some taunting with no "Goodbye"
Bug #3991: Journal can still be opened at main menu
Bug #3995: Dispel cancels every temporary magic effect
Bug #4002: Build broken on OpenBSD with clang
Bug #4003: Reduce Render Area of Inventory Doll to Fit Within Border
Bug #4004: Manis Virmaulese attacks without saying anything
Bug #4010: AiWander: "return to the spawn position" feature does not work properly
Bug #4016: Closing menus with spacebar will still send certain assigned actions through afterwards
Bug #4017: GetPCRunning and GetPCSneaking should check that the PC is actually moving
Bug #4024: Poor music track distribution
Bug #4025: Custom spell with copy-pasted name always sorts to top of spell list
Bug #4027: Editor: OpenMW-CS misreports its own name as "OpenCS", under Mac OS
Bug #4033: Archers don't attack if the arrows have run out and there is no other weapon
Bug #4037: Editor: New greetings do not work in-game.
Bug #4049: Reloading a saved game while falling prevents damage
Bug #4056: Draw animation should not be played when player equips a new weapon
Bug #4074: Editor: Merging of LAND/LTEX records
Bug #4076: Disposition bar is not updated when "goodbye" selected in dialogue
Bug #4079: Alchemy skill increases do not take effect until next batch
Bug #4093: GetResistFire, getResistFrost and getResistShock doesn't work as in vanilla
Bug #4094: Level-up messages for levels past 20 are hardcoded not to be used
Bug #4095: Error in framelistener when take all items from a dead corpse
Bug #4096: Messagebox with the "%0.f" format should use 0 digit precision
Bug #4104: Cycling through weapons does not skip broken ones
Bug #4105: birthsign generation menu does not show full details
Bug #4107: Editor: Left pane in Preferences window is too narrow
Bug #4112: Inventory sort order is inconsistent
Bug #4113: 'Resolution not supported in fullscreen' message is inconvenient
Bug #4131: Pickpocketing behaviour is different from vanilla
Bug #4155: NPCs don't equip a second ring in some cases
Bug #4156: Snow doesn't create water ripples
Bug #4165: NPCs autoequip new clothing with the same price
Feature #452: Rain-induced water ripples
Feature #824: Fading for doors and teleport commands
Feature #933: Editor: LTEX record table
Feature #936: Editor: LAND record table
Feature #1374: AI: Resurface to breathe
Feature #2320: ess-Importer: convert projectiles
Feature #2509: Editor: highlighting occurrences of a word in a script
Feature #2748: Editor: Should use one resource manager per document
Feature #2834: Have openMW's UI remember what menu items were 'pinned' across boots.
Feature #2923: Option to show the damage of the arrows through tooltip.
Feature #3099: Disabling inventory while dragging an item forces you to drop it
Feature #3274: Editor: Script Editor - Shortcuts and context menu options for commenting code out and uncommenting code respectively
Feature #3275: Editor: User Settings- Add an option to reset settings to their default status (per category / all)
Feature #3400: Add keyboard shortcuts for menus
Feature #3492: Show success rate while enchanting
Feature #3530: Editor: Reload data files
Feature #3682: Editor: Default key binding reset
Feature #3921: Combat AI: aggro priorities
Feature #3941: Allow starting at an unnamed exterior cell with --start
Feature #3952: Add Visual Studio 2017 support
Feature #3953: Combat AI: use "WhenUsed" enchantments
Feature #4082: Leave the stack of ingredients or potions grabbed after using an ingredient/potion
Task #2258: Windows installer: launch OpenMW tickbox
Task #4152: The Windows CI script is moving files around that CMake should be dealing with
0.42.0
------

@ -320,9 +320,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
"ffmpeg-3.2.4-dev-win${BITS}.zip"
# MyGUI
download "MyGUI 3.2.3-git" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" \
"MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z"
download "MyGUI 3.2.2" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \
"MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z"
# OpenAL
download "OpenAL-Soft 1.17.2" \
@ -350,9 +350,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# SDL2
download "SDL 2.0.4" \
"https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip" \
"SDL2-2.0.4.zip"
download "SDL 2.0.7" \
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
"SDL2-2.0.7.zip"
fi
cd .. #/..
@ -474,20 +474,20 @@ cd $DEPS
echo
# MyGUI
printf "MyGUI 3.2.3-git... "
printf "MyGUI 3.2.2... "
{
cd $DEPS_INSTALL
if [ -d MyGUI ] && \
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
grep "MYGUI_VERSION_PATCH 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf MyGUI
eval 7z x -y "${DEPS}/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}" MyGUI
eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI
fi
export MYGUI_HOME="$(real_pwd)/MyGUI"
@ -632,18 +632,18 @@ cd $DEPS
echo
# SDL2
printf "SDL 2.0.4... "
printf "SDL 2.0.7... "
{
if [ -d SDL2-2.0.4 ]; then
if [ -d SDL2-2.0.7 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.4
eval 7z x -y SDL2-2.0.4.zip $STRIP
rm -rf SDL2-2.0.7
eval 7z x -y SDL2-2.0.7.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.4"
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll"
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
echo Done.
}

@ -54,7 +54,7 @@ endif()
message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 42)
set(OPENMW_VERSION_MINOR 43)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "")
@ -435,15 +435,18 @@ if(WIN32)
FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES
"${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt"
"${OpenMW_BINARY_DIR}/settings-default.cfg"
"${OpenMW_BINARY_DIR}/gamecontrollerdb.txt"
DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
if(BUILD_MYGUI_PLUGIN)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug)
@ -455,7 +458,9 @@ if(WIN32)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
ENDIF()
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".")
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)

@ -33,6 +33,7 @@ Furthermore, we advise to:
* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title.
* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards).
* Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway.
* Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged.
Guidelines for original engine "fixes"
=================================

@ -7,7 +7,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind
OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.
* Version: 0.42.0
* Version: 0.43.0
* License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information)
* Website: http://www.openmw.org
* IRC: #openmw on irc.freenode.net

@ -252,7 +252,8 @@ endif()
if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
endif()
if (MSVC)

@ -192,7 +192,8 @@ void CSMPrefs::State::declare()
setTooltip ("Acceleration factor during drag operations while holding down shift").
setRange (0.001, 100.0);
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
declareCategory ("Rendering");
declareDouble ("camera-fov", "Camera FOV", 90.).setPrecision(5).setRange(10.0, 160.0);
declareBool ("camera-ortho", "Orthographic projection for camera", false);

@ -3,6 +3,7 @@
#include <stdexcept>
#include <iostream>
#include <osg/Depth>
#include <osg/Group>
#include <osg/PositionAttitudeTransform>
@ -21,6 +22,7 @@
#include "../../model/world/universalid.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "../../model/prefs/state.hpp"
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightutil.hpp>
@ -220,7 +222,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<2; ++i)
{
float length = i ? shaftLength : 0;
float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis));
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis));
@ -285,15 +287,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<8; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f, 1.0f));
axis==2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i=8; i<8+4+1; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f,
axis==2 ? 1.0f : 0.0f, 1.0f));
axis==2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
geode->addDrawable (geometry);
@ -305,11 +307,11 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
{
const float Pi = 3.14159265f;
const float InnerRadius = mBaseNode->getBound().radius();
const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f;
const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24;
@ -334,6 +336,9 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t index = i * VerticesPerSegment;
@ -344,13 +349,17 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
}
colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f);
colors->at(0) = osg::Vec4f (
axis==0 ? 1.0f : 0.2f,
axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f,
mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i)
{
@ -374,7 +383,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable (geometry);
@ -382,6 +391,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
return geode;
}
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis)
{
switch (axis)
@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int
CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode,
const std::string& id, bool referenceable, bool forceBaseToZero)
: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero),
mScaleOverride (1), mOverrideFlags (0), mSubMode (-1)
mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f)
{
mRootNode = new osg::PositionAttitudeTransform;
@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected)
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["3D Scene Input"]["object-marker-alpha"].toDouble();
updateMarker();
}
@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale)
adjustTransform();
}
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
{
const CSMWorld::RefCollection& collection = mData.getReferences();

@ -4,6 +4,7 @@
#include <string>
#include <osg/ref_ptr>
#include <osg/Geometry>
#include <osg/Referenced>
#include <components/esm/defs.hpp>
@ -96,6 +97,7 @@ namespace CSVRender
int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
/// Not implemented
Object (const Object&);
@ -121,6 +123,9 @@ namespace CSVRender
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker (int axis);
osg::ref_ptr<osg::Node> makeRotateMarker (int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition (float x, float y, float z, int axis);
public:
@ -179,6 +184,8 @@ namespace CSVRender
/// Set override scale
void setScale (float scale);
void setMarkerTransparency(float value);
/// Apply override changes via command and end edit mode
void apply (CSMWorld::CommandMacro& commands);

@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
, mToolTipPos (-1, -1)
, mShowToolTips(false)
, mToolTipDelay(0)
, mInConstructor(true)
{
setAcceptDrops(true);
@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag()));
mInConstructor = false;
}
CSVRender::WorldspaceWidget::~WorldspaceWidget ()
@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti
mDragWheelFactor = setting->toDouble();
else if (*setting=="3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble();
else if (*setting=="3D Scene Input/object-marker-alpha" && !mInConstructor)
{
float alpha = setting->toDouble();
// getSelection is virtual, thus this can not be called from the constructor
auto selection = getSelection(Mask_Reference);
for (osg::ref_ptr<TagBase> tag : selection)
{
if (auto objTag = dynamic_cast<ObjectTag*>(tag.get()))
objTag->mObject->setMarkerTransparency(alpha);
}
}
else if (*setting=="Tooltips/scene-delay")
mToolTipDelay = setting->toInt();
else if (*setting=="Tooltips/scene")

@ -65,6 +65,7 @@ namespace CSVRender
QPoint mToolTipPos;
bool mShowToolTips;
int mToolTipDelay;
bool mInConstructor;
public:

@ -191,6 +191,9 @@ namespace MWBase
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) = 0;
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0;
///Returns the list of actors which are siding with the given actor in fights
/**ie AiFollow or AiEscort is active and the target is the actor **/
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;

@ -146,7 +146,6 @@ namespace MWDialogue
// TODO play sound
}
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
callback->addResponse("", Interpreter::fixDefinesDialog(info->mResponse, interpreterContext));
executeScript (info->mResultScript, mActor);
@ -387,7 +386,7 @@ namespace MWDialogue
{
Filter filter (mActor, mChoice, mTalkedTo);
if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting)
if (dialogue->mType == ESM::Dialogue::Topic || dialogue->mType == ESM::Dialogue::Greeting)
{
if (const ESM::DialInfo *info = filter.search (*dialogue, true))
{
@ -401,15 +400,18 @@ namespace MWDialogue
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
callback->addResponse("", Interpreter::fixDefinesDialog(text, interpreterContext));
// Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group,
// in which case it should not be added to the journal.
for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue->mInfo.begin();
iter!=dialogue->mInfo.end(); ++iter)
if (dialogue->mType == ESM::Dialogue::Topic)
{
if (iter->mId == info->mId)
// Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group,
// in which case it should not be added to the journal
for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue->mInfo.begin();
iter!=dialogue->mInfo.end(); ++iter)
{
MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor);
break;
if (iter->mId == info->mId)
{
MWBase::Environment::get().getJournal()->addTopic (Misc::StringUtils::lowerCase(mLastTopic), info->mId, mActor);
break;
}
}
}

@ -8,7 +8,9 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "widgets.hpp"
@ -70,8 +72,14 @@ namespace MWGui
updateBirths();
updateSpells();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList);
}
// Show the current birthsign by default
const std::string &signId =
MWBase::Environment::get().getWorld()->getPlayer().getBirthSign();
if (!signId.empty())
setBirthId(signId);
}
void BirthDialog::setBirthId(const std::string &birthId)
{

@ -16,6 +16,7 @@ class BookPageImpl;
static bool ucsSpace (int codePoint);
static bool ucsLineBreak (int codePoint);
static bool ucsCarriageReturn (int codePoint);
static bool ucsBreakingSpace (int codePoint);
struct BookTypesetter::Style { virtual ~Style () {} };
@ -280,7 +281,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
style.mActiveColour = fontColour;
style.mNormalColour = fontColour;
style.mInteractiveId = 0;
return &style;
}
@ -342,7 +343,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
writeImpl (static_cast <StyleImpl*> (style), begin_, end_);
}
void lineBreak (float margin)
{
assert (margin == 0); //TODO: figure out proper behavior here...
@ -352,7 +353,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
mRun = NULL;
mLine = NULL;
}
void sectionBreak (int margin)
{
add_partial_text();
@ -1188,6 +1189,9 @@ public:
{
Utf8Stream::UnicodeChar code_point = stream.consume ();
if (ucsCarriageReturn (code_point))
continue;
if (!ucsSpace (code_point))
glyphStream.emitGlyph (code_point);
else
@ -1331,6 +1335,11 @@ static bool ucsLineBreak (int codePoint)
return codePoint == '\n';
}
static bool ucsCarriageReturn (int codePoint)
{
return codePoint == '\r';
}
static bool ucsSpace (int codePoint)
{
switch (codePoint)

@ -7,6 +7,9 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwworld/esmstore.hpp"
#include "tooltips.hpp"
@ -131,8 +134,16 @@ namespace MWGui
updateClasses();
updateStats();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList);
}
// Show the current class by default
MWWorld::Ptr player = MWMechanics::getPlayer();
const std::string &classId =
player.get<ESM::NPC>()->mBase->mClass;
if (!classId.empty())
setClassId(classId);
}
void PickClassDialog::setClassId(const std::string &classId)
{

@ -154,7 +154,8 @@ namespace MWGui
{
WindowBase::onClose();
mModel->onClose();
if (mModel)
mModel->onClose();
}
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)

@ -5,8 +5,10 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "textcolours.hpp"
#include <components/fontloader/fontloader.hpp>
#include <components/misc/utf8stream.hpp>
#include "textcolours.hpp"
namespace
{
@ -154,8 +156,8 @@ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
typedef TypesetBook::Ptr book;
JournalBooks::JournalBooks (JournalViewModel::Ptr model) :
mModel (model)
JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) :
mModel (model), mEncoding(encoding)
{
}
@ -217,34 +219,82 @@ book JournalBooks::createQuestBook (const std::string& questName)
}
book JournalBooks::createTopicIndexBook ()
{
bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251);
BookTypesetter::Ptr typesetter = isRussian ? createCyrillicJournalIndex() : createLatinJournalIndex();
return typesetter->complete ();
}
BookTypesetter::Ptr JournalBooks::createLatinJournalIndex ()
{
BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250);
typesetter->setSectionAlignment (BookTypesetter::AlignCenter);
BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
char ch = 'A';
BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
for (int i = 0; i < 26; ++i)
{
char ch = 'A' + i;
char buffer [32];
sprintf (buffer, "( %c )", ch);
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,
textColours.journalTopicOver,
textColours.journalTopicPressed, ch);
textColours.journalTopicPressed, (uint32_t) ch);
if (i == 13)
typesetter->sectionBreak ();
typesetter->write (style, to_utf8_span (buffer));
typesetter->lineBreak ();
ch++;
}
return typesetter->complete ();
return typesetter;
}
BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex ()
{
BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250);
typesetter->setSectionAlignment (BookTypesetter::AlignCenter);
BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8
for (int i = 0; i < 32; ++i)
{
char buffer [32];
sprintf(buffer, "( %c%c )", ch[0], ch[1]);
Utf8Stream stream ((char*) ch);
uint32_t first = stream.peek();
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,
textColours.journalTopicOver,
textColours.journalTopicPressed, first);
ch[1]++;
// Words can not be started with these characters
if (i == 26 || i == 28)
continue;
if (i == 15)
typesetter->sectionBreak ();
typesetter->write (style, to_utf8_span (buffer));
typesetter->lineBreak ();
}
return typesetter;
}
BookTypesetter::Ptr JournalBooks::createTypesetter ()

@ -4,6 +4,8 @@
#include "bookpage.hpp"
#include "journalviewmodel.hpp"
#include <components/to_utf8/to_utf8.hpp>
namespace MWGui
{
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text);
@ -13,7 +15,7 @@ namespace MWGui
typedef TypesetBook::Ptr Book;
JournalViewModel::Ptr mModel;
JournalBooks (JournalViewModel::Ptr model);
JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding);
Book createEmptyJournalBook ();
Book createJournalBook ();
@ -22,8 +24,12 @@ namespace MWGui
Book createQuestBook (const std::string& questName);
Book createTopicIndexBook ();
ToUTF8::FromType mEncoding;
private:
BookTypesetter::Ptr createTypesetter ();
BookTypesetter::Ptr createLatinJournalIndex ();
BookTypesetter::Ptr createCyrillicJournalIndex ();
};
}

@ -6,6 +6,8 @@
#include <MyGUI_LanguageManager.h>
#include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/utf8stream.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
@ -305,18 +307,37 @@ struct JournalViewModelImpl : JournalViewModel
visitor (toUtf8Span (topic.getName()));
}
void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const
void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const
{
MWBase::Journal * journal = MWBase::Environment::get().getJournal();
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
{
if (i->first [0] != Misc::StringUtils::toLower(character))
Utf8Stream stream (i->first.c_str());
uint32_t first = toUpper(stream.peek());
if (first != character)
continue;
visitor (i->second.getName());
}
}
static uint32_t toUpper(uint32_t ch)
{
// Russian alphabet
if (ch >= 0x0430 && ch < 0x0450)
ch -= 0x20;
// Cyrillic IO character
if (ch == 0x0451)
ch -= 0x50;
// Latin alphabet
if (ch >= 0x61 && ch < 0x80)
ch -= 0x20;
return ch;
}
struct TopicEntryImpl : BaseEntry <MWDialogue::Topic::TEntryIter, TopicEntry>

@ -75,8 +75,8 @@ namespace MWGui
/// provides the name of the topic specified by its id
virtual void visitTopicName (TopicId topicId, std::function <void (Utf8Span)> visitor) const = 0;
/// walks over the topics whose names start with the specified character providing the topics name
virtual void visitTopicNamesStartingWith (char character, std::function < void (const std::string&) > visitor) const = 0;
/// walks over the topics whose names start with the character
virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0;
/// walks over the topic entries for the topic specified by its identifier
virtual void visitTopicEntries (TopicId topicId, std::function <void (TopicEntry const &)> visitor) const = 0;

@ -100,8 +100,8 @@ namespace
return getWidget <MWGui::BookPage> (name);
}
JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList)
: JournalBooks (Model), JournalWindow()
JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding)
: JournalBooks (Model, encoding), JournalWindow()
{
center();
@ -161,6 +161,15 @@ namespace
Gui::ImageButton* showActiveButton = getWidget<Gui::ImageButton>(ShowActiveBTN);
Gui::ImageButton* showAllButton = getWidget<Gui::ImageButton>(ShowAllBTN);
Gui::ImageButton* questsButton = getWidget<Gui::ImageButton>(QuestsBTN);
Gui::ImageButton* nextButton = getWidget<Gui::ImageButton>(NextPageBTN);
if (nextButton->getSize().width == 64)
{
// english button has a 7 pixel wide strip of garbage on its right edge
nextButton->setSize(64-7, nextButton->getSize().height);
nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height));
}
if (!questList)
{
// If tribunal is not installed (-> no options button), we still want the Topics button available,
@ -176,6 +185,8 @@ namespace
showActiveButton->setVisible(false);
showAllButton->setVisible(false);
questsButton->setVisible(false);
adjustButton(TopicsBTN);
}
else
{
@ -188,23 +199,25 @@ namespace
adjustButton(ShowActiveBTN);
adjustButton(OptionsBTN);
adjustButton(QuestsBTN);
}
Gui::ImageButton* nextButton = getWidget<Gui::ImageButton>(NextPageBTN);
if (nextButton->getSize().width == 64)
{
// english button has a 7 pixel wide strip of garbage on its right edge
nextButton->setSize(64-7, nextButton->getSize().height);
nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height));
}
adjustButton(TopicsBTN);
int topicsWidth = getWidget<MyGUI::Widget>(TopicsBTN)->getSize().width;
int cancelLeft = getWidget<MyGUI::Widget>(CancelBTN)->getPosition().left;
int cancelRight = getWidget<MyGUI::Widget>(CancelBTN)->getPosition().left + getWidget<MyGUI::Widget>(CancelBTN)->getSize().width;
adjustButton(TopicsBTN);
int topicsWidth = getWidget<MyGUI::Widget>(TopicsBTN)->getSize().width;
int cancelLeft = getWidget<MyGUI::Widget>(CancelBTN)->getPosition().left;
int cancelRight = getWidget<MyGUI::Widget>(CancelBTN)->getPosition().left + getWidget<MyGUI::Widget>(CancelBTN)->getSize().width;
getWidget<MyGUI::Widget>(QuestsBTN)->setPosition(cancelRight, getWidget<MyGUI::Widget>(QuestsBTN)->getPosition().top);
getWidget<MyGUI::Widget>(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget<MyGUI::Widget>(TopicsBTN)->getPosition().top);
getWidget<MyGUI::Widget>(QuestsBTN)->setPosition(cancelRight, getWidget<MyGUI::Widget>(QuestsBTN)->getPosition().top);
// Usually Topics, Quests, and Cancel buttons have the 64px width, so we can place the Topics left-up from the Cancel button, and the Quests right-up from the Cancel button.
// But in some installations, e.g. German one, the Topics button has the 128px width, so we should place it exactly left from the Quests button.
if (topicsWidth == 64)
{
getWidget<MyGUI::Widget>(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget<MyGUI::Widget>(TopicsBTN)->getPosition().top);
}
else
{
int questLeft = getWidget<MyGUI::Widget>(QuestsBTN)->getPosition().left;
getWidget<MyGUI::Widget>(TopicsBTN)->setPosition(questLeft - topicsWidth, getWidget<MyGUI::Widget>(TopicsBTN)->getPosition().top);
}
}
mQuestMode = false;
mAllQuests = false;
@ -464,7 +477,7 @@ namespace
MWBase::Environment::get().getWindowManager()->playSound("book page");
}
void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character)
void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index)
{
setVisible (LeftTopicIndex, false);
setVisible (RightTopicIndex, false);
@ -477,7 +490,7 @@ namespace
AddNamesToList add(list);
mModel->visitTopicNamesStartingWith((char) character, add);
mModel->visitTopicNamesStartingWith(index, add);
list->adjustSize();
@ -632,9 +645,9 @@ namespace
}
// glue the implementation to the interface
MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList)
MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList, ToUTF8::FromType encoding)
{
return new JournalWindowImpl (Model, questList);
return new JournalWindowImpl (Model, questList, encoding);
}
MWGui::JournalWindow::JournalWindow()

@ -3,6 +3,8 @@
#include "windowbase.hpp"
#include <components/to_utf8/to_utf8.hpp>
#include <memory>
namespace MWBase { class WindowManager; }
@ -16,7 +18,7 @@ namespace MWGui
JournalWindow();
/// construct a new instance of the one JournalWindow implementation
static JournalWindow * create (std::shared_ptr <JournalViewModel> Model, bool questList);
static JournalWindow * create (std::shared_ptr <JournalViewModel> Model, bool questList, ToUTF8::FromType encoding);
/// destroy this instance of the JournalWindow implementation
virtual ~JournalWindow () {};

@ -60,7 +60,7 @@ namespace MWGui
{
newSpell.mType = Spell::Type_Spell;
std::string cost = std::to_string(spell->mData.mCost);
std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor)));
std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, NULL, true, true)));
newSpell.mCostColumn = cost + "/" + chance;
}
else

@ -193,6 +193,7 @@ namespace MWGui
, mRestAllowed(true)
, mFallbackMap(fallbackMap)
, mShowOwned(0)
, mEncoding(encoding)
, mVersionDescription(versionDescription)
{
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
@ -346,7 +347,7 @@ namespace MWGui
mGuiModeStates[GM_Console] = GuiModeState(mConsole);
bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds");
JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList);
JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding);
mWindows.push_back(journal);
mGuiModeStates[GM_Journal] = GuiModeState(journal);
mGuiModeStates[GM_Journal].mCloseSound = "book close";
@ -530,6 +531,7 @@ namespace MWGui
delete mDragAndDrop;
delete mSoulgemDialog;
delete mCursorManager;
delete mToolTips;
cleanupGarbage();
@ -2068,5 +2070,4 @@ namespace MWGui
for (unsigned int i=0; i<mWindows.size(); ++i)
mWindows[i]->setVisible(visible);
}
}

@ -511,6 +511,8 @@ namespace MWGui
int mShowOwned;
ToUTF8::FromType mEncoding;
std::string mVersionDescription;
MWGui::TextColours mTextColours;

@ -232,7 +232,10 @@ namespace MWInput
if (mControlSwitch["playercontrols"])
{
if (action == A_Use)
mPlayer->setAttackingOrSpell(currentValue != 0);
{
MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState();
mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing);
}
else if (action == A_Jump)
mAttemptJump = (currentValue == 1.0 && previousValue == 0.0);
}

@ -1148,6 +1148,46 @@ namespace MWMechanics
}
}
void Actors::updateCombatMusic ()
{
MWWorld::Ptr player = getPlayer();
int hostilesCount = 0; // need to know this to play Battle music
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
{
bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2()
<= sqrAiProcessingDistance;
if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange)
{
if (iter->first != player)
{
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++;
}
}
}
}
// check if we still have any player enemies to switch music
static int currentMusic = 0;
if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() &&
MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
currentMusic = 1;
}
else if (currentMusic != 2 && hostilesCount > 0)
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
currentMusic = 2;
}
}
void Actors::update (float duration, bool paused)
{
if(!paused)
@ -1165,8 +1205,6 @@ namespace MWMechanics
MWWorld::Ptr player = getPlayer();
int hostilesCount = 0; // need to know this to play Battle music
/// \todo move update logic to Actor class where appropriate
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> > cachedAllies; // will be filled as engageCombat iterates
@ -1257,8 +1295,6 @@ namespace MWMechanics
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
if (isConscious(iter->first))
stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration);
if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++;
}
}
@ -1331,21 +1367,6 @@ namespace MWMechanics
killDeadActors();
// check if we still have any player enemies to switch music
static int currentMusic = 0;
if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() &&
MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
currentMusic = 1;
}
else if (currentMusic != 2 && hostilesCount > 0)
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
currentMusic = 2;
}
static float sneakTimer = 0.f; // times update of sneak icon
// if player is in sneak state see if anyone detects him
@ -1412,6 +1433,8 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
}
}
updateCombatMusic();
}
void Actors::killDeadActors()
@ -1627,6 +1650,17 @@ namespace MWMechanics
}
}
bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius)
{
for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
{
if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius)
return true;
}
return false;
}
std::list<MWWorld::Ptr> Actors::getActorsSidingWith(const MWWorld::Ptr& actor)
{
std::list<MWWorld::Ptr> list;

@ -81,6 +81,9 @@ namespace MWMechanics
void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore);
///< Deregister all actors (except for \a ignore) in the given cell.
void updateCombatMusic();
///< Update combat music state
void update (float duration, bool paused);
///< Update actor stats and store desired velocity vectors in \a movement
@ -115,15 +118,17 @@ namespace MWMechanics
bool isRunning(const MWWorld::Ptr& ptr);
bool isSneaking(const MWWorld::Ptr& ptr);
void forceStateUpdate(const MWWorld::Ptr &ptr);
void forceStateUpdate(const MWWorld::Ptr &ptr);
bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
void skipAnimation(const MWWorld::Ptr& ptr);
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
void persistAnimationStates();
bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
void skipAnimation(const MWWorld::Ptr& ptr);
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
void persistAnimationStates();
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out);
bool isAnyObjectInRange(const osg::Vec3f& position, float radius);
void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId);
///Returns the list of actors which are siding with the given actor in fights

@ -13,6 +13,7 @@
#include "../mwrender/animation.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
#include "movement.hpp"
@ -372,7 +373,7 @@ namespace MWMechanics
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
{
if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i))
if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
{
points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]);
}

@ -14,6 +14,7 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "steering.hpp"
@ -100,14 +101,23 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
{
bool wasShortcutting = mIsShortcutting;
bool destInLOS = false;
if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first
const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
// check if actor can move along z-axis
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| world->isFlying(actor);
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
if (!mIsShortcutting)
{
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
{
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
mRotateOnTheRunChecks = 3;
// give priority to go directly on target if there is minimal opportunity
@ -220,20 +230,23 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
}
}
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS)
const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell)
{
const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
// check if actor can move along z-axis
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| world->isFlying(actor);
// don't use pathgrid when actor can move in 3 dimensions
bool isPathClear = actorCanMoveByZ;
const ESM::CellId& id = cell->getCell()->getCellId();
// static cache is OK for now, pathgrids can never change during runtime
typedef std::map<ESM::CellId, std::unique_ptr<MWMechanics::PathgridGraph> > CacheMap;
static CacheMap cache;
CacheMap::iterator found = cache.find(id);
if (found == cache.end())
{
cache.insert(std::make_pair(id, std::unique_ptr<MWMechanics::PathgridGraph>(new MWMechanics::PathgridGraph(cell))));
}
return *cache[id].get();
}
if (!isPathClear
&& (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST))
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
{
if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
{
// check if target is clearly visible
isPathClear = !MWBase::Environment::get().getWorld()->castRay(

@ -27,6 +27,7 @@ namespace MWMechanics
const float AI_REACTION_TIME = 0.25f;
class CharacterController;
class PathgridGraph;
/// \brief Base class for AI packages
class AiPackage
@ -110,7 +111,7 @@ namespace MWMechanics
/// If a shortcut is possible then path will be cleared and filled with the destination point.
/// \param destInLOS If not NULL function will return ray cast check result
/// \return If can shortcut the path
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS);
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear);
/// Check if the way to the destination is clear, taking into account actor speed
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
@ -119,6 +120,8 @@ namespace MWMechanics
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
// TODO: all this does not belong here, move into temporary storage
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;

@ -17,6 +17,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
#include "movement.hpp"
@ -217,7 +218,7 @@ namespace MWMechanics
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking);
@ -264,9 +265,20 @@ namespace MWMechanics
getAllowedNodes(actor, currentCell->getCell(), storage);
}
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| MWBase::Environment::get().getWorld()->isFlying(actor);
if(actorCanMoveByZ && mDistance > 0) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) {
wanderNearStart(actor, storage, mDistance);
}
storage.mCanWanderAlongPathGrid = false;
}
// If the package has a wander distance but no pathgrid is available,
// randomly idle or wander near spawn point
if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) {
else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 96) {
wanderNearStart(actor, storage, mDistance);
@ -349,7 +361,7 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
{
@ -372,7 +384,7 @@ namespace MWMechanics
do {
// Determine a random location within radius of original position
const float pi = 3.14159265359f;
const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance;
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi;
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
@ -383,7 +395,7 @@ namespace MWMechanics
// Check if land creature will walk onto water or if water creature will swim onto land
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell());
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
mPathFinder.addPointToPath(destinationPosition);
if (mPathFinder.isPathConstructed())
@ -660,13 +672,14 @@ namespace MWMechanics
{
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size());
ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
// don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
{
@ -799,20 +812,80 @@ namespace MWMechanics
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size());
ESM::Pathgrid::Point dest = storage.mAllowedNodes[index];
state.moveIn(new AiWanderStorage());
ESM::Pathgrid::Point worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
// add offset only if the selected pathgrid is occupied by another actor
if (isPathGridOccupied)
{
ESM::Pathgrid::PointList points;
getNeighbouringNodes(dest, actor.getCell(), points);
// there are no neighbouring nodes, nowhere to move
if (points.empty())
return;
int initialSize = points.size();
bool isOccupied = false;
// AI will try to move the NPC towards every neighboring node until suitable place will be found
for (int i = 0; i < initialSize; i++)
{
int randomIndex = Misc::Rng::rollDice(points.size());
ESM::Pathgrid::Point connDest = points[randomIndex];
// add an offset towards random neighboring node
osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest);
float length = dir.length();
dir.normalize();
for (int j = 1; j <= 3; j++)
{
// move for 5-15% towards random neighboring node
dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
if (!isOccupied)
break;
}
if (!isOccupied)
break;
// Will try an another neighboring node
points.erase(points.begin()+randomIndex);
}
// there is no free space, nowhere to move
if (isOccupied)
return;
}
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground.
// Adding 20 in adjustPosition() is not enough.
dest.mZ += 60;
dest.mX += OffsetToPreventOvercrowding();
dest.mY += OffsetToPreventOvercrowding();
ToWorldCoordinates(dest, actor.getCell()->getCell());
state.moveIn(new AiWanderStorage());
MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX),
static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
actor.getClass().adjustPosition(actor, false);
}
int AiWander::OffsetToPreventOvercrowding()
void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
{
return static_cast<int>(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f));
const ESM::Pathgrid *pathgrid =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest));
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
}
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage)
@ -853,7 +926,7 @@ namespace MWMechanics
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
if((npcPos - nodePos).length2() <= mDistance * mDistance &&
cellStore->isPointConnected(closestPointIndex, counter))
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
{
storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]);
pointIndex = counter;

@ -104,6 +104,8 @@ namespace MWMechanics
bool mHasDestination;
osg::Vec3f mDestination;
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage);
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);

@ -1278,7 +1278,16 @@ bool CharacterController::updateWeaponState()
bool animPlaying;
if(mAttackingOrSpell)
{
mIdleState = CharState_None;
MWWorld::Ptr player = getPlayer();
// We should reset player's idle animation in the first-person mode.
if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson())
mIdleState = CharState_None;
// In other cases we should not break swim and sneak animations
if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
mIdleState = CharState_None;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
@ -1288,7 +1297,7 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
mAttackingOrSpell = false;
if (mPtr == getPlayer())
if (mPtr == player)
{
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
@ -1298,7 +1307,7 @@ bool CharacterController::updateWeaponState()
// For the player, set the spell we want to cast
// This has to be done at the start of the casting animation,
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
if (mPtr == getPlayer())
if (mPtr == player)
{
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
stats.getSpells().setSelectedSpell(selectedSpell);
@ -1310,7 +1319,7 @@ bool CharacterController::updateWeaponState()
MWMechanics::CastSpell cast(mPtr, NULL);
cast.playSpellCastingEffects(spellid);
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
const ESM::MagicEffect *effect;
@ -1639,7 +1648,7 @@ void CharacterController::update(float duration)
float speed = 0.f;
updateMagicEffects();
if (isKnockedOut())
mTimeUntilWake -= duration;

@ -1578,6 +1578,11 @@ namespace MWMechanics
mActors.getObjectsInRange(position, radius, objects);
}
bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius)
{
return mActors.isAnyObjectInRange(position, radius);
}
std::list<MWWorld::Ptr> MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor)
{
return mActors.getActorsSidingWith(actor);

@ -158,6 +158,9 @@ namespace MWMechanics
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects);
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects);
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius);
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);

@ -5,16 +5,16 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "pathgrid.hpp"
#include "coordinateconverter.hpp"
namespace
{
// Chooses a reachable end pathgrid point. start is assumed reachable.
std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid,
const MWWorld::CellStore *cell,
const MWMechanics::PathgridGraph *graph,
const osg::Vec3f& pos, int start)
{
assert(grid && !grid->mPoints.empty());
@ -31,7 +31,7 @@ namespace
if (potentialDistBetween < closestDistanceReachable)
{
// found a closer one
if (cell->isPointConnected(start, counter))
if (graph->isPointConnected(start, counter))
{
closestDistanceReachable = potentialDistBetween;
closestReachableIndex = counter;
@ -45,7 +45,7 @@ namespace
}
// post-condition: start and endpoint must be connected
assert(cell->isPointConnected(start, closestReachableIndex));
assert(graph->isPointConnected(start, closestReachableIndex));
// AiWander has logic that depends on whether a path was created, deleting
// allowed nodes if not. Hence a path needs to be created even if the start
@ -120,8 +120,8 @@ namespace MWMechanics
}
PathFinder::PathFinder()
: mPathgrid(NULL),
mCell(NULL)
: mPathgrid(NULL)
, mCell(NULL)
{
}
@ -169,14 +169,15 @@ namespace MWMechanics
*/
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell)
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
{
mPath.clear();
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
if(mCell != cell || !mPathgrid)
{
mCell = cell;
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell());
mPathgrid = pathgridGraph.getPathgrid();
}
// Refer to AiWander reseach topic on openmw forums for some background.
@ -200,7 +201,7 @@ namespace MWMechanics
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, cell,
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
endPointInLocalCoords,
startNode);
@ -228,7 +229,7 @@ namespace MWMechanics
}
else
{
mPath = mCell->aStarSearch(startNode, endNode.first);
mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
// convert supplied path to world coordinates
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
@ -301,18 +302,18 @@ namespace MWMechanics
// see header for the rationale
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell)
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
{
if (mPath.size() < 2)
{
// if path has one point, then it's the destination.
// don't need to worry about bad path for this case
buildPath(startPoint, endPoint, cell);
buildPath(startPoint, endPoint, cell, pathgridGraph);
}
else
{
const ESM::Pathgrid::Point oldStart(*getPath().begin());
buildPath(startPoint, endPoint, cell);
buildPath(startPoint, endPoint, cell, pathgridGraph);
if (mPath.size() >= 2)
{
// if 2nd waypoint of new path == 1st waypoint of old,

@ -14,6 +14,8 @@ namespace MWWorld
namespace MWMechanics
{
class PathgridGraph;
float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
float getZAngleToDir(const osg::Vec3f& dir);
@ -54,7 +56,7 @@ namespace MWMechanics
void clearPath();
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell);
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
///< \Returns true if we are within \a tolerance units of the last path point.
@ -89,7 +91,7 @@ namespace MWMechanics
Which results in NPC "running in a circle" back to the just passed waypoint.
*/
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell);
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void addPointToPath(const ESM::Pathgrid::Point &point)
{

@ -49,7 +49,7 @@ namespace
namespace MWMechanics
{
PathgridGraph::PathgridGraph()
PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell)
: mCell(NULL)
, mPathgrid(NULL)
, mIsExterior(0)
@ -58,6 +58,7 @@ namespace MWMechanics
, mSCCId(0)
, mSCCIndex(0)
{
load(cell);
}
/*
@ -130,6 +131,11 @@ namespace MWMechanics
return true;
}
const ESM::Pathgrid *PathgridGraph::getPathgrid() const
{
return mPathgrid;
}
// v is the pathgrid point index (some call them vertices)
void PathgridGraph::recursiveStrongConnect(int v)
{
@ -214,6 +220,16 @@ namespace MWMechanics
return (mGraph[start].componentId == mGraph[end].componentId);
}
void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const
{
for(int i = 0; i < static_cast<int> (mGraph[index].edges.size()); i++)
{
int neighbourIndex = mGraph[index].edges[i].index;
if (neighbourIndex != index)
nodes.push_back(mPathgrid->mPoints[neighbourIndex]);
}
}
/*
* NOTE: Based on buildPath2(), please check git history if interested
* Should consider using a 3rd party library version (e.g. boost)

@ -20,14 +20,19 @@ namespace MWMechanics
class PathgridGraph
{
public:
PathgridGraph();
PathgridGraph(const MWWorld::CellStore* cell);
bool load(const MWWorld::CellStore *cell);
const ESM::Pathgrid* getPathgrid() const;
// returns true if end point is strongly connected (i.e. reachable
// from start point) both start and end are pathgrid point indexes
bool isPointConnected(const int start, const int end) const;
// get neighbouring nodes for index node and put them to "nodes" vector
void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const;
// the input parameters are pathgrid point indexes
// the output list is in local (internal cells) or world (external
// cells) coordinates

@ -115,7 +115,7 @@ namespace MWMechanics
return castChance;
}
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap)
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
{
bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
@ -135,6 +135,9 @@ namespace MWMechanics
if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100;
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode)
return 0;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100;
@ -149,11 +152,11 @@ namespace MWMechanics
return std::max(0.f, std::min(100.f, castChance));
}
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap)
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
{
const ESM::Spell* spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
return getSpellSuccessChance(spell, actor, effectiveSchool, cap);
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
}

@ -31,11 +31,12 @@ namespace MWMechanics
* @param actor calculate spell success chance for this actor (depends on actor's skills)
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
* @param cap cap the result to 100%?
* @param checkMagicka check magicka?
* @note actor can be an NPC or a creature
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
*/
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true);
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true);
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false);
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false);
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);

@ -445,10 +445,6 @@ namespace MWWorld
loadRefs ();
mState = State_Loaded;
// TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else.
// In a simple test, loading the graph for all cells in MW + expansions took 200 ms
mPathgridGraph.load(this);
}
}
@ -937,16 +933,6 @@ namespace MWWorld
return !(left==right);
}
bool CellStore::isPointConnected(const int start, const int end) const
{
return mPathgridGraph.isPointConnected(start, end);
}
std::list<ESM::Pathgrid::Point> CellStore::aStarSearch(const int start, const int end) const
{
return mPathgridGraph.aStarSearch(start, end);
}
void CellStore::setFog(ESM::FogState *fog)
{
mFogState.reset(fog);

@ -32,13 +32,12 @@
#include <components/esm/loadmisc.hpp>
#include <components/esm/loadbody.hpp>
#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld
#include "timestamp.hpp"
#include "ptr.hpp"
namespace ESM
{
struct Cell;
struct CellState;
struct FogState;
struct CellId;
@ -376,10 +375,6 @@ namespace MWWorld
void respawn ();
///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded.
bool isPointConnected(const int start, const int end) const;
std::list<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
private:
/// Run through references and store IDs
@ -391,8 +386,6 @@ namespace MWWorld
///< Make case-adjustments to \a ref and insert it into the respective container.
///
/// Invalid \a ref objects are silently dropped.
MWMechanics::PathgridGraph mPathgridGraph;
};
template<>

@ -4,6 +4,7 @@
#include <iostream>
#include <osg/PositionAttitudeTransform>
#include <osg/ComputeBoundsVisitor>
#include <components/esm/esmwriter.hpp>
#include <components/esm/projectilestate.hpp>
@ -202,6 +203,12 @@ namespace MWWorld
osg::ref_ptr<osg::Node> projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo);
osg::ref_ptr<osg::ComputeBoundsVisitor> boundVisitor = new osg::ComputeBoundsVisitor();
projectile->accept(*boundVisitor.get());
osg::BoundingBox bb = boundVisitor->getBoundingBox();
state.mNode->setPivotPoint(bb.center());
if (state.mIdMagic.size() > 1)
for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter)
{
@ -316,6 +323,7 @@ namespace MWWorld
state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
state.mThrown = projectile.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown;
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
MWWorld::Ptr ptr = ref.getPtr();
@ -378,7 +386,6 @@ namespace MWWorld
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
osg::Vec3f pos(it->mNode->getPosition());
@ -458,7 +465,22 @@ namespace MWWorld
osg::Vec3f newPos = pos + it->mVelocity * duration;
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity);
if (it->mThrown)
orient.set(
osg::Matrixd::rotate(it->mEffectAnimationTime->getTime() * -10.0,osg::Vec3f(0,0,1)) *
osg::Matrixd::rotate(osg::PI / 2.0,osg::Vec3f(0,1,0)) *
osg::Matrixd::rotate(-1 * osg::PI / 2.0,osg::Vec3f(1,0,0)) *
osg::Matrixd::inverse(
osg::Matrixd::lookAt(
osg::Vec3f(0,0,0),
it->mVelocity,
osg::Vec3f(0,0,1))
)
);
else
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity);
it->mNode->setAttitude(orient);
it->mNode->setPosition(newPos);
@ -596,6 +618,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
state.mThrown = ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown;
}
catch(...)
{

@ -112,6 +112,7 @@ namespace MWWorld
osg::Vec3f mVelocity;
float mAttackStrength;
bool mThrown;
};
std::vector<MagicBoltState> mMagicBolts;

@ -70,10 +70,10 @@ endif()
libfind_pkg_detect(SDL2 sdl2
FIND_PATH SDL.h
HINTS $ENV{SDL2DIR}
PATH_SUFFIXES include SDL2
PATH_SUFFIXES include SDL2 include/SDL2
FIND_LIBRARY SDL2
HINTS $ENV{SDL2DIR}
PATH_SUFFIXES ${_sdl_lib_suffix}
PATH_SUFFIXES lib ${_sdl_lib_suffix}
)
libfind_version_n_header(SDL2 NAMES SDL_version.h DEFINES SDL_MAJOR_VERSION SDL_MINOR_VERSION SDL_PATCHLEVEL)
@ -85,7 +85,7 @@ IF(NOT SDL2_BUILDING_LIBRARY AND NOT APPLE)
libfind_pkg_detect(SDL2MAIN sdl2
FIND_LIBRARY SDL2main
HINTS $ENV{SDL2DIR}
PATH_SUFFIXES ${_sdl_lib_suffix}
PATH_SUFFIXES lib ${_sdl_lib_suffix}
)
set(SDL2MAIN_FIND_QUIETLY TRUE)
libfind_process(SDL2MAIN)

@ -18,6 +18,11 @@ public:
{
}
Utf8Stream (const char * str) :
cur ((unsigned char*) str), nxt ((unsigned char*) str), end ((unsigned char*) str + strlen(str)), val(Utf8Stream::sBadChar())
{
}
Utf8Stream (std::pair <Point, Point> range) :
cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar())
{

@ -292,7 +292,7 @@ public:
ostream << "# to its default, simply remove it from this file. For available" << std::endl;
ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl;
ostream << "#" << std::endl;
ostream << "# https://wiki.openmw.org/index.php?title=Settings" << std::endl;
ostream << "# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl;
}
// We still have one more thing to do before we're completely done writing the file.

@ -87,7 +87,7 @@ namespace Gui
setValue(std::max(mValue-1, mMinValue));
eventValueChanged(mValue);
}
else
else if (character == 0 || (character >= '0' && character <= '9'))
Base::onKeyButtonPressed(key, character);
}

@ -76,9 +76,7 @@
<Widget type="HBox" skin="" position="110 374 452 24" align="Bottom Right">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="CreateButton">
<Property key="Caption" value="#{sCreate}"/>

@ -17,9 +17,7 @@
<!-- Dialog buttons -->
<Widget type="HBox" position="0 338 511 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
<Property key="Caption" value="#{sBack}"/>
</Widget>

@ -74,9 +74,7 @@
<!-- Dialog buttons -->
<Widget type="HBox" position="0 276 475 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
<Property key="Caption" value="#{sBack}"/>
</Widget>

@ -75,9 +75,7 @@
<!-- Dialog buttons -->
<Widget type="HBox" position="0 158 482 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="DescriptionButton">
<Property key="Caption" value="#{sCreateClassMenu1}"/>
</Widget>

@ -76,9 +76,7 @@
<!-- Dialog buttons -->
<Widget type="HBox" position="0 393 572 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" position="471 397 53 23" name="BackButton">
<Property key="Caption" value="#{sBack}"/>

@ -112,9 +112,7 @@
<!-- Dialogue Buttons -->
<Widget type="HBox" position="0 388 525 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
<Property key="Caption" value="#{sBack}"/>
</Widget>

@ -94,9 +94,7 @@
</Widget>
<Widget type="HBox" position="8 266 336 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="DeleteButton">
<Property key="Caption" value="#{sDelete}"/>

@ -16,9 +16,7 @@
<Widget type="HBox" position="13 200 303 24">
<Property key="Spacing" value="6"/>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="DeleteButton">
<Property key="Caption" value="#{sDelete}"/>
</Widget>

@ -36,9 +36,7 @@
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="0 0 50 50" name="ItemBox">
</Widget>
<Widget type="Widget" position="0 0 8 0">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedTextBox" skin="NormalText">
<Property key="Caption" value="#{sSoulGem}"/>
@ -141,9 +139,7 @@
<UserString key="Caption_Text" value="#{sEnchantmentHelp7}"/>
</Widget>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedTextBox" skin="NormalText" name="PriceTextLabel">
<Property key="Caption" value="#{sBarterDialog7}"/>

@ -156,9 +156,7 @@
<Widget type="HBox" skin="" position="0 0 330 24">
<UserString key="HStretch" value="true"/>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 0 24" name="OkButton">
<Property key="Caption" value="#{sOk}"/>
</Widget>

@ -27,9 +27,7 @@
</Widget>
</Widget>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
<Property key="Caption" value="#{sCancel}"/>

@ -34,9 +34,7 @@
</Widget>
</Widget>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
<Property key="Caption" value="#{sCancel}"/>

@ -68,9 +68,7 @@
<Widget type="HBox" position="0 336 558 60">
<Property key="Padding" value="16"/>
<Widget type="Widget" position="0 0 0 0">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedTextBox" skin="NormalText">
<Property key="Caption" value="#{sBarterDialog7}"/>

@ -103,9 +103,7 @@
<Widget type="VBox">
<UserString key="VStretch" value="true"/>
<Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="HandToHandImage"/>
<Widget type="Widget">
<UserString key="VStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="44 8 248 284" align="Left Top" name="HandToHandText">
@ -122,9 +120,7 @@
<Widget type="VBox">
<UserString key="VStretch" value="true"/>
<Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="HealthImage"/>
<Widget type="Widget">
<UserString key="VStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
</Widget>
<Widget type="AutoSizedEditBox" skin="SandText" position="44 8 392 0" align="Left Top" name="HealthDescription">

@ -3,7 +3,7 @@
<MyGUI type="Layout">
<Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 600 360" name="_Main">
<Property key="Visible" value="false"/>
<Property key="MinSize" value="380 245"/>
<Property key="MinSize" value="428 245"/>
<!-- Categories -->
<Widget type="HBox" position="8 8 566 24" align="Left Top HStretch" name="Categories">
@ -34,32 +34,39 @@
</Widget>
<Widget type="Widget" skin="" position="8 231 566 92" name="BottomPane" align="Left Bottom HStretch">
<Widget type="TextBox" skin="SandText" position="192 0 374 24" name="PlayerGold" align="Left Top HStretch">
<Property key="TextAlign" value="Right"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="192 28 374 24" name="MerchantGold" align="Left Top HStretch">
<Property key="TextAlign" value="Right"/>
</Widget>
<Widget type="Button" skin="MW_Button" position="0 0 40 24" name="IncreaseButton" align="Left Top">
<Property key="Caption" value="+"/>
</Widget>
<Widget type="Button" skin="MW_Button" position="0 28 40 24" name="DecreaseButton" align="Left Top">
<Property key="Caption" value="-"/>
<Widget type="HBox" position="0 0 566 24" align="Left Top HStretch">
<Widget type="Button" skin="MW_Button" position="0 0 40 24" name="IncreaseButton" align="Left Top">
<Property key="Caption" value="+"/>
<Property key="NeedKey" value="false"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="48 0 140 24" name="TotalBalanceLabel" align="Left Top">
<Property key="TextAlign" value="Left VCenter"/>
</Widget>
<Widget type="Spacer" />
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 374 24" name="PlayerGold" align="Right Top">
<Property key="TextAlign" value="Right"/>
</Widget>
</Widget>
<Widget type="TextBox" skin="SandText" position="48 0 140 24" name="TotalBalanceLabel" align="Left Top "/>
<Widget type="NumericEditBox" skin="MW_TextEdit" position="48 28 140 24" name="TotalBalance" align="Left Top">
<Property key="TextAlign" value="Center"/>
<Widget type="HBox" position="0 28 566 24" align="Left Top HStretch">
<Widget type="Button" skin="MW_Button" position="0 0 40 24" name="DecreaseButton" align="Left Top">
<Property key="Caption" value="-"/>
<Property key="NeedKey" value="false"/>
</Widget>
<Widget type="NumericEditBox" skin="MW_TextEdit" position="48 0 140 24" name="TotalBalance" align="Left Top">
<Property key="TextAlign" value="Center"/>
</Widget>
<Widget type="Spacer" />
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 374 24" name="MerchantGold" align="Right Top">
<Property key="TextAlign" value="Right"/>
</Widget>
</Widget>
<Widget type="HBox" position="0 60 566 24" align="Left Bottom HStretch">
<Widget type="AutoSizedButton" skin="MW_Button" name="MaxSaleButton">
<Property key="Caption" value="#{sMaxSale}"/>
</Widget>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer" />
<Widget type="AutoSizedButton" skin="MW_Button" name="OfferButton">
<Property key="Caption" value="#{sBarterDialog8}"/>
</Widget>

@ -26,9 +26,7 @@
<Widget type="HBox">
<UserString key="HStretch" value="true"/>
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
<Widget type="Spacer"/>
<Widget type="AutoSizedButton" skin="MW_Button" name="UntilHealedButton">
<Property key="Caption" value="#{sUntilHealed}"/>

@ -6,9 +6,9 @@
# ranges of recommended values. For detailed explanations of the
# significance of each setting, interaction with other settings, hard
# limits on value ranges and more information in general, please read
# the detailed documentation at the OpenMW Wiki page:
# the detailed documentation at:
#
# https://wiki.openmw.org/index.php?title=Settings
# http://openmw.readthedocs.io/en/master/reference/modding/settings/index.html
#
[Camera]

Loading…
Cancel
Save