summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBret Curtis <psi29a@gmail.com>2014-01-14 09:30:33 +0100
committerBret Curtis <psi29a@gmail.com>2014-01-14 09:30:33 +0100
commit6d784a4f6c96f8339e9ea50fe88870628261d27b (patch)
tree01c7e746eea088b016cc9f53e6cb7bb71d6d9546
parent4dca3c5ce1400f655b3a7184e21d23460447a521 (diff)
Imported Upstream version 0.28.0upstream/0.28.0
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml8
-rw-r--r--CMakeLists.txt66
-rw-r--r--README_Mac.md160
-rw-r--r--apps/bsatool/bsatool.cpp2
-rw-r--r--apps/esmtool/esmtool.cpp2
-rw-r--r--apps/esmtool/record.cpp9
-rw-r--r--apps/esmtool/record.hpp7
-rw-r--r--apps/launcher/CMakeLists.txt5
-rw-r--r--apps/launcher/datafilespage.cpp13
-rw-r--r--apps/launcher/graphicspage.cpp43
-rw-r--r--apps/launcher/graphicspage.hpp18
-rw-r--r--apps/launcher/main.cpp2
-rw-r--r--apps/launcher/maindialog.cpp36
-rw-r--r--apps/launcher/settings/gamesettings.cpp41
-rw-r--r--apps/launcher/settings/gamesettings.hpp11
-rw-r--r--apps/mwiniimporter/importer.cpp2
-rw-r--r--apps/opencs/CMakeLists.txt12
-rw-r--r--apps/opencs/editor.cpp28
-rw-r--r--apps/opencs/main.cpp11
-rw-r--r--apps/opencs/model/doc/document.cpp4
-rw-r--r--apps/opencs/model/doc/documentmanager.cpp4
-rw-r--r--apps/opencs/model/doc/saving.cpp7
-rw-r--r--apps/opencs/model/doc/savingstages.cpp113
-rw-r--r--apps/opencs/model/doc/savingstages.hpp33
-rw-r--r--apps/opencs/model/filter/andnode.hpp2
-rw-r--r--apps/opencs/model/filter/ornode.hpp2
-rw-r--r--apps/opencs/model/filter/parser.cpp8
-rw-r--r--apps/opencs/model/settings/settingsitem.hpp6
-rw-r--r--apps/opencs/model/settings/usersettings.cpp2
-rw-r--r--apps/opencs/model/world/collection.hpp126
-rw-r--r--apps/opencs/model/world/collectionbase.hpp10
-rw-r--r--apps/opencs/model/world/columnbase.hpp4
-rw-r--r--apps/opencs/model/world/columnimp.hpp339
-rw-r--r--apps/opencs/model/world/columns.cpp24
-rw-r--r--apps/opencs/model/world/columns.hpp14
-rw-r--r--apps/opencs/model/world/commands.cpp22
-rw-r--r--apps/opencs/model/world/commands.hpp16
-rw-r--r--apps/opencs/model/world/data.cpp69
-rw-r--r--apps/opencs/model/world/data.hpp11
-rw-r--r--apps/opencs/model/world/idtable.cpp28
-rw-r--r--apps/opencs/model/world/idtable.hpp21
-rw-r--r--apps/opencs/model/world/info.hpp14
-rw-r--r--apps/opencs/model/world/infocollection.cpp184
-rw-r--r--apps/opencs/model/world/infocollection.hpp50
-rw-r--r--apps/opencs/model/world/ref.cpp2
-rw-r--r--apps/opencs/model/world/ref.hpp2
-rw-r--r--apps/opencs/model/world/refidcollection.cpp9
-rw-r--r--apps/opencs/model/world/refidcollection.hpp8
-rw-r--r--apps/opencs/model/world/universalid.cpp4
-rw-r--r--apps/opencs/model/world/universalid.hpp4
-rw-r--r--apps/opencs/view/doc/subviewfactoryimp.hpp11
-rw-r--r--apps/opencs/view/doc/view.cpp54
-rw-r--r--apps/opencs/view/doc/view.hpp6
-rw-r--r--apps/opencs/view/doc/viewmanager.cpp4
-rw-r--r--apps/opencs/view/render/scenewidget.cpp132
-rw-r--r--apps/opencs/view/render/scenewidget.hpp40
-rw-r--r--apps/opencs/view/world/dialoguecreator.cpp2
-rw-r--r--apps/opencs/view/world/genericcreator.cpp4
-rw-r--r--apps/opencs/view/world/genericcreator.hpp2
-rw-r--r--apps/opencs/view/world/idvalidator.cpp20
-rw-r--r--apps/opencs/view/world/idvalidator.hpp5
-rw-r--r--apps/opencs/view/world/infocreator.cpp81
-rw-r--r--apps/opencs/view/world/infocreator.hpp42
-rw-r--r--apps/opencs/view/world/scenesubview.cpp13
-rw-r--r--apps/opencs/view/world/subviews.cpp9
-rw-r--r--apps/opencs/view/world/table.cpp109
-rw-r--r--apps/opencs/view/world/table.hpp9
-rw-r--r--apps/opencs/view/world/tablesubview.cpp4
-rw-r--r--apps/opencs/view/world/tablesubview.hpp2
-rw-r--r--apps/openmw/CMakeLists.txt24
-rw-r--r--apps/openmw/crashcatcher.cpp449
-rw-r--r--apps/openmw/engine.cpp34
-rw-r--r--apps/openmw/engine.hpp6
-rw-r--r--apps/openmw/main.cpp46
-rw-r--r--apps/openmw/mwbase/environment.cpp7
-rw-r--r--apps/openmw/mwbase/environment.hpp5
-rw-r--r--apps/openmw/mwbase/mechanicsmanager.hpp9
-rw-r--r--apps/openmw/mwbase/windowmanager.hpp5
-rw-r--r--apps/openmw/mwbase/world.hpp56
-rw-r--r--apps/openmw/mwclass/apparatus.cpp2
-rw-r--r--apps/openmw/mwclass/armor.cpp64
-rw-r--r--apps/openmw/mwclass/book.cpp3
-rw-r--r--apps/openmw/mwclass/clothing.cpp52
-rw-r--r--apps/openmw/mwclass/container.cpp8
-rw-r--r--apps/openmw/mwclass/creature.cpp20
-rw-r--r--apps/openmw/mwclass/door.cpp8
-rw-r--r--apps/openmw/mwclass/ingredient.cpp13
-rw-r--r--apps/openmw/mwclass/light.cpp8
-rw-r--r--apps/openmw/mwclass/lockpick.cpp13
-rw-r--r--apps/openmw/mwclass/misc.cpp15
-rw-r--r--apps/openmw/mwclass/misc.hpp2
-rw-r--r--apps/openmw/mwclass/npc.cpp183
-rw-r--r--apps/openmw/mwclass/potion.cpp19
-rw-r--r--apps/openmw/mwclass/probe.cpp13
-rw-r--r--apps/openmw/mwclass/repair.cpp7
-rw-r--r--apps/openmw/mwclass/weapon.cpp74
-rw-r--r--apps/openmw/mwdialogue/dialoguemanagerimp.cpp6
-rw-r--r--apps/openmw/mwdialogue/filter.cpp4
-rw-r--r--apps/openmw/mwgui/alchemywindow.cpp4
-rw-r--r--apps/openmw/mwgui/charactercreation.cpp71
-rw-r--r--apps/openmw/mwgui/charactercreation.hpp10
-rw-r--r--apps/openmw/mwgui/class.cpp2
-rw-r--r--apps/openmw/mwgui/class.hpp4
-rw-r--r--apps/openmw/mwgui/companionwindow.cpp3
-rw-r--r--apps/openmw/mwgui/console.cpp5
-rw-r--r--apps/openmw/mwgui/container.cpp14
-rw-r--r--apps/openmw/mwgui/containeritemmodel.cpp4
-rw-r--r--apps/openmw/mwgui/cursor.cpp53
-rw-r--r--apps/openmw/mwgui/cursor.hpp17
-rw-r--r--apps/openmw/mwgui/dialogue.cpp11
-rw-r--r--apps/openmw/mwgui/enchantingdialog.cpp7
-rw-r--r--apps/openmw/mwgui/hud.cpp67
-rw-r--r--apps/openmw/mwgui/imagebutton.cpp5
-rw-r--r--apps/openmw/mwgui/imagebutton.hpp2
-rw-r--r--apps/openmw/mwgui/inventoryitemmodel.cpp16
-rw-r--r--apps/openmw/mwgui/inventorywindow.cpp143
-rw-r--r--apps/openmw/mwgui/inventorywindow.hpp8
-rw-r--r--apps/openmw/mwgui/journalwindow.cpp27
-rw-r--r--apps/openmw/mwgui/levelupdialog.cpp3
-rw-r--r--apps/openmw/mwgui/loadingscreen.cpp36
-rw-r--r--apps/openmw/mwgui/mainmenu.cpp17
-rw-r--r--apps/openmw/mwgui/mapwindow.cpp292
-rw-r--r--apps/openmw/mwgui/mapwindow.hpp9
-rw-r--r--apps/openmw/mwgui/merchantrepair.cpp5
-rw-r--r--apps/openmw/mwgui/messagebox.cpp115
-rw-r--r--apps/openmw/mwgui/messagebox.hpp15
-rw-r--r--apps/openmw/mwgui/mode.hpp1
-rw-r--r--apps/openmw/mwgui/pickpocketitemmodel.cpp8
-rw-r--r--apps/openmw/mwgui/quickkeysmenu.cpp54
-rw-r--r--apps/openmw/mwgui/recharge.cpp196
-rw-r--r--apps/openmw/mwgui/recharge.hpp42
-rw-r--r--apps/openmw/mwgui/savegamedialog.cpp49
-rw-r--r--apps/openmw/mwgui/savegamedialog.hpp37
-rw-r--r--apps/openmw/mwgui/settingswindow.cpp48
-rw-r--r--apps/openmw/mwgui/settingswindow.hpp9
-rw-r--r--apps/openmw/mwgui/soulgemdialog.cpp3
-rw-r--r--apps/openmw/mwgui/spellbuyingwindow.cpp4
-rw-r--r--apps/openmw/mwgui/spellcreationdialog.cpp11
-rw-r--r--apps/openmw/mwgui/spellicons.cpp182
-rw-r--r--apps/openmw/mwgui/spellicons.hpp14
-rw-r--r--apps/openmw/mwgui/spellwindow.cpp91
-rw-r--r--apps/openmw/mwgui/statswindow.cpp52
-rw-r--r--apps/openmw/mwgui/statswindow.hpp2
-rw-r--r--apps/openmw/mwgui/textinput.hpp4
-rw-r--r--apps/openmw/mwgui/tooltips.cpp7
-rw-r--r--apps/openmw/mwgui/tradeitemmodel.cpp7
-rw-r--r--apps/openmw/mwgui/tradewindow.cpp63
-rw-r--r--apps/openmw/mwgui/tradewindow.hpp2
-rw-r--r--apps/openmw/mwgui/trainingwindow.cpp4
-rw-r--r--apps/openmw/mwgui/travelwindow.cpp8
-rw-r--r--apps/openmw/mwgui/widgets.cpp40
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.cpp148
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.hpp18
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.cpp43
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.hpp8
-rw-r--r--apps/openmw/mwmechanics/activespells.cpp262
-rw-r--r--apps/openmw/mwmechanics/activespells.hpp73
-rw-r--r--apps/openmw/mwmechanics/actors.cpp416
-rw-r--r--apps/openmw/mwmechanics/actors.hpp22
-rw-r--r--apps/openmw/mwmechanics/aiactivate.cpp2
-rw-r--r--apps/openmw/mwmechanics/aiactivate.hpp2
-rw-r--r--apps/openmw/mwmechanics/aicombat.cpp148
-rw-r--r--apps/openmw/mwmechanics/aicombat.hpp36
-rw-r--r--apps/openmw/mwmechanics/aiescort.cpp2
-rw-r--r--apps/openmw/mwmechanics/aiescort.hpp2
-rw-r--r--apps/openmw/mwmechanics/aifollow.cpp2
-rw-r--r--apps/openmw/mwmechanics/aifollow.hpp2
-rw-r--r--apps/openmw/mwmechanics/aipackage.hpp5
-rw-r--r--apps/openmw/mwmechanics/aisequence.cpp35
-rw-r--r--apps/openmw/mwmechanics/aisequence.hpp7
-rw-r--r--apps/openmw/mwmechanics/aitravel.cpp2
-rw-r--r--apps/openmw/mwmechanics/aitravel.hpp2
-rw-r--r--apps/openmw/mwmechanics/aiwander.cpp9
-rw-r--r--apps/openmw/mwmechanics/aiwander.hpp2
-rw-r--r--apps/openmw/mwmechanics/alchemy.cpp140
-rw-r--r--apps/openmw/mwmechanics/character.cpp132
-rw-r--r--apps/openmw/mwmechanics/character.hpp8
-rw-r--r--apps/openmw/mwmechanics/creaturestats.cpp69
-rw-r--r--apps/openmw/mwmechanics/creaturestats.hpp27
-rw-r--r--apps/openmw/mwmechanics/enchanting.cpp45
-rw-r--r--apps/openmw/mwmechanics/enchanting.hpp1
-rw-r--r--apps/openmw/mwmechanics/magiceffects.cpp22
-rw-r--r--apps/openmw/mwmechanics/magiceffects.hpp16
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.cpp78
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.hpp16
-rw-r--r--apps/openmw/mwmechanics/npcstats.cpp4
-rw-r--r--apps/openmw/mwmechanics/objects.cpp10
-rw-r--r--apps/openmw/mwmechanics/objects.hpp1
-rw-r--r--apps/openmw/mwmechanics/pathfinding.cpp17
-rw-r--r--apps/openmw/mwmechanics/pathfinding.hpp12
-rw-r--r--apps/openmw/mwmechanics/repair.cpp7
-rw-r--r--apps/openmw/mwmechanics/security.cpp5
-rw-r--r--apps/openmw/mwmechanics/spellcasting.cpp500
-rw-r--r--apps/openmw/mwmechanics/spellcasting.hpp211
-rw-r--r--apps/openmw/mwmechanics/spells.cpp100
-rw-r--r--apps/openmw/mwmechanics/spells.hpp13
-rw-r--r--apps/openmw/mwmechanics/spellsuccess.hpp118
-rw-r--r--apps/openmw/mwmechanics/stat.hpp22
-rw-r--r--apps/openmw/mwrender/actors.cpp14
-rw-r--r--apps/openmw/mwrender/actors.hpp4
-rw-r--r--apps/openmw/mwrender/animation.cpp284
-rw-r--r--apps/openmw/mwrender/animation.hpp65
-rw-r--r--apps/openmw/mwrender/camera.cpp57
-rw-r--r--apps/openmw/mwrender/camera.hpp6
-rw-r--r--apps/openmw/mwrender/characterpreview.cpp27
-rw-r--r--apps/openmw/mwrender/compositors.cpp108
-rw-r--r--apps/openmw/mwrender/compositors.hpp64
-rw-r--r--apps/openmw/mwrender/localmap.cpp100
-rw-r--r--apps/openmw/mwrender/npcanimation.cpp353
-rw-r--r--apps/openmw/mwrender/npcanimation.hpp81
-rw-r--r--apps/openmw/mwrender/objects.cpp25
-rw-r--r--apps/openmw/mwrender/objects.hpp6
-rw-r--r--apps/openmw/mwrender/renderingmanager.cpp122
-rw-r--r--apps/openmw/mwrender/renderingmanager.hpp11
-rw-r--r--apps/openmw/mwrender/sky.cpp19
-rw-r--r--apps/openmw/mwrender/sky.hpp4
-rw-r--r--apps/openmw/mwrender/videoplayer.cpp43
-rw-r--r--apps/openmw/mwrender/water.cpp1
-rw-r--r--apps/openmw/mwrender/water.hpp2
-rw-r--r--apps/openmw/mwscript/aiextensions.cpp43
-rw-r--r--apps/openmw/mwscript/cellextensions.cpp14
-rw-r--r--apps/openmw/mwscript/containerextensions.cpp55
-rw-r--r--apps/openmw/mwscript/docs/vmformat.txt8
-rw-r--r--apps/openmw/mwscript/miscextensions.cpp67
-rw-r--r--apps/openmw/mwscript/statsextensions.cpp24
-rw-r--r--apps/openmw/mwscript/transformationextensions.cpp122
-rw-r--r--apps/openmw/mwsound/ffmpeg_decoder.cpp35
-rw-r--r--apps/openmw/mwsound/soundmanagerimp.cpp6
-rw-r--r--apps/openmw/mwworld/actionapply.hpp1
-rw-r--r--apps/openmw/mwworld/actioneat.cpp34
-rw-r--r--apps/openmw/mwworld/actionequip.cpp19
-rw-r--r--apps/openmw/mwworld/actionteleport.cpp8
-rw-r--r--apps/openmw/mwworld/actiontrap.cpp16
-rw-r--r--apps/openmw/mwworld/actiontrap.hpp29
-rw-r--r--apps/openmw/mwworld/cellfunctors.hpp8
-rw-r--r--apps/openmw/mwworld/cells.cpp16
-rw-r--r--apps/openmw/mwworld/cells.hpp7
-rw-r--r--apps/openmw/mwworld/cellstore.hpp7
-rw-r--r--apps/openmw/mwworld/class.cpp2
-rw-r--r--apps/openmw/mwworld/class.hpp4
-rw-r--r--apps/openmw/mwworld/containerstore.cpp123
-rw-r--r--apps/openmw/mwworld/containerstore.hpp32
-rw-r--r--apps/openmw/mwworld/esmstore.cpp2
-rw-r--r--apps/openmw/mwworld/inventorystore.cpp483
-rw-r--r--apps/openmw/mwworld/inventorystore.hpp95
-rw-r--r--apps/openmw/mwworld/manualref.hpp3
-rw-r--r--apps/openmw/mwworld/physicssystem.cpp28
-rw-r--r--apps/openmw/mwworld/player.cpp28
-rw-r--r--apps/openmw/mwworld/player.hpp24
-rw-r--r--apps/openmw/mwworld/ptr.cpp6
-rw-r--r--apps/openmw/mwworld/refdata.hpp5
-rw-r--r--apps/openmw/mwworld/scene.cpp17
-rw-r--r--apps/openmw/mwworld/scene.hpp2
-rw-r--r--apps/openmw/mwworld/store.hpp2
-rw-r--r--apps/openmw/mwworld/weather.cpp75
-rw-r--r--apps/openmw/mwworld/weather.hpp4
-rw-r--r--apps/openmw/mwworld/worldimp.cpp608
-rw-r--r--apps/openmw/mwworld/worldimp.hpp82
-rw-r--r--cmake/FindBullet.cmake5
-rw-r--r--cmake/FindOGRE.cmake1
-rw-r--r--components/CMakeLists.txt15
-rw-r--r--components/compiler/extensions0.cpp4
-rw-r--r--components/compiler/opcodes.hpp8
-rw-r--r--components/contentselector/model/contentmodel.cpp9
-rw-r--r--components/esm/cellref.hpp4
-rw-r--r--components/esm/loadinfo.cpp32
-rw-r--r--components/esm/loadinfo.hpp13
-rw-r--r--components/esm/loadland.cpp4
-rw-r--r--components/esm/loadland.hpp4
-rw-r--r--components/esm/loadmgef.cpp92
-rw-r--r--components/esm/loadmgef.hpp19
-rw-r--r--components/esm/loadnpc.cpp12
-rw-r--r--components/esm/loadnpc.hpp9
-rw-r--r--components/esm/loadtes3.cpp12
-rw-r--r--components/files/configurationmanager.cpp22
-rw-r--r--components/files/configurationmanager.hpp2
-rw-r--r--components/files/filelibrary.cpp120
-rw-r--r--components/files/filelibrary.hpp49
-rw-r--r--components/files/fileops.cpp120
-rw-r--r--components/files/fileops.hpp38
-rw-r--r--components/files/fixedpath.hpp29
-rw-r--r--components/files/linuxpath.cpp90
-rw-r--r--components/files/linuxpath.hpp22
-rw-r--r--components/files/macospath.cpp83
-rw-r--r--components/files/macospath.hpp6
-rw-r--r--components/files/windowspath.cpp14
-rw-r--r--components/files/windowspath.hpp6
-rw-r--r--components/interpreter/interpreter.cpp6
-rw-r--r--components/misc/utf8stream.hpp4
-rw-r--r--components/nif/controller.hpp2
-rw-r--r--components/nif/niffile.cpp2
-rw-r--r--components/nif/niffile.hpp2
-rw-r--r--components/nif/node.hpp3
-rw-r--r--components/nif/record.hpp1
-rw-r--r--components/nifogre/controller.hpp95
-rw-r--r--components/nifogre/material.cpp41
-rw-r--r--components/nifogre/material.hpp2
-rw-r--r--components/nifogre/mesh.cpp47
-rw-r--r--components/nifogre/ogrenifloader.cpp658
-rw-r--r--components/nifogre/ogrenifloader.hpp43
-rw-r--r--components/nifogre/particles.cpp (renamed from libs/openengine/ogre/particles.cpp)89
-rw-r--r--components/nifogre/particles.hpp (renamed from libs/openengine/ogre/particles.hpp)0
-rw-r--r--components/nifogre/skeleton.cpp14
-rw-r--r--components/ogreinit/ogreinit.cpp172
-rw-r--r--components/ogreinit/ogreinit.hpp75
-rw-r--r--components/ogreinit/ogreplugin.cpp (renamed from components/files/ogreplugin.cpp)0
-rw-r--r--components/ogreinit/ogreplugin.hpp (renamed from components/files/ogreplugin.hpp)0
-rw-r--r--components/terrain/material.cpp54
-rw-r--r--components/terrain/material.hpp10
-rw-r--r--components/terrain/quadtreenode.cpp30
-rw-r--r--components/terrain/storage.cpp36
-rw-r--r--components/terrain/storage.hpp13
-rw-r--r--extern/oics/tinyxml.h2
-rw-r--r--extern/sdl4ogre/cursormanager.hpp3
-rw-r--r--extern/sdl4ogre/events.h2
-rw-r--r--extern/sdl4ogre/sdlcursormanager.cpp23
-rw-r--r--extern/sdl4ogre/sdlcursormanager.hpp3
-rw-r--r--extern/sdl4ogre/sdlinputwrapper.cpp66
-rw-r--r--extern/sdl4ogre/sdlinputwrapper.hpp13
-rw-r--r--extern/shiny/Main/Factory.hpp2
-rw-r--r--extern/shiny/Platforms/Ogre/OgrePlatform.cpp20
-rw-r--r--extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp29
-rw-r--r--files/materials/core.h2
-rw-r--r--files/materials/objects.mat48
-rw-r--r--files/materials/objects.shader185
-rw-r--r--files/materials/terrain.shader156
-rw-r--r--files/materials/water.mat2
-rw-r--r--files/mygui/CMakeLists.txt2
-rw-r--r--files/mygui/openmw_chargen_birth.layout10
-rw-r--r--files/mygui/openmw_chargen_class.layout44
-rw-r--r--files/mygui/openmw_chargen_create_class.layout45
-rw-r--r--files/mygui/openmw_chargen_review.layout42
-rw-r--r--files/mygui/openmw_itemselection_dialog.layout6
-rw-r--r--files/mygui/openmw_list.skin.xml13
-rw-r--r--files/mygui/openmw_messagebox.layout10
-rw-r--r--files/mygui/openmw_recharge_dialog.layout29
-rw-r--r--files/mygui/openmw_resources.xml18
-rw-r--r--files/mygui/openmw_savegame_dialog.layout81
-rw-r--r--files/mygui/openmw_settings_window.layout25
-rw-r--r--files/mygui/openmw_stats_window.layout2
-rw-r--r--files/opencs.desktop1
-rw-r--r--files/openmw.desktop1
-rw-r--r--files/settings-default.cfg71
-rw-r--r--libs/openengine/bullet/BtOgre.cpp2
-rw-r--r--libs/openengine/bullet/CMotionState.cpp46
-rw-r--r--libs/openengine/bullet/CMotionState.h52
-rw-r--r--libs/openengine/bullet/physic.cpp10
-rw-r--r--libs/openengine/bullet/physic.hpp17
-rw-r--r--libs/openengine/gui/layout.hpp2
-rw-r--r--libs/openengine/ogre/renderer.cpp194
-rw-r--r--libs/openengine/ogre/renderer.hpp95
-rw-r--r--manual/opencs/.gitignore6
-rw-r--r--manual/opencs/creating_file.tex25
-rw-r--r--manual/opencs/files_and_directories.tex120
-rw-r--r--manual/opencs/filters.tex205
-rw-r--r--manual/opencs/img/water.pngbin0 -> 914 bytes
-rw-r--r--manual/opencs/main.tex34
-rw-r--r--manual/opencs/tables.tex103
-rw-r--r--manual/opencs/windows.tex54
-rw-r--r--readme.txt170
361 files changed, 11288 insertions, 4636 deletions
diff --git a/.gitignore b/.gitignore
index f22f1bd49c..c061ca637e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@ CMakeFiles
*/CMakeFiles
CMakeCache.txt
cmake_install.cmake
-CMakeLists.txt.user
Makefile
makefile
build
@@ -22,6 +21,8 @@ Doxygen
.project
.settings
.directory
+## qt-creator
+CMakeLists.txt.user*
## resources
data
diff --git a/.travis.yml b/.travis.yml
index caf2e33899..04d019c0d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,13 +9,13 @@ before_install:
- pwd
- git submodule update --init --recursive
- echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
- - echo "yes" | sudo apt-add-repository ppa:openmw/deps
+ - echo "yes" | sudo apt-add-repository ppa:openmw/openmw
- sudo apt-get update -qq
- - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev
+ - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
- - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev
+ - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build
- sudo cmake .. -DBUILD_SHARED_LIBS=1
@@ -26,7 +26,7 @@ before_script:
- cd -
- mkdir build
- cd build
- - cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1
+ - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1
script:
- make -j4
after_script:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01f02ddb96..927753f1fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,10 +4,6 @@ if (APPLE)
set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app")
set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}")
-
- set(CMAKE_EXE_LINKER_FLAGS "-F /Library/Frameworks")
- set(CMAKE_SHARED_LINKER_FLAGS "-F /Library/Frameworks")
- set(CMAKE_MODULE_LINKER_FLAGS "-F /Library/Frameworks")
endif (APPLE)
# Macros
@@ -19,7 +15,7 @@ include (OpenMWMacros)
# Version
set (OPENMW_VERSION_MAJOR 0)
-set (OPENMW_VERSION_MINOR 27)
+set (OPENMW_VERSION_MINOR 28)
set (OPENMW_VERSION_RELEASE 0)
set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
@@ -50,7 +46,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON)
# OS X deployment
option(OPENMW_OSX_DEPLOYMENT OFF)
-find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
+if(UNIX AND NOT APPLE)
+ option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF)
+ if(BUILD_WITH_DPKG)
+ find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
+ endif(BUILD_WITH_DPKG)
+endif(UNIX AND NOT APPLE)
# Location of morrowind data files
if (APPLE)
@@ -79,7 +80,6 @@ set(OENGINE_OGRE
${LIBDIR}/openengine/ogre/renderer.cpp
${LIBDIR}/openengine/ogre/fader.cpp
${LIBDIR}/openengine/ogre/lights.cpp
- ${LIBDIR}/openengine/ogre/particles.cpp
${LIBDIR}/openengine/ogre/selectionbuffer.cpp
${LIBDIR}/openengine/ogre/imagerotate.cpp
)
@@ -95,8 +95,6 @@ set(OENGINE_BULLET
${LIBDIR}/openengine/bullet/BtOgreExtras.h
${LIBDIR}/openengine/bullet/BtOgreGP.h
${LIBDIR}/openengine/bullet/BtOgrePG.h
- ${LIBDIR}/openengine/bullet/CMotionState.cpp
- ${LIBDIR}/openengine/bullet/CMotionState.h
${LIBDIR}/openengine/bullet/physic.cpp
${LIBDIR}/openengine/bullet/physic.hpp
${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp
@@ -190,10 +188,6 @@ if (MSVC10)
set(PLATFORM_INCLUDE_DIR "")
endif()
-if (APPLE)
- set(Boost_USE_STATIC_LIBS ON)
-endif (APPLE)
-
# Dependencies
# Fix for not visible pthreads functions for linker with glibc 2.15
@@ -208,7 +202,7 @@ if (HAVE_UNORDERED_MAP)
endif ()
-set(BOOST_COMPONENTS system filesystem program_options thread date_time wave)
+set(BOOST_COMPONENTS system filesystem program_options)
IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON)
@@ -246,8 +240,14 @@ if (APPLE)
# List used Ogre plugins
SET(USED_OGRE_PLUGINS ${OGRE_RenderSystem_GL_LIBRARY_REL}
${OGRE_Plugin_OctreeSceneManager_LIBRARY_REL}
- ${OGRE_Plugin_CgProgramManager_LIBRARY_REL}
${OGRE_Plugin_ParticleFX_LIBRARY_REL})
+
+ # Actually we must use OGRE_Plugin_CgProgramManager_FOUND but it's
+ # not reliable and equals TRUE even if there's no Ogre Cg plugin
+ if (Cg_FOUND)
+ set(USED_OGRE_PLUGINS ${USED_OGRE_PLUGINS}
+ ${OGRE_Plugin_CgProgramManager_LIBRARY_REL})
+ endif ()
if (${OGRE_PLUGIN_DIR_REL}})
set(OGRE_PLUGINS_REL_FOUND TRUE)
@@ -286,7 +286,8 @@ endif()
add_definitions(-DOGRE_PLUGIN_DIR_REL="${OGRE_PLUGIN_DIR_REL}")
add_definitions(-DOGRE_PLUGIN_DIR_DBG="${OGRE_PLUGIN_DIR_DBG}")
if (APPLE AND OPENMW_OSX_DEPLOYMENT)
- add_definitions(-DOGRE_PLUGIN_DIR="${APP_BUNDLE_NAME}/Contents/Plugins")
+ # make it empty so plugin loading code can check this and try to find plugins inside app bundle
+ add_definitions(-DOGRE_PLUGIN_DIR="")
else()
add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}")
endif()
@@ -319,7 +320,7 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg
"${OpenMW_BINARY_DIR}/opencs.cfg")
-
+
configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
"${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY)
@@ -687,9 +688,9 @@ if (APPLE)
set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
- set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app")
+ set(OPENCS_BUNDLE_NAME "OpenCS.app")
+ set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}")
- set(PLUGINS "")
set(ABSOLUTE_PLUGINS "")
foreach (PLUGIN ${USED_OGRE_PLUGINS})
@@ -697,12 +698,25 @@ if (APPLE)
set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS})
endforeach ()
- set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}/Contents/Plugins")
- install(FILES ${ABSOLUTE_PLUGINS} DESTINATION "${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}/Contents/Plugins" COMPONENT Runtime)
- foreach (PLUGIN ${ABSOLUTE_PLUGINS})
- get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME)
- set(PLUGINS ${PLUGINS} "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}")
- endforeach ()
+ # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX})
+ # and returns list of install paths for all installed plugins
+ function (install_plugins_for_bundle bundle_path plugins_var)
+ set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/Plugins")
+ install(FILES ${ABSOLUTE_PLUGINS} DESTINATION ${RELATIVE_PLUGIN_INSTALL_BASE} COMPONENT Runtime)
+
+ set(PLUGINS "")
+ set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}")
+
+ foreach (PLUGIN ${ABSOLUTE_PLUGINS})
+ get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME)
+ set(PLUGINS ${PLUGINS} "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}")
+ endforeach ()
+
+ set(${plugins_var} ${PLUGINS} PARENT_SCOPE)
+ endfunction (install_plugins_for_bundle)
+
+ install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS)
+ install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS)
#For now, search unresolved dependencies only in default system paths, so if you put unresolveable (i.e. with @executable_path in id name) lib or framework somewhere else, it would fail
set(DIRS "")
@@ -750,7 +764,7 @@ if (APPLE)
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
- fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\")
+ fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\")
" COMPONENT Runtime)
include(CPack)
endif (APPLE)
diff --git a/README_Mac.md b/README_Mac.md
deleted file mode 100644
index dc39183680..0000000000
--- a/README_Mac.md
+++ /dev/null
@@ -1,160 +0,0 @@
-#Getting OpenMW Working on OS X
-
-## Initial setup
-First of all, clone OpenMW repo.
-
- $ git clone github.com/zinnschlag/openmw
-
-Or use your github url if you forked.
-
-About dependencies: I prefer not to install them globally (i. e. in /usr/local/), so I'm installing them in directory in my home directory. If OpenMW sources is in $HOME/path/openmw, I'm using $HOME/path/libs/root as prefix for boost and other libs.
-
-It's useful to create env var for lib install prefix:
-
- $ export OMW_LIB_PREFIX=$HOME/path/libs/root`
-
-Most of libs can be installed from [Homebrew][homebrew]. Only mpg123 needs to be installed from source (due to lack of universal compilation support). I think that some of libs can be installed from MacPorts or Fink too.
-
-As OpenMW currently only supports i386 architecture on OS X, denendencies also should support it. Set some env vars in current terminal:
-
- $ export CFLAGS="-arch i386"
- $ export CXXFLAGS="-arch i386"
- $ export LDFLAGS="-arch i386"
-
-If you close your terminal, you should set env vars again before pcoceeding to next steps!
-
-## Boost
-Download [boost][boost] and install it with the following command:
-
- $ cd /path/to/boost/source
- $ ./bootstrap.sh --prefix=$OMW_LIB_PREFIX
- $ ./bjam --build-dir=build --layout=versioned \
- --toolset=darwin architecture=x86 address-model=32 \
- --link-shared,static --prefix=$OMW_LIB_PREFIX install
-
-
-Alternatively you can install boost with homebrew:
-
- $ brew install boost --universal
-
-I think MacPorts also should support universal build for boost.
-
-## Ogre
-Download [Ogre][] SDK (tested with 1.7.3), unpack it somewhere and move
-`lib/Release/Ogre.framework` into `/Library/Frameworks`.
-
-## OIS
-Download patched [OIS][] and use the XCode project provided. Be sure to set your build architecture to
- `i386`. Once it built, locate built OIS.framework with Xcode and move it to `/Library/Frameworks`.
-
-## mpg123
-Download [MPG 123][mpg123] and build it:
-
- $ cd /path/to/mpg123/source
- $ ./configure --prefix=$OMW_LIB_PREFIX --disable-debug \
- --disable-dependency-tracking \
- --with-optimization=4 \
- --with-audio=dummy \
- --with-default-audio=dummy \
- --with-cpu=sse_alone \
- $ make install
-
-## libsndfile
-Download [libsndfile][] and build it:
-
- $ cd /path/to/libsndfile/source
- $ ./configure --prefix=$OMW_LIB_PREFIX \
- --disable-dependency-tracking
- $ make install
-
-or install with homebrew:
-
- $ brew install libsndfile --universal
-
-## Bullet
-Download [Bullet][] and build it:
-
- $ cd /path/to/bullet/source
- $ mkdir build
- $ cd build
- $ cmake -DCMAKE_BUILD_TYPE=Release \
- -DCMAKE_INSTALL_PREFIX=$OMW_LIB_PREFIX \
- -DBUILD_EXTRAS=OFF \
- -DBUILD_DEMOS=OFF \
- -DCMAKE_OSX_ARCHITECTURES=i386 \
- -DCMAKE_INSTALL_NAME_DIR=$OMW_LIB_RPEFIX/lib \
- -G"Unix Makefiles" ../
- $ make install
-
-or install with homebrew:
-
- $ brew install bullet --HEAD --universal
-
-I prefer head because 2.79 has some issue which causes OpenMW to lag. Also you can edit formula and install 2.77, which is stable and haven't mentioned issue.
-
-## Qt
-Install [Qt][qt]. Qt SDK distributed by Nokia is not an option because it's 64 bit only, and OpenMW currently doesn't build for 64 bit on OS X. I'm installing it from Homebrew:
-
- $ brew install qt --universal
-
-## Run CMake
-Generate the Makefile for OpenMW as follows and build OpenMW:
-
- $ mkdir /path/to/openmw/build/dir
- $ cd /path/to/open/build/dir
- $ cmake \
- -D CMAKE_OSX_ARCHITECTURES=i386 \
- -D OGRE_SDK=/path/to/ogre/sdk \
- -D BOOST_INCLUDEDIR=$OMW_LIB_PREFIX/include/boost-1_45 \
- -D BOOST_LIBRARYDIR=$OMW_LIB_PREFIX/lib \
- -D SNDFILE_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
- -D SNDFILE_LIBRARY=$OMW_LIB_PREFIX/lib/libsndfile.a \
- -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \
- -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
- -D BULLET_DYNAMICS_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletDynamics.a \
- -D BULLET_COLLISION_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletCollision.a \
- -D BULLET_MATH_LIBRARY=$OMW_LIB_PREFIX/lib/libLinearMath.a \
- -D BULLET_SOFTBODY_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletSoftBody.a \
- -D BULLET_INCLUDE_DIR=$OMW_LIB_PREFIX/include/bullet/ \
- -G "Unix Makefiles" /path/to/openmw/source/dir
- $ make
-
-You can use `-G"Xcode"` if you prefer Xcode, or -G"Eclipse CDT4 - Unix Makefiles"
-if you prefer Eclipse. You also can specify `-D CMAKE_BUILD_TYPE=Debug` for debug
-build. As for CMake 2.8.7 and Xcode 4.3, Xcode generator is broken. Sadly Eclipse CDT also cannot import generated project at least on my machine.
-
-If all libs installed via homebrew (excluding mpg123), then command would be even simplier:
-
- $ cmake \
- -D CMAKE_OSX_ARCHITECTURES="i386" \
- -D OGRE_SDK=/path/to/ogre/sdk \
- -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \
- -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
- -G "Unix Makefiles" /path/to/openmw/source/dir
- $ make
-
-Note for users with recent Xcode versions: you must explicitly specify what set of compilers do you use! If not, gcc will be used for C and Clang for C++. Just add this two -D's to command: `-D CMAKE_C_COMPILER=/usr/bin/clang` and `-D CMAKE_CXX_COMPILER=/usr/bin/clang`
-
-Note for Xcode 4.3 users: you should specify full path to used SDK, because current CMake (2.8.7) couldn't find SDKs inside Xcode app bundle:
-
- -D CMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk"
-
-# Run
-From your build directory run:
-
- $ OpenMW.app/Contents/MacOS/openmw
-or:
-
- $ open OpenMW.app
-Enjoy!
-
-[homebrew]: https://github.com/mxcl/homebrew
-[boost]: http://www.boost.org
-[Ogre]: http://www.ogre3d.org
-[Bullet]: http://bulletphysics.org
-[OIS]: https://github.com/corristo/ois-fork
-[mpg123]: http://www.mpg123.de
-[libsndfile]: http://www.mega-nerd.com/libsndfile
-[official website]: http://openmw.com
-[Will Thimbleby's Ogre Framework]: http://www.thimbleby.net/ogre/
-[qt]: http://qt.nokia.com/ \ No newline at end of file
diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp
index e6fcc2567d..3781dd066e 100644
--- a/apps/bsatool/bsatool.cpp
+++ b/apps/bsatool/bsatool.cpp
@@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info)
{
// List all files
const Bsa::BSAFile::FileList &files = bsa.getList();
- for(int i=0; i<files.size(); i++)
+ for(unsigned int i=0; i<files.size(); i++)
{
if(info.longformat)
{
diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp
index 6ccf9c3f3b..27980096e6 100644
--- a/apps/esmtool/esmtool.cpp
+++ b/apps/esmtool/esmtool.cpp
@@ -339,6 +339,8 @@ int load(Arguments& info)
}
std::string id = esm.getHNOString("NAME");
+ if (id.empty())
+ id = esm.getHNOString("INAM");
if(!quiet && interested)
std::cout << "\nRecord: " << n.toString()
diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp
index 68e0dcc09d..cc09452c91 100644
--- a/apps/esmtool/record.cpp
+++ b/apps/esmtool/record.cpp
@@ -740,8 +740,8 @@ void Record<ESM::DialInfo>::print()
if (mData.mClass != "")
std::cout << " Class: " << mData.mClass << std::endl;
std::cout << " Factionless: " << mData.mFactionLess << std::endl;
- if (mData.mNpcFaction != "")
- std::cout << " NPC Faction: " << mData.mNpcFaction << std::endl;
+ if (mData.mFaction != "")
+ std::cout << " NPC Faction: " << mData.mFaction << std::endl;
if (mData.mData.mRank != -1)
std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl;
if (mData.mPcFaction != "")
@@ -989,8 +989,7 @@ void Record<ESM::NPC>::print()
std::cout << " Faction: " << mData.mFaction << std::endl;
std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl;
- // Seriously?
- if (mData.mNpdt52.mGold == -10)
+ if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl;
std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl;
@@ -1022,7 +1021,7 @@ void Record<ESM::NPC>::print()
std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl;
std::cout << " Skills:" << std::endl;
- for (int i = 0; i != 27; i++)
+ for (int i = 0; i != ESM::Skill::Length; i++)
std::cout << " " << skillLabel(i) << ": "
<< (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl;
diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp
index 78cf5d436e..45b6d04266 100644
--- a/apps/esmtool/record.hpp
+++ b/apps/esmtool/record.hpp
@@ -24,7 +24,12 @@ namespace EsmTool
bool mPrintPlain;
public:
- RecordBase () { mPrintPlain = false; }
+ RecordBase ()
+ : mFlags(0)
+ , mPrintPlain(false)
+ {
+ }
+
virtual ~RecordBase() {}
const std::string &getId() const {
diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt
index 18c555a249..e4638c31b4 100644
--- a/apps/launcher/CMakeLists.txt
+++ b/apps/launcher/CMakeLists.txt
@@ -137,8 +137,3 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(omwlauncher gcov)
endif()
-# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream
-if (UNIX AND NOT APPLE)
-target_link_libraries(omwlauncher dl Xt)
-endif()
-
diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp
index 734277b97d..362d7562c6 100644
--- a/apps/launcher/datafilespage.cpp
+++ b/apps/launcher/datafilespage.cpp
@@ -75,14 +75,8 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText());
foreach(const ContentSelectorModel::EsmFile *item, items) {
-
- if (item->gameFiles().size() == 0) {
- mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
- mGameSettings.setMultiValue(QString("content"), item->fileName());
- } else {
- mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
- mGameSettings.setMultiValue(QString("content"), item->fileName());
- }
+ mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
+ mGameSettings.setMultiValue(QString("content"), item->fileName());
}
}
@@ -116,8 +110,7 @@ void Launcher::DataFilesPage::buildView()
void Launcher::DataFilesPage::removeProfile(const QString &profile)
{
- mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game"));
- mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon"));
+ mLauncherSettings.remove(QString("Profiles/") + profile);
}
QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const
diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp
index 516c3d8233..8abdf00198 100644
--- a/apps/launcher/graphicspage.cpp
+++ b/apps/launcher/graphicspage.cpp
@@ -12,12 +12,9 @@
#include <SDL.h>
-#include <cstdlib>
-
#include <boost/math/common_factor.hpp>
#include <components/files/configurationmanager.hpp>
-#include <components/files/ogreplugin.hpp>
#include <components/contentselector/model/naturalsort.hpp>
@@ -57,13 +54,9 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsS
bool Launcher::GraphicsPage::setupOgre()
{
- // Create a log manager so we can surpress debug text to stdout/stderr
- Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager;
- logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false);
-
try
{
- mOgre = new Ogre::Root("", "", "./launcherOgre.log");
+ mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log");
}
catch(Ogre::Exception &ex)
{
@@ -81,40 +74,6 @@ bool Launcher::GraphicsPage::setupOgre()
return false;
}
-
- std::string pluginDir;
- const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR");
- if (pluginEnv)
- pluginDir = pluginEnv;
- else
- {
-#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- pluginDir = ".\\";
-#endif
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- pluginDir = OGRE_PLUGIN_DIR;
-#endif
-#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
- pluginDir = OGRE_PLUGIN_DIR_REL;
-#endif
- }
-
- QDir dir(QString::fromStdString(pluginDir));
- pluginDir = dir.absolutePath().toStdString();
-
- Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre);
- Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mOgre);
- Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre);
-
-#ifdef ENABLE_PLUGIN_GL
- mGLPlugin = new Ogre::GLPlugin();
- mOgre->installPlugin(mGLPlugin);
-#endif
-#ifdef ENABLE_PLUGIN_Direct3D9
- mD3D9Plugin = new Ogre::D3D9Plugin();
- mOgre->installPlugin(mD3D9Plugin);
-#endif
-
// Get the available renderers and put them in the combobox
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp
index 7f5dcae1ee..da4cb9fb33 100644
--- a/apps/launcher/graphicspage.hpp
+++ b/apps/launcher/graphicspage.hpp
@@ -5,16 +5,9 @@
#include <OgreRoot.h>
#include <OgreRenderSystem.h>
-//#include <OgreConfigFile.h>
-//#include <OgreConfigDialog.h>
-// Static plugin headers
-#ifdef ENABLE_PLUGIN_GL
-# include "OgreGLPlugin.h"
-#endif
-#ifdef ENABLE_PLUGIN_Direct3D9
-# include "OgreD3D9Plugin.h"
-#endif
+#include <components/ogreinit/ogreinit.hpp>
+
#include "ui_graphicspage.h"
@@ -44,16 +37,11 @@ namespace Launcher
void slotStandardToggled(bool checked);
private:
+ OgreInit::OgreInit mOgreInit;
Ogre::Root *mOgre;
Ogre::RenderSystem *mSelectedRenderSystem;
Ogre::RenderSystem *mOpenGLRenderSystem;
Ogre::RenderSystem *mDirect3DRenderSystem;
- #ifdef ENABLE_PLUGIN_GL
- Ogre::GLPlugin* mGLPlugin;
- #endif
- #ifdef ENABLE_PLUGIN_Direct3D9
- Ogre::D3D9Plugin* mD3D9Plugin;
- #endif
Files::ConfigurationManager &mCfgMgr;
GraphicsSettings &mGraphicsSettings;
diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp
index 9f89f28102..156bbf65bc 100644
--- a/apps/launcher/main.cpp
+++ b/apps/launcher/main.cpp
@@ -12,8 +12,6 @@
#include <SDL.h>
#include "maindialog.hpp"
-// SDL workaround
-#include "graphicspage.hpp"
int main(int argc, char *argv[])
{
diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp
index 4012a1fbd5..9b3c4e1b02 100644
--- a/apps/launcher/maindialog.cpp
+++ b/apps/launcher/maindialog.cpp
@@ -219,7 +219,7 @@ bool Launcher::MainDialog::showFirstRunDialog()
}
// Create the file if it doesn't already exist, else the importer will fail
- QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg");
+ QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg");
QFile file(path);
if (!file.exists()) {
@@ -334,7 +334,7 @@ bool Launcher::MainDialog::setupLauncherSettings()
{
mLauncherSettings.setMultiValueEnabled(true);
- QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QStringList paths;
paths.append(QString("launcher.cfg"));
@@ -440,9 +440,35 @@ bool Launcher::expansions(Launcher::UnshieldThread& cd)
bool Launcher::MainDialog::setupGameSettings()
{
- QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
+ // Load the user config file first, separately
+ // So we can write it properly, uncontaminated
+ QString path = userPath + QLatin1String("openmw.cfg");
+ QFile file(path);
+
+ qDebug() << "Loading config file:" << qPrintable(path);
+
+ if (file.exists()) {
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(QObject::tr("<br><b>Could not open %0 for reading</b><br><br> \
+ Please make sure you have the right permissions \
+ and try again.<br>").arg(file.fileName()));
+ msgBox.exec();
+ return false;
+ }
+ QTextStream stream(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
+
+ mGameSettings.readUserFile(stream);
+ }
+
+ // Now the rest
QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
@@ -565,7 +591,7 @@ bool Launcher::MainDialog::setupGraphicsSettings()
{
mGraphicsSettings.setMultiValueEnabled(false);
- QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
QFile localDefault(QString("settings-default.cfg"));
@@ -652,7 +678,7 @@ bool Launcher::MainDialog::writeSettings()
mGraphicsPage->saveSettings();
mDataFilesPage->saveSettings();
- QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QDir dir(userPath);
if (!dir.exists()) {
diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp
index 41113c35aa..e7e5cf1ea7 100644
--- a/apps/launcher/settings/gamesettings.cpp
+++ b/apps/launcher/settings/gamesettings.cpp
@@ -91,6 +91,16 @@ QStringList Launcher::GameSettings::values(const QString &key, const QStringList
bool Launcher::GameSettings::readFile(QTextStream &stream)
{
+ return readFile(stream, mSettings);
+}
+
+bool Launcher::GameSettings::readUserFile(QTextStream &stream)
+{
+ return readFile(stream, mUserSettings);
+}
+
+bool Launcher::GameSettings::readFile(QTextStream &stream, QMap<QString, QString> &settings)
+{
QMap<QString, QString> cache;
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
@@ -107,10 +117,10 @@ bool Launcher::GameSettings::readFile(QTextStream &stream)
// Don't remove existing data entries
if (key != QLatin1String("data"))
- mSettings.remove(key);
+ settings.remove(key);
QStringList values = cache.values(key);
- values.append(mSettings.values(key));
+ values.append(settings.values(key));
if (!values.contains(value)) {
cache.insertMulti(key, value);
@@ -118,23 +128,24 @@ bool Launcher::GameSettings::readFile(QTextStream &stream)
}
}
- if (mSettings.isEmpty()) {
- mSettings = cache; // This is the first time we read a file
+ if (settings.isEmpty()) {
+ settings = cache; // This is the first time we read a file
validatePaths();
return true;
}
// Merge the changed keys with those which didn't
- mSettings.unite(cache);
+ settings.unite(cache);
validatePaths();
return true;
}
+
bool Launcher::GameSettings::writeFile(QTextStream &stream)
{
// Iterate in reverse order to preserve insertion order
- QMapIterator<QString, QString> i(mSettings);
+ QMapIterator<QString, QString> i(mUserSettings);
i.toBack();
while (i.hasPrevious()) {
@@ -162,7 +173,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream)
}
- QStringList content = mSettings.values(QString("content"));
+ QStringList content = mUserSettings.values(QString("content"));
for (int i = content.count(); i--;) {
stream << "content=" << content.at(i) << "\n";
}
@@ -172,14 +183,14 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream)
bool Launcher::GameSettings::hasMaster()
{
- bool result = false;
- QStringList content = mSettings.values(QString("content"));
- for (int i = 0; i < content.count(); ++i) {
- if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) {
- result = true;
- break;
+ bool result = false;
+ QStringList content = mSettings.values(QString("content"));
+ for (int i = 0; i < content.count(); ++i) {
+ if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) {
+ result = true;
+ break;
+ }
}
- }
- return result;
+ return result;
}
diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp
index 60236200a9..df82150742 100644
--- a/apps/launcher/settings/gamesettings.hpp
+++ b/apps/launcher/settings/gamesettings.hpp
@@ -31,6 +31,7 @@ namespace Launcher
inline void setValue(const QString &key, const QString &value)
{
mSettings.insert(key, value);
+ mUserSettings.insert(key, value);
}
inline void setMultiValue(const QString &key, const QString &value)
@@ -38,11 +39,16 @@ namespace Launcher
QStringList values = mSettings.values(key);
if (!values.contains(value))
mSettings.insertMulti(key, value);
+
+ values = mUserSettings.values(key);
+ if (!values.contains(value))
+ mUserSettings.insertMulti(key, value);
}
inline void remove(const QString &key)
{
mSettings.remove(key);
+ mUserSettings.remove(key);
}
inline QStringList getDataDirs() { return mDataDirs; }
@@ -52,7 +58,11 @@ namespace Launcher
bool hasMaster();
QStringList values(const QString &key, const QStringList &defaultValues = QStringList());
+
bool readFile(QTextStream &stream);
+ bool readFile(QTextStream &stream, QMap<QString, QString> &settings);
+ bool readUserFile(QTextStream &stream);
+
bool writeFile(QTextStream &stream);
private:
@@ -60,6 +70,7 @@ namespace Launcher
void validatePaths();
QMap<QString, QString> mSettings;
+ QMap<QString, QString> mUserSettings;
QStringList mDataDirs;
QString mDataLocal;
diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp
index b8b7e4c9da..cf15891144 100644
--- a/apps/mwiniimporter/importer.cpp
+++ b/apps/mwiniimporter/importer.cpp
@@ -16,7 +16,7 @@ MwIniImporter::MwIniImporter()
const char *map[][2] =
{
{ "fps", "General:Show FPS" },
- { "nosound", "General:Disable Audio" },
+ { "no-sound", "General:Disable Audio" },
{ 0, 0 }
};
const char *fallback[] = {
diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
index f87650b331..e2dffdbde4 100644
--- a/apps/opencs/CMakeLists.txt
+++ b/apps/opencs/CMakeLists.txt
@@ -24,11 +24,11 @@ opencs_units (model/world
opencs_units_noqt (model/world
universalid record commands columnbase scriptcontext cell refidcollection
- refidadapter refiddata refidadapterimp ref collectionbase refcollection columns
+ refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection
)
opencs_hdrs_noqt (model/world
- columnimp idcollection collection
+ columnimp idcollection collection info
)
@@ -60,9 +60,13 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
- scenetoolmode
+ scenetoolmode infocreator
)
+opencs_units (view/render
+ scenewidget
+ )
+
opencs_units_noqt (view/world
dialoguesubview subviews
enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
@@ -139,7 +143,7 @@ if(WIN32)
set(QT_USE_QTMAIN TRUE)
endif(WIN32)
-find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED)
+find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
include(${QT_USE_FILE})
qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp
index e879cb25e8..5d5ac4c551 100644
--- a/apps/opencs/editor.cpp
+++ b/apps/opencs/editor.cpp
@@ -6,9 +6,11 @@
#include <QLocalSocket>
#include <QMessageBox>
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+
#include "model/doc/document.hpp"
#include "model/world/data.hpp"
-#include <iostream>
CS::Editor::Editor()
: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager)
@@ -84,10 +86,6 @@ void CS::Editor::setupDataFiles()
return;
}
- // Set the charset for reading the esm/esp files
- // QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
- //mFileDialog.setEncoding(encoding);
-
dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end());
mDocumentManager.setResourceDir (variables["resources"].as<std::string>());
@@ -212,6 +210,26 @@ int CS::Editor::run()
if (mLocal.empty())
return 1;
+// temporarily disable OGRE-integration (need to fix path problem first)
+#if 0
+ // TODO: setting
+ Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem"));
+
+ Ogre::Root::getSingleton().initialise(false);
+
+ // Create a hidden background window to keep resources
+ Ogre::NameValuePairList params;
+ params.insert(std::make_pair("title", ""));
+ params.insert(std::make_pair("FSAA", "0"));
+ params.insert(std::make_pair("vsync", "false"));
+ params.insert(std::make_pair("hidden", "true"));
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ params.insert(std::make_pair("macAPI", "cocoa"));
+#endif
+ Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, &params);
+ hiddenWindow->setActive(false);
+#endif
+
mStartup.show();
QApplication::setQuitOnLastWindowClosed (true);
diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp
index 344a9360f6..931d633127 100644
--- a/apps/opencs/main.cpp
+++ b/apps/opencs/main.cpp
@@ -7,6 +7,8 @@
#include <QApplication>
#include <QIcon>
+#include <components/ogreinit/ogreinit.hpp>
+
#ifdef Q_OS_MAC
#include <QDir>
#endif
@@ -37,7 +39,16 @@ class Application : public QApplication
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE (resources);
+
+ // TODO: Ogre startup shouldn't be here, but it currently has to:
+ // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :(
+
Application mApplication (argc, argv);
+// temporarily disable OGRE-integration (need to fix path problem first)
+#if 0
+ OgreInit::OgreInit ogreInit;
+ ogreInit.init("./opencsOgre.log"); // TODO log path?
+#endif
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp
index 27f4f498a4..3ef14ee7e5 100644
--- a/apps/opencs/model/doc/document.cpp
+++ b/apps/opencs/model/doc/document.cpp
@@ -2221,7 +2221,7 @@ void CSMDoc::Document::createBase()
CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_)
: mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir),
- mProjectPath ((configuration.getUserPath() / "projects") /
+ mProjectPath ((configuration.getUserDataPath() / "projects") /
(savePath.filename().string() + ".project")),
mSaving (*this, mProjectPath)
{
@@ -2254,7 +2254,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co
}
else
{
- boost::filesystem::path locCustomFiltersPath (configuration.getUserPath());
+ boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath());
locCustomFiltersPath /= "defaultfilters";
if (boost::filesystem::exists(locCustomFiltersPath))
diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp
index 024c46beae..3ff75c9c15 100644
--- a/apps/opencs/model/doc/documentmanager.cpp
+++ b/apps/opencs/model/doc/documentmanager.cpp
@@ -15,7 +15,7 @@
CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration)
: mConfiguration (configuration)
{
- boost::filesystem::path projectPath = configuration.getUserPath() / "projects";
+ boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects";
if (!boost::filesystem::is_directory (projectPath))
boost::filesystem::create_directories (projectPath);
@@ -53,4 +53,4 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document)
void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir)
{
mResDir = boost::filesystem::system_complete(parResDir);
-} \ No newline at end of file
+}
diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp
index b756a9dc19..73278ba975 100644
--- a/apps/opencs/model/doc/saving.cpp
+++ b/apps/opencs/model/doc/saving.cpp
@@ -58,12 +58,9 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Spell> >
(mDocument.getData().getSpells(), mState));
- /// \todo deal with info records for topcis and journals
- appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Dialogue> >
- (mDocument.getData().getTopics(), mState));
+ appendStage (new WriteDialogueCollectionStage (mDocument, mState, false));
- appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Dialogue> >
- (mDocument.getData().getJournals(), mState));
+ appendStage (new WriteDialogueCollectionStage (mDocument, mState, true));
appendStage (new WriteRefIdCollectionStage (mDocument, mState));
diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp
index d68c723179..8e9bcfc0de 100644
--- a/apps/opencs/model/doc/savingstages.cpp
+++ b/apps/opencs/model/doc/savingstages.cpp
@@ -7,6 +7,10 @@
#include <QUndoStack>
+#include <components/esm/loaddial.hpp>
+
+#include "../world/infocollection.hpp"
+
#include "document.hpp"
#include "savingstate.hpp"
@@ -80,6 +84,115 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector<std::string>& mes
}
+CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document,
+ SavingState& state, bool journal)
+: mDocument (document), mState (state),
+ mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()),
+ mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos())
+{}
+
+int CSMDoc::WriteDialogueCollectionStage::setup()
+{
+ return mTopics.getSize();
+}
+
+void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const CSMWorld::Record<ESM::Dialogue>& topic = mTopics.getRecord (stage);
+
+ CSMWorld::RecordBase::State state = topic.mState;
+
+ if (state==CSMWorld::RecordBase::State_Deleted)
+ {
+ // if the topic is deleted, we do not need to bother with INFO records.
+
+ /// \todo wrote record with delete flag
+
+ return;
+ }
+
+ // Test, if we need to save anything associated info records.
+ bool infoModified = false;
+
+ CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId);
+
+ for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
+ {
+ CSMWorld::RecordBase::State state = iter->mState;
+
+ if (state==CSMWorld::RecordBase::State_Modified ||
+ state==CSMWorld::RecordBase::State_ModifiedOnly ||
+ state==CSMWorld::RecordBase::State_Deleted)
+ {
+ infoModified = true;
+ break;
+ }
+ }
+
+ if (state==CSMWorld::RecordBase::State_Modified ||
+ state==CSMWorld::RecordBase::State_ModifiedOnly ||
+ infoModified)
+ {
+ // always write the topic record
+ std::string type;
+ for (int i=0; i<4; ++i)
+ /// \todo make endianess agnostic (change ESMWriter interface?)
+ type += reinterpret_cast<const char *> (&topic.mModified.sRecordId)[i];
+
+ mState.getWriter().startRecord (type);
+ mState.getWriter().writeHNCString ("NAME", topic.mModified.mId);
+ topic.mModified.save (mState.getWriter());
+ mState.getWriter().endRecord (type);
+
+ // write modified selected info records
+ for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second;
+ ++iter)
+ {
+ CSMWorld::RecordBase::State state = iter->mState;
+
+ if (state==CSMWorld::RecordBase::State_Deleted)
+ {
+ /// \todo wrote record with delete flag
+ }
+ else if (state==CSMWorld::RecordBase::State_Modified ||
+ state==CSMWorld::RecordBase::State_ModifiedOnly)
+ {
+ ESM::DialInfo info = iter->get();
+ info.mId = info.mId.substr (info.mId.find_last_of ('#')+1);
+
+ if (iter!=range.first)
+ {
+ CSMWorld::InfoCollection::RecordConstIterator prev = iter;
+ --prev;
+
+ info.mPrev =
+ prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1);
+ }
+
+ CSMWorld::InfoCollection::RecordConstIterator next = iter;
+ ++next;
+
+ if (next!=range.second)
+ {
+ info.mNext =
+ next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1);
+ }
+
+ std::string type;
+ for (int i=0; i<4; ++i)
+ /// \todo make endianess agnostic (change ESMWriter interface?)
+ type += reinterpret_cast<const char *> (&info.sRecordId)[i];
+
+ mState.getWriter().startRecord (type);
+ mState.getWriter().writeHNCString ("INAM", info.mId);
+ info.save (mState.getWriter());
+ mState.getWriter().endRecord (type);
+ }
+ }
+ }
+}
+
+
CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state)
: mDocument (document), mState (state)
{}
diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp
index ff94116fdb..ca5586511d 100644
--- a/apps/opencs/model/doc/savingstages.hpp
+++ b/apps/opencs/model/doc/savingstages.hpp
@@ -3,13 +3,23 @@
#include "stage.hpp"
-#include "savingstate.hpp"
-
#include "../world/record.hpp"
#include "../world/idcollection.hpp"
#include "../filter/filter.hpp"
+#include "savingstate.hpp"
+
+namespace ESM
+{
+ struct Dialogue;
+}
+
+namespace CSMWorld
+{
+ class InfoCollection;
+}
+
namespace CSMDoc
{
class Document;
@@ -106,6 +116,25 @@ namespace CSMDoc
}
+ class WriteDialogueCollectionStage : public Stage
+ {
+ Document& mDocument;
+ SavingState& mState;
+ const CSMWorld::IdCollection<ESM::Dialogue>& mTopics;
+ CSMWorld::InfoCollection& mInfos;
+
+ public:
+
+ WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this stage will be appended to \a messages.
+ };
+
+
class WriteRefIdCollectionStage : public Stage
{
Document& mDocument;
diff --git a/apps/opencs/model/filter/andnode.hpp b/apps/opencs/model/filter/andnode.hpp
index 980ea554c6..8871757008 100644
--- a/apps/opencs/model/filter/andnode.hpp
+++ b/apps/opencs/model/filter/andnode.hpp
@@ -7,8 +7,6 @@ namespace CSMFilter
{
class AndNode : public NAryNode
{
- bool mAnd;
-
public:
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);
diff --git a/apps/opencs/model/filter/ornode.hpp b/apps/opencs/model/filter/ornode.hpp
index 63ed2f10ce..c39e350956 100644
--- a/apps/opencs/model/filter/ornode.hpp
+++ b/apps/opencs/model/filter/ornode.hpp
@@ -7,8 +7,6 @@ namespace CSMFilter
{
class OrNode : public NAryNode
{
- bool mAnd;
-
public:
OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);
diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp
index 8f4fcb70c9..6e286d943b 100644
--- a/apps/opencs/model/filter/parser.cpp
+++ b/apps/opencs/model/filter/parser.cpp
@@ -61,11 +61,11 @@ namespace CSMFilter
bool isString() const;
};
- Token::Token (Type type) : mType (type) {}
+ Token::Token (Type type) : mType (type), mNumber(0.0) {}
- Token::Token (Type type, const std::string& string) : mType (type), mString (string) {}
+ Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {}
- Token::Token (const std::string& string) : mType (Type_String), mString (string) {}
+ Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {}
Token::Token (double number) : mType (Type_Number), mNumber (number) {}
@@ -614,4 +614,4 @@ boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
throw std::logic_error ("No filter available");
return mFilter;
-} \ No newline at end of file
+}
diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp
index a1daee4ac9..87a85e8e4e 100644
--- a/apps/opencs/model/settings/settingsitem.hpp
+++ b/apps/opencs/model/settings/settingsitem.hpp
@@ -44,7 +44,11 @@ namespace CSMSettings
inline QStringPair *getValuePair() { return mValuePair; }
/// set value range (spinbox / integer use)
- inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); }
+ inline void setValuePair (QStringPair valuePair)
+ {
+ delete mValuePair;
+ mValuePair = new QStringPair(valuePair);
+ }
inline bool isMultivalue () { return mIsMultiValue; }
diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp
index 1ce28ed75c..94cee8a43b 100644
--- a/apps/opencs/model/settings/usersettings.cpp
+++ b/apps/opencs/model/settings/usersettings.cpp
@@ -251,7 +251,7 @@ void CSMSettings::UserSettings::loadSettings (const QString &fileName)
bool localOk = loadFromFile(localFilePath);
//user
- mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName;
+ mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName;
loadFromFile(mUserFilePath);
if (!(localOk || globalOk))
diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp
index 84a00cef82..df144ba7bf 100644
--- a/apps/opencs/model/world/collection.hpp
+++ b/apps/opencs/model/world/collection.hpp
@@ -51,6 +51,18 @@ namespace CSMWorld
Collection (const Collection&);
Collection& operator= (const Collection&);
+ protected:
+
+ const std::map<std::string, int>& getIdMap() const;
+
+ const std::vector<Record<ESXRecordT> >& getRecords() const;
+
+ bool reorderRowsImp (int baseIndex, const std::vector<int>& newOrder);
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+ ///
+ /// \return Success?
+
public:
Collection();
@@ -104,7 +116,8 @@ namespace CSMWorld
virtual const Record<ESXRecordT>& getRecord (int index) const;
- virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const;
+ virtual int getAppendIndex (const std::string& id,
+ UniversalId::Type type = UniversalId::Type_None) const;
///< \param type Will be ignored, unless the collection supports multiple record types
virtual std::vector<std::string> getIds (bool listDeleted = true) const;
@@ -112,6 +125,21 @@ namespace CSMWorld
///
/// \param listDeleted include deleted record in the list
+ virtual void insertRecord (const RecordBase& record, int index,
+ UniversalId::Type type = UniversalId::Type_None);
+ ///< Insert record before index.
+ ///
+ /// If the record type does not match, an exception is thrown.
+ ///
+ /// If the index is invalid either generally (by being out of range) or for the particular
+ /// record, an exception is thrown.
+
+ virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder);
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+ ///
+ /// \return Success?
+
void addColumn (Column<ESXRecordT> *column);
void setRecord (int index, const Record<ESXRecordT>& record);
@@ -119,6 +147,53 @@ namespace CSMWorld
};
template<typename ESXRecordT, typename IdAccessorT>
+ const std::map<std::string, int>& Collection<ESXRecordT, IdAccessorT>::getIdMap() const
+ {
+ return mIndex;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ const std::vector<Record<ESXRecordT> >& Collection<ESXRecordT, IdAccessorT>::getRecords() const
+ {
+ return mRecords;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ bool Collection<ESXRecordT, IdAccessorT>::reorderRowsImp (int baseIndex,
+ const std::vector<int>& newOrder)
+ {
+ if (!newOrder.empty())
+ {
+ int size = static_cast<int> (newOrder.size());
+
+ // check that all indices are present
+ std::vector<int> test (newOrder);
+ std::sort (test.begin(), test.end());
+ if (*test.begin()!=0 || *--test.end()!=size-1)
+ return false;
+
+ // reorder records
+ std::vector<Record<ESXRecordT> > buffer (size);
+
+ for (int i=0; i<size; ++i)
+ {
+ buffer[newOrder[i]] = mRecords [baseIndex+i];
+ buffer[newOrder[i]].setModified (buffer[newOrder[i]].get());
+ }
+
+ std::copy (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex);
+
+ // adjust index
+ for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end();
+ ++iter)
+ if (iter->second>=baseIndex && iter->second<baseIndex+size)
+ iter->second = newOrder.at (iter->second-baseIndex)+baseIndex;
+ }
+
+ return true;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
Collection<ESXRecordT, IdAccessorT>::Collection()
{}
@@ -142,8 +217,7 @@ namespace CSMWorld
record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record;
- mRecords.push_back (record2);
- mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1));
+ insertRecord (record2, getAppendIndex (id));
}
else
{
@@ -260,7 +334,12 @@ namespace CSMWorld
ESXRecordT record;
IdAccessorT().getId (record) = id;
record.blank();
- add (record);
+
+ Record<ESXRecordT> record2;
+ record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
+ record2.mModified = record;
+
+ insertRecord (record2, getAppendIndex (id, type), type);
}
template<typename ESXRecordT, typename IdAccessorT>
@@ -286,14 +365,14 @@ namespace CSMWorld
void Collection<ESXRecordT, IdAccessorT>::appendRecord (const RecordBase& record,
UniversalId::Type type)
{
- mRecords.push_back (dynamic_cast<const Record<ESXRecordT>&> (record));
- mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId (
- dynamic_cast<const Record<ESXRecordT>&> (record).get())),
- mRecords.size()-1));
+ insertRecord (record,
+ getAppendIndex (IdAccessorT().getId (
+ dynamic_cast<const Record<ESXRecordT>&> (record).get()), type), type);
}
template<typename ESXRecordT, typename IdAccessorT>
- int Collection<ESXRecordT, IdAccessorT>::getAppendIndex (UniversalId::Type type) const
+ int Collection<ESXRecordT, IdAccessorT>::getAppendIndex (const std::string& id,
+ UniversalId::Type type) const
{
return static_cast<int> (mRecords.size());
}
@@ -327,6 +406,29 @@ namespace CSMWorld
}
template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::insertRecord (const RecordBase& record, int index,
+ UniversalId::Type type)
+ {
+ if (index<0 || index>static_cast<int> (mRecords.size()))
+ throw std::runtime_error ("index out of range");
+
+ const Record<ESXRecordT>& record2 = dynamic_cast<const Record<ESXRecordT>&> (record);
+
+ mRecords.insert (mRecords.begin()+index, record2);
+
+ if (index<static_cast<int> (mRecords.size())-1)
+ {
+ for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end();
+ ++iter)
+ if (iter->second>=index)
+ ++(iter->second);
+ }
+
+ mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId (
+ record2.get())), index));
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::setRecord (int index, const Record<ESXRecordT>& record)
{
if (IdAccessorT().getId (mRecords.at (index).get())!=IdAccessorT().getId (record.get()))
@@ -334,6 +436,12 @@ namespace CSMWorld
mRecords.at (index) = record;
}
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ bool Collection<ESXRecordT, IdAccessorT>::reorderRows (int baseIndex, const std::vector<int>& newOrder)
+ {
+ return false;
+ }
}
#endif
diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp
index 1056a961d5..ab7a9ebecb 100644
--- a/apps/opencs/model/world/collectionbase.hpp
+++ b/apps/opencs/model/world/collectionbase.hpp
@@ -2,6 +2,7 @@
#define CSM_WOLRD_COLLECTIONBASE_H
#include <string>
+#include <vector>
#include "universalid.hpp"
#include "columns.hpp"
@@ -77,7 +78,8 @@ namespace CSMWorld
virtual const RecordBase& getRecord (int index) const = 0;
- virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const = 0;
+ virtual int getAppendIndex (const std::string& id,
+ UniversalId::Type type = UniversalId::Type_None) const = 0;
///< \param type Will be ignored, unless the collection supports multiple record types
virtual std::vector<std::string> getIds (bool listDeleted = true) const = 0;
@@ -85,6 +87,12 @@ namespace CSMWorld
///
/// \param listDeleted include deleted record in the list
+ virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder) = 0;
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+ ///
+ /// \return Success?
+
int searchColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, -1 is returned.
diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp
index 9b8d7dafb8..70f38c5341 100644
--- a/apps/opencs/model/world/columnbase.hpp
+++ b/apps/opencs/model/world/columnbase.hpp
@@ -44,7 +44,9 @@ namespace CSMWorld
Display_WeaponType,
Display_RecordState,
Display_RefRecordType,
- Display_DialogueType
+ Display_DialogueType,
+ Display_QuestStatusType,
+ Display_Gender
};
int mColumnId;
diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp
index 8575480fd4..18aac9e0be 100644
--- a/apps/opencs/model/world/columnimp.hpp
+++ b/apps/opencs/model/world/columnimp.hpp
@@ -9,6 +9,7 @@
#include "columnbase.hpp"
#include "columns.hpp"
+#include "info.hpp"
namespace CSMWorld
{
@@ -814,14 +815,14 @@ namespace CSMWorld
virtual QVariant get (const Record<ESXRecordT>& record) const
{
- return QString::fromUtf8 (record.get().mCellId.c_str());
+ return QString::fromUtf8 (record.get().mCell.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
- record2.mCellId = data.toString().toUtf8().constData();
+ record2.mCell = data.toString().toUtf8().constData();
record.setModified (record2);
}
@@ -1348,6 +1349,340 @@ namespace CSMWorld
return false;
}
};
+
+ template<typename ESXRecordT>
+ struct QuestStatusTypeColumn : public Column<ESXRecordT>
+ {
+ QuestStatusTypeColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return static_cast<int> (record.get().mQuestStatus);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mQuestStatus = static_cast<Info::QuestStatus> (data.toInt());
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct QuestDescriptionColumn : public Column<ESXRecordT>
+ {
+ QuestDescriptionColumn() : Column<ESXRecordT> (Columns::ColumnId_QuestDescription, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mResponse.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mResponse = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct QuestIndexColumn : public Column<ESXRecordT>
+ {
+ QuestIndexColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mDisposition;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mData.mDisposition = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct TopicColumn : public Column<ESXRecordT>
+ {
+ TopicColumn (bool journal) : Column<ESXRecordT> (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mTopicId.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mTopicId = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+
+ virtual bool isUserEditable() const
+ {
+ return false;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ActorColumn : public Column<ESXRecordT>
+ {
+ ActorColumn() : Column<ESXRecordT> (Columns::ColumnId_Actor, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mActor.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mActor = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct RaceColumn : public Column<ESXRecordT>
+ {
+ RaceColumn() : Column<ESXRecordT> (Columns::ColumnId_Race, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mRace.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mRace = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ClassColumn : public Column<ESXRecordT>
+ {
+ ClassColumn() : Column<ESXRecordT> (Columns::ColumnId_Class, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mClass.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mClass = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct PcFactionColumn : public Column<ESXRecordT>
+ {
+ PcFactionColumn() : Column<ESXRecordT> (Columns::ColumnId_PcFaction, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mPcFaction.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mPcFaction = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ResponseColumn : public Column<ESXRecordT>
+ {
+ ResponseColumn() : Column<ESXRecordT> (Columns::ColumnId_Response, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mResponse.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mResponse = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct DispositionColumn : public Column<ESXRecordT>
+ {
+ DispositionColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Disposition, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mDisposition;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mData.mDisposition = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct RankColumn : public Column<ESXRecordT>
+ {
+ RankColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Rank, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return static_cast<int> (record.get().mData.mRank);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mData.mRank = static_cast<signed char> (data.toInt());
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct PcRankColumn : public Column<ESXRecordT>
+ {
+ PcRankColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_PcRank, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return static_cast<int> (record.get().mData.mPCrank);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mData.mPCrank = static_cast<signed char> (data.toInt());
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct GenderColumn : public Column<ESXRecordT>
+ {
+ GenderColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Gender, ColumnBase::Display_Gender)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return static_cast<int> (record.get().mData.mGender);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mGender = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
}
#endif
diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp
index a842e7b8de..7a13137205 100644
--- a/apps/opencs/model/world/columns.cpp
+++ b/apps/opencs/model/world/columns.cpp
@@ -160,6 +160,18 @@ namespace CSMWorld
{ ColumnId_DoorPositionYRot, "Teleport Rot Y" },
{ ColumnId_DoorPositionZRot, "Teleport Rot Z" },
{ ColumnId_DialogueType, "Dialogue Type" },
+ { ColumnId_QuestIndex, "Quest Index" },
+ { ColumnId_QuestStatusType, "Quest Status" },
+ { ColumnId_QuestDescription, "Quest Description" },
+ { ColumnId_Topic, "Topic" },
+ { ColumnId_Journal, "Journal" },
+ { ColumnId_Actor, "Actor" },
+ { ColumnId_PcFaction, "PC Faction" },
+ { ColumnId_Response, "Response" },
+ { ColumnId_Disposition, "Disposition" },
+ { ColumnId_Rank, "Rank" },
+ { ColumnId_Gender, "Gender" },
+ { ColumnId_PcRank, "PC Rank" },
{ ColumnId_Scope, "Scope", },
{ ColumnId_UseValue1, "Use value 1" },
@@ -276,6 +288,16 @@ namespace
"Topic", "Voice", "Greeting", "Persuasion", 0
};
+ static const char *sQuestStatusTypes[] =
+ {
+ "None", "Name", "Finished", "Restart", 0
+ };
+
+ static const char *sGenderEnums[] =
+ {
+ "Male", "Female", 0
+ };
+
const char **getEnumNames (CSMWorld::Columns::ColumnId column)
{
switch (column)
@@ -291,6 +313,8 @@ namespace
case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums;
case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums;
case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums;
+ case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes;
+ case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums;
default: return 0;
}
diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp
index 55d085a96f..52e022e789 100644
--- a/apps/opencs/model/world/columns.hpp
+++ b/apps/opencs/model/world/columns.hpp
@@ -153,7 +153,19 @@ namespace CSMWorld
ColumnId_DoorPositionYRot = 140,
ColumnId_DoorPositionZRot = 141,
ColumnId_DialogueType = 142,
- ColumnId_Scope = 143,
+ ColumnId_QuestIndex = 143,
+ ColumnId_QuestStatusType = 144,
+ ColumnId_QuestDescription = 145,
+ ColumnId_Topic = 146,
+ ColumnId_Journal = 147,
+ ColumnId_Actor = 148,
+ ColumnId_PcFaction = 149,
+ ColumnId_Response = 150,
+ ColumnId_Disposition = 151,
+ ColumnId_Rank = 152,
+ ColumnId_Gender = 153,
+ ColumnId_PcRank = 154,
+ ColumnId_Scope = 155,
// Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values.
diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp
index f6f421c6ae..21feb14be6 100644
--- a/apps/opencs/model/world/commands.cpp
+++ b/apps/opencs/model/world/commands.cpp
@@ -122,4 +122,26 @@ void CSMWorld::DeleteCommand::redo()
void CSMWorld::DeleteCommand::undo()
{
mModel.setRecord (mId, *mOld);
+}
+
+
+CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex,
+ const std::vector<int>& newOrder)
+: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder)
+{}
+
+void CSMWorld::ReorderRowsCommand::redo()
+{
+ mModel.reorderRows (mBaseIndex, mNewOrder);
+}
+
+void CSMWorld::ReorderRowsCommand::undo()
+{
+ int size = static_cast<int> (mNewOrder.size());
+ std::vector<int> reverse (size);
+
+ for (int i=0; i<size; ++i)
+ reverse.at (mNewOrder[i]) = i;
+
+ mModel.reorderRows (mBaseIndex, reverse);
} \ No newline at end of file
diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp
index 3fc2522ea5..f0480a369d 100644
--- a/apps/opencs/model/world/commands.hpp
+++ b/apps/opencs/model/world/commands.hpp
@@ -5,6 +5,7 @@
#include <string>
#include <map>
+#include <vector>
#include <QVariant>
#include <QUndoCommand>
@@ -99,6 +100,21 @@ namespace CSMWorld
virtual void undo();
};
+
+ class ReorderRowsCommand : public QUndoCommand
+ {
+ IdTable& mModel;
+ int mBaseIndex;
+ std::vector<int> mNewOrder;
+
+ public:
+
+ ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector<int>& newOrder);
+
+ virtual void redo();
+
+ virtual void undo();
+ };
}
#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp
index 10e56765fa..04e35cdaaf 100644
--- a/apps/opencs/model/world/data.cpp
+++ b/apps/opencs/model/world/data.cpp
@@ -160,6 +160,29 @@ CSMWorld::Data::Data() : mRefs (mCells)
mJournals.addColumn (new RecordStateColumn<ESM::Dialogue>);
mJournals.addColumn (new DialogueTypeColumn<ESM::Dialogue> (true));
+ mTopicInfos.addColumn (new StringIdColumn<Info> (true));
+ mTopicInfos.addColumn (new RecordStateColumn<Info>);
+ mTopicInfos.addColumn (new TopicColumn<Info> (false));
+ mTopicInfos.addColumn (new ActorColumn<Info>);
+ mTopicInfos.addColumn (new RaceColumn<Info>);
+ mTopicInfos.addColumn (new ClassColumn<Info>);
+ mTopicInfos.addColumn (new FactionColumn<Info>);
+ mTopicInfos.addColumn (new CellColumn<Info>);
+ mTopicInfos.addColumn (new DispositionColumn<Info>);
+ mTopicInfos.addColumn (new RankColumn<Info>);
+ mTopicInfos.addColumn (new GenderColumn<Info>);
+ mTopicInfos.addColumn (new PcFactionColumn<Info>);
+ mTopicInfos.addColumn (new PcRankColumn<Info>);
+ mTopicInfos.addColumn (new SoundFileColumn<Info>);
+ mTopicInfos.addColumn (new ResponseColumn<Info>);
+
+ mJournalInfos.addColumn (new StringIdColumn<Info> (true));
+ mJournalInfos.addColumn (new RecordStateColumn<Info>);
+ mJournalInfos.addColumn (new TopicColumn<Info> (true));
+ mJournalInfos.addColumn (new QuestStatusTypeColumn<Info>);
+ mJournalInfos.addColumn (new QuestIndexColumn<Info>);
+ mJournalInfos.addColumn (new QuestDescriptionColumn<Info>);
+
mCells.addColumn (new StringIdColumn<Cell>);
mCells.addColumn (new RecordStateColumn<Cell>);
mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell));
@@ -218,6 +241,8 @@ CSMWorld::Data::Data() : mRefs (mCells)
addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell);
addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic);
addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal);
+ addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo);
+ addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo);
addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell);
addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables,
UniversalId::Type_Referenceable);
@@ -362,6 +387,25 @@ CSMWorld::IdCollection<ESM::Dialogue>& CSMWorld::Data::getJournals()
return mJournals;
}
+const CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() const
+{
+ return mTopicInfos;
+}
+
+CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos()
+{
+ return mTopicInfos;
+}
+
+const CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() const
+{
+ return mJournalInfos;
+}
+
+CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos()
+{
+ return mJournalInfos;
+}
const CSMWorld::IdCollection<CSMWorld::Cell>& CSMWorld::Data::getCells() const
{
@@ -441,6 +485,8 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, b
reader.open (path.string());
+ const ESM::Dialogue *dialogue = 0;
+
mAuthor = reader.getAuthor();
mDescription = reader.getDesc();
@@ -505,9 +551,13 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, b
if (record.mType==ESM::Dialogue::Journal)
{
mJournals.load (record, base);
+ dialogue = &mJournals.getRecord (id).get();
}
else if (record.mType==ESM::Dialogue::Deleted)
{
+ dialogue = 0; // record vector can be shuffled around which would make pointer
+ // to record invalid
+
if (mJournals.tryDelete (id))
{
/// \todo handle info records
@@ -524,8 +574,26 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, b
else
{
mTopics.load (record, base);
+ dialogue = &mTopics.getRecord (id).get();
+ }
+
+ break;
+ }
+
+ case ESM::REC_INFO:
+ {
+ if (!dialogue)
+ {
+ /// \todo INFO record without matching DIAL record -> report to user
+ reader.skipRecord();
+ break;
}
+ if (dialogue->mType==ESM::Dialogue::Journal)
+ mJournalInfos.load (reader, base, *dialogue);
+ else
+ mTopicInfos.load (reader, base, *dialogue);
+
break;
}
@@ -545,6 +613,7 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, b
default:
/// \todo throw an exception instead, once all records are implemented
+ /// or maybe report error and continue?
reader.skipRecord();
}
}
diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp
index 1a9eae2d26..152c3ac419 100644
--- a/apps/opencs/model/world/data.hpp
+++ b/apps/opencs/model/world/data.hpp
@@ -29,6 +29,7 @@
#include "cell.hpp"
#include "refidcollection.hpp"
#include "refcollection.hpp"
+#include "infocollection.hpp"
class QAbstractItemModel;
@@ -51,6 +52,8 @@ namespace CSMWorld
IdCollection<ESM::Spell> mSpells;
IdCollection<ESM::Dialogue> mTopics;
IdCollection<ESM::Dialogue> mJournals;
+ InfoCollection mTopicInfos;
+ InfoCollection mJournalInfos;
IdCollection<Cell> mCells;
RefIdCollection mReferenceables;
RefCollection mRefs;
@@ -131,6 +134,14 @@ namespace CSMWorld
IdCollection<ESM::Dialogue>& getJournals();
+ const InfoCollection& getTopicInfos() const;
+
+ InfoCollection& getTopicInfos();
+
+ const InfoCollection& getJournalInfos() const;
+
+ InfoCollection& getJournalInfos();
+
const IdCollection<Cell>& getCells() const;
IdCollection<Cell>& getCells();
diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp
index b7b1a9db05..809d64339c 100644
--- a/apps/opencs/model/world/idtable.cpp
+++ b/apps/opencs/model/world/idtable.cpp
@@ -4,15 +4,12 @@
#include "collectionbase.hpp"
#include "columnbase.hpp"
-CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection)
-{
-
-}
+CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering)
+: mIdCollection (idCollection), mReordering (reordering)
+{}
CSMWorld::IdTable::~IdTable()
-{
-
-}
+{}
int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const
{
@@ -118,7 +115,7 @@ QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const
void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type)
{
- int index = mIdCollection->getAppendIndex();
+ int index = mIdCollection->getAppendIndex (id, type);
beginInsertRows (QModelIndex(), index, index);
@@ -138,7 +135,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco
if (index==-1)
{
- int index = mIdCollection->getAppendIndex();
+ int index = mIdCollection->getAppendIndex (id);
beginInsertRows (QModelIndex(), index, index);
@@ -167,4 +164,17 @@ int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const
int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const
{
return mIdCollection->findColumnIndex (id);
+}
+
+void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector<int>& newOrder)
+{
+ if (!newOrder.empty())
+ if (mIdCollection->reorderRows (baseIndex, newOrder))
+ emit dataChanged (index (baseIndex, 0),
+ index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1));
+}
+
+CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const
+{
+ return mReordering;
} \ No newline at end of file
diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp
index 00c5772366..e4ae58fd04 100644
--- a/apps/opencs/model/world/idtable.hpp
+++ b/apps/opencs/model/world/idtable.hpp
@@ -1,6 +1,8 @@
#ifndef CSM_WOLRD_IDTABLE_H
#define CSM_WOLRD_IDTABLE_H
+#include <vector>
+
#include <QAbstractItemModel>
#include "universalid.hpp"
@@ -15,7 +17,18 @@ namespace CSMWorld
{
Q_OBJECT
+ public:
+
+ enum Reordering
+ {
+ Reordering_None,
+ Reordering_WithinTopic
+ };
+
+ private:
+
CollectionBase *mIdCollection;
+ Reordering mReordering;
// not implemented
IdTable (const IdTable&);
@@ -23,7 +36,7 @@ namespace CSMWorld
public:
- IdTable (CollectionBase *idCollection);
+ IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic);
///< The ownership of \a idCollection is not transferred.
virtual ~IdTable();
@@ -63,6 +76,12 @@ namespace CSMWorld
int findColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, an exception is
/// thrown.
+
+ void reorderRows (int baseIndex, const std::vector<int>& newOrder);
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+
+ Reordering getReordering() const;
};
}
diff --git a/apps/opencs/model/world/info.hpp b/apps/opencs/model/world/info.hpp
new file mode 100644
index 0000000000..1bcb2dc2d0
--- /dev/null
+++ b/apps/opencs/model/world/info.hpp
@@ -0,0 +1,14 @@
+#ifndef CSM_WOLRD_INFO_H
+#define CSM_WOLRD_INFO_H
+
+#include <components/esm/loadinfo.hpp>
+
+namespace CSMWorld
+{
+ struct Info : public ESM::DialInfo
+ {
+ std::string mTopicId;
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp
new file mode 100644
index 0000000000..87bb925c2e
--- /dev/null
+++ b/apps/opencs/model/world/infocollection.cpp
@@ -0,0 +1,184 @@
+
+#include "infocollection.hpp"
+
+#include <stdexcept>
+#include <iterator>
+
+#include <components/esm/esmreader.hpp>
+#include <components/esm/loaddial.hpp>
+
+#include <components/misc/stringops.hpp>
+
+void CSMWorld::InfoCollection::load (const Info& record, bool base)
+{
+ int index = searchId (record.mId);
+
+ if (index==-1)
+ {
+ // new record
+ Record<Info> record2;
+ record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
+ (base ? record2.mBase : record2.mModified) = record;
+
+ int index = -1;
+
+ std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId);
+
+ if (!record2.get().mPrev.empty())
+ {
+ index = getIndex (record2.get().mPrev, topic);
+
+ if (index!=-1)
+ ++index;
+ }
+
+ if (index==-1 && !record2.get().mNext.empty())
+ {
+ index = getIndex (record2.get().mNext, topic);
+ }
+
+ if (index==-1)
+ {
+ Range range = getTopicRange (topic);
+
+ index = std::distance (getRecords().begin(), range.second);
+ }
+
+ insertRecord (record2, index);
+ }
+ else
+ {
+ // old record
+ Record<Info> record2 = getRecord (index);
+
+ if (base)
+ record2.mBase = record;
+ else
+ record2.setModified (record);
+
+ setRecord (index, record2);
+ }
+}
+
+int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string& topic) const
+{
+ std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id;
+
+ std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (topic);
+
+ for (; range.first!=range.second; ++range.first)
+ if (Misc::StringUtils::lowerCase (range.first->get().mId)==fullId)
+ return std::distance (getRecords().begin(), range.first);
+
+ return -1;
+}
+
+int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const
+{
+ std::string::size_type separator = id.find_last_of ('#');
+
+ if (separator==std::string::npos)
+ throw std::runtime_error ("invalid info ID: " + id);
+
+ std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (id.substr (0, separator));
+
+ if (range.first==range.second)
+ return Collection<Info, IdAccessor<Info> >::getAppendIndex (id, type);
+
+ return std::distance (getRecords().begin(), range.second);
+}
+
+bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int>& newOrder)
+{
+ // check if the range is valid
+ int lastIndex = baseIndex + newOrder.size() -1;
+
+ if (lastIndex>=getSize())
+ return false;
+
+ // Check that topics match
+ if (getRecord (baseIndex).get().mTopicId!=getRecord (lastIndex).get().mTopicId)
+ return false;
+
+ // reorder
+ return reorderRowsImp (baseIndex, newOrder);
+}
+
+void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
+{
+ std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" +
+ reader.getHNOString ("INAM");
+
+ if (reader.isNextSub ("DELE"))
+ {
+ int index = searchId (id);
+
+ reader.skipRecord();
+
+ if (index==-1)
+ {
+ // deleting a record that does not exist
+
+ // ignore it for now
+
+ /// \todo report the problem to the user
+ }
+ else if (base)
+ {
+ removeRows (index, 1);
+ }
+ else
+ {
+ Record<Info> record = getRecord (index);
+ record.mState = RecordBase::State_Deleted;
+ setRecord (index, record);
+ }
+ }
+ else
+ {
+ Info record;
+ record.mTopicId = dialogue.mId;
+ record.mId = id;
+ record.load (reader);
+
+ load (record, base);
+ }
+}
+
+CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic)
+ const
+{
+ std::string topic2 = Misc::StringUtils::lowerCase (topic);
+
+ std::map<std::string, int>::const_iterator iter = getIdMap().lower_bound (topic2);
+
+ // Skip invalid records: The beginning of a topic string could be identical to another topic
+ // string.
+ for (; iter!=getIdMap().end(); ++iter)
+ {
+ std::string testTopicId =
+ Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId);
+
+ if (testTopicId==topic2)
+ break;
+
+ std::size_t size = topic2.size();
+
+ if (testTopicId.size()<size || testTopicId.substr (0, size)!=topic2)
+ return Range (getRecords().end(), getRecords().end());
+ }
+
+ if (iter==getIdMap().end())
+ return Range (getRecords().end(), getRecords().end());
+
+ RecordConstIterator begin = getRecords().begin()+iter->second;
+
+ // Find end
+ RecordConstIterator end = begin;
+
+ for (; end!=getRecords().end(); ++end)
+ if (Misc::StringUtils::lowerCase (end->get().mTopicId)!=topic2)
+ break;
+
+ return Range (begin, end);
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp
new file mode 100644
index 0000000000..ae61f5d391
--- /dev/null
+++ b/apps/opencs/model/world/infocollection.hpp
@@ -0,0 +1,50 @@
+#ifndef CSM_WOLRD_INFOCOLLECTION_H
+#define CSM_WOLRD_INFOCOLLECTION_H
+
+#include "collection.hpp"
+#include "info.hpp"
+
+namespace ESM
+{
+ class Dialogue;
+}
+
+namespace CSMWorld
+{
+ class InfoCollection : public Collection<Info, IdAccessor<Info> >
+ {
+ public:
+
+ typedef std::vector<Record<Info> >::const_iterator RecordConstIterator;
+ typedef std::pair<RecordConstIterator, RecordConstIterator> Range;
+
+ private:
+
+ void load (const Info& record, bool base);
+
+ int getIndex (const std::string& id, const std::string& topic) const;
+ ///< Return index for record \a id or -1 (if not present; deleted records are considered)
+ ///
+ /// \param id info ID without topic prefix
+
+ public:
+
+ virtual int getAppendIndex (const std::string& id,
+ UniversalId::Type type = UniversalId::Type_None) const;
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder);
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+ ///
+ /// \return Success?
+
+ void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue);
+
+ Range getTopicRange (const std::string& topic) const;
+ ///< Return iterators that point to the beginning and past the end of the range for
+ /// the given topic.
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp
index af363bafb4..74f60419b9 100644
--- a/apps/opencs/model/world/ref.cpp
+++ b/apps/opencs/model/world/ref.cpp
@@ -6,7 +6,7 @@
void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id)
{
mId = id;
- mCellId = cell.mId;
+ mCell = cell.mId;
if (!mDeleted)
cell.addRef (mId);
diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp
index 3d107d6754..fcf016ee24 100644
--- a/apps/opencs/model/world/ref.hpp
+++ b/apps/opencs/model/world/ref.hpp
@@ -16,7 +16,7 @@ namespace CSMWorld
struct CellRef : public ESM::CellRef
{
std::string mId;
- std::string mCellId;
+ std::string mCell;
void load (ESM::ESMReader &esm, Cell& cell, const std::string& id);
///< Load cell ref and register it with \a cell.
diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp
index 3a6f70d310..86a542c5c3 100644
--- a/apps/opencs/model/world/refidcollection.cpp
+++ b/apps/opencs/model/world/refidcollection.cpp
@@ -530,7 +530,7 @@ void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, Univers
}
}
-int CSMWorld::RefIdCollection::getAppendIndex (UniversalId::Type type) const
+int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const
{
return mData.getAppendIndex (type);
}
@@ -540,7 +540,12 @@ std::vector<std::string> CSMWorld::RefIdCollection::getIds (bool listDeleted) co
return mData.getIds (listDeleted);
}
+bool CSMWorld::RefIdCollection::reorderRows (int baseIndex, const std::vector<int>& newOrder)
+{
+ return false;
+}
+
void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const
{
mData.save (index, writer);
-} \ No newline at end of file
+}
diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp
index a479735db5..5ff4a70bf2 100644
--- a/apps/opencs/model/world/refidcollection.hpp
+++ b/apps/opencs/model/world/refidcollection.hpp
@@ -92,7 +92,7 @@ namespace CSMWorld
void load (ESM::ESMReader& reader, bool base, UniversalId::Type type);
- virtual int getAppendIndex (UniversalId::Type type) const;
+ virtual int getAppendIndex (const std::string& id, UniversalId::Type type) const;
///< \param type Will be ignored, unless the collection supports multiple record types
virtual std::vector<std::string> getIds (bool listDeleted) const;
@@ -100,6 +100,12 @@ namespace CSMWorld
///
/// \param listDeleted include deleted record in the list
+ virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder);
+ ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
+ /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
+ ///
+ /// \return Success?
+
void save (int index, ESM::ESMWriter& writer) const;
};
}
diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp
index 6201a3cdaa..e633f4f696 100644
--- a/apps/opencs/model/world/universalid.cpp
+++ b/apps/opencs/model/world/universalid.cpp
@@ -31,6 +31,8 @@ namespace
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables,
"Referenceables", 0 },
@@ -58,6 +60,8 @@ namespace
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 },
{ CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" },
diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp
index ffd99e572f..0c17da03be 100644
--- a/apps/opencs/model/world/universalid.hpp
+++ b/apps/opencs/model/world/universalid.hpp
@@ -91,6 +91,10 @@ namespace CSMWorld
Type_Topic,
Type_Journals,
Type_Journal,
+ Type_TopicInfos,
+ Type_TopicInfo,
+ Type_JournalInfos,
+ Type_JournalInfo,
Type_Scene
};
diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp
index 2c4158f854..059b24fd0f 100644
--- a/apps/opencs/view/doc/subviewfactoryimp.hpp
+++ b/apps/opencs/view/doc/subviewfactoryimp.hpp
@@ -26,16 +26,25 @@ namespace CSVDoc
template<class SubViewT, class CreatorFactoryT>
class SubViewFactoryWithCreator : public SubViewFactoryBase
{
+ bool mSorting;
+
public:
+ SubViewFactoryWithCreator (bool sorting = true);
+
virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
};
template<class SubViewT, class CreatorFactoryT>
+ SubViewFactoryWithCreator<SubViewT, CreatorFactoryT>::SubViewFactoryWithCreator (bool sorting)
+ : mSorting (sorting)
+ {}
+
+ template<class SubViewT, class CreatorFactoryT>
CSVDoc::SubView *SubViewFactoryWithCreator<SubViewT, CreatorFactoryT>::makeSubView (
const CSMWorld::UniversalId& id, CSMDoc::Document& document)
{
- return new SubViewT (id, document, CreatorFactoryT());
+ return new SubViewT (id, document, CreatorFactoryT(), mSorting);
}
}
diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp
index 5713449f23..533ca7f570 100644
--- a/apps/opencs/view/doc/view.cpp
+++ b/apps/opencs/view/doc/view.cpp
@@ -136,41 +136,54 @@ void CSVDoc::View::setupMechanicsMenu()
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
mechanics->addAction (gmsts);
+ QAction *scripts = new QAction (tr ("Scripts"), this);
+ connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
+ mechanics->addAction (scripts);
+
+ QAction *spells = new QAction (tr ("Spells"), this);
+ connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView()));
+ mechanics->addAction (spells);
+}
+
+void CSVDoc::View::setupCharacterMenu()
+{
+ QMenu *characters = menuBar()->addMenu (tr ("Characters"));
+
QAction *skills = new QAction (tr ("Skills"), this);
connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView()));
- mechanics->addAction (skills);
+ characters->addAction (skills);
QAction *classes = new QAction (tr ("Classes"), this);
connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView()));
- mechanics->addAction (classes);
+ characters->addAction (classes);
QAction *factions = new QAction (tr ("Factions"), this);
connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView()));
- mechanics->addAction (factions);
+ characters->addAction (factions);
QAction *races = new QAction (tr ("Races"), this);
connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView()));
- mechanics->addAction (races);
-
- QAction *scripts = new QAction (tr ("Scripts"), this);
- connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
- mechanics->addAction (scripts);
+ characters->addAction (races);
QAction *birthsigns = new QAction (tr ("Birthsigns"), this);
connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView()));
- mechanics->addAction (birthsigns);
-
- QAction *spells = new QAction (tr ("Spells"), this);
- connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView()));
- mechanics->addAction (spells);
+ characters->addAction (birthsigns);
QAction *topics = new QAction (tr ("Topics"), this);
connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView()));
- mechanics->addAction (topics);
+ characters->addAction (topics);
QAction *journals = new QAction (tr ("Journals"), this);
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
- mechanics->addAction (journals);
+ characters->addAction (journals);
+
+ QAction *topicInfos = new QAction (tr ("Topic Infos"), this);
+ connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
+ characters->addAction (topicInfos);
+
+ QAction *journalInfos = new QAction (tr ("Journal Infos"), this);
+ connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView()));
+ characters->addAction (journalInfos);
}
void CSVDoc::View::setupAssetsMenu()
@@ -189,6 +202,7 @@ void CSVDoc::View::setupUi()
setupViewMenu();
setupWorldMenu();
setupMechanicsMenu();
+ setupCharacterMenu();
setupAssetsMenu();
}
@@ -430,6 +444,16 @@ void CSVDoc::View::addJournalsSubView()
addSubView (CSMWorld::UniversalId::Type_Journals);
}
+void CSVDoc::View::addTopicInfosSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_TopicInfos);
+}
+
+void CSVDoc::View::addJournalInfosSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_JournalInfos);
+}
+
void CSVDoc::View::abortOperation (int type)
{
mDocument->abortOperation (type);
diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp
index 2a31d9d807..13c15ec9bb 100644
--- a/apps/opencs/view/doc/view.hpp
+++ b/apps/opencs/view/doc/view.hpp
@@ -63,6 +63,8 @@ namespace CSVDoc
void setupMechanicsMenu();
+ void setupCharacterMenu();
+
void setupAssetsMenu();
void setupUi();
@@ -170,6 +172,10 @@ namespace CSVDoc
void addJournalsSubView();
+ void addTopicInfosSubView();
+
+ void addJournalInfosSubView();
+
void toggleShowStatusBar (bool show);
};
}
diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp
index a4849795b2..4a4dbc1244 100644
--- a/apps/opencs/view/doc/viewmanager.cpp
+++ b/apps/opencs/view/doc/viewmanager.cpp
@@ -75,7 +75,9 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager)
{ CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false },
{ CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false },
{ CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false },
- { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false }
+ { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false },
+ { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false },
+ { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }
};
for (std::size_t i=0; i<sizeof (sMapping)/sizeof (Mapping); ++i)
diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp
new file mode 100644
index 0000000000..8cbfc72c60
--- /dev/null
+++ b/apps/opencs/view/render/scenewidget.cpp
@@ -0,0 +1,132 @@
+#include "scenewidget.hpp"
+
+#include <QEvent>
+#include <QResizeEvent>
+
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+#include <OgreEntity.h>
+
+namespace CSVRender
+{
+
+ SceneWidget::SceneWidget(QWidget *parent)
+ : QWidget(parent)
+ , mWindow(NULL)
+ , mCamera(NULL)
+ , mSceneMgr(NULL)
+ {
+ setAttribute(Qt::WA_PaintOnScreen);
+ setAttribute(Qt::WA_NoSystemBackground);
+
+ mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
+
+ // Throw in a random color just to make sure multiple scenes work
+ Ogre::Real r = Ogre::Math::RangeRandom(0, 1);
+ Ogre::Real g = Ogre::Math::RangeRandom(0, 1);
+ Ogre::Real b = Ogre::Math::RangeRandom(0, 1);
+ mSceneMgr->setAmbientLight(Ogre::ColourValue(r,g,b,1));
+
+ Ogre::Light* l = mSceneMgr->createLight();
+ l->setType (Ogre::Light::LT_DIRECTIONAL);
+ l->setDirection (Ogre::Vector3(-0.4, -0.7, 0.3));
+ l->setDiffuseColour (Ogre::ColourValue(0.7,0.7,0.7));
+
+ mCamera = mSceneMgr->createCamera("foo");
+
+ Ogre::Entity* ent = mSceneMgr->createEntity("cube", Ogre::SceneManager::PT_CUBE);
+ ent->setMaterialName("BaseWhite");
+
+ mSceneMgr->getRootSceneNode()->attachObject(ent);
+
+ mCamera->setPosition(300,300,300);
+ mCamera->lookAt(0,0,0);
+ mCamera->setNearClipDistance(0.1);
+ mCamera->setFarClipDistance(3000);
+ }
+
+ void SceneWidget::updateOgreWindow()
+ {
+ if (mWindow)
+ {
+ Ogre::Root::getSingleton().destroyRenderTarget(mWindow);
+ mWindow = NULL;
+ }
+
+ std::stringstream windowHandle;
+ windowHandle << this->winId();
+
+ std::stringstream windowTitle;
+ static int count=0;
+ windowTitle << ++count;
+
+ Ogre::NameValuePairList params;
+
+ params.insert(std::make_pair("externalWindowHandle", windowHandle.str()));
+ params.insert(std::make_pair("title", windowTitle.str()));
+ params.insert(std::make_pair("FSAA", "0")); // TODO setting
+ params.insert(std::make_pair("vsync", "false")); // TODO setting
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ params.insert(std::make_pair("macAPI", "cocoa"));
+ params.insert(std::make_pair("macAPICocoaUseNSView", "true"));
+#endif
+
+ mWindow = Ogre::Root::getSingleton().createRenderWindow(windowTitle.str(), this->width(), this->height(), false, &params);
+ mWindow->addViewport(mCamera)->setBackgroundColour(Ogre::ColourValue(0.3,0.3,0.3,1));
+
+ Ogre::Real aspectRatio = Ogre::Real(width()) / Ogre::Real(height());
+ mCamera->setAspectRatio(aspectRatio);
+ }
+
+ SceneWidget::~SceneWidget()
+ {
+ Ogre::Root::getSingleton().destroyRenderTarget(mWindow);
+ }
+
+ void SceneWidget::paintEvent(QPaintEvent* e)
+ {
+ if (!mWindow)
+ updateOgreWindow();
+
+ mWindow->update();
+ e->accept();
+ }
+
+
+ QPaintEngine* SceneWidget::paintEngine() const
+ {
+ // We don't want another paint engine to get in the way.
+ // So we return nothing.
+ return NULL;
+ }
+
+ void SceneWidget::resizeEvent(QResizeEvent *e)
+ {
+ if (!mWindow)
+ return;
+
+ const QSize &newSize = e->size();
+
+ // TODO: Fix Ogre to handle this more consistently (fixed in 1.9)
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+ mWindow->resize(newSize.width(), newSize.height());
+#else
+ mWindow->windowMovedOrResized();
+#endif
+
+ Ogre::Real aspectRatio = Ogre::Real(newSize.width()) / Ogre::Real(newSize.height());
+ mCamera->setAspectRatio(aspectRatio);
+ }
+
+ bool SceneWidget::event(QEvent *e)
+ {
+ if (e->type() == QEvent::WinIdChange)
+ {
+ // I haven't actually seen this happen yet.
+ if (mWindow)
+ updateOgreWindow();
+ }
+ return QWidget::event(e);
+ }
+
+}
diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp
new file mode 100644
index 0000000000..355a6331e9
--- /dev/null
+++ b/apps/opencs/view/render/scenewidget.hpp
@@ -0,0 +1,40 @@
+#ifndef OPENCS_VIEW_SCENEWIDGET_H
+#define OPENCS_VIEW_SCENEWIDGET_H
+
+#include <QWidget>
+
+namespace Ogre
+{
+ class Camera;
+ class SceneManager;
+ class RenderWindow;
+}
+
+namespace CSVRender
+{
+
+ class SceneWidget : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+ SceneWidget(QWidget *parent);
+ virtual ~SceneWidget(void);
+
+ QPaintEngine* paintEngine() const;
+
+ private:
+ void paintEvent(QPaintEvent* e);
+ void resizeEvent(QResizeEvent* e);
+ bool event(QEvent* e);
+
+ void updateOgreWindow();
+
+ Ogre::Camera* mCamera;
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::RenderWindow* mWindow;
+ };
+
+}
+
+#endif
diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp
index c16214283e..3523d5e32b 100644
--- a/apps/opencs/view/world/dialoguecreator.cpp
+++ b/apps/opencs/view/world/dialoguecreator.cpp
@@ -19,7 +19,7 @@ void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand&
CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack,
const CSMWorld::UniversalId& id, int type)
-: GenericCreator (data, undoStack, id), mType (type)
+: GenericCreator (data, undoStack, id, true), mType (type)
{}
CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMWorld::Data& data,
diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp
index df43d6c5f5..ba2b3665a9 100644
--- a/apps/opencs/view/world/genericcreator.cpp
+++ b/apps/opencs/view/world/genericcreator.cpp
@@ -57,14 +57,14 @@ const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const
}
CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack,
- const CSMWorld::UniversalId& id)
+ const CSMWorld::UniversalId& id, bool relaxedIdRules)
: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false)
{
mLayout = new QHBoxLayout;
mLayout->setContentsMargins (0, 0, 0, 0);
mId = new QLineEdit;
- mId->setValidator (new IdValidator (this));
+ mId->setValidator (new IdValidator (relaxedIdRules, this));
mLayout->addWidget (mId, 1);
mCreate = new QPushButton ("Create");
diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp
index 6752d8591f..8dd2ca911c 100644
--- a/apps/opencs/view/world/genericcreator.hpp
+++ b/apps/opencs/view/world/genericcreator.hpp
@@ -51,7 +51,7 @@ namespace CSVWorld
public:
GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack,
- const CSMWorld::UniversalId& id);
+ const CSMWorld::UniversalId& id, bool relaxedIdRules = false);
virtual void setEditLock (bool locked);
diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp
index cf6e5d77ba..7c210daaec 100644
--- a/apps/opencs/view/world/idvalidator.cpp
+++ b/apps/opencs/view/world/idvalidator.cpp
@@ -12,15 +12,25 @@ bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const
return false;
}
-CSVWorld::IdValidator::IdValidator (QObject *parent) : QValidator (parent) {}
+CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent)
+: QValidator (parent), mRelaxed (relaxed)
+{}
QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const
{
- bool first = true;
-
- for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false)
- if (!isValid (*iter, first))
+ if (mRelaxed)
+ {
+ if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1)
return QValidator::Invalid;
+ }
+ else
+ {
+ bool first = true;
+
+ for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false)
+ if (!isValid (*iter, first))
+ return QValidator::Invalid;
+ }
return QValidator::Acceptable;
} \ No newline at end of file
diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp
index db0ecb27a7..8ca162440b 100644
--- a/apps/opencs/view/world/idvalidator.hpp
+++ b/apps/opencs/view/world/idvalidator.hpp
@@ -7,13 +7,16 @@ namespace CSVWorld
{
class IdValidator : public QValidator
{
+ bool mRelaxed;
+
private:
bool isValid (const QChar& c, bool first) const;
public:
- IdValidator (QObject *parent = 0);
+ IdValidator (bool relaxed = false, QObject *parent = 0);
+ ///< \param relaxed Relaxed rules for IDs that also functino as user visible text
virtual State validate (QString& input, int& pos) const;
diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp
new file mode 100644
index 0000000000..f09222930e
--- /dev/null
+++ b/apps/opencs/view/world/infocreator.cpp
@@ -0,0 +1,81 @@
+
+#include "infocreator.hpp"
+
+#include <algorithm>
+
+#include <QLabel>
+#include <QLineEdit>
+#include <QUuid>
+
+#include <components/misc/stringops.hpp>
+
+#include "../../model/world/data.hpp"
+#include "../../model/world/commands.hpp"
+#include "../../model/world/columns.hpp"
+#include "../../model/world/idtable.hpp"
+
+std::string CSVWorld::InfoCreator::getId() const
+{
+ std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData());
+
+ std::string unique = QUuid::createUuid().toByteArray().data();
+
+ unique.erase (std::remove (unique.begin(), unique.end(), '-'), unique.end());
+
+ unique = unique.substr (1, unique.size()-2);
+
+ return id + '#' + unique;
+}
+
+void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
+{
+ int index =
+ dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId())).
+ findColumnIndex (
+ getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ?
+ CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal);
+
+ command.addValue (index, mTopic->text());
+}
+
+CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: GenericCreator (data, undoStack, id)
+{
+ QLabel *label = new QLabel ("Topic", this);
+ insertBeforeButtons (label, false);
+
+ mTopic = new QLineEdit (this);
+ insertBeforeButtons (mTopic, true);
+
+ setManualEditing (false);
+
+ connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged()));
+}
+
+void CSVWorld::InfoCreator::reset()
+{
+ mTopic->setText ("");
+ GenericCreator::reset();
+}
+
+std::string CSVWorld::InfoCreator::getErrors() const
+{
+ // We ignore errors from GenericCreator here, because they can never happen in an InfoCreator.
+ std::string errors;
+
+ std::string topic = mTopic->text().toUtf8().constData();
+
+ if ((getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ?
+ getData().getTopics() : getData().getJournals()).searchId (topic)==-1)
+ {
+ errors += "Invalid Topic ID";
+ }
+
+ return errors;
+}
+
+void CSVWorld::InfoCreator::topicChanged()
+{
+ update();
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp
new file mode 100644
index 0000000000..e9cb7e5960
--- /dev/null
+++ b/apps/opencs/view/world/infocreator.hpp
@@ -0,0 +1,42 @@
+#ifndef CSV_WORLD_INFOCREATOR_H
+#define CSV_WORLD_INFOCREATOR_H
+
+#include "genericcreator.hpp"
+
+class QLineEdit;
+
+namespace CSMWorld
+{
+ class InfoCollection;
+}
+
+namespace CSVWorld
+{
+ class InfoCreator : public GenericCreator
+ {
+ Q_OBJECT
+
+ QLineEdit *mTopic;
+
+ virtual std::string getId() const;
+
+ virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const;
+
+ public:
+
+ InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id);
+
+ virtual void reset();
+
+ virtual std::string getErrors() const;
+ ///< Return formatted error descriptions for the current state of the creator. if an empty
+ /// string is returned, there is no error.
+
+ private slots:
+
+ void topicChanged();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp
index e3618c5493..33ae327a0d 100644
--- a/apps/opencs/view/world/scenesubview.cpp
+++ b/apps/opencs/view/world/scenesubview.cpp
@@ -9,6 +9,8 @@
#include "../filter/filterbox.hpp"
+#include "../render/scenewidget.hpp"
+
#include "tablebottombox.hpp"
#include "creator.hpp"
#include "scenetoolbar.hpp"
@@ -41,6 +43,14 @@ toolbar->addTool (new SceneToolMode (toolbar));
toolbar->addTool (new SceneToolMode (toolbar));
layout2->addWidget (toolbar, 0);
+// temporarily disable OGRE-integration (need to fix path problem first)
+#if 0
+ CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this);
+
+ layout2->addWidget (sceneWidget, 1);
+
+ layout->insertLayout (0, layout2, 1);
+#endif
/// \todo replace with rendering widget
QPalette palette2 (palette());
palette2.setColor (QPalette::Background, Qt::white);
@@ -53,6 +63,7 @@ toolbar->addTool (new SceneToolMode (toolbar));
layout->insertLayout (0, layout2, 1);
+
CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this);
layout->insertWidget (0, filterBox);
@@ -79,4 +90,4 @@ void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, con
void CSVWorld::SceneSubView::setStatusBar (bool show)
{
mBottom->setStatusBar (show);
-} \ No newline at end of file
+}
diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp
index 3d98cf73ce..74ce03cce6 100644
--- a/apps/opencs/view/world/subviews.cpp
+++ b/apps/opencs/view/world/subviews.cpp
@@ -15,6 +15,7 @@
#include "referencecreator.hpp"
#include "scenesubview.hpp"
#include "dialoguecreator.hpp"
+#include "infocreator.hpp"
void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
{
@@ -57,9 +58,15 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
manager.add (CSMWorld::UniversalId::Type_Topics,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, TopicCreatorFactory>);
- manager.add (CSMWorld::UniversalId::Type_Journal,
+ manager.add (CSMWorld::UniversalId::Type_Journals,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, JournalCreatorFactory>);
+ manager.add (CSMWorld::UniversalId::Type_TopicInfos,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<InfoCreator> > (false));
+
+ manager.add (CSMWorld::UniversalId::Type_JournalInfos,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<InfoCreator> > (false));
+
// Subviews for editing/viewing individual records
manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory<ScriptSubView>);
diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp
index a58eb873f3..71bdb9000e 100644
--- a/apps/opencs/view/world/table.cpp
+++ b/apps/opencs/view/world/table.cpp
@@ -12,6 +12,7 @@
#include "../../model/world/idtableproxymodel.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/record.hpp"
+#include "../../model/world/columns.hpp"
#include "recordstatusdelegate.hpp"
#include "util.hpp"
@@ -32,11 +33,43 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
if (mCreateAction)
menu.addAction (mCreateAction);
- if (listRevertableSelectedIds().size()>0)
- menu.addAction (mRevertAction);
+ /// \todo Reverting temporarily disabled on tables that support reordering, because
+ /// revert logic currently can not handle reordering.
+ if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None)
+ if (listRevertableSelectedIds().size()>0)
+ menu.addAction (mRevertAction);
if (listDeletableSelectedIds().size()>0)
menu.addAction (mDeleteAction);
+
+ if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic)
+ {
+ /// \todo allow reordering of multiple rows
+ if (selectedRows.size()==1)
+ {
+ int row =selectedRows.begin()->row();
+
+ int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic);
+
+ if (column==-1)
+ column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal);
+
+ if (column!=-1)
+ {
+ if (row>0 && mProxyModel->data (mProxyModel->index (row, column))==
+ mProxyModel->data (mProxyModel->index (row-1, column)))
+ {
+ menu.addAction (mMoveUpAction);
+ }
+
+ if (row<mProxyModel->rowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))==
+ mProxyModel->data (mProxyModel->index (row+1, column)))
+ {
+ menu.addAction (mMoveDownAction);
+ }
+ }
+ }
+ }
}
menu.exec (event->globalPos());
@@ -121,7 +154,7 @@ std::vector<std::string> CSVWorld::Table::listDeletableSelectedIds() const
}
CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack,
- bool createAndDelete)
+ bool createAndDelete, bool sorting)
: mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0)
{
mModel = &dynamic_cast<CSMWorld::IdTable&> (*data.getTableModel (id));
@@ -132,7 +165,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q
setModel (mProxyModel);
horizontalHeader()->setResizeMode (QHeaderView::Interactive);
verticalHeader()->hide();
- setSortingEnabled (true);
+ setSortingEnabled (sorting);
setSelectionBehavior (QAbstractItemView::SelectRows);
setSelectionMode (QAbstractItemView::ExtendedSelection);
@@ -176,6 +209,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q
connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord()));
addAction (mDeleteAction);
+ mMoveUpAction = new QAction (tr ("Move Up"), this);
+ connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
+ addAction (mMoveUpAction);
+
+ mMoveDownAction = new QAction (tr ("Move Down"), this);
+ connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
+ addAction (mMoveDownAction);
+
connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
this, SLOT (tableSizeUpdate()));
@@ -254,6 +295,64 @@ void CSVWorld::Table::editRecord()
}
}
+void CSVWorld::Table::moveUpRecord()
+{
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ if (selectedRows.size()==1)
+ {
+ int row2 =selectedRows.begin()->row();
+
+ if (row2>0)
+ {
+ int row = row2-1;
+
+ row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
+ row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row();
+
+ if (row2<=row)
+ throw std::runtime_error ("Inconsistent row order");
+
+ std::vector<int> newOrder (row2-row+1);
+ newOrder[0] = row2-row;
+ newOrder[row2-row] = 0;
+ for (int i=1; i<row2-row; ++i)
+ newOrder[i] = i;
+
+ mUndoStack.push (new CSMWorld::ReorderRowsCommand (*mModel, row, newOrder));
+ }
+ }
+}
+
+void CSVWorld::Table::moveDownRecord()
+{
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ if (selectedRows.size()==1)
+ {
+ int row =selectedRows.begin()->row();
+
+ if (row<mProxyModel->rowCount()-1)
+ {
+ int row2 = row+1;
+
+ row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row();
+ row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row();
+
+ if (row2<=row)
+ throw std::runtime_error ("Inconsistent row order");
+
+ std::vector<int> newOrder (row2-row+1);
+ newOrder[0] = row2-row;
+ newOrder[row2-row] = 0;
+ for (int i=1; i<row2-row; ++i)
+ newOrder[i] = i;
+
+ mUndoStack.push (new CSMWorld::ReorderRowsCommand (*mModel, row, newOrder));
+ }
+ }
+}
+
void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue)
{
int columns = mModel->columnCount();
@@ -311,4 +410,4 @@ void CSVWorld::Table::requestFocus (const std::string& id)
void CSVWorld::Table::recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter)
{
mProxyModel->setFilter (filter);
-} \ No newline at end of file
+}
diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp
index d931090563..889e2847ac 100644
--- a/apps/opencs/view/world/table.hpp
+++ b/apps/opencs/view/world/table.hpp
@@ -34,6 +34,8 @@ namespace CSVWorld
QAction *mCreateAction;
QAction *mRevertAction;
QAction *mDeleteAction;
+ QAction *mMoveUpAction;
+ QAction *mMoveDownAction;
CSMWorld::IdTableProxyModel *mProxyModel;
CSMWorld::IdTable *mModel;
bool mEditLock;
@@ -49,8 +51,9 @@ namespace CSVWorld
public:
- Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete);
+ Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting);
///< \param createAndDelete Allow creation and deletion of records.
+ /// \param sorting Allow changing order of rows in the view via column headers.
void setEditLock (bool locked);
@@ -79,6 +82,10 @@ namespace CSVWorld
void editRecord();
+ void moveUpRecord();
+
+ void moveDownRecord();
+
public slots:
void tableSizeUpdate();
diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp
index 1e05fbf51c..55ded09de2 100644
--- a/apps/opencs/view/world/tablesubview.cpp
+++ b/apps/opencs/view/world/tablesubview.cpp
@@ -12,7 +12,7 @@
#include "creator.hpp"
CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
- const CreatorFactoryBase& creatorFactory)
+ const CreatorFactoryBase& creatorFactory, bool sorting)
: SubView (id)
{
QVBoxLayout *layout = new QVBoxLayout;
@@ -23,7 +23,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0);
layout->insertWidget (0, mTable =
- new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2);
+ new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting), 2);
CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this);
diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp
index d61c789356..56a441a4d5 100644
--- a/apps/opencs/view/world/tablesubview.hpp
+++ b/apps/opencs/view/world/tablesubview.hpp
@@ -26,7 +26,7 @@ namespace CSVWorld
public:
TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
- const CreatorFactoryBase& creatorFactory);
+ const CreatorFactoryBase& creatorFactory, bool sorting);
virtual void setEditLock (bool locked);
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index 807b1b5ff1..57773507fe 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -7,6 +7,9 @@ set(GAME
main.cpp
engine.cpp
)
+if(NOT WIN32)
+ set(GAME ${GAME} crashcatcher.cpp)
+endif()
set(GAME_HEADER
engine.hpp
config.hpp
@@ -16,8 +19,8 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
actors objects renderinginterface localmap occlusionquery water shadows
- compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction
- terrainstorage
+ characterpreview externalrendering globalmap videoplayer ripplesimulation refraction
+ terrainstorage renderconst
)
add_openmw_dir (mwinput
@@ -34,7 +37,8 @@ add_openmw_dir (mwgui
enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
- tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers
+ tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog
+ recharge
)
add_openmw_dir (mwdialogue
@@ -58,7 +62,7 @@ add_openmw_dir (mwworld
cells localscripts customdata weather inventorystore ptr actionopen actionread
actionequip timestamp actionalchemy cellstore actionapply actioneat
esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
- contentloader esmloader omwloader
+ contentloader esmloader omwloader actiontrap
)
add_openmw_dir (mwclass
@@ -69,7 +73,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
- aiescort aiactivate repair enchanting pathfinding security
+ aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
)
add_openmw_dir (mwbase
@@ -78,6 +82,8 @@ add_openmw_dir (mwbase
)
# Main executable
+set(BOOST_COMPONENTS system filesystem program_options thread wave)
+find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
IF(OGRE_STATIC)
ADD_DEFINITIONS(-DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL)
@@ -107,6 +113,7 @@ add_definitions(${SOUND_DEFINE})
target_link_libraries(openmw
${OGRE_LIBRARIES}
${OGRE_STATIC_PLUGINS}
+ ${SHINY_LIBRARIES}
${Boost_LIBRARIES}
${OPENAL_LIBRARY}
${SOUND_INPUT_LIBRARY}
@@ -114,7 +121,6 @@ target_link_libraries(openmw
${MYGUI_LIBRARIES}
${SDL2_LIBRARY}
${MYGUI_PLATFORM_LIBRARIES}
- ${SHINY_LIBRARIES}
"oics"
"sdl4ogre"
components
@@ -133,12 +139,6 @@ if (UNIX AND NOT APPLE)
target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT})
endif()
-# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream
-if (UNIX AND NOT APPLE)
-target_link_libraries(openmw dl Xt)
-endif()
-
-
if(APPLE)
find_library(COCOA_FRAMEWORK Cocoa)
find_library(IOKIT_FRAMEWORK IOKit)
diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp
new file mode 100644
index 0000000000..6663306663
--- /dev/null
+++ b/apps/openmw/crashcatcher.cpp
@@ -0,0 +1,449 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <sys/ucontext.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <sys/ptrace.h>
+
+#include <string>
+
+#include <SDL_messagebox.h>
+
+#ifdef __linux__
+#include <sys/prctl.h>
+#ifndef PR_SET_PTRACER
+#define PR_SET_PTRACER 0x59616d61
+#endif
+#elif defined (__APPLE__)
+#include <signal.h>
+#endif
+
+
+static const char crash_switch[] = "--cc-handle-crash";
+
+static const char fatal_err[] = "\n\n*** Fatal Error ***\n";
+static const char pipe_err[] = "!!! Failed to create pipe\n";
+static const char fork_err[] = "!!! Failed to fork debug process\n";
+static const char exec_err[] = "!!! Failed to exec debug process\n";
+
+static char argv0[PATH_MAX];
+
+static char altstack[SIGSTKSZ];
+
+
+static struct {
+ int signum;
+ pid_t pid;
+ int has_siginfo;
+ siginfo_t siginfo;
+ char buf[1024];
+} crash_info;
+
+
+static const struct {
+ const char *name;
+ int signum;
+} signals[] = {
+ { "Segmentation fault", SIGSEGV },
+ { "Illegal instruction", SIGILL },
+ { "FPU exception", SIGFPE },
+ { "System BUS error", SIGBUS },
+ { NULL, 0 }
+};
+
+static const struct {
+ int code;
+ const char *name;
+} sigill_codes[] = {
+#ifndef __FreeBSD__
+ { ILL_ILLOPC, "Illegal opcode" },
+ { ILL_ILLOPN, "Illegal operand" },
+ { ILL_ILLADR, "Illegal addressing mode" },
+ { ILL_ILLTRP, "Illegal trap" },
+ { ILL_PRVOPC, "Privileged opcode" },
+ { ILL_PRVREG, "Privileged register" },
+ { ILL_COPROC, "Coprocessor error" },
+ { ILL_BADSTK, "Internal stack error" },
+#endif
+ { 0, NULL }
+};
+
+static const struct {
+ int code;
+ const char *name;
+} sigfpe_codes[] = {
+ { FPE_INTDIV, "Integer divide by zero" },
+ { FPE_INTOVF, "Integer overflow" },
+ { FPE_FLTDIV, "Floating point divide by zero" },
+ { FPE_FLTOVF, "Floating point overflow" },
+ { FPE_FLTUND, "Floating point underflow" },
+ { FPE_FLTRES, "Floating point inexact result" },
+ { FPE_FLTINV, "Floating point invalid operation" },
+ { FPE_FLTSUB, "Subscript out of range" },
+ { 0, NULL }
+};
+
+static const struct {
+ int code;
+ const char *name;
+} sigsegv_codes[] = {
+#ifndef __FreeBSD__
+ { SEGV_MAPERR, "Address not mapped to object" },
+ { SEGV_ACCERR, "Invalid permissions for mapped object" },
+#endif
+ { 0, NULL }
+};
+
+static const struct {
+ int code;
+ const char *name;
+} sigbus_codes[] = {
+#ifndef __FreeBSD__
+ { BUS_ADRALN, "Invalid address alignment" },
+ { BUS_ADRERR, "Non-existent physical address" },
+ { BUS_OBJERR, "Object specific hardware error" },
+#endif
+ { 0, NULL }
+};
+
+static int (*cc_user_info)(char*, char*);
+
+
+static void gdb_info(pid_t pid)
+{
+ char respfile[64];
+ char cmd_buf[128];
+ FILE *f;
+ int fd;
+
+ /* Create a temp file to put gdb commands into */
+ strcpy(respfile, "gdb-respfile-XXXXXX");
+ if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL)
+ {
+ fprintf(f, "attach %d\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Loaded Libraries\"\n"
+ "info sharedlibrary\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Threads\"\n"
+ "info threads\n"
+ "shell echo \"\"\n"
+ "shell echo \"* FPU Status\"\n"
+ "info float\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Registers\"\n"
+ "info registers\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Backtrace\"\n"
+ "thread apply all backtrace full\n"
+ "detach\n"
+ "quit\n", pid);
+ fclose(f);
+
+ /* Run gdb and print process info. */
+ snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile);
+ printf("Executing: %s\n", cmd_buf);
+ fflush(stdout);
+
+ system(cmd_buf);
+ /* Clean up */
+ remove(respfile);
+ }
+ else
+ {
+ /* Error creating temp file */
+ if(fd >= 0)
+ {
+ close(fd);
+ remove(respfile);
+ }
+ printf("!!! Could not create gdb command file\n");
+ }
+ fflush(stdout);
+}
+
+static void sys_info(void)
+{
+#ifdef __unix__
+ system("echo \"System: `uname -a`\"");
+ putchar('\n');
+ fflush(stdout);
+#endif
+}
+
+
+static size_t safe_write(int fd, const void *buf, size_t len)
+{
+ size_t ret = 0;
+ while(ret < len)
+ {
+ ssize_t rem;
+ if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1)
+ {
+ if(errno == EINTR)
+ continue;
+ break;
+ }
+ ret += rem;
+ }
+ return ret;
+}
+
+static void crash_catcher(int signum, siginfo_t *siginfo, void *context)
+{
+ //ucontext_t *ucontext = (ucontext_t*)context;
+ pid_t dbg_pid;
+ int fd[2];
+
+ /* Make sure the effective uid is the real uid */
+ if(getuid() != geteuid())
+ {
+ raise(signum);
+ return;
+ }
+
+ safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1);
+ if(pipe(fd) == -1)
+ {
+ safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1);
+ raise(signum);
+ return;
+ }
+
+ crash_info.signum = signum;
+ crash_info.pid = getpid();
+ crash_info.has_siginfo = !!siginfo;
+ if(siginfo)
+ crash_info.siginfo = *siginfo;
+ if(cc_user_info)
+ cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf));
+
+ /* Fork off to start a crash handler */
+ switch((dbg_pid=fork()))
+ {
+ /* Error */
+ case -1:
+ safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1);
+ raise(signum);
+ return;
+
+ case 0:
+ dup2(fd[0], STDIN_FILENO);
+ close(fd[0]);
+ close(fd[1]);
+
+ execl(argv0, argv0, crash_switch, NULL);
+
+ safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1);
+ _exit(1);
+
+ default:
+#ifdef __linux__
+ prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0);
+#endif
+ safe_write(fd[1], &crash_info, sizeof(crash_info));
+ close(fd[0]);
+ close(fd[1]);
+
+ /* Wait; we'll be killed when gdb is done */
+ do {
+ int status;
+ if(waitpid(dbg_pid, &status, 0) == dbg_pid &&
+ (WIFEXITED(status) || WIFSIGNALED(status)))
+ {
+ /* The debug process died before it could kill us */
+ raise(signum);
+ break;
+ }
+ } while(1);
+ }
+}
+
+static void crash_handler(const char *logfile)
+{
+ const char *sigdesc = "";
+ int i;
+
+ if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1)
+ {
+ fprintf(stderr, "!!! Failed to retrieve info from crashed process\n");
+ exit(1);
+ }
+
+ /* Get the signal description */
+ for(i = 0;signals[i].name;++i)
+ {
+ if(signals[i].signum == crash_info.signum)
+ {
+ sigdesc = signals[i].name;
+ break;
+ }
+ }
+
+ if(crash_info.has_siginfo)
+ {
+ switch(crash_info.signum)
+ {
+ case SIGSEGV:
+ for(i = 0;sigsegv_codes[i].name;++i)
+ {
+ if(sigsegv_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigsegv_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGFPE:
+ for(i = 0;sigfpe_codes[i].name;++i)
+ {
+ if(sigfpe_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigfpe_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGILL:
+ for(i = 0;sigill_codes[i].name;++i)
+ {
+ if(sigill_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigill_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGBUS:
+ for(i = 0;sigbus_codes[i].name;++i)
+ {
+ if(sigbus_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigbus_codes[i].name;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum);
+ if(crash_info.has_siginfo)
+ fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr);
+ fputc('\n', stderr);
+
+ if(logfile)
+ {
+ /* Create crash log file and redirect shell output to it */
+ if(freopen(logfile, "wa", stdout) != stdout)
+ {
+ fprintf(stderr, "!!! Could not create %s following signal\n", logfile);
+ exit(1);
+ }
+ fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid);
+
+ printf("*** Fatal Error ***\n"
+ "%s (signal %i)\n", sigdesc, crash_info.signum);
+ if(crash_info.has_siginfo)
+ printf("Address: %p\n", crash_info.siginfo.si_addr);
+ fputc('\n', stdout);
+ fflush(stdout);
+ }
+
+ sys_info();
+
+ crash_info.buf[sizeof(crash_info.buf)-1] = '\0';
+ printf("%s\n", crash_info.buf);
+ fflush(stdout);
+
+ if(crash_info.pid > 0)
+ {
+ gdb_info(crash_info.pid);
+ kill(crash_info.pid, SIGKILL);
+ }
+
+ if(logfile)
+ {
+ char cwd[MAXPATHLEN];
+ getcwd(cwd, MAXPATHLEN);
+
+ std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
+ SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL);
+ }
+ exit(0);
+}
+
+int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
+{
+ struct sigaction sa;
+ stack_t altss;
+ int retval;
+
+ if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
+ crash_handler(logfile);
+
+ cc_user_info = user_info;
+
+ if(argv[0][0] == '/')
+ snprintf(argv0, sizeof(argv0), "%s", argv[0]);
+ else
+ {
+ getcwd(argv0, sizeof(argv0));
+ retval = strlen(argv0);
+ snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
+ }
+
+ /* Set an alternate signal stack so SIGSEGVs caused by stack overflows
+ * still run */
+ altss.ss_sp = altstack;
+ altss.ss_flags = 0;
+ altss.ss_size = sizeof(altstack);
+ sigaltstack(&altss, NULL);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = crash_catcher;
+ sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK;
+ sigemptyset(&sa.sa_mask);
+
+ retval = 0;
+ while(num_signals--)
+ {
+ if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT &&
+ *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1)
+ {
+ *signals = 0;
+ retval = -1;
+ }
+ ++signals;
+ }
+ return retval;
+}
+
+
+// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
+bool
+is_debugger_attached(void)
+{
+ bool rc = false;
+ FILE *fd = fopen("/tmp", "r");
+
+ if (fileno(fd) > 5)
+ {
+ rc = true;
+ }
+
+ fclose(fd);
+ return rc;
+}
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
index e9c70e2aa1..3c2423345b 100644
--- a/apps/openmw/engine.cpp
+++ b/apps/openmw/engine.cpp
@@ -1,6 +1,6 @@
#include "engine.hpp"
-#include "components/esm/loadcell.hpp"
+#include <stdexcept>
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
@@ -18,6 +18,8 @@
#include <components/nifbullet/bulletnifloader.hpp>
#include <components/nifogre/ogrenifloader.hpp>
+#include <components/esm/loadcell.hpp>
+
#include "mwinput/inputmanagerimp.hpp"
#include "mwgui/windowmanagerimp.hpp"
@@ -63,10 +65,6 @@ void OMW::Engine::executeLocalScripts()
localScripts.setIgnore (MWWorld::Ptr());
}
-void OMW::Engine::setAnimationVerbose(bool animverbose)
-{
-}
-
bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt)
{
bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode();
@@ -147,6 +145,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(NULL)
, mActivationDistanceOverride(-1)
+ , mGrab(true)
{
std::srand ( std::time(NULL) );
@@ -210,7 +209,9 @@ void OMW::Engine::loadBSA()
}
else
{
- std::cout << "Archive " << *archive << " not found" << std::endl;
+ std::stringstream message;
+ message << "Archive '" << *archive << "' not found";
+ throw std::runtime_error(message.str());
}
}
}
@@ -296,7 +297,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings)
throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed.");
// load user settings if they exist, otherwise just load the default settings as user settings
- const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg";
+ const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg";
if (boost::filesystem::exists(settingspath))
settings.loadUser(settingspath);
else if (boost::filesystem::exists(localdefault))
@@ -337,8 +338,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mOgre->configure(
mCfgMgr.getLogPath().string(),
renderSystem,
- Settings::Manager::getString("opengl rtt mode", "Video"),
- false);
+ Settings::Manager::getString("opengl rtt mode", "Video"));
// This has to be added BEFORE MyGUI is initialized, as it needs
// to find core.xml here.
@@ -369,9 +369,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// Create input and UI first to set up a bootstrapping environment for
// showing a loading screen and keeping the window responsive while doing so
- std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string();
+ std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input.xml").string();
bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
- MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists);
+ MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab);
mEnvironment.setInputManager (input);
MWGui::WindowManager* window = new MWGui::WindowManager(
@@ -411,13 +411,16 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mVerboseScripts, *mScriptContext));
// Create game mechanics system
- mEnvironment.setMechanicsManager (new MWMechanics::MechanicsManager);
+ MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
+ mEnvironment.setMechanicsManager (mechanics);
// Create dialog system
mEnvironment.setJournal (new MWDialogue::Journal);
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage));
mEnvironment.getWorld()->renderPlayer();
+ mechanics->buildPlayer();
+ window->updatePlayer();
if (!mNewGame)
{
@@ -482,7 +485,8 @@ void OMW::Engine::go()
MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript);
// Start the main rendering loop
- mOgre->start();
+ while (!mEnvironment.getRequestExit())
+ Ogre::Root::getSingleton().renderOneFrame();
// Save user settings
settings.saveUser(settingspath);
@@ -509,6 +513,8 @@ void OMW::Engine::activate()
std::string script = MWWorld::Class::get (ptr).getScript (ptr);
+ MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
if (!script.empty())
{
MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr);
@@ -526,7 +532,7 @@ void OMW::Engine::screenshot()
// Count screenshots.
int shotCount = 0;
- const std::string screenshotPath = mCfgMgr.getUserPath().string();
+ const std::string& screenshotPath = mCfgMgr.getUserDataPath().string();
// Find the first unused filename with a do-while
std::ostringstream stream;
diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp
index 553d290687..9292e81bb3 100644
--- a/apps/openmw/engine.hpp
+++ b/apps/openmw/engine.hpp
@@ -79,6 +79,8 @@ namespace OMW
bool mScriptConsoleMode;
std::string mStartupScript;
int mActivationDistanceOverride;
+ // Grab mouse?
+ bool mGrab;
Compiler::Extensions mExtensions;
Compiler::Context *mScriptContext;
@@ -152,6 +154,8 @@ namespace OMW
/// Start as a new game.
void setNewGame(bool newGame);
+ void setGrabMouse(bool grab) { mGrab = grab; }
+
/// Initialise and enter main loop.
void go();
@@ -167,8 +171,6 @@ namespace OMW
/// Font encoding
void setEncoding(const ToUTF8::FromType& encoding);
- void setAnimationVerbose(bool animverbose);
-
void setFallbackValues(std::map<std::string,std::string> map);
/// Enable console-only script functionality
diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp
index 33f740b311..2bf48c1bb9 100644
--- a/apps/openmw/main.cpp
+++ b/apps/openmw/main.cpp
@@ -1,8 +1,9 @@
#include <iostream>
+#include <cstdio>
#include <components/files/configurationmanager.hpp>
-#include <SDL_main.h>
+#include <SDL.h>
#include "engine.hpp"
#if defined(_WIN32) && !defined(_CONSOLE)
@@ -16,6 +17,13 @@
#endif
+
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+#include <csignal>
+extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*));
+extern int is_debugger_attached(void);
+#endif
+
// for Ogre::macBundlePath
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
#include <OSX/macUtils.h>
@@ -113,10 +121,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
- ("anim-verbose", bpo::value<bool>()->implicit_value(true)
- ->default_value(false), "output animation indices files")
-
- ("nosound", bpo::value<bool>()->implicit_value(true)
+ ("no-sound", bpo::value<bool>()->implicit_value(true)
->default_value(false), "disable all sounds")
("script-verbose", bpo::value<bool>()->implicit_value(true)
@@ -147,6 +152,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values")
+ ("no-grab", "Don't grab mouse cursor")
+
("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override");
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
@@ -158,8 +165,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
bpo::store(valid_opts, variables);
bpo::notify(variables);
- cfgMgr.readConfiguration(variables, desc);
-
bool run = true;
if (variables.count ("help"))
@@ -177,6 +182,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
if (!run)
return false;
+ cfgMgr.readConfiguration(variables, desc);
+
+ engine.setGrabMouse(!variables.count("no-grab"));
+
// Font encoding settings
std::string encoding(variables["encoding"].as<std::string>());
std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl;
@@ -225,10 +234,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.setNewGame(variables["new-game"].as<bool>());
// other settings
- engine.setSoundUsage(!variables["nosound"].as<bool>());
+ engine.setSoundUsage(!variables["no-sound"].as<bool>());
engine.setScriptsVerbosity(variables["script-verbose"].as<bool>());
engine.setCompileAll(variables["script-all"].as<bool>());
- engine.setAnimationVerbose(variables["anim-verbose"].as<bool>());
engine.setFallbackValues(variables["fallback"].as<FallbackMap>().mMap);
engine.setScriptConsoleMode (variables["script-console"].as<bool>());
engine.setStartupScript (variables["script-run"].as<std::string>());
@@ -239,6 +247,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
int main(int argc, char**argv)
{
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ // Unix crash catcher
+ if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached())
+ {
+ int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
+ cc_install_handlers(argc, argv, 5, s, "crash.log", NULL);
+ std::cout << "Installing crash catcher" << std::endl;
+ }
+ else
+ std::cout << "Running in a debugger, not installing crash catcher" << std::endl;
+#endif
+
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
// set current dir to bundle path
boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path();
@@ -257,7 +277,13 @@ int main(int argc, char**argv)
}
catch (std::exception &e)
{
- std::cout << "\nERROR: " << e.what() << std::endl;
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO))
+ std::cerr << "\nERROR: " << e.what() << std::endl;
+ else
+#endif
+ SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL);
+
return 1;
}
diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp
index 5a13a50ec9..4db0b45b96 100644
--- a/apps/openmw/mwbase/environment.cpp
+++ b/apps/openmw/mwbase/environment.cpp
@@ -13,6 +13,7 @@
#include "windowmanager.hpp"
MWBase::Environment *MWBase::Environment::sThis = 0;
+bool MWBase::Environment::sExit = false;
MWBase::Environment::Environment()
: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
@@ -140,15 +141,15 @@ void MWBase::Environment::cleanup()
delete mScriptManager;
mScriptManager = 0;
+ delete mWindowManager;
+ mWindowManager = 0;
+
delete mWorld;
mWorld = 0;
delete mSoundManager;
mSoundManager = 0;
- delete mWindowManager;
- mWindowManager = 0;
-
delete mInputManager;
mInputManager = 0;
}
diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp
index a80e7ef870..4663029077 100644
--- a/apps/openmw/mwbase/environment.hpp
+++ b/apps/openmw/mwbase/environment.hpp
@@ -32,6 +32,8 @@ namespace MWBase
InputManager *mInputManager;
float mFrameDuration;
+ static bool sExit;
+
Environment (const Environment&);
///< not implemented
@@ -44,6 +46,9 @@ namespace MWBase
~Environment();
+ static void setRequestExit () { sExit = true; }
+ static bool getRequestExit () { return sExit; }
+
void setWorld (World *world);
void setSoundManager (SoundManager *soundManager);
diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp
index 7e09f9b4d7..3ab234de1c 100644
--- a/apps/openmw/mwbase/mechanicsmanager.hpp
+++ b/apps/openmw/mwbase/mechanicsmanager.hpp
@@ -59,6 +59,8 @@ namespace MWBase
/// \param paused In game type does not currently advance (this usually means some GUI
/// component is up).
+ virtual void advanceTime (float duration) = 0;
+
virtual void setPlayerName (const std::string& name) = 0;
///< Set player name.
@@ -114,6 +116,13 @@ namespace MWBase
/// references that are currently not in the scene should be ignored.
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
+
+ /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
+ /// paused we may want to do it manually (after equipping permanent enchantment)
+ virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;
+
+ virtual void toggleAI() = 0;
+ virtual bool isAIActive() = 0;
};
}
diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp
index 1cd8672230..1300efc753 100644
--- a/apps/openmw/mwbase/windowmanager.hpp
+++ b/apps/openmw/mwbase/windowmanager.hpp
@@ -204,6 +204,7 @@ namespace MWBase
virtual void activateQuickKey (int index) = 0;
+ virtual std::string getSelectedSpell() = 0;
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0;
virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0;
virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0;
@@ -265,6 +266,7 @@ namespace MWBase
virtual void showCompanionWindow(MWWorld::Ptr actor) = 0;
virtual void startSpellMaking(MWWorld::Ptr actor) = 0;
virtual void startEnchanting(MWWorld::Ptr actor) = 0;
+ virtual void startRecharge(MWWorld::Ptr soulgem) = 0;
virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0;
virtual void startTraining(MWWorld::Ptr actor) = 0;
virtual void startRepair(MWWorld::Ptr actor) = 0;
@@ -283,6 +285,9 @@ namespace MWBase
virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0;
virtual Loading::Listener* getLoadingScreen() = 0;
+
+ /// Should the cursor be visible?
+ virtual bool getCursorVisible() = 0;
};
}
diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
index c39e878268..3a88971149 100644
--- a/apps/openmw/mwbase/world.hpp
+++ b/apps/openmw/mwbase/world.hpp
@@ -80,7 +80,6 @@ namespace MWBase
Render_CollisionDebug,
Render_Wireframe,
Render_Pathgrid,
- Render_Compositors,
Render_BoundingBoxes
};
@@ -131,7 +130,7 @@ namespace MWBase
virtual Ogre::Vector2 getNorthVector (MWWorld::CellStore* cell) = 0;
///< get north vector (OGRE coordinates) for given interior cell
- virtual std::vector<DoorMarker> getDoorMarkers (MWWorld::CellStore* cell) = 0;
+ virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector<DoorMarker>& out) = 0;
///< get a list of teleport door markers for a given cell, to be displayed on the local map
virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0;
@@ -241,7 +240,7 @@ namespace MWBase
virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
- virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0;
+ virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0;
///< place an object in a "safe" location (ie not in the void, etc).
virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false)
@@ -309,14 +308,19 @@ namespace MWBase
virtual void update (float duration, bool paused) = 0;
- virtual bool placeObject(const MWWorld::Ptr& object, float cursorX, float cursorY) = 0;
- ///< place an object into the gameworld at the specified cursor position
+ virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0;
+ ///< copy and place an object into the gameworld at the specified cursor position
/// @param object
/// @param cursor X (relative 0-1)
/// @param cursor Y (relative 0-1)
+ /// @param number of objects to place
/// @return true if the object was placed, or false if it was rejected because the position is too far away
- virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object) = 0;
+ virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0;
+ ///< copy and place an object into the gameworld at the given actor's position
+ /// @param actor giving the dropped object position
+ /// @param object
+ /// @param number of objects to place
virtual bool canPlaceObject (float cursorX, float cursorY) = 0;
///< @return true if it is possible to place on object at specified cursor location
@@ -357,6 +361,9 @@ namespace MWBase
virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out) = 0;
///< get all items in active cells owned by this Npc
+ virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
+ ///< get Line of Sight (morrowind stupid implementation)
+
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0;
@@ -406,6 +413,43 @@ namespace MWBase
virtual bool getGodModeState() = 0;
virtual bool toggleGodMode() = 0;
+
+ /**
+ * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met.
+ * @param actor
+ * @return true if the spell can be casted (i.e. the animation should start)
+ */
+ virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0;
+
+ virtual void castSpell (const MWWorld::Ptr& actor) = 0;
+
+ virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
+ const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
+
+ virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0;
+
+ // Are we in an exterior or pseudo-exterior cell and it's night?
+ virtual bool isDark() const = 0;
+
+ virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) = 0;
+
+ /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
+ /// closest to \a worldPos.
+ /// @note id must be lower case
+ virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr,
+ const std::string& id, Ogre::Vector3 worldPos) = 0;
+
+ enum DetectionType
+ {
+ Detect_Enchantment,
+ Detect_Key,
+ Detect_Creature
+ };
+ /// List all references (filtered by \a type) detected by \a ptr. The range
+ /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type.
+ /// @note This also works for references in containers.
+ virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector<MWWorld::Ptr>& out,
+ DetectionType type) = 0;
};
}
diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp
index 697b755792..53c62273dc 100644
--- a/apps/openmw/mwclass/apparatus.cpp
+++ b/apps/openmw/mwclass/apparatus.cpp
@@ -124,7 +124,7 @@ namespace MWClass
std::string text;
text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality);
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp
index a511207c41..0fae1b05c3 100644
--- a/apps/openmw/mwclass/armor.cpp
+++ b/apps/openmw/mwclass/armor.cpp
@@ -94,7 +94,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Armor> *ref =
ptr.get<ESM::Armor>();
- std::vector<int> slots;
+ std::vector<int> slots_;
const int size = 11;
@@ -116,11 +116,11 @@ namespace MWClass
for (int i=0; i<size; ++i)
if (sMapping[i][0]==ref->mBase->mData.mType)
{
- slots.push_back (int (sMapping[i][1]));
+ slots_.push_back (int (sMapping[i][1]));
break;
}
- return std::make_pair (slots, false);
+ return std::make_pair (slots_, false);
}
int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const
@@ -169,7 +169,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Armor> *ref =
ptr.get<ESM::Armor>();
- return ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mCharge == -1)
+ return ref->mBase->mData.mValue;
+ else
+ return ref->mBase->mData.mValue * (static_cast<float>(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr));
}
void Armor::registerSelf()
@@ -245,7 +248,7 @@ namespace MWClass
+ MWGui::ToolTips::toString(ref->mBase->mData.mHealth);
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")";
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
@@ -281,50 +284,43 @@ namespace MWClass
newItem.mEnchant=enchId;
const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
ref->mBase = record;
+ ref->mRef.mRefID = record->mId;
}
std::pair<int, std::string> Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
{
MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
+ if (ptr.getCellRef().mCharge == 0)
+ return std::make_pair(0, "#{sInventoryMessage1}");
+
// slots that this item can be equipped in
- std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+ std::pair<std::vector<int>, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+
+ if (slots_.first.empty())
+ return std::make_pair(0, "");
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
- for (std::vector<int>::const_iterator slot=slots.first.begin();
- slot!=slots.first.end(); ++slot)
+ // Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
+ const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
+ if(race->mData.mFlags & ESM::Race::Beast)
{
+ std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
- // Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
- const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
- if(race->mData.mFlags & ESM::Race::Beast)
+ for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
{
- std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
-
- if(*slot == MWWorld::InventoryStore::Slot_Helmet)
- {
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
- {
- if((*itr).mPart == ESM::PRT_Head)
- {
- return std::make_pair(0, "#{sNotifyMessage13}");
- }
- }
- }
-
- if (*slot == MWWorld::InventoryStore::Slot_Boots)
- {
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
- {
- if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
- {
- return std::make_pair(0, "#{sNotifyMessage14}");
- }
- }
- }
+ if((*itr).mPart == ESM::PRT_Head)
+ return std::make_pair(0, "#{sNotifyMessage13}");
+ if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
+ return std::make_pair(0, "#{sNotifyMessage14}");
}
+ }
+ for (std::vector<int>::const_iterator slot=slots_.first.begin();
+ slot!=slots_.first.end(); ++slot)
+ {
+ // If equipping a shield, check if there's a twohanded weapon conflicting with it
if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft)
{
MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp
index a692b30d8f..1da9209706 100644
--- a/apps/openmw/mwclass/book.cpp
+++ b/apps/openmw/mwclass/book.cpp
@@ -136,7 +136,7 @@ namespace MWClass
std::string text;
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
@@ -171,6 +171,7 @@ namespace MWClass
newItem.mEnchant=enchId;
const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
ref->mBase = record;
+ ref->mRef.mRefID = record->mId;
}
boost::shared_ptr<MWWorld::Action> Book::use (const MWWorld::Ptr& ptr) const
diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp
index 2dbb7ee6fd..ffa96260df 100644
--- a/apps/openmw/mwclass/clothing.cpp
+++ b/apps/openmw/mwclass/clothing.cpp
@@ -78,12 +78,12 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Clothing> *ref =
ptr.get<ESM::Clothing>();
- std::vector<int> slots;
+ std::vector<int> slots_;
if (ref->mBase->mData.mType==ESM::Clothing::Ring)
{
- slots.push_back (int (MWWorld::InventoryStore::Slot_LeftRing));
- slots.push_back (int (MWWorld::InventoryStore::Slot_RightRing));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_LeftRing));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_RightRing));
}
else
{
@@ -105,12 +105,12 @@ namespace MWClass
for (int i=0; i<size; ++i)
if (sMapping[i][0]==ref->mBase->mData.mType)
{
- slots.push_back (int (sMapping[i][1]));
+ slots_.push_back (int (sMapping[i][1]));
break;
}
}
- return std::make_pair (slots, false);
+ return std::make_pair (slots_, false);
}
int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const
@@ -191,7 +191,7 @@ namespace MWClass
std::string text;
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
@@ -227,44 +227,34 @@ namespace MWClass
newItem.mEnchant=enchId;
const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
ref->mBase = record;
+ ref->mRef.mRefID = record->mId;
}
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
{
// slots that this item can be equipped in
- std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+ std::pair<std::vector<int>, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+
+ if (slots_.first.empty())
+ return std::make_pair(0, "");
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
- for (std::vector<int>::const_iterator slot=slots.first.begin();
- slot!=slots.first.end(); ++slot)
+ // Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
+ const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
+ if(race->mData.mFlags & ESM::Race::Beast)
{
+ std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
- // Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
- const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
- if(race->mData.mFlags & ESM::Race::Beast)
+ for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
{
- std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
-
- if(*slot == MWWorld::InventoryStore::Slot_Helmet)
- {
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
- {
- if((*itr).mPart == ESM::PRT_Head)
- return std::make_pair(0, "#{sNotifyMessage13}");
- }
- }
-
- if (*slot == MWWorld::InventoryStore::Slot_Boots)
- {
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
- {
- if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
- return std::make_pair(0, "#{sNotifyMessage15}");
- }
- }
+ if((*itr).mPart == ESM::PRT_Head)
+ return std::make_pair(0, "#{sNotifyMessage13}");
+ if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
+ return std::make_pair(0, "#{sNotifyMessage15}");
}
}
+
return std::make_pair (1, "");
}
diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp
index 783eabff68..55b15a7d29 100644
--- a/apps/openmw/mwclass/container.cpp
+++ b/apps/openmw/mwclass/container.cpp
@@ -13,8 +13,8 @@
#include "../mwworld/containerstore.hpp"
#include "../mwworld/customdata.hpp"
#include "../mwworld/cellstore.hpp"
-#include "../mwworld/actionapply.hpp"
#include "../mwworld/actionopen.hpp"
+#include "../mwworld/actiontrap.hpp"
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
@@ -147,11 +147,9 @@ namespace MWClass
}
else
{
- // Trap activation goes here
- std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl;
- boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap));
+ // Activate trap
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr));
action->setSound(trapActivationSound);
- ptr.getCellRef().mTrap = "";
return action;
}
}
diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp
index 20f95ab0e9..b225441aaf 100644
--- a/apps/openmw/mwclass/creature.cpp
+++ b/apps/openmw/mwclass/creature.cpp
@@ -68,14 +68,14 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
// creature stats
- data->mCreatureStats.getAttribute(0).set (ref->mBase->mData.mStrength);
- data->mCreatureStats.getAttribute(1).set (ref->mBase->mData.mIntelligence);
- data->mCreatureStats.getAttribute(2).set (ref->mBase->mData.mWillpower);
- data->mCreatureStats.getAttribute(3).set (ref->mBase->mData.mAgility);
- data->mCreatureStats.getAttribute(4).set (ref->mBase->mData.mSpeed);
- data->mCreatureStats.getAttribute(5).set (ref->mBase->mData.mEndurance);
- data->mCreatureStats.getAttribute(6).set (ref->mBase->mData.mPersonality);
- data->mCreatureStats.getAttribute(7).set (ref->mBase->mData.mLuck);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality);
+ data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck);
data->mCreatureStats.setHealth (ref->mBase->mData.mHealth);
data->mCreatureStats.setMagicka (ref->mBase->mData.mMana);
data->mCreatureStats.setFatigue (ref->mBase->mData.mFatigue);
@@ -98,6 +98,8 @@ namespace MWClass
data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr),
MWBase::Environment::get().getWorld()->getStore());
+ data->mContainerStore.add("gold_001", ref->mBase->mData.mGold, ptr);
+
// store
ptr.getRefData().setCustomData (data.release());
}
@@ -197,7 +199,7 @@ namespace MWClass
else
{
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
- fatigue.setCurrent(fatigue.getCurrent() - damage);
+ fatigue.setCurrent(fatigue.getCurrent() - damage, true);
getCreatureStats(ptr).setFatigue(fatigue);
}
}
diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp
index 3a0e4d0bab..3adb4f6fc3 100644
--- a/apps/openmw/mwclass/door.cpp
+++ b/apps/openmw/mwclass/door.cpp
@@ -12,12 +12,12 @@
#include "../mwworld/ptr.hpp"
#include "../mwworld/nullaction.hpp"
#include "../mwworld/failedaction.hpp"
-#include "../mwworld/actionapply.hpp"
#include "../mwworld/actionteleport.hpp"
#include "../mwworld/actiondoor.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/actiontrap.hpp"
#include "../mwgui/tooltips.hpp"
@@ -109,12 +109,8 @@ namespace MWClass
if(!ptr.getCellRef().mTrap.empty())
{
// Trap activation
- std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl;
-
- boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap));
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr));
action->setSound(trapActivationSound);
- ptr.getCellRef().mTrap = "";
-
return action;
}
diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp
index f629cc15d1..4296d4e1b6 100644
--- a/apps/openmw/mwclass/ingredient.cpp
+++ b/apps/openmw/mwclass/ingredient.cpp
@@ -145,7 +145,7 @@ namespace MWClass
std::string text;
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
@@ -156,6 +156,9 @@ namespace MWClass
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
+ static const float fWortChanceValue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->getFloat();
+
MWGui::Widgets::SpellEffectList list;
for (int i=0; i<4; ++i)
{
@@ -166,10 +169,10 @@ namespace MWClass
params.mAttribute = ref->mBase->mData.mAttributes[i];
params.mSkill = ref->mBase->mData.mSkills[i];
- params.mKnown = ( (i == 0 && alchemySkill >= 15)
- || (i == 1 && alchemySkill >= 30)
- || (i == 2 && alchemySkill >= 45)
- || (i == 3 && alchemySkill >= 60));
+ params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue)
+ || (i == 1 && alchemySkill >= fWortChanceValue*2)
+ || (i == 2 && alchemySkill >= fWortChanceValue*3)
+ || (i == 3 && alchemySkill >= fWortChanceValue*4));
list.push_back(params);
}
diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp
index a593eb295a..6a6133cb92 100644
--- a/apps/openmw/mwclass/light.cpp
+++ b/apps/openmw/mwclass/light.cpp
@@ -121,12 +121,12 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Light> *ref =
ptr.get<ESM::Light>();
- std::vector<int> slots;
+ std::vector<int> slots_;
if (ref->mBase->mData.mFlags & ESM::Light::Carry)
- slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft));
- return std::make_pair (slots, false);
+ return std::make_pair (slots_, false);
}
int Light::getValue (const MWWorld::Ptr& ptr) const
@@ -183,7 +183,7 @@ namespace MWClass
std::string text;
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp
index 5931a0102d..e1dc5b2e14 100644
--- a/apps/openmw/mwclass/lockpick.cpp
+++ b/apps/openmw/mwclass/lockpick.cpp
@@ -74,11 +74,11 @@ namespace MWClass
std::pair<std::vector<int>, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const
{
- std::vector<int> slots;
+ std::vector<int> slots_;
- slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
- return std::make_pair (slots, false);
+ return std::make_pair (slots_, false);
}
int Lockpick::getValue (const MWWorld::Ptr& ptr) const
@@ -86,7 +86,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Lockpick> *ref =
ptr.get<ESM::Lockpick>();
- return ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mCharge == -1)
+ return ref->mBase->mData.mValue;
+ else
+ return ref->mBase->mData.mValue * (static_cast<float>(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr));
}
void Lockpick::registerSelf()
@@ -138,7 +141,7 @@ namespace MWClass
text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses);
text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality);
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp
index 6247191a92..d211891035 100644
--- a/apps/openmw/mwclass/misc.cpp
+++ b/apps/openmw/mwclass/misc.cpp
@@ -219,7 +219,6 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
newRef.getPtr().get<ESM::Miscellaneous>();
newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell);
- newPtr.getRefData ().setCount(1);
newPtr.getCellRef().mGoldValue = goldAmount;
} else {
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
@@ -242,7 +241,12 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
item.get<ESM::Miscellaneous>();
- return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc);
+ return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc)
+ && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001")
+ && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_005")
+ && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_010")
+ && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_025")
+ && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_100");
}
float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const
@@ -252,4 +256,11 @@ namespace MWClass
return ref->mBase->mData.mWeight;
}
+ bool Miscellaneous::isKey(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+ return ref->mBase->mData.mIsKey;
+ }
+
}
diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp
index 16a8e8c055..16e9ca10b0 100644
--- a/apps/openmw/mwclass/misc.hpp
+++ b/apps/openmw/mwclass/misc.hpp
@@ -57,6 +57,8 @@ namespace MWClass
virtual float getWeight (const MWWorld::Ptr& ptr) const;
virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+
+ virtual bool isKey (const MWWorld::Ptr &ptr) const;
};
}
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index 01a0c0a6f9..f4e15423d3 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -20,6 +20,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/movement.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/actiontalk.hpp"
@@ -62,11 +63,10 @@ namespace
bool male = (npc->mFlags & ESM::NPC::Female) == 0;
int level = creatureStats.getLevel();
-
for (int i=0; i<ESM::Attribute::Length; ++i)
{
const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
- creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale);
+ creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
}
// class bonus
@@ -78,13 +78,13 @@ namespace
int attribute = class_->mData.mAttribute[i];
if (attribute>=0 && attribute<8)
{
- creatureStats.getAttribute(attribute).setBase (
+ creatureStats.setAttribute(attribute,
creatureStats.getAttribute(attribute).getBase() + 10);
}
}
// skill bonus
- for (int attribute=0; attribute<ESM::Attribute::Length; ++attribute)
+ for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
{
float modifierSum = 0;
@@ -109,7 +109,7 @@ namespace
}
modifierSum += add;
}
- creatureStats.getAttribute(attribute).setBase ( std::min(creatureStats.getAttribute(attribute).getBase()
+ creatureStats.setAttribute(attribute, std::min(creatureStats.getAttribute(attribute).getBase()
+ static_cast<int>((level-1) * modifierSum+0.5), 100) );
}
@@ -130,6 +130,89 @@ namespace
creatureStats.setHealth(static_cast<int> (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
}
+
+ /**
+ * @brief autoCalculateSkills
+ *
+ * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
+ *
+ * Skills: (Level - 1) Ɨ (Majority Multiplier + Specialization Multiplier)
+ *
+ * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
+ *
+ * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
+ * zero for other Skills.
+ *
+ * and by adding class, race, specialization bonus.
+ */
+ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
+ {
+ const ESM::Class *class_ =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
+
+ unsigned int level = npcStats.getLevel();
+
+ const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
+
+
+ for (int i = 0; i < 2; ++i)
+ {
+ int bonus = (i==0) ? 10 : 25;
+
+ for (int i2 = 0; i2 < 5; ++i2)
+ {
+ int index = class_->mData.mSkills[i2][i];
+ if (index >= 0 && index < ESM::Skill::Length)
+ {
+ npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
+ }
+ }
+ }
+
+ for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
+ {
+ float majorMultiplier = 0.1f;
+ float specMultiplier = 0.0f;
+
+ int raceBonus = 0;
+ int specBonus = 0;
+
+ for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
+ {
+ if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
+ {
+ raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
+ break;
+ }
+ }
+
+ for (int k = 0; k < 5; ++k)
+ {
+ // is this a minor or major skill?
+ if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
+ {
+ majorMultiplier = 1.0f;
+ break;
+ }
+ }
+
+ // is this skill in the same Specialization as the class?
+ const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex);
+ if (skill->mData.mSpecialization == class_->mData.mSpecialization)
+ {
+ specMultiplier = 0.5f;
+ specBonus = 5;
+ }
+
+ npcStats.getSkill(skillIndex).setBase(
+ std::min(
+ npcStats.getSkill(skillIndex).getBase()
+ + 5
+ + raceBonus
+ + specBonus
+ + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100.0f));
+ }
+ }
}
namespace MWClass
@@ -172,7 +255,7 @@ namespace MWClass
{
std::string faction = ref->mBase->mFaction;
Misc::StringUtils::toLower(faction);
- if(ref->mBase->mNpdt52.mGold != -10)
+ if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank;
}
@@ -183,19 +266,23 @@ namespace MWClass
}
// creature stats
- if(ref->mBase->mNpdt52.mGold != -10)
+ int gold=0;
+ if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
- for (int i=0; i<27; ++i)
+ gold = ref->mBase->mNpdt52.mGold;
+
+ for (unsigned int i=0; i< ESM::Skill::Length; ++i)
data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]);
- data->mNpcStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength);
- data->mNpcStats.getAttribute(1).set (ref->mBase->mNpdt52.mIntelligence);
- data->mNpcStats.getAttribute(2).set (ref->mBase->mNpdt52.mWillpower);
- data->mNpcStats.getAttribute(3).set (ref->mBase->mNpdt52.mAgility);
- data->mNpcStats.getAttribute(4).set (ref->mBase->mNpdt52.mSpeed);
- data->mNpcStats.getAttribute(5).set (ref->mBase->mNpdt52.mEndurance);
- data->mNpcStats.getAttribute(6).set (ref->mBase->mNpdt52.mPersonality);
- data->mNpcStats.getAttribute(7).set (ref->mBase->mNpdt52.mLuck);
+ data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength);
+ data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence);
+ data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower);
+ data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility);
+ data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed);
+ data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance);
+ data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality);
+ data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck);
+
data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth);
data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana);
data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue);
@@ -206,6 +293,8 @@ namespace MWClass
}
else
{
+ gold = ref->mBase->mNpdt12.mGold;
+
for (int i=0; i<3; ++i)
data->mNpcStats.setDynamic (i, 10);
@@ -214,6 +303,7 @@ namespace MWClass
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats);
+ autoCalculateSkills(ref->mBase, data->mNpcStats);
}
data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
@@ -235,6 +325,8 @@ namespace MWClass
// store
ptr.getRefData().setCustomData (data.release());
+ getContainerStore(ptr).add("gold_001", gold, ptr);
+
getInventoryStore(ptr).autoEquip(ptr);
}
}
@@ -254,7 +346,7 @@ namespace MWClass
void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
{
- renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr));
+ renderingInterface.getActors().insertNPC(ptr);
}
void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
@@ -401,6 +493,11 @@ namespace MWClass
if (!MWBase::Environment::get().getWorld()->getGodModeState())
weapon.getCellRef().mCharge -= std::min(std::max(1,
(int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
+
+ // Weapon broken? unequip it
+ if (weapon.getCellRef().mCharge == 0)
+ weapon = *inv.unequipItem(weapon, ptr);
+
}
healthdmg = true;
}
@@ -444,6 +541,39 @@ namespace MWClass
if(ptr.getRefData().getHandle() == "player")
skillUsageSucceeded(ptr, weapskill, 0);
+ // Apply "On hit" enchanted weapons
+ std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
+ if (!enchantmentName.empty())
+ {
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
+ enchantmentName);
+ if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
+ {
+ // Check if we have enough charges
+ const float enchantCost = enchantment->mData.mCost;
+ int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+ const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
+
+ if (weapon.getCellRef().mEnchantmentCharge == -1)
+ weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
+ if (weapon.getCellRef().mEnchantmentCharge < castCost)
+ {
+ if (ptr.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
+ }
+ else
+ {
+ weapon.getCellRef().mEnchantmentCharge -= castCost;
+
+ MWMechanics::CastSpell cast(ptr, victim);
+ cast.cast(weapon);
+
+ if (ptr.getRefData().getHandle() == "player")
+ skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
+ }
+ }
+ }
+
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
}
@@ -519,6 +649,11 @@ namespace MWClass
armorref.mCharge = armor.get<ESM::Armor>()->mBase->mData.mHealth;
armorref.mCharge -= std::min(std::max(1, (int)damagediff),
armorref.mCharge);
+
+ // Armor broken? unequip it
+ if (armorref.mCharge == 0)
+ inv.unequipItem(armor, ptr);
+
switch(get(armor).getEquipmentSkill(armor))
{
case ESM::Skill::LightArmor:
@@ -545,7 +680,7 @@ namespace MWClass
else
{
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
- fatigue.setCurrent(fatigue.getCurrent() - damage);
+ fatigue.setCurrent(fatigue.getCurrent() - damage, true);
getCreatureStats(ptr).setFatigue(fatigue);
}
}
@@ -587,6 +722,8 @@ namespace MWClass
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
if(get(actor).getStance(actor, MWWorld::Class::Sneak))
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
+ if(get(ptr).getCreatureStats(ptr).isHostile())
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
@@ -711,7 +848,8 @@ namespace MWClass
float moveSpeed;
if(normalizedEncumbrance >= 1.0f)
moveSpeed = 0.0f;
- else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0)
+ else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0 &&
+ world->isLevitationEnabled())
{
float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
@@ -899,11 +1037,8 @@ namespace MWClass
bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
const MWWorld::Ptr& actor) const
{
- MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
-
- /// \todo consider instant effects
-
- return stats.getActiveSpells().addSpell (id, actor);
+ MWMechanics::CastSpell cast(ptr, ptr);
+ return cast.cast(id);
}
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp
index 08683a6684..e276c58aa5 100644
--- a/apps/openmw/mwclass/potion.cpp
+++ b/apps/openmw/mwclass/potion.cpp
@@ -11,6 +11,7 @@
#include "../mwworld/actiontake.hpp"
#include "../mwworld/actionapply.hpp"
#include "../mwworld/cellstore.hpp"
+#include "../mwworld/containerstore.hpp"
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/nullaction.hpp"
@@ -127,7 +128,7 @@ namespace MWClass
std::string text;
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects);
@@ -136,13 +137,14 @@ namespace MWClass
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
int i=0;
+ static const float fWortChanceValue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->getFloat();
for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it)
{
- /// \todo this code is duplicated from mwclass/ingredient, put it in a helper function
- it->mKnown = ( (i == 0 && alchemySkill >= 15)
- || (i == 1 && alchemySkill >= 30)
- || (i == 2 && alchemySkill >= 45)
- || (i == 3 && alchemySkill >= 60));
+ it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue)
+ || (i == 1 && alchemySkill >= fWortChanceValue*2)
+ || (i == 2 && alchemySkill >= fWortChanceValue*3)
+ || (i == 3 && alchemySkill >= fWortChanceValue*4));
++i;
}
@@ -164,10 +166,11 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Potion> *ref =
ptr.get<ESM::Potion>();
- ptr.getRefData().setCount (ptr.getRefData().getCount()-1);
-
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ // remove used potion (assume it is present in inventory)
+ ptr.getContainerStore()->remove(ptr, 1, actor);
+
boost::shared_ptr<MWWorld::Action> action (
new MWWorld::ActionApply (actor, ref->mBase->mId));
diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp
index 951265f40e..b54464acdc 100644
--- a/apps/openmw/mwclass/probe.cpp
+++ b/apps/openmw/mwclass/probe.cpp
@@ -73,11 +73,11 @@ namespace MWClass
std::pair<std::vector<int>, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const
{
- std::vector<int> slots;
+ std::vector<int> slots_;
- slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
- return std::make_pair (slots, false);
+ return std::make_pair (slots_, false);
}
int Probe::getValue (const MWWorld::Ptr& ptr) const
@@ -85,7 +85,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Probe> *ref =
ptr.get<ESM::Probe>();
- return ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mCharge == -1)
+ return ref->mBase->mData.mValue;
+ else
+ return ref->mBase->mData.mValue * (static_cast<float>(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr));
}
void Probe::registerSelf()
@@ -137,7 +140,7 @@ namespace MWClass
text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses);
text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality);
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp
index 38c15ac92e..ce2b4ff10c 100644
--- a/apps/openmw/mwclass/repair.cpp
+++ b/apps/openmw/mwclass/repair.cpp
@@ -76,7 +76,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Repair> *ref =
ptr.get<ESM::Repair>();
- return ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mCharge == -1)
+ return ref->mBase->mData.mValue;
+ else
+ return ref->mBase->mData.mValue * (static_cast<float>(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr));
}
void Repair::registerSelf()
@@ -141,7 +144,7 @@ namespace MWClass
text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses);
text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality);
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp
index 4cb090328e..5e93e0d81b 100644
--- a/apps/openmw/mwclass/weapon.cpp
+++ b/apps/openmw/mwclass/weapon.cpp
@@ -100,23 +100,23 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Weapon> *ref =
ptr.get<ESM::Weapon>();
- std::vector<int> slots;
+ std::vector<int> slots_;
bool stack = false;
if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt)
{
- slots.push_back (int (MWWorld::InventoryStore::Slot_Ammunition));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition));
stack = true;
}
else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown)
{
- slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
stack = true;
}
else
- slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+ slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
- return std::make_pair (slots, stack);
+ return std::make_pair (slots_, stack);
}
int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const
@@ -154,7 +154,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Weapon> *ref =
ptr.get<ESM::Weapon>();
- return ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mCharge == -1)
+ return ref->mBase->mData.mValue;
+ else
+ return ref->mBase->mData.mValue * (static_cast<float>(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr));
}
void Weapon::registerSelf()
@@ -343,7 +346,7 @@ namespace MWClass
}
text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
- text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
info.enchant = ref->mBase->mEnchant;
@@ -370,42 +373,41 @@ namespace MWClass
void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
{
- MWWorld::LiveCellRef<ESM::Weapon> *ref =
- ptr.get<ESM::Weapon>();
-
- ESM::Weapon newItem = *ref->mBase;
- newItem.mId="";
- newItem.mName=newName;
- newItem.mData.mEnchant=enchCharge;
- newItem.mEnchant=enchId;
- const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
- ref->mBase = record;
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ ESM::Weapon newItem = *ref->mBase;
+ newItem.mId="";
+ newItem.mName=newName;
+ newItem.mData.mEnchant=enchCharge;
+ newItem.mEnchant=enchId;
+ const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
+ ref->mBase = record;
+ ref->mRef.mRefID = record->mId;
}
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
{
- std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+ if (ptr.getCellRef().mCharge == 0)
+ return std::make_pair(0, "#{sInventoryMessage1}");
+
+ std::pair<std::vector<int>, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
- // equip the item in the first free slot
- for (std::vector<int>::const_iterator slot=slots.first.begin();
- slot!=slots.first.end(); ++slot)
+ if (slots_.first.empty())
+ return std::make_pair (0, "");
+
+ if(ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
+ ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
{
- if(*slot == MWWorld::InventoryStore::Slot_CarriedRight)
- {
- if(ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
- ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
- {
- return std::make_pair (2, "");
- }
- }
- return std::make_pair(1, "");
+ return std::make_pair (2, "");
}
- return std::make_pair (0, "");
+
+ return std::make_pair(1, "");
}
boost::shared_ptr<MWWorld::Action> Weapon::use (const MWWorld::Ptr& ptr) const
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
index 52493bf765..3951cedcbc 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -413,10 +413,6 @@ namespace MWDialogue
void DialogueManager::goodbyeSelected()
{
- // Do not close the dialogue window if the player has to answer a question
- if (mIsInChoice)
- return;
-
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
// Apply disposition change to NPC's base disposition
@@ -474,6 +470,8 @@ namespace MWDialogue
void DialogueManager::goodbye()
{
+ mIsInChoice = true;
+
MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
win->goodbye();
diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp
index a7b0f19244..11dccde42d 100644
--- a/apps/openmw/mwdialogue/filter.cpp
+++ b/apps/openmw/mwdialogue/filter.cpp
@@ -58,13 +58,13 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
}
// NPC faction
- if (!info.mNpcFaction.empty())
+ if (!info.mFaction.empty())
{
if (isCreature)
return false;
MWMechanics::NpcStats& stats = MWWorld::Class::get (mActor).getNpcStats (mActor);
- std::map<std::string, int>::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mNpcFaction));
+ std::map<std::string, int>::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mFaction));
if (iter==stats.getFactionRanks().end())
return false;
diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp
index 1e203dcd0b..09f692e4f5 100644
--- a/apps/openmw/mwgui/alchemywindow.cpp
+++ b/apps/openmw/mwgui/alchemywindow.cpp
@@ -47,8 +47,6 @@ namespace MWGui
, mIngredients (4)
, mSortModel(NULL)
{
- mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
-
getWidget(mCreateButton, "CreateButton");
getWidget(mCancelButton, "CancelButton");
getWidget(mIngredients[0], "Ingredient1");
@@ -145,6 +143,8 @@ namespace MWGui
void AlchemyWindow::open()
{
+ mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
mSortModel = new SortFilterItemModel(model);
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp
index 816f42e3d1..b829f219d7 100644
--- a/apps/openmw/mwgui/charactercreation.cpp
+++ b/apps/openmw/mwgui/charactercreation.cpp
@@ -219,9 +219,14 @@ namespace MWGui
mReviewDialog->setClass(mPlayerClass);
mReviewDialog->setBirthSign(mPlayerBirthSignId);
- mReviewDialog->setHealth(mPlayerHealth);
- mReviewDialog->setMagicka(mPlayerMagicka);
- mReviewDialog->setFatigue(mPlayerFatigue);
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats stats = MWWorld::Class::get(player).getCreatureStats(player);
+
+ mReviewDialog->setHealth ( stats.getHealth() );
+ mReviewDialog->setMagicka( stats.getMagicka() );
+ mReviewDialog->setFatigue( stats.getFatigue() );
+ }
{
std::map<int, MWMechanics::Stat<int> > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
@@ -258,21 +263,6 @@ namespace MWGui
mRaceDialog->doRenderUpdate();
}
- void CharacterCreation::setPlayerHealth (const MWMechanics::DynamicStat<float>& value)
- {
- mPlayerHealth = value;
- }
-
- void CharacterCreation::setPlayerMagicka (const MWMechanics::DynamicStat<float>& value)
- {
- mPlayerMagicka = value;
- }
-
- void CharacterCreation::setPlayerFatigue (const MWMechanics::DynamicStat<float>& value)
- {
- mPlayerFatigue = value;
- }
-
void CharacterCreation::onReviewDialogDone(WindowBase* parWindow)
{
MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
@@ -285,7 +275,9 @@ namespace MWGui
{
MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
mReviewDialog = 0;
+ mCreationStage = CSE_ReviewBack;
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
}
@@ -332,24 +324,22 @@ namespace MWGui
mPickClassDialog = 0;
}
+ updatePlayerHealth();
+
//TODO This bit gets repeated a few times; wrap it in a function
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage == CSE_ReviewNext)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else if (mCreationStage >= CSE_ClassChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
}
else
{
mCreationStage = CSE_ClassChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
-
- updatePlayerHealth();
}
void CharacterCreation::onPickClassDialogBack()
@@ -403,20 +393,18 @@ namespace MWGui
mNameDialog = 0;
}
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage == CSE_ReviewNext)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else if (mCreationStage >= CSE_NameChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race);
}
else
{
mCreationStage = CSE_NameChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
}
@@ -462,23 +450,21 @@ namespace MWGui
mRaceDialog = 0;
}
+ updatePlayerHealth();
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage == CSE_ReviewNext)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else if (mCreationStage >= CSE_RaceChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
}
else
{
mCreationStage = CSE_RaceChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
-
- updatePlayerHealth();
}
void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow)
@@ -492,18 +478,17 @@ namespace MWGui
mBirthSignDialog = 0;
}
+ updatePlayerHealth();
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage >= CSE_BirthSignChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else
{
mCreationStage = CSE_BirthSignChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
-
- updatePlayerHealth();
}
void CharacterCreation::onBirthSignDialogBack()
@@ -552,23 +537,21 @@ namespace MWGui
mCreateClassDialog = 0;
}
+ updatePlayerHealth();
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage == CSE_ReviewNext)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else if (mCreationStage >= CSE_ClassChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
}
else
{
mCreationStage = CSE_ClassChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
-
- updatePlayerHealth();
}
void CharacterCreation::onCreateClassDialogBack()
@@ -722,23 +705,21 @@ namespace MWGui
mPlayerClass = *klass;
MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass);
+ updatePlayerHealth();
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
if (mCreationStage == CSE_ReviewNext)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review);
}
else if (mCreationStage >= CSE_ClassChosen)
{
- MWBase::Environment::get().getWindowManager()->popGuiMode();
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
}
else
{
mCreationStage = CSE_ClassChosen;
- MWBase::Environment::get().getWindowManager()->popGuiMode();
}
-
- updatePlayerHealth();
}
CharacterCreation::~CharacterCreation()
diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp
index bd88266776..b80aaae41c 100644
--- a/apps/openmw/mwgui/charactercreation.hpp
+++ b/apps/openmw/mwgui/charactercreation.hpp
@@ -31,12 +31,6 @@ namespace MWGui
//Show a dialog
void spawnDialog(const char id);
- void setPlayerHealth (const MWMechanics::DynamicStat<float>& value);
-
- void setPlayerMagicka (const MWMechanics::DynamicStat<float>& value);
-
- void setPlayerFatigue (const MWMechanics::DynamicStat<float>& value);
-
void setValue (const std::string& id, const MWMechanics::Stat<int>& value);
void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value);
@@ -60,9 +54,6 @@ namespace MWGui
std::string mPlayerRaceId;
std::string mPlayerBirthSignId;
ESM::Class mPlayerClass;
- MWMechanics::DynamicStat<float> mPlayerHealth;
- MWMechanics::DynamicStat<float> mPlayerMagicka;
- MWMechanics::DynamicStat<float> mPlayerFatigue;
//Class generation vars
unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog
@@ -104,6 +95,7 @@ namespace MWGui
CSE_RaceChosen,
CSE_ClassChosen,
CSE_BirthSignChosen,
+ CSE_ReviewBack,
CSE_ReviewNext
};
diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp
index c33e54d6b4..6c46f21763 100644
--- a/apps/openmw/mwgui/class.cpp
+++ b/apps/openmw/mwgui/class.cpp
@@ -466,7 +466,7 @@ namespace MWGui
std::string CreateClassDialog::getName() const
{
- return mEditName->getOnlyText();
+ return mEditName->getCaption();
}
std::string CreateClassDialog::getDescription() const
diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp
index 15fc89658f..e74370a4cd 100644
--- a/apps/openmw/mwgui/class.hpp
+++ b/apps/openmw/mwgui/class.hpp
@@ -228,8 +228,8 @@ namespace MWGui
DescriptionDialog();
~DescriptionDialog();
- std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; }
- void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); }
+ std::string getTextInput() const { return mTextEdit->getCaption(); }
+ void setTextInput(const std::string &text) { mTextEdit->setCaption(text); }
protected:
void onOkClicked(MyGUI::Widget* _sender);
diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp
index 9698608d69..a0a34108eb 100644
--- a/apps/openmw/mwgui/companionwindow.cpp
+++ b/apps/openmw/mwgui/companionwindow.cpp
@@ -86,12 +86,13 @@ void CompanionWindow::onBackgroundSelected()
void CompanionWindow::open(const MWWorld::Ptr& npc)
{
mPtr = npc;
- setTitle(MWWorld::Class::get(npc).getName(npc));
updateEncumbranceBar();
mModel = new CompanionItemModel(npc);
mSortModel = new SortFilterItemModel(mModel);
mItemView->setModel(mSortModel);
+
+ setTitle(MWWorld::Class::get(npc).getName(npc));
}
void CompanionWindow::onFrame()
diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp
index a1e3fb7381..b8d20709d9 100644
--- a/apps/openmw/mwgui/console.cpp
+++ b/apps/openmw/mwgui/console.cpp
@@ -246,7 +246,7 @@ namespace MWGui
{
if(mCurrent != mCommandHistory.end())
{
- --mCurrent;
+ ++mCurrent;
if(mCurrent != mCommandHistory.end())
mCommandLine->setCaption(*mCurrent);
@@ -406,13 +406,14 @@ namespace MWGui
setTitle("#{sConsoleTitle} (" + object.getCellRef().mRefID + ")");
mPtr = object;
}
+ // User clicked on an object. Restore focus to the console command line.
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
}
else
{
setTitle("#{sConsoleTitle}");
mPtr = MWWorld::Ptr();
}
- MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
}
void Console::onReferenceUnavailable()
diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp
index bc869e5fef..b7c6e3367d 100644
--- a/apps/openmw/mwgui/container.cpp
+++ b/apps/openmw/mwgui/container.cpp
@@ -61,8 +61,9 @@ namespace MWGui
mDraggedWidget = baseWidget;
MyGUI::ImageBox* image = baseWidget->createWidget<MyGUI::ImageBox>("ImageBox",
MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
- int pos = path.rfind(".");
- path.erase(pos);
+ size_t pos = path.rfind(".");
+ if (pos != std::string::npos)
+ path.erase(pos);
path.append(".dds");
image->setImageTexture(path);
image->setNeedMouseFocus(false);
@@ -100,6 +101,9 @@ namespace MWGui
finish();
targetView->update();
+
+ // We need to update the view since an other item could be auto-equipped.
+ mSourceView->update();
}
void DragAndDrop::finish()
@@ -217,11 +221,13 @@ namespace MWGui
mDisposeCorpseButton->setVisible(loot);
- setTitle(MWWorld::Class::get(container).getName(container));
-
mSortModel = new SortFilterItemModel(mModel);
mItemView->setModel (mSortModel);
+
+ // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last
+ // or we end up using a possibly invalid model.
+ setTitle(MWWorld::Class::get(container).getName(container));
}
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp
index eff8fbcc1b..6b0fbd8903 100644
--- a/apps/openmw/mwgui/containeritemmodel.cpp
+++ b/apps/openmw/mwgui/containeritemmodel.cpp
@@ -94,9 +94,7 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
{
if (stacks(*it, item.mBase))
{
- int refCount = it->getRefData().getCount();
- it->getRefData().setCount(std::max(0, refCount - toRemove));
- toRemove -= refCount;
+ toRemove -= store.remove(*it, toRemove, *source);
if (toRemove <= 0)
return;
}
diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp
index c069eca15a..9c64f94caa 100644
--- a/apps/openmw/mwgui/cursor.cpp
+++ b/apps/openmw/mwgui/cursor.cpp
@@ -73,57 +73,4 @@ namespace MWGui
return mSize;
}
- // ----------------------------------------------------------------------------------------
-
- Cursor::Cursor()
- {
- // hide mygui's pointer since we're rendering it ourselves (because mygui's pointer doesn't support rotation)
- MyGUI::PointerManager::getInstance().setVisible(false);
-
- MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &Cursor::onCursorChange);
-
- mWidget = MyGUI::Gui::getInstance().createWidget<MyGUI::ImageBox>("RotatingSkin",0,0,0,0,MyGUI::Align::Default,"Pointer","");
-
- onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
- }
-
- Cursor::~Cursor()
- {
- }
-
- void Cursor::onCursorChange(const std::string &name)
- {
- ResourceImageSetPointerFix* imgSetPtr = dynamic_cast<ResourceImageSetPointerFix*>(
- MyGUI::PointerManager::getInstance().getByName(name));
- assert(imgSetPtr != NULL);
-
- MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet();
-
- std::string texture = imgSet->getIndexInfo(0,0).texture;
-
- mSize = imgSetPtr->getSize();
- mHotSpot = imgSetPtr->getHotSpot();
-
- int rotation = imgSetPtr->getRotation();
-
- mWidget->setImageTexture(texture);
- MyGUI::ISubWidget* main = mWidget->getSubWidgetMain();
- MyGUI::RotatingSkin* rotatingSubskin = main->castType<MyGUI::RotatingSkin>();
- rotatingSubskin->setCenter(MyGUI::IntPoint(mSize.width/2,mSize.height/2));
- rotatingSubskin->setAngle(Ogre::Degree(rotation).valueRadians());
- }
-
- void Cursor::update()
- {
- MyGUI::IntPoint position = MyGUI::InputManager::getInstance().getMousePosition();
-
- mWidget->setPosition(position - mHotSpot);
- mWidget->setSize(mSize);
- }
-
- void Cursor::setVisible(bool visible)
- {
- mWidget->setVisible(visible);
- }
-
}
diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp
index badf82262b..4e3eb90972 100644
--- a/apps/openmw/mwgui/cursor.hpp
+++ b/apps/openmw/mwgui/cursor.hpp
@@ -39,23 +39,6 @@ namespace MWGui
int mRotation; // rotation in degrees
};
- class Cursor
- {
- public:
- Cursor();
- ~Cursor();
- void update ();
-
- void setVisible (bool visible);
-
- void onCursorChange (const std::string& name);
-
- private:
- MyGUI::ImageBox* mWidget;
-
- MyGUI::IntSize mSize;
- MyGUI::IntPoint mHotSpot;
- };
}
#endif
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index c9a7806918..914302d84e 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -12,6 +12,8 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
#include "../mwdialogue/dialoguemanagerimp.hpp"
@@ -67,23 +69,24 @@ namespace MWGui
void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
{
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
MWBase::MechanicsManager::PersuasionType type;
if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire;
else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate;
else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt;
else if (sender == mBribe10Button)
{
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-10);
+ player.getClass().getContainerStore(player).remove("gold_001", 10, player);
type = MWBase::MechanicsManager::PT_Bribe10;
}
else if (sender == mBribe100Button)
{
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100);
+ player.getClass().getContainerStore(player).remove("gold_001", 100, player);
type = MWBase::MechanicsManager::PT_Bribe100;
}
else /*if (sender == mBribe1000Button)*/
{
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000);
+ player.getClass().getContainerStore(player).remove("gold_001", 1000, player);
type = MWBase::MechanicsManager::PT_Bribe1000;
}
@@ -299,6 +302,8 @@ namespace MWGui
void DialogueWindow::onByeClicked(MyGUI::Widget* _sender)
{
+ if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice())
+ return;
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
}
diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp
index 98ba8ec2f2..d2e914d17e 100644
--- a/apps/openmw/mwgui/enchantingdialog.cpp
+++ b/apps/openmw/mwgui/enchantingdialog.cpp
@@ -4,6 +4,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
@@ -298,9 +299,15 @@ namespace MWGui
int result = mEnchanting.create();
if(result==1)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("enchant success", 1.f, 1.f);
MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}");
+ }
else
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("enchant fail", 1.f, 1.f);
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}");
+ }
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
}
diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp
index e7b9f9c015..8ef5e59d08 100644
--- a/apps/openmw/mwgui/hud.cpp
+++ b/apps/openmw/mwgui/hud.cpp
@@ -55,6 +55,8 @@ namespace MWGui
, mWorldMouseOver(false)
, mEnemyHealthTimer(0)
, mIsDrowning(false)
+ , mWeaponSpellTimer(0.f)
+ , mDrowningFlashTheta(0.f)
{
setCoord(0,0, width, height);
@@ -174,38 +176,32 @@ namespace MWGui
void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
{
- static const char *ids[] =
- {
- "HBar", "MBar", "FBar", 0
- };
+ int current = std::max(0, static_cast<int>(value.getCurrent()));
+ int modified = static_cast<int>(value.getModified());
- for (int i=0; ids[i]; ++i)
- if (ids[i]==id)
- {
- MyGUI::Widget* w;
- std::string valStr = boost::lexical_cast<std::string>(value.getCurrent()) + "/" + boost::lexical_cast<std::string>(value.getModified());
- switch (i)
- {
- case 0:
- mHealth->setProgressRange (value.getModified());
- mHealth->setProgressPosition (value.getCurrent());
- getWidget(w, "HealthFrame");
- w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
- break;
- case 1:
- mMagicka->setProgressRange (value.getModified());
- mMagicka->setProgressPosition (value.getCurrent());
- getWidget(w, "MagickaFrame");
- w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr);
- break;
- case 2:
- mStamina->setProgressRange (value.getModified());
- mStamina->setProgressPosition (value.getCurrent());
- getWidget(w, "FatigueFrame");
- w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
- break;
- }
- }
+ MyGUI::Widget* w;
+ std::string valStr = boost::lexical_cast<std::string>(current) + "/" + boost::lexical_cast<std::string>(modified);
+ if (id == "HBar")
+ {
+ mHealth->setProgressRange(modified);
+ mHealth->setProgressPosition(current);
+ getWidget(w, "HealthFrame");
+ w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
+ }
+ else if (id == "MBar")
+ {
+ mMagicka->setProgressRange (modified);
+ mMagicka->setProgressPosition (current);
+ getWidget(w, "MagickaFrame");
+ w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr);
+ }
+ else if (id == "FBar")
+ {
+ mStamina->setProgressRange (modified);
+ mStamina->setProgressPosition (current);
+ getWidget(w, "FatigueFrame");
+ w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
+ }
}
void HUD::setDrowningTimeLeft(float time)
@@ -243,21 +239,16 @@ namespace MWGui
float mouseX = cursorPosition.left / float(viewSize.width);
float mouseY = cursorPosition.top / float(viewSize.height);
- int origCount = object.getRefData().getCount();
- object.getRefData().setCount(mDragAndDrop->mDraggedCount);
-
if (world->canPlaceObject(mouseX, mouseY))
- world->placeObject(object, mouseX, mouseY);
+ world->placeObject(object, mouseX, mouseY, mDragAndDrop->mDraggedCount);
else
- world->dropObjectOnGround(world->getPlayer().getPlayer(), object);
+ world->dropObjectOnGround(world->getPlayer().getPlayer(), object, mDragAndDrop->mDraggedCount);
MWBase::Environment::get().getWindowManager()->changePointer("arrow");
std::string sound = MWWorld::Class::get(object).getDownSoundId(object);
MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
- object.getRefData().setCount(origCount);
-
// remove object from the container it was coming from
mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount);
mDragAndDrop->finish();
diff --git a/apps/openmw/mwgui/imagebutton.cpp b/apps/openmw/mwgui/imagebutton.cpp
index 98f05373ba..f2565f5c00 100644
--- a/apps/openmw/mwgui/imagebutton.cpp
+++ b/apps/openmw/mwgui/imagebutton.cpp
@@ -42,12 +42,13 @@ namespace MWGui
ImageBox::onMouseButtonPressed(_left, _top, _id);
}
- MyGUI::IntSize ImageButton::getRequestedSize()
+ MyGUI::IntSize ImageButton::getRequestedSize(bool logError)
{
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal);
if (texture.isNull())
{
- std::cerr << "ImageButton: can't find " << mImageNormal << std::endl;
+ if (logError)
+ std::cerr << "ImageButton: can't find " << mImageNormal << std::endl;
return MyGUI::IntSize(0,0);
}
return MyGUI::IntSize (texture->getWidth(), texture->getHeight());
diff --git a/apps/openmw/mwgui/imagebutton.hpp b/apps/openmw/mwgui/imagebutton.hpp
index f531e22469..f4191a3a54 100644
--- a/apps/openmw/mwgui/imagebutton.hpp
+++ b/apps/openmw/mwgui/imagebutton.hpp
@@ -14,7 +14,7 @@ namespace MWGui
MYGUI_RTTI_DERIVED(ImageButton)
public:
- MyGUI::IntSize getRequestedSize();
+ MyGUI::IntSize getRequestedSize(bool logError = true);
protected:
virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp
index 62a5a75f06..672ea9c16f 100644
--- a/apps/openmw/mwgui/inventoryitemmodel.cpp
+++ b/apps/openmw/mwgui/inventoryitemmodel.cpp
@@ -52,18 +52,12 @@ void InventoryItemModel::copyItem (const ItemStack& item, size_t count)
void InventoryItemModel::removeItem (const ItemStack& item, size_t count)
{
MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor);
+ int removed = store.remove(item.mBase, count, mActor);
- for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
- {
- if (*it == item.mBase)
- {
- if (it->getRefData().getCount() < static_cast<int>(count))
- throw std::runtime_error("Not enough items in the stack to remove");
- it->getRefData().setCount(it->getRefData().getCount() - count);
- return;
- }
- }
- throw std::runtime_error("Item to remove not found in container store");
+ if (removed == 0)
+ throw std::runtime_error("Item to remove not found in container store");
+ else if (removed < static_cast<int>(count))
+ throw std::runtime_error("Not enough items in the stack to remove");
}
void InventoryItemModel::update()
diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp
index 4f616b3126..c14971f6e2 100644
--- a/apps/openmw/mwgui/inventorywindow.cpp
+++ b/apps/openmw/mwgui/inventorywindow.cpp
@@ -37,10 +37,6 @@ namespace MWGui
, mPreviewDirty(true)
, mDragAndDrop(dragAndDrop)
, mSelectedItem(-1)
- , mPositionInventory(0, 342, 498, 258)
- , mPositionContainer(0, 342, 498, 258)
- , mPositionCompanion(0, 342, 498, 258)
- , mPositionBarter(0, 342, 498, 258)
, mGuiMode(GM_Inventory)
{
static_cast<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize);
@@ -63,8 +59,6 @@ namespace MWGui
mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected);
mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected);
- updatePlayer();
-
mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
@@ -73,10 +67,19 @@ namespace MWGui
mFilterAll->setStateSelected(true);
- setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height);
- onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
+ setGuiMode(mGuiMode);
- mPreview.setup();
+ adjustPanes();
+ }
+
+ void InventoryWindow::adjustPanes()
+ {
+ const float aspect = 0.5; // fixed aspect ratio for the left pane
+ mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 );
+ mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4,
+ mRightPane->getPosition().top,
+ mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15,
+ mMainWidget->getSize().height-44 );
}
void InventoryWindow::updatePlayer()
@@ -91,27 +94,39 @@ namespace MWGui
void InventoryWindow::setGuiMode(GuiMode mode)
{
+ std::string setting = "inventory";
mGuiMode = mode;
switch(mode) {
case GM_Container:
setPinButtonVisible(false);
- mMainWidget->setCoord(mPositionContainer);
+ setting += " container";
break;
case GM_Companion:
setPinButtonVisible(false);
- mMainWidget->setCoord(mPositionCompanion);
+ setting += " companion";
break;
case GM_Barter:
setPinButtonVisible(false);
- mMainWidget->setCoord(mPositionBarter);
+ setting += " barter";
break;
case GM_Inventory:
default:
setPinButtonVisible(true);
- mMainWidget->setCoord(mPositionInventory);
break;
}
- onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
+
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ MyGUI::IntPoint pos (Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width,
+ Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height);
+ MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width,
+ Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height);
+
+ if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight())
+ mPreviewDirty = true;
+
+ mMainWidget->setPosition(pos);
+ mMainWidget->setSize(size);
+ adjustPanes();
}
TradeItemModel* InventoryWindow::getTradeModel()
@@ -145,10 +160,46 @@ namespace MWGui
const ItemStack& item = mTradeModel->getItem(index);
- unequipItem(item.mBase);
-
MWWorld::Ptr object = item.mBase;
int count = item.mCount;
+
+ // Bound items may not be moved
+ if (item.mBase.getCellRef().mRefID.size() > 6
+ && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_")
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}");
+ return;
+ }
+
+ if (item.mType == ItemStack::Type_Equipped)
+ {
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
+ MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr);
+
+ // The unequipped item was re-stacked. We have to update the index
+ // since the item pointed does not exist anymore.
+ if (item.mBase != newStack)
+ {
+ // newIndex will store the index of the ItemStack the item was stacked on
+ int newIndex = -1;
+ for (size_t i=0; i < mTradeModel->getItemCount(); ++i)
+ {
+ if (mTradeModel->getItem(i).mBase == newStack)
+ {
+ newIndex = i;
+ break;
+ }
+ }
+
+ if (newIndex == -1)
+ throw std::runtime_error("Can't find restacked item");
+
+ index = newIndex;
+ object = mTradeModel->getItem(index).mBase;
+ }
+
+ }
+
bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
if (MyGUI::InputManager::getInstance().isControlPressed())
count = 1;
@@ -225,37 +276,44 @@ namespace MWGui
void InventoryWindow::open()
{
+ mPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
updateEncumbranceBar();
mItemView->update();
notifyContentChanged();
+ adjustPanes();
}
void InventoryWindow::onWindowResize(MyGUI::Window* _sender)
{
- const float aspect = 0.5; // fixed aspect ratio for the left pane
- mLeftPane->setSize( (_sender->getSize().height-44) * aspect, _sender->getSize().height-44 );
- mRightPane->setCoord( mLeftPane->getPosition().left + (_sender->getSize().height-44) * aspect + 4,
- mRightPane->getPosition().top,
- _sender->getSize().width - 12 - (_sender->getSize().height-44) * aspect - 15,
- _sender->getSize().height-44 );
-
+ adjustPanes();
+ std::string setting = "inventory";
switch(mGuiMode) {
case GM_Container:
- mPositionContainer = _sender->getCoord();
+ setting += " container";
break;
case GM_Companion:
- mPositionCompanion = _sender->getCoord();
+ setting += " companion";
break;
case GM_Barter:
- mPositionBarter = _sender->getCoord();
+ setting += " barter";
break;
- case GM_Inventory:
default:
- mPositionInventory = _sender->getCoord();
+ break;
}
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ float x = _sender->getPosition().left / float(viewSize.width);
+ float y = _sender->getPosition().top / float(viewSize.height);
+ float w = _sender->getSize().width / float(viewSize.width);
+ float h = _sender->getSize().height / float(viewSize.height);
+ Settings::Manager::setFloat(setting + " x", "Windows", x);
+ Settings::Manager::setFloat(setting + " y", "Windows", y);
+ Settings::Manager::setFloat(setting + " w", "Windows", w);
+ Settings::Manager::setFloat(setting + " h", "Windows", h);
+
if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize)
{
mLastXSize = mMainWidget->getSize().width;
@@ -375,27 +433,6 @@ namespace MWGui
return MWWorld::Ptr();
}
- void InventoryWindow::unequipItem(const MWWorld::Ptr& item)
- {
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
-
- for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
- {
- MWWorld::ContainerStoreIterator it = invStore.getSlot(slot);
- if (it != invStore.end() && *it == item)
- {
- invStore.equip(slot, invStore.end());
- std::string script = MWWorld::Class::get(*it).getScript(*it);
-
- // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
- if(script != "")
- (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
-
- return;
- }
- }
- }
-
void InventoryWindow::updateEncumbranceBar()
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
@@ -450,14 +487,6 @@ namespace MWGui
if (MWBase::Environment::get().getWindowManager()->getSpellWindow())
MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells();
- // update selected weapon icon
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
- MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
- if (weaponSlot == invStore.end())
- MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon();
- else
- MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot);
-
mPreviewDirty = true;
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp
index 35140437d1..94ecfd4c8a 100644
--- a/apps/openmw/mwgui/inventorywindow.hpp
+++ b/apps/openmw/mwgui/inventorywindow.hpp
@@ -76,11 +76,6 @@ namespace MWGui
MyGUI::Button* mFilterMagic;
MyGUI::Button* mFilterMisc;
- MyGUI::IntCoord mPositionInventory;
- MyGUI::IntCoord mPositionContainer;
- MyGUI::IntCoord mPositionCompanion;
- MyGUI::IntCoord mPositionBarter;
-
GuiMode mGuiMode;
int mLastXSize;
@@ -103,9 +98,10 @@ namespace MWGui
void onAvatarClicked(MyGUI::Widget* _sender);
void onPinToggled();
- void unequipItem(const MWWorld::Ptr& item);
void updateEncumbranceBar();
void notifyContentChanged();
+
+ void adjustPanes();
};
}
diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp
index ab8dc1584a..f3c9e9c73a 100644
--- a/apps/openmw/mwgui/journalwindow.cpp
+++ b/apps/openmw/mwgui/journalwindow.cpp
@@ -137,15 +137,28 @@ namespace
getPage (QuestsPage)->adviseLinkClicked (callback);
}
- adjustButton(OptionsBTN);
+ adjustButton(OptionsBTN, true);
adjustButton(PrevPageBTN);
adjustButton(NextPageBTN);
adjustButton(CloseBTN);
adjustButton(CancelBTN);
- adjustButton(ShowAllBTN);
- adjustButton(ShowActiveBTN);
+ adjustButton(ShowAllBTN, true);
+ adjustButton(ShowActiveBTN, true);
adjustButton(JournalBTN);
+ MWGui::ImageButton* optionsButton = getWidget<MWGui::ImageButton>(OptionsBTN);
+ if (optionsButton->getWidth() == 0)
+ {
+ // If tribunal is not installed (-> no options button), we still want the Topics button available,
+ // so place it where the options button would have been
+ MWGui::ImageButton* topicsButton = getWidget<MWGui::ImageButton>(TopicsBTN);
+ topicsButton->detachFromWidget();
+ topicsButton->attachToWidget(optionsButton->getParent());
+ topicsButton->setPosition(optionsButton->getPosition());
+ topicsButton->eventMouseButtonClick.clear();
+ topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions);
+ }
+
MWGui::ImageButton* nextButton = getWidget<MWGui::ImageButton>(NextPageBTN);
if (nextButton->getSize().width == 64)
{
@@ -155,7 +168,7 @@ namespace
}
adjustButton(TopicsBTN);
- adjustButton(QuestsBTN);
+ adjustButton(QuestsBTN, true);
int width = getWidget<MyGUI::Widget>(TopicsBTN)->getSize().width + getWidget<MyGUI::Widget>(QuestsBTN)->getSize().width;
int topicsWidth = getWidget<MyGUI::Widget>(TopicsBTN)->getSize().width;
int pageWidth = getWidget<MyGUI::Widget>(RightBookPage)->getSize().width;
@@ -167,12 +180,12 @@ namespace
mAllQuests = false;
}
- void adjustButton (char const * name)
+ void adjustButton (char const * name, bool optional = false)
{
MWGui::ImageButton* button = getWidget<MWGui::ImageButton>(name);
- MyGUI::IntSize diff = button->getSize() - button->getRequestedSize();
- button->setSize(button->getRequestedSize());
+ MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional);
+ button->setSize(button->getRequestedSize(!optional));
if (button->getAlign().isRight())
button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0));
diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
index 7c0191d495..a772b3a15a 100644
--- a/apps/openmw/mwgui/levelupdialog.cpp
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -166,11 +166,12 @@ namespace MWGui
// increase attributes
for (int i=0; i<3; ++i)
{
- MWMechanics::Stat<int>& attribute = creatureStats.getAttribute(mSpentAttributes[i]);
+ MWMechanics::Stat<int> attribute = creatureStats.getAttribute(mSpentAttributes[i]);
attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i]));
if (attribute.getBase() >= 100)
attribute.setBase(100);
+ creatureStats.setAttribute(mSpentAttributes[i], attribute);
}
creatureStats.levelUp();
diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp
index 4bd383c2f3..868b582096 100644
--- a/apps/openmw/mwgui/loadingscreen.cpp
+++ b/apps/openmw/mwgui/loadingscreen.cpp
@@ -1,8 +1,6 @@
#include "loadingscreen.hpp"
#include <OgreRenderWindow.h>
-#include <OgreCompositorManager.h>
-#include <OgreCompositorChain.h>
#include <openengine/ogre/fader.hpp>
@@ -199,28 +197,7 @@ namespace MWGui
MWBase::Environment::get().getInputManager()->update(0, true);
- Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain(mWindow->getViewport(0));
-
- bool hasCompositor = chain->getCompositor ("gbufferFinalizer");
-
-
- if (!hasCompositor)
- {
- mWindow->getViewport(0)->setClearEveryFrame(false);
- }
- else
- {
- if (!mFirstLoad)
- {
- mBackgroundMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(chain->getCompositor ("gbufferFinalizer")->getTextureInstance ("no_mrt_output", 0)->getName());
- mRectangle->setVisible(true);
- }
-
- for (unsigned int i = 0; i<chain->getNumCompositors(); ++i)
- {
- Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), false);
- }
- }
+ mWindow->getViewport(0)->setClearEveryFrame(false);
// First, swap buffers from last draw, then, queue an update of the
// window contents, but don't swap buffers (which would have
@@ -231,15 +208,8 @@ namespace MWGui
mWindow->update(false);
- if (!hasCompositor)
- mWindow->getViewport(0)->setClearEveryFrame(true);
- else
- {
- for (unsigned int i = 0; i<chain->getNumCompositors(); ++i)
- {
- Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), true);
- }
- }
+ mWindow->getViewport(0)->setClearEveryFrame(true);
+
mRectangle->setVisible(false);
diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp
index 1db6e9ecd2..fa7ed2aceb 100644
--- a/apps/openmw/mwgui/mainmenu.cpp
+++ b/apps/openmw/mwgui/mainmenu.cpp
@@ -9,6 +9,8 @@
#include "../mwbase/journal.hpp"
#include "../mwbase/dialoguemanager.hpp"
+#include "savegamedialog.hpp"
+
namespace MWGui
{
@@ -77,7 +79,7 @@ namespace MWGui
else if (sender == mButtons["options"])
MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings);
else if (sender == mButtons["exitgame"])
- Ogre::Root::getSingleton ().queueEndRendering ();
+ MWBase::Environment::get().setRequestExit();
else if (sender == mButtons["newgame"])
{
MWBase::Environment::get().getWorld()->startNewGame();
@@ -85,6 +87,19 @@ namespace MWGui
MWBase::Environment::get().getDialogueManager()->clear();
MWBase::Environment::get().getJournal()->clear();
}
+
+ else if (sender == mButtons["loadgame"])
+ {
+ MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog();
+ dialog->setLoadOrSave(true);
+ dialog->setVisible(true);
+ }
+ else if (sender == mButtons["savegame"])
+ {
+ MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog();
+ dialog->setLoadOrSave(false);
+ dialog->setVisible(true);
+ }
}
}
diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp
index 5ed002d7b3..2f34df2360 100644
--- a/apps/openmw/mwgui/mapwindow.cpp
+++ b/apps/openmw/mwgui/mapwindow.cpp
@@ -103,27 +103,80 @@ namespace MWGui
void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2)
{
+ // Workaround to not make the marker visible if it's under fog of war
applyFogOfWar ();
}
void LocalMapBase::onMarkerUnfocused (MyGUI::Widget* w1, MyGUI::Widget* w2)
{
+ // Workaround to not make the marker visible if it's under fog of war
applyFogOfWar ();
}
+ MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerPosition& markerPos)
+ {
+ MyGUI::IntPoint widgetPos;
+ // normalized cell coordinates
+ float nX,nY;
+
+ markerPos.interior = mInterior;
+
+ if (!mInterior)
+ {
+ int cellX, cellY;
+ MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY);
+ const int cellSize = 8192;
+ nX = (worldX - cellSize * cellX) / cellSize;
+ // Image space is -Y up, cells are Y up
+ nY = 1 - (worldY - cellSize * cellY) / cellSize;
+
+ float cellDx = cellX - mCurX;
+ float cellDy = cellY - mCurY;
+
+ markerPos.cellX = cellX;
+ markerPos.cellY = cellY;
+
+ widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellDx) * 512,
+ nY * 512 - (cellDy-1) * 512);
+ }
+ else
+ {
+ int cellX, cellY;
+ Ogre::Vector2 worldPos (worldX, worldY);
+ MWBase::Environment::get().getWorld ()->getInteriorMapPosition (worldPos, nX, nY, cellX, cellY);
+
+ markerPos.cellX = cellX;
+ markerPos.cellY = cellY;
+
+ widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellX-mCurX) * 512,
+ nY * 512 + (1+cellY-mCurY) * 512);
+ }
+
+ markerPos.nX = nX;
+ markerPos.nY = nY;
+ return widgetPos;
+ }
+
void LocalMapBase::setActiveCell(const int x, const int y, bool interior)
{
- if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell
+ if (x==mCurX && y==mCurY && mInterior==interior && !mChanged)
+ return; // don't do anything if we're still in the same cell
+
+ mCurX = x;
+ mCurY = y;
+ mInterior = interior;
+ mChanged = false;
// clear all previous markers
for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i)
{
- if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker")
+ if (mLocalMap->getChildAt(i)->getName ().substr (0, 4) == "Door")
{
MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i));
}
}
+ // Update the map textures
for (int mx=0; mx<3; ++mx)
{
for (int my=0; my<3; ++my)
@@ -138,78 +191,57 @@ namespace MWGui
box->setImageTexture(image);
else
box->setImageTexture("black.png");
+ }
+ }
+ MWBase::World* world = MWBase::Environment::get().getWorld();
- // door markers
-
- // interior map only consists of one cell, so handle the markers only once
- if (interior && (mx != 2 || my != 2))
- continue;
-
- MWWorld::CellStore* cell;
- if (interior)
- cell = MWBase::Environment::get().getWorld ()->getInterior (mPrefix);
- else
- cell = MWBase::Environment::get().getWorld ()->getExterior (x+mx-1, y-(my-1));
-
- std::vector<MWBase::World::DoorMarker> doors = MWBase::Environment::get().getWorld ()->getDoorMarkers (cell);
-
- for (std::vector<MWBase::World::DoorMarker>::iterator it = doors.begin(); it != doors.end(); ++it)
+ // Retrieve the door markers we want to show
+ std::vector<MWBase::World::DoorMarker> doors;
+ if (interior)
+ {
+ MWWorld::CellStore* cell = world->getInterior (mPrefix);
+ world->getDoorMarkers(cell, doors);
+ }
+ else
+ {
+ for (int dX=-1; dX<2; ++dX)
+ {
+ for (int dY=-1; dY<2; ++dY)
{
- MWBase::World::DoorMarker marker = *it;
-
- // convert world coordinates to normalized cell coordinates
- MyGUI::IntCoord widgetCoord;
- float nX,nY;
- int cellDx, cellDy;
- if (!interior)
- {
- const int cellSize = 8192;
-
- nX = (marker.x - cellSize * (x+mx-1)) / cellSize;
- nY = 1 - (marker.y - cellSize * (y-(my-1))) / cellSize;
-
- widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + mx * 512, nY * 512 - 4 + my * 512, 8, 8);
- }
- else
- {
- Ogre::Vector2 position (marker.x, marker.y);
- MWBase::Environment::get().getWorld ()->getInteriorMapPosition (position, nX, nY, cellDx, cellDy);
-
- widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + (1+cellDx-x) * 512, nY * 512 - 4 + (1+cellDy-y) * 512, 8, 8);
- }
-
- static int counter = 0;
- ++counter;
- MyGUI::Button* markerWidget = mLocalMap->createWidget<MyGUI::Button>("ButtonImage",
- widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast<std::string>(counter));
- markerWidget->setImageResource("DoorMarker");
- markerWidget->setUserString("ToolTipType", "Layout");
- markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
- markerWidget->setUserString("Caption_TextOneLine", marker.name);
- markerWidget->setUserString("IsMarker", "true");
- markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused);
- markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused);
-
- MarkerPosition markerPos;
- markerPos.interior = interior;
- markerPos.cellX = interior ? cellDx : x + mx - 1;
- markerPos.cellY = interior ? cellDy : y + ((my - 1)*-1);
- markerPos.nX = nX;
- markerPos.nY = nY;
-
- markerWidget->setUserData(markerPos);
+ MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY);
+ world->getDoorMarkers(cell, doors);
}
-
-
}
}
- mInterior = interior;
- mCurX = x;
- mCurY = y;
- mChanged = false;
- // fog of war
+ // Create a widget for each marker
+ int counter = 0;
+ for (std::vector<MWBase::World::DoorMarker>::iterator it = doors.begin(); it != doors.end(); ++it)
+ {
+ MWBase::World::DoorMarker marker = *it;
+
+ MarkerPosition markerPos;
+ MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, markerPos);
+ MyGUI::IntCoord widgetCoord(widgetPos.left - 4,
+ widgetPos.top - 4,
+ 8, 8);
+ ++counter;
+ MyGUI::Button* markerWidget = mLocalMap->createWidget<MyGUI::Button>("ButtonImage",
+ widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast<std::string>(counter));
+ markerWidget->setImageResource("DoorMarker");
+ markerWidget->setUserString("ToolTipType", "Layout");
+ markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
+ markerWidget->setUserString("Caption_TextOneLine", marker.name);
+ markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused);
+ markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused);
+ // Used by tooltips to not show the tooltip if marker is hidden by fog of war
+ markerWidget->setUserString("IsMarker", "true");
+ markerWidget->setUserData(markerPos);
+ }
+
+ updateMarkers();
+
applyFogOfWar();
// set the compass texture again, because MyGUI determines sorting of ImageBox widgets
@@ -222,6 +254,8 @@ namespace MWGui
void LocalMapBase::setPlayerPos(const float x, const float y)
{
+ updateMarkers();
+
if (x == mLastPositionX && y == mLastPositionY)
return;
@@ -255,12 +289,95 @@ namespace MWGui
mLastDirectionY = y;
}
+ void LocalMapBase::addDetectionMarkers(int type)
+ {
+ std::vector<MWWorld::Ptr> markers;
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ world->listDetectedReferences(
+ world->getPlayer().getPlayer(),
+ markers, MWBase::World::DetectionType(type));
+ if (markers.empty())
+ return;
+
+ std::string markerTexture;
+ MyGUI::Colour markerColour;
+ if (type == MWBase::World::Detect_Creature)
+ {
+ markerTexture = "textures\\menu_map_dcreature.dds";
+ markerColour = MyGUI::Colour(1,0,0,1);
+ }
+ if (type == MWBase::World::Detect_Key)
+ {
+ markerTexture = "textures\\menu_map_dkey.dds";
+ markerColour = MyGUI::Colour(0,1,0,1);
+ }
+ if (type == MWBase::World::Detect_Enchantment)
+ {
+ markerTexture = "textures\\menu_map_dmagic.dds";
+ markerColour = MyGUI::Colour(0,0,1,1);
+ }
+
+ int counter = 0;
+ for (std::vector<MWWorld::Ptr>::iterator it = markers.begin(); it != markers.end(); ++it)
+ {
+ const ESM::Position& worldPos = it->getRefData().getPosition();
+ MarkerPosition markerPos;
+ MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos);
+ MyGUI::IntCoord widgetCoord(widgetPos.left - 4,
+ widgetPos.top - 4,
+ 8, 8);
+ ++counter;
+ MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
+ widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast<std::string>(counter));
+ markerWidget->setImageTexture(markerTexture);
+ markerWidget->setUserString("IsMarker", "true");
+ markerWidget->setUserData(markerPos);
+ markerWidget->setColour(markerColour);
+ }
+ }
+
+ void LocalMapBase::updateMarkers()
+ {
+ // clear all previous markers
+ for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i)
+ {
+ if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker")
+ {
+ MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i));
+ }
+ }
+
+ addDetectionMarkers(MWBase::World::Detect_Creature);
+ addDetectionMarkers(MWBase::World::Detect_Key);
+ addDetectionMarkers(MWBase::World::Detect_Enchantment);
+
+ // Add marker for the spot marked with Mark magic effect
+ MWWorld::CellStore* markedCell = NULL;
+ ESM::Position markedPosition;
+ MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition);
+ if (markedCell && markedCell->isExterior() == !mInterior
+ && (!mInterior || Misc::StringUtils::ciEqual(markedCell->mCell->mName, mPrefix)))
+ {
+ MarkerPosition markerPos;
+ MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos);
+ MyGUI::IntCoord widgetCoord(widgetPos.left - 4,
+ widgetPos.top - 4,
+ 8, 8);
+ MyGUI::ImageBox* markerWidget = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
+ widgetCoord, MyGUI::Align::Default, "MarkerMarked");
+ markerWidget->setImageTexture("textures\\menu_map_smark.dds");
+ markerWidget->setUserString("IsMarker", "true");
+ markerWidget->setUserData(markerPos);
+ }
+ }
+
// ------------------------------------------------------------------------------------------
MapWindow::MapWindow(const std::string& cacheDir)
: MWGui::WindowPinnableBase("openmw_map_window.layout")
, mGlobal(false)
, mGlobalMap(0)
+ , mGlobalMapRender(0)
{
setCoord(500,0,320,300);
@@ -318,7 +435,7 @@ namespace MWGui
static int _counter=0;
MyGUI::Button* markerWidget = mGlobalMapImage->createWidget<MyGUI::Button>("ButtonImage",
- widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast<std::string>(_counter));
+ widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast<std::string>(_counter));
markerWidget->setImageResource("DoorMarker");
markerWidget->setUserString("ToolTipType", "Layout");
markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
@@ -384,7 +501,7 @@ namespace MWGui
for (unsigned int i=0; i<mGlobalMapImage->getChildCount (); ++i)
{
- if (mGlobalMapImage->getChildAt (i)->getName().substr(0,6) == "Marker")
+ if (mGlobalMapImage->getChildAt (i)->getName().substr(0,4) == "Door")
mGlobalMapImage->getChildAt (i)->castType<MyGUI::Button>()->setImageResource("DoorMarker");
}
@@ -395,20 +512,18 @@ namespace MWGui
void MapWindow::globalMapUpdatePlayer ()
{
- Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition ();
- Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation ();
- Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y);
-
- float worldX, worldY;
- mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY);
- worldX *= mGlobalMapRender->getWidth();
- worldY *= mGlobalMapRender->getHeight();
-
-
- // for interiors, we have no choice other than using the last position & direction.
- /// \todo save this last position in the savegame?
+ // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition
if (MWBase::Environment::get().getWorld ()->isCellExterior ())
{
+ Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition ();
+ Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation ();
+ Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y);
+
+ float worldX, worldY;
+ mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY);
+ worldX *= mGlobalMapRender->getWidth();
+ worldY *= mGlobalMapRender->getHeight();
+
mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(worldX - 16, worldY - 16));
MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain();
@@ -443,4 +558,19 @@ namespace MWGui
"#{sWorld}");
}
+ void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY)
+ {
+ float x, y;
+ mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y);
+ x *= mGlobalMapRender->getWidth();
+ y *= mGlobalMapRender->getHeight();
+
+ mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(x - 16, y - 16));
+
+ // set the view offset so that player is in the center
+ MyGUI::IntSize viewsize = mGlobalMap->getSize();
+ MyGUI::IntPoint viewoffs(0.5*viewsize.width - x, 0.5*viewsize.height - y);
+ mGlobalMap->setViewOffset(viewoffs);
+ }
+
}
diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp
index 5518ab4a8f..7df2105dcf 100644
--- a/apps/openmw/mwgui/mapwindow.hpp
+++ b/apps/openmw/mwgui/mapwindow.hpp
@@ -55,9 +55,16 @@ namespace MWGui
void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2);
void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2);
+ MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerPosition& markerPos);
+
virtual void notifyPlayerUpdate() {}
virtual void notifyMapChanged() {}
+ // Update markers (Detect X effects, Mark/Recall effects)
+ // Note, door markers handled in setActiveCell
+ void updateMarkers();
+ void addDetectionMarkers(int type);
+
OEngine::GUI::Layout* mLayout;
bool mMapDragAndDrop;
@@ -81,6 +88,8 @@ namespace MWGui
void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map
void cellExplored(int x, int y);
+ void setGlobalMapPlayerPosition (float worldX, float worldY);
+
virtual void open();
private:
diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp
index 530594ddaa..4da1668209 100644
--- a/apps/openmw/mwgui/merchantrepair.cpp
+++ b/apps/openmw/mwgui/merchantrepair.cpp
@@ -13,7 +13,6 @@
#include "../mwworld/containerstore.hpp"
#include "inventorywindow.hpp"
-#include "tradewindow.hpp"
namespace MWGui
{
@@ -119,7 +118,9 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender)
MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);
int price = boost::lexical_cast<int>(sender->getUserString("Price"));
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ player.getClass().getContainerStore(player).remove("gold_001", price, player);
startRepair(mActor);
}
diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp
index 48d7ec1717..378e76e467 100644
--- a/apps/openmw/mwgui/messagebox.cpp
+++ b/apps/openmw/mwgui/messagebox.cpp
@@ -10,56 +10,43 @@ namespace MWGui
MessageBoxManager::MessageBoxManager ()
{
- // defines
mMessageBoxSpeed = 0.1;
mInterMessageBoxe = NULL;
mStaticMessageBox = NULL;
mLastButtonPressed = -1;
}
- void MessageBoxManager::onFrame (float frameDuration)
+ MessageBoxManager::~MessageBoxManager ()
{
- std::vector<MessageBoxManagerTimer>::iterator it;
- for(it = mTimers.begin(); it != mTimers.end();)
+ std::vector<MessageBox*>::iterator it(mMessageBoxes.begin());
+ for (; it != mMessageBoxes.end(); ++it)
{
- // if this messagebox is already deleted, remove the timer and move on
- if (std::find(mMessageBoxes.begin(), mMessageBoxes.end(), it->messageBox) == mMessageBoxes.end())
- {
- it = mTimers.erase(it);
- continue;
- }
+ delete *it;
+ }
+ }
- it->current += frameDuration;
- if(it->current >= it->max)
+ void MessageBoxManager::onFrame (float frameDuration)
+ {
+ std::vector<MessageBox*>::iterator it;
+ for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();)
+ {
+ (*it)->mCurrentTime += frameDuration;
+ if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox)
{
- it->messageBox->mMarkedToDelete = true;
-
- if(*mMessageBoxes.begin() == it->messageBox) // if this box is the last one
- {
- // collect all with mMarkedToDelete and delete them.
- // and place the other messageboxes on the right position
- int height = 0;
- std::vector<MessageBox*>::iterator it2 = mMessageBoxes.begin();
- while(it2 != mMessageBoxes.end())
- {
- if((*it2)->mMarkedToDelete)
- {
- delete (*it2);
- it2 = mMessageBoxes.erase(it2);
- }
- else {
- (*it2)->update(height);
- height += (*it2)->getHeight();
- ++it2;
- }
- }
- }
- it = mTimers.erase(it);
+ delete *it;
+ it = mMessageBoxes.erase(it);
}
else
- {
++it;
- }
+ }
+
+ float height = 0;
+ it = mMessageBoxes.begin();
+ while(it != mMessageBoxes.end())
+ {
+ (*it)->update(height);
+ height += (*it)->getHeight();
+ ++it;
}
if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) {
@@ -74,14 +61,13 @@ namespace MWGui
void MessageBoxManager::createMessageBox (const std::string& message, bool stat)
{
MessageBox *box = new MessageBox(*this, message);
+ box->mCurrentTime = 0;
+ box->mMaxTime = message.length()*mMessageBoxSpeed;
if(stat)
mStaticMessageBox = box;
- else
- removeMessageBox(message.length()*mMessageBoxSpeed, box);
mMessageBoxes.push_back(box);
- std::vector<MessageBox*>::iterator it;
if(mMessageBoxes.size() > 3) {
delete *mMessageBoxes.begin();
@@ -89,7 +75,7 @@ namespace MWGui
}
int height = 0;
- for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
+ for(std::vector<MessageBox*>::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
{
(*it)->update(height);
height += (*it)->getHeight();
@@ -119,15 +105,6 @@ namespace MWGui
return mInterMessageBoxe != NULL;
}
- void MessageBoxManager::removeMessageBox (float time, MessageBox *msgbox)
- {
- MessageBoxManagerTimer timer;
- timer.current = 0;
- timer.max = time;
- timer.messageBox = msgbox;
-
- mTimers.insert(mTimers.end(), timer);
- }
bool MessageBoxManager::removeMessageBox (MessageBox *msgbox)
{
@@ -169,56 +146,32 @@ namespace MWGui
: Layout("openmw_messagebox.layout")
, mMessageBoxManager(parMessageBoxManager)
, mMessage(message)
+ , mCurrentTime(0)
+ , mMaxTime(0)
{
// defines
- mFixedWidth = 300;
mBottomPadding = 20;
mNextBoxPadding = 20;
- mMarkedToDelete = false;
getWidget(mMessageWidget, "message");
mMessageWidget->setOverflowToTheLeft(true);
mMessageWidget->setCaptionWithReplacing(mMessage);
-
- MyGUI::IntSize size;
- size.width = mFixedWidth;
- size.height = 100; // dummy
-
- MyGUI::IntCoord coord;
- coord.left = 10; // dummy
- coord.top = 10; // dummy
-
- mMessageWidget->setSize(size);
-
- MyGUI::IntSize textSize = mMessageWidget->getTextSize();
-
- size.height = mHeight = textSize.height + 20; // this is the padding between the text and the box
-
- mMainWidget->setSize(size);
- size.width -= 15; // this is to center the text (see messagebox.layout, Widget type="Edit" position="-2 -3 0 0")
- mMessageWidget->setSize(size);
}
void MessageBox::update (int height)
{
MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize();
- MyGUI::IntCoord coord;
- coord.left = (gameWindowSize.width - mFixedWidth)/2;
- coord.top = (gameWindowSize.height - mHeight - height - mBottomPadding);
-
- MyGUI::IntSize size;
- size.width = mFixedWidth;
- size.height = mHeight;
+ MyGUI::IntPoint pos;
+ pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2;
+ pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding);
- mMainWidget->setCoord(coord);
- mMainWidget->setSize(size);
- mMainWidget->setVisible(true);
+ mMainWidget->setPosition(pos);
}
int MessageBox::getHeight ()
{
- return mHeight+mNextBoxPadding; // 20 is the padding between this and the next MessageBox
+ return mMainWidget->getHeight()+mNextBoxPadding; // 20 is the padding between this and the next MessageBox
}
diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp
index 63840cfe2c..0288f366ce 100644
--- a/apps/openmw/mwgui/messagebox.hpp
+++ b/apps/openmw/mwgui/messagebox.hpp
@@ -19,24 +19,17 @@ namespace MWGui
class InteractiveMessageBox;
class MessageBoxManager;
class MessageBox;
-
- struct MessageBoxManagerTimer {
- float current;
- float max;
- MessageBox *messageBox;
- };
-
class MessageBoxManager
{
public:
MessageBoxManager ();
+ ~MessageBoxManager ();
void onFrame (float frameDuration);
void createMessageBox (const std::string& message, bool stat = false);
void removeStaticMessageBox ();
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
bool isInteractiveMessageBox ();
- void removeMessageBox (float time, MessageBox *msgbox);
bool removeMessageBox (MessageBox *msgbox);
void setMessageBoxSpeed (int speed);
@@ -54,7 +47,6 @@ namespace MWGui
std::vector<MessageBox*> mMessageBoxes;
InteractiveMessageBox* mInterMessageBoxe;
MessageBox* mStaticMessageBox;
- std::vector<MessageBoxManagerTimer> mTimers;
float mMessageBoxSpeed;
int mLastButtonPressed;
};
@@ -67,14 +59,13 @@ namespace MWGui
int getHeight ();
void update (int height);
- bool mMarkedToDelete;
+ float mCurrentTime;
+ float mMaxTime;
protected:
MessageBoxManager& mMessageBoxManager;
- int mHeight;
const std::string& mMessage;
MyGUI::EditBox* mMessageWidget;
- int mFixedWidth;
int mBottomPadding;
int mNextBoxPadding;
};
diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp
index 879fcb483f..50d53abacd 100644
--- a/apps/openmw/mwgui/mode.hpp
+++ b/apps/openmw/mwgui/mode.hpp
@@ -28,6 +28,7 @@ namespace MWGui
GM_Travel,
GM_SpellCreation,
GM_Enchanting,
+ GM_Recharge,
GM_Training,
GM_MerchantRepair,
diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp
index 16be5f6cca..13ee4396d0 100644
--- a/apps/openmw/mwgui/pickpocketitemmodel.cpp
+++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp
@@ -40,6 +40,14 @@ namespace MWGui
for (size_t i = 0; i<mSourceModel->getItemCount(); ++i)
{
const ItemStack& item = mSourceModel->getItem(i);
+
+ // Bound items may not be stolen
+ if (item.mBase.getCellRef().mRefID.size() > 6
+ && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_")
+ {
+ continue;
+ }
+
if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end()
&& item.mType != ItemStack::Type_Equipped)
mItems.push_back(item);
diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp
index 5f749d3d3d..b8f52cd1fb 100644
--- a/apps/openmw/mwgui/quickkeysmenu.cpp
+++ b/apps/openmw/mwgui/quickkeysmenu.cpp
@@ -5,7 +5,7 @@
#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
-#include "../mwmechanics/spellsuccess.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "../mwgui/inventorywindow.hpp"
#include "../mwgui/bookwindow.hpp"
#include "../mwgui/scrollwindow.hpp"
@@ -269,13 +269,39 @@ namespace MWGui
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
- MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
- MWMechanics::Spells& spells = stats.getSpells();
+
+ if (type == Type_Item || type == Type_MagicItem)
+ {
+ MWWorld::Ptr item = *button->getChildAt (0)->getUserData<MWWorld::Ptr>();
+ // make sure the item is available
+ if (item.getRefData ().getCount() < 1)
+ {
+ // Try searching for a compatible replacement
+ std::string id = item.getCellRef().mRefID;
+
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, id))
+ {
+ item = *it;
+ button->getChildAt(0)->setUserData(item);
+ break;
+ }
+ }
+
+ if (item.getRefData().getCount() < 1)
+ {
+ // No replacement was found
+ MWBase::Environment::get().getWindowManager ()->messageBox (
+ "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item));
+ return;
+ }
+ }
+ }
if (type == Type_Magic)
{
std::string spellId = button->getChildAt(0)->getUserString("Spell");
- spells.setSelectedSpell(spellId);
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
}
@@ -283,14 +309,6 @@ namespace MWGui
{
MWWorld::Ptr item = *button->getChildAt (0)->getUserData<MWWorld::Ptr>();
- // make sure the item is available
- if (item.getRefData ().getCount() == 0)
- {
- MWBase::Environment::get().getWindowManager ()->messageBox (
- "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item));
- return;
- }
-
boost::shared_ptr<MWWorld::Action> action = MWWorld::Class::get(item).use(item);
action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
@@ -309,14 +327,6 @@ namespace MWGui
{
MWWorld::Ptr item = *button->getChildAt (0)->getUserData<MWWorld::Ptr>();
- // make sure the item is available
- if (item.getRefData ().getCount() == 0)
- {
- MWBase::Environment::get().getWindowManager ()->messageBox (
- "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item));
- return;
- }
-
// retrieve ContainerStoreIterator to the item
MWWorld::ContainerStoreIterator it = store.begin();
for (; it != store.end(); ++it)
@@ -335,13 +345,9 @@ namespace MWGui
MWWorld::ActionEquip action(item);
action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ());
-
- // since we changed equipping status, update the inventory window
- MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
}
store.setSelectedEnchantItem(it);
- spells.setSelectedSpell("");
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item);
}
}
diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp
new file mode 100644
index 0000000000..b700360bac
--- /dev/null
+++ b/apps/openmw/mwgui/recharge.cpp
@@ -0,0 +1,196 @@
+#include "recharge.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "widgets.hpp"
+
+namespace MWGui
+{
+
+Recharge::Recharge()
+ : WindowBase("openmw_recharge_dialog.layout")
+{
+ getWidget(mBox, "Box");
+ getWidget(mView, "View");
+ getWidget(mGemBox, "GemBox");
+ getWidget(mGemIcon, "GemIcon");
+ getWidget(mChargeLabel, "ChargeLabel");
+ getWidget(mCancelButton, "CancelButton");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel);
+
+ setVisible(false);
+}
+
+void Recharge::open()
+{
+ center();
+}
+
+void Recharge::start (const MWWorld::Ptr &item)
+{
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ mGemIcon->setImageTexture (path);
+ mGemIcon->setUserString("ToolTipType", "ItemPtr");
+ mGemIcon->setUserData(item);
+
+ updateView();
+}
+
+void Recharge::updateView()
+{
+ MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
+
+ std::string soul = gem.getCellRef().mSoul;
+ const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
+
+ mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast<std::string>(creature->mData.mSoul));
+
+ bool toolBoxVisible = (gem.getRefData().getCount() != 0);
+ mGemBox->setVisible(toolBoxVisible);
+
+ bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top);
+
+ if (toolBoxVisible && !toolBoxWasVisible)
+ {
+ // shrink
+ mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height));
+ mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height));
+ }
+ else if (!toolBoxVisible && toolBoxWasVisible)
+ {
+ // expand
+ mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top));
+ mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height));
+ }
+
+ while (mView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0));
+
+ int currentY = 0;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
+ for (MWWorld::ContainerStoreIterator iter (store.begin());
+ iter!=store.end(); ++iter)
+ {
+ std::string enchantmentName = iter->getClass().getEnchantment(*iter);
+ if (enchantmentName.empty())
+ continue;
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
+ if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge
+ || iter->getCellRef().mEnchantmentCharge == -1)
+ continue;
+
+ MyGUI::TextBox* text = mView->createWidget<MyGUI::TextBox> (
+ "SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default);
+ text->setCaption(MWWorld::Class::get(*iter).getName(*iter));
+ text->setNeedMouseFocus(false);
+ currentY += 19;
+
+ MyGUI::ImageBox* icon = mView->createWidget<MyGUI::ImageBox> (
+ "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(*iter).getInventoryIcon(*iter);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ icon->setImageTexture (path);
+ icon->setUserString("ToolTipType", "ItemPtr");
+ icon->setUserData(*iter);
+ icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked);
+ icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel);
+
+ Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget<Widgets::MWDynamicStat>
+ ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default);
+ chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge);
+ chargeWidget->setNeedMouseFocus(false);
+
+ currentY += 32 + 4;
+ }
+ mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY)));
+}
+
+void Recharge::onCancel(MyGUI::Widget *sender)
+{
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge);
+}
+
+void Recharge::onItemClicked(MyGUI::Widget *sender)
+{
+ MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
+
+ if (!gem.getRefData().getCount())
+ return;
+
+ MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
+ MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
+
+ float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified();
+ if (luckTerm < 1|| luckTerm > 10)
+ luckTerm = 1;
+
+ float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
+
+ if (intelligenceTerm > 20)
+ intelligenceTerm = 20;
+ if (intelligenceTerm < 1)
+ intelligenceTerm = 1;
+
+ float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < x)
+ {
+ std::string soul = gem.getCellRef().mSoul;
+ const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
+
+ float restored = creature->mData.mSoul * (roll / x);
+
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
+ item.getClass().getEnchantment(item));
+ item.getCellRef().mEnchantmentCharge =
+ std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast<float>(enchantment->mData.mCharge));
+
+ player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
+ }
+
+ gem.getContainerStore()->remove(gem, 1, player);
+
+ if (gem.getRefData().getCount() == 0)
+ {
+ std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage51")->getString();
+ message = boost::str(boost::format(message) % gem.getClass().getName(gem));
+ MWBase::Environment::get().getWindowManager()->messageBox(message);
+ }
+
+ updateView();
+}
+
+void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+{
+ if (mView->getViewOffset().top + _rel*0.3 > 0)
+ mView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3));
+}
+
+}
diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp
new file mode 100644
index 0000000000..2ffc5e10f8
--- /dev/null
+++ b/apps/openmw/mwgui/recharge.hpp
@@ -0,0 +1,42 @@
+#ifndef OPENMW_MWGUI_RECHARGE_H
+#define OPENMW_MWGUI_RECHARGE_H
+
+#include "windowbase.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+
+class Recharge : public WindowBase
+{
+public:
+ Recharge();
+
+ virtual void open();
+
+ void start (const MWWorld::Ptr& gem);
+
+protected:
+ MyGUI::Widget* mBox;
+ MyGUI::ScrollView* mView;
+
+ MyGUI::Widget* mGemBox;
+
+ MyGUI::ImageBox* mGemIcon;
+
+ MyGUI::TextBox* mChargeLabel;
+
+ MyGUI::Button* mCancelButton;
+
+ void updateView();
+
+ void onItemClicked (MyGUI::Widget* sender);
+ void onCancel (MyGUI::Widget* sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp
new file mode 100644
index 0000000000..a1acd35884
--- /dev/null
+++ b/apps/openmw/mwgui/savegamedialog.cpp
@@ -0,0 +1,49 @@
+#include "savegamedialog.hpp"
+#include "widgets.hpp"
+
+
+namespace MWGui
+{
+
+ SaveGameDialog::SaveGameDialog()
+ : WindowModal("openmw_savegame_dialog.layout")
+ {
+ getWidget(mScreenshot, "Screenshot");
+ getWidget(mCharacterSelection, "SelectCharacter");
+ getWidget(mInfoText, "InfoText");
+ getWidget(mOkButton, "OkButton");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mSaveList, "SaveList");
+ getWidget(mSaveNameEdit, "SaveNameEdit");
+ getWidget(mSpacer, "Spacer");
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked);
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
+
+ }
+
+ void SaveGameDialog::open()
+ {
+ center();
+ }
+
+ void SaveGameDialog::setLoadOrSave(bool load)
+ {
+ mSaveNameEdit->setVisible(!load);
+ mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
+ mCharacterSelection->setVisible(load);
+ mSpacer->setUserString("Hidden", load ? "false" : "true");
+
+ center();
+ }
+
+ void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender)
+ {
+ setVisible(false);
+ }
+
+ void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender)
+ {
+ setVisible(false);
+ }
+
+}
diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp
new file mode 100644
index 0000000000..1a3178ef30
--- /dev/null
+++ b/apps/openmw/mwgui/savegamedialog.hpp
@@ -0,0 +1,37 @@
+#ifndef OPENMW_MWGUI_SAVEGAMEDIALOG_H
+#define OPENMW_MWGUI_SAVEGAMEDIALOG_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+
+ class SaveGameDialog : public MWGui::WindowModal
+ {
+ public:
+ SaveGameDialog();
+
+ virtual void open();
+
+ void setLoadOrSave(bool load);
+
+ void onCancelButtonClicked (MyGUI::Widget* sender);
+ void onOkButtonClicked (MyGUI::Widget* sender);
+
+
+ private:
+ MyGUI::ImageBox* mScreenshot;
+
+ MyGUI::ComboBox* mCharacterSelection;
+ MyGUI::EditBox* mInfoText;
+ MyGUI::Button* mOkButton;
+ MyGUI::Button* mCancelButton;
+ MyGUI::ListBox* mSaveList;
+ MyGUI::EditBox* mSaveNameEdit;
+ MyGUI::Widget* mSpacer;
+
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp
index 923b9d01d6..c99e2d0de3 100644
--- a/apps/openmw/mwgui/settingswindow.cpp
+++ b/apps/openmw/mwgui/settingswindow.cpp
@@ -31,9 +31,7 @@ namespace
std::string textureFilteringToStr(const std::string& val)
{
- if (val == "none")
- return "None";
- else if (val == "anisotropic")
+ if (val == "anisotropic")
return "Anisotropic";
else if (val == "bilinear")
return "Bilinear";
@@ -94,6 +92,7 @@ namespace MWGui
{
getWidget(mOkButton, "OkButton");
getWidget(mBestAttackButton, "BestAttackButton");
+ getWidget(mGrabCursorButton, "GrabCursorButton");
getWidget(mSubtitlesButton, "SubtitlesButton");
getWidget(mCrosshairButton, "CrosshairButton");
getWidget(mResolutionList, "ResolutionList");
@@ -135,6 +134,7 @@ namespace MWGui
mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mBestAttackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mGrabCursorButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked);
mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled);
@@ -145,7 +145,7 @@ namespace MWGui
mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
- mTextureFilteringButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringToggled);
+ mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged);
mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled);
mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
@@ -157,7 +157,7 @@ namespace MWGui
mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
- mShadowsTextureSize->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSize);
+ mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged);
mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
@@ -203,6 +203,7 @@ namespace MWGui
mSubtitlesButton->setCaptionWithReplacing(Settings::Manager::getBool("subtitles", "GUI") ? "#{sOn}" : "#{sOff}");
mCrosshairButton->setCaptionWithReplacing(Settings::Manager::getBool("crosshair", "HUD") ? "#{sOn}" : "#{sOff}");
mBestAttackButton->setCaptionWithReplacing(Settings::Manager::getBool("best attack", "Game") ? "#{sOn}" : "#{sOff}");
+ mGrabCursorButton->setCaptionWithReplacing(Settings::Manager::getBool("grab cursor", "Input") ? "#{sOn}" : "#{sOff}");
float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin);
mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1));
@@ -297,22 +298,9 @@ namespace MWGui
mResolutionList->setIndexSelected(MyGUI::ITEM_NONE);
}
- void SettingsWindow::onShadowTextureSize(MyGUI::Widget* _sender)
+ void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos)
{
- std::string size = mShadowsTextureSize->getCaption();
-
- if (size == "512")
- size = "1024";
- else if (size == "1024")
- size = "2048";
- else if (size == "2048")
- size = "4096";
- else
- size = "512";
-
- mShadowsTextureSize->setCaption(size);
-
- Settings::Manager::setString("texture size", "Shadows", size);
+ Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos));
apply();
}
@@ -408,7 +396,8 @@ namespace MWGui
Settings::Manager::setBool("subtitles", "GUI", newState);
else if (_sender == mBestAttackButton)
Settings::Manager::setBool("best attack", "Game", newState);
-
+ else if (_sender == mGrabCursorButton)
+ Settings::Manager::setBool("grab cursor", "Input", newState);
apply();
}
}
@@ -482,22 +471,9 @@ namespace MWGui
apply();
}
- void SettingsWindow::onTextureFilteringToggled(MyGUI::Widget* _sender)
+ void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos)
{
- std::string current = Settings::Manager::getString("texture filtering", "General");
- std::string next;
- if (current == "none")
- next = "bilinear";
- else if (current == "bilinear")
- next = "trilinear";
- else if (current == "trilinear")
- next = "anisotropic";
- else
- next = "none";
-
- mTextureFilteringButton->setCaption(textureFilteringToStr(next));
-
- Settings::Manager::setString("texture filtering", "General", next);
+ Settings::Manager::setString("texture filtering", "General", Misc::StringUtils::lowerCase(_sender->getItemNameAt(pos)));
apply();
}
diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp
index a585bda7e1..6b9ce414b8 100644
--- a/apps/openmw/mwgui/settingswindow.hpp
+++ b/apps/openmw/mwgui/settingswindow.hpp
@@ -33,6 +33,7 @@ namespace MWGui
MyGUI::Button* mSubtitlesButton;
MyGUI::Button* mCrosshairButton;
MyGUI::Button* mBestAttackButton;
+ MyGUI::Button* mGrabCursorButton;
// graphics
MyGUI::ListBox* mResolutionList;
@@ -42,7 +43,7 @@ namespace MWGui
MyGUI::ScrollBar* mViewDistanceSlider;
MyGUI::ScrollBar* mFOVSlider;
MyGUI::ScrollBar* mAnisotropySlider;
- MyGUI::Button* mTextureFilteringButton;
+ MyGUI::ComboBox* mTextureFilteringButton;
MyGUI::TextBox* mAnisotropyLabel;
MyGUI::Widget* mAnisotropyBox;
MyGUI::Button* mWaterShaderButton;
@@ -55,7 +56,7 @@ namespace MWGui
MyGUI::Button* mShadowsEnabledButton;
MyGUI::Button* mShadowsLargeDistance;
- MyGUI::Button* mShadowsTextureSize;
+ MyGUI::ComboBox* mShadowsTextureSize;
MyGUI::Button* mActorShadows;
MyGUI::Button* mStaticsShadows;
MyGUI::Button* mMiscShadows;
@@ -76,7 +77,7 @@ namespace MWGui
void onOkButtonClicked(MyGUI::Widget* _sender);
void onFpsToggled(MyGUI::Widget* _sender);
- void onTextureFilteringToggled(MyGUI::Widget* _sender);
+ void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos);
void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos);
void onButtonToggled(MyGUI::Widget* _sender);
void onResolutionSelected(MyGUI::ListBox* _sender, size_t index);
@@ -85,7 +86,7 @@ namespace MWGui
void onShadersToggled(MyGUI::Widget* _sender);
void onShaderModeToggled(MyGUI::Widget* _sender);
- void onShadowTextureSize(MyGUI::Widget* _sender);
+ void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);
void onRebindAction(MyGUI::Widget* _sender);
void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel);
diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp
index b95eec0b67..6d70c85d9d 100644
--- a/apps/openmw/mwgui/soulgemdialog.cpp
+++ b/apps/openmw/mwgui/soulgemdialog.cpp
@@ -21,7 +21,8 @@ namespace MWGui
{
if (button == 0)
{
- /// \todo show recharge enchanted item dialog here
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge);
+ MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem);
}
else
{
diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp
index a7fcfdd021..bbd28b2de6 100644
--- a/apps/openmw/mwgui/spellbuyingwindow.cpp
+++ b/apps/openmw/mwgui/spellbuyingwindow.cpp
@@ -10,11 +10,11 @@
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "inventorywindow.hpp"
-#include "tradewindow.hpp"
namespace MWGui
{
@@ -123,7 +123,7 @@ namespace MWGui
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
spells.add (mSpellsWidgetMap.find(_sender)->second);
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+ player.getClass().getContainerStore(player).remove("gold_001", price, player);
startSpellBuying(mPtr);
MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp
index dc86fd825f..b9324fea17 100644
--- a/apps/openmw/mwgui/spellcreationdialog.cpp
+++ b/apps/openmw/mwgui/spellcreationdialog.cpp
@@ -8,13 +8,13 @@
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
-#include "../mwmechanics/spellsuccess.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "tooltips.hpp"
#include "class.hpp"
#include "inventorywindow.hpp"
-#include "tradewindow.hpp"
namespace
{
@@ -89,6 +89,8 @@ namespace MWGui
mEffect.mMagnMax = 1;
mEffect.mDuration = 1;
mEffect.mArea = 0;
+ mEffect.mSkill = -1;
+ mEffect.mAttribute = -1;
eventEffectAdded(mEffect);
onRangeButtonClicked(mRangeButton);
@@ -340,13 +342,14 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption();
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast<int>(mPriceLabel->getCaption()));
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ player.getClass().getContainerStore(player).remove("gold_001", boost::lexical_cast<int>(mPriceLabel->getCaption()), player);
MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
spells.add (spell->mId);
diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp
index 0c303485af..e93e96c4b2 100644
--- a/apps/openmw/mwgui/spellicons.cpp
+++ b/apps/openmw/mwgui/spellicons.cpp
@@ -21,120 +21,41 @@
namespace MWGui
{
- void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
+ void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
+ const std::string& sourceName, float magnitude, float remainingTime)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MagicEffectInfo newEffectSource;
+ newEffectSource.mKey = key;
+ newEffectSource.mMagnitude = magnitude;
+ newEffectSource.mPermanent = mIsPermanent;
+ newEffectSource.mRemainingTime = remainingTime;
+ newEffectSource.mSource = sourceName;
+
+ mEffectSources[key.mId].push_back(newEffectSource);
+ }
- std::map <int, std::vector<MagicEffectInfo> > effects;
- // add permanent item enchantments
- MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
- for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
- {
- MWWorld::ContainerStoreIterator it = store.getSlot(slot);
- if (it == store.end())
- continue;
- std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it);
- if (enchantment.empty())
- continue;
- const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantment);
- if (enchant->mData.mType != ESM::Enchantment::ConstantEffect)
- continue;
-
- const ESM::EffectList& list = enchant->mEffects;
- for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
- effectIt != list.mList.end(); ++effectIt)
- {
- const ESM::MagicEffect* magicEffect =
- MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
-
- MagicEffectInfo effectInfo;
- effectInfo.mSource = MWWorld::Class::get(*it).getName(*it);
- effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
- if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
- effectInfo.mKey.mArg = effectIt->mSkill;
- else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
- effectInfo.mKey.mArg = effectIt->mAttribute;
- // just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense
- effectInfo.mMagnitude = effectIt->mMagnMin;
- effectInfo.mPermanent = true;
- effects[effectIt->mEffectID].push_back (effectInfo);
- }
- }
+ void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
+ {
+ // TODO: Tracking add/remove/expire would be better than force updating every frame
- // add permanent spells
- const MWMechanics::Spells& spells = stats.getSpells();
- for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
- {
- const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(it->first);
-
- // these are the spell types that are permanently in effect
- if (!(spell->mData.mType == ESM::Spell::ST_Ability)
- && !(spell->mData.mType == ESM::Spell::ST_Disease)
- && !(spell->mData.mType == ESM::Spell::ST_Curse)
- && !(spell->mData.mType == ESM::Spell::ST_Blight))
- continue;
- const ESM::EffectList& list = spell->mEffects;
- for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
- effectIt != list.mList.end(); ++effectIt)
- {
- const ESM::MagicEffect* magicEffect =
- MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
- MagicEffectInfo effectInfo;
- effectInfo.mSource = getSpellDisplayName (it->first);
- effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
- if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
- effectInfo.mKey.mArg = effectIt->mSkill;
- else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
- effectInfo.mKey.mArg = effectIt->mAttribute;
- // just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense
- effectInfo.mMagnitude = effectIt->mMagnMin;
- effectInfo.mPermanent = true;
-
- effects[effectIt->mEffectID].push_back (effectInfo);
- }
- }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
- // add lasting effect spells/potions etc
- const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells();
- for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin();
- it != activeSpells.end(); ++it)
- {
- const ESM::EffectList& list = getSpellEffectList(it->first);
- float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
+ EffectSourceVisitor visitor;
- for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
- effectIt != list.mList.end(); ++effectIt)
- {
- const ESM::MagicEffect* magicEffect =
- MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
-
- MagicEffectInfo effectInfo;
- effectInfo.mSource = getSpellDisplayName (it->first);
- effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
- if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
- effectInfo.mKey.mArg = effectIt->mSkill;
- else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
- effectInfo.mKey.mArg = effectIt->mAttribute;
- effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * it->second.second;
- effectInfo.mRemainingTime = effectIt->mDuration +
- (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
-
- // ingredients need special casing for their magnitude / duration
- /// \todo duplicated from ActiveSpells, helper function?
- if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first))
- {
- effectInfo.mRemainingTime = effectIt->mDuration * it->second.second +
- (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
+ // permanent item enchantments & permanent spells
+ visitor.mIsPermanent = true;
+ MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
+ store.visitEffectSources(visitor);
+ stats.getSpells().visitEffectSources(visitor);
- effectInfo.mMagnitude = static_cast<int> (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost));
- }
+ // now add lasting effects
+ visitor.mIsPermanent = false;
+ stats.getActiveSpells().visitEffectSources(visitor);
- effects[effectIt->mEffectID].push_back (effectInfo);
- }
- }
+ std::map <int, std::vector<MagicEffectInfo> >& effects = visitor.mEffectSources;
int w=2;
@@ -268,55 +189,4 @@ namespace MWGui
}
}
-
- std::string SpellIcons::getSpellDisplayName (const std::string& id)
- {
- if (const ESM::Spell *spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
- return spell->mName;
-
- if (const ESM::Potion *potion =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
- return potion->mName;
-
- if (const ESM::Ingredient *ingredient =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
- return ingredient->mName;
-
- throw std::runtime_error ("ID " + id + " has no display name");
- }
-
- ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id)
- {
- if (const ESM::Spell *spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
- return spell->mEffects;
-
- if (const ESM::Potion *potion =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
- return potion->mEffects;
-
- if (const ESM::Ingredient *ingredient =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
- {
- const ESM::MagicEffect *magicEffect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
- ingredient->mData.mEffectID[0]);
-
- ESM::ENAMstruct effect;
- effect.mEffectID = ingredient->mData.mEffectID[0];
- effect.mSkill = ingredient->mData.mSkills[0];
- effect.mAttribute = ingredient->mData.mAttributes[0];
- effect.mRange = 0;
- effect.mArea = 0;
- effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1;
- effect.mMagnMin = 1;
- effect.mMagnMax = 1;
- ESM::EffectList result;
- result.mList.push_back (effect);
- return result;
- }
- throw std::runtime_error("ID " + id + " does not have effects");
- }
-
}
diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp
index 818d67b5bb..a29e2a00ab 100644
--- a/apps/openmw/mwgui/spellicons.hpp
+++ b/apps/openmw/mwgui/spellicons.hpp
@@ -2,6 +2,7 @@
#define MWGUI_SPELLICONS_H
#include <string>
+#include <vector>
#include "../mwmechanics/magiceffects.hpp"
@@ -34,14 +35,23 @@ namespace MWGui
bool mPermanent; // the effect is permanent
};
+ class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor
+ {
+ public:
+ bool mIsPermanent;
+
+ std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
+
+ virtual void visit (MWMechanics::EffectKey key,
+ const std::string& sourceName, float magnitude, float remainingTime = -1);
+ };
+
class SpellIcons
{
public:
void updateWidgets(MyGUI::Widget* parent, bool adjustSize);
private:
- std::string getSpellDisplayName (const std::string& id);
- ESM::EffectList getSpellEffectList (const std::string& id);
std::map<int, MyGUI::ImageBox*> mWidgetMap;
};
diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp
index d5e3abc110..e2817c38b8 100644
--- a/apps/openmw/mwgui/spellwindow.cpp
+++ b/apps/openmw/mwgui/spellwindow.cpp
@@ -9,7 +9,7 @@
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
-#include "../mwmechanics/spellsuccess.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "spellicons.hpp"
#include "inventorywindow.hpp"
@@ -51,8 +51,6 @@ namespace MWGui
setCoord(498, 300, 302, 300);
- updateSpells();
-
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize);
}
@@ -88,61 +86,8 @@ namespace MWGui
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
- // the following code switches between selected enchanted item and selected spell (only one of these
- // can be active at a time)
- std::string selectedSpell = spells.getSelectedSpell();
- MWWorld::Ptr selectedItem;
- if (store.getSelectedEnchantItem() != store.end())
- {
- selectedSpell = "";
- selectedItem = *store.getSelectedEnchantItem();
-
- bool allowSelectedItem = true;
-
- // make sure that the item is still in the player inventory, otherwise it can't be selected
- bool found = false;
- for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
- {
- if (*it == selectedItem)
- found = true;
- }
- if (!found)
- allowSelectedItem = false;
-
- // if the selected item can be equipped, make sure that it actually is equipped
- std::pair<std::vector<int>, bool> slots;
- slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem);
- if (!slots.first.empty())
- {
- bool equipped = false;
- for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
- {
- if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem)
- {
- equipped = true;
- break;
- }
- }
-
- if (!equipped)
- allowSelectedItem = false;
- }
-
- if (!allowSelectedItem)
- {
- store.setSelectedEnchantItem(store.end());
- spells.setSelectedSpell("");
- MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
- selectedItem = MWWorld::Ptr();
- }
- }
-
-
-
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
- {
spellList.push_back (it->first);
- }
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
@@ -212,7 +157,7 @@ namespace MWGui
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
- if (*it == selectedSpell)
+ if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell())
t->setStateSelected(true);
mHeight += spellHeight;
@@ -231,7 +176,7 @@ namespace MWGui
t->setUserString("Spell", *it);
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
- t->setStateSelected(*it == selectedSpell);
+ t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell());
// cost / success chance
MyGUI::Button* costChance = mSpellView->createWidget<MyGUI::Button>("SpellText",
@@ -241,7 +186,7 @@ namespace MWGui
costChance->setCaption(cost + "/" + chance);
costChance->setTextAlign(MyGUI::Align::Right);
costChance->setNeedMouseFocus(false);
- costChance->setStateSelected(*it == selectedSpell);
+ costChance->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell());
mHeight += spellHeight;
@@ -278,14 +223,24 @@ namespace MWGui
t->setUserString("Equipped", equipped ? "true" : "false");
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
- t->setStateSelected(item == selectedItem);
+ if (store.getSelectedEnchantItem() != store.end())
+ t->setStateSelected(item == *store.getSelectedEnchantItem());
+
// cost / charge
MyGUI::Button* costCharge = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
- std::string cost = boost::lexical_cast<std::string>(enchant->mData.mCost);
- std::string charge = boost::lexical_cast<std::string>(enchant->mData.mCharge); /// \todo track current charge
+ float enchantCost = enchant->mData.mCost;
+ MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
+ int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+ int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
+
+ std::string cost = boost::lexical_cast<std::string>(castCost);
+ int currentCharge = int(item.getCellRef().mEnchantmentCharge);
+ if (currentCharge == -1)
+ currentCharge = enchant->mData.mCharge;
+ std::string charge = boost::lexical_cast<std::string>(currentCharge);
if (enchant->mData.mType == ESM::Enchantment::CastOnce)
{
// this is Morrowind behaviour
@@ -296,7 +251,8 @@ namespace MWGui
costCharge->setCaption(cost + "/" + charge);
costCharge->setTextAlign(MyGUI::Align::Right);
costCharge->setNeedMouseFocus(false);
- costCharge->setStateSelected(item == selectedItem);
+ if (store.getSelectedEnchantItem() != store.end())
+ costCharge->setStateSelected(item == *store.getSelectedEnchantItem());
mHeight += spellHeight;
}
@@ -343,9 +299,7 @@ namespace MWGui
void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
- MWMechanics::Spells& spells = stats.getSpells();
MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
// retrieve ContainerStoreIterator to the item
@@ -373,7 +327,6 @@ namespace MWGui
}
store.setSelectedEnchantItem(it);
- spells.setSelectedSpell("");
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item);
updateSpells();
@@ -383,9 +336,7 @@ namespace MWGui
{
std::string spellId = _sender->getUserString("Spell");
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
- MWMechanics::Spells& spells = stats.getSpells();
if (MyGUI::InputManager::getInstance().isShiftPressed())
{
@@ -413,7 +364,6 @@ namespace MWGui
}
else
{
- spells.setSelectedSpell(spellId);
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
}
@@ -444,10 +394,7 @@ namespace MWGui
MWMechanics::Spells& spells = stats.getSpells();
if (spells.getSelectedSpell() == mSpellToDelete)
- {
- spells.setSelectedSpell("");
MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
- }
spells.remove(mSpellToDelete);
diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
index 9facdac404..ab6096b7e6 100644
--- a/apps/openmw/mwgui/statswindow.cpp
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -134,38 +134,28 @@ namespace MWGui
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
{
- static const char *ids[] =
- {
- "HBar", "MBar", "FBar",
- 0
- };
+ int current = std::max(0, static_cast<int>(value.getCurrent()));
+ int modified = static_cast<int>(value.getModified());
- for (int i=0; ids[i]; ++i)
- {
- if (ids[i]==id)
- {
- std::string id (ids[i]);
- setBar (id, id + "T", static_cast<int>(value.getCurrent()), static_cast<int>(value.getModified()));
+ setBar (id, id + "T", current, modified);
- // health, magicka, fatigue tooltip
- MyGUI::Widget* w;
- std::string valStr = boost::lexical_cast<std::string>(value.getCurrent()) + "/" + boost::lexical_cast<std::string>(value.getModified());
- if (i==0)
- {
- getWidget(w, "Health");
- w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
- }
- else if (i==1)
- {
- getWidget(w, "Magicka");
- w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr);
- }
- else if (i==2)
- {
- getWidget(w, "Fatigue");
- w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
- }
- }
+ // health, magicka, fatigue tooltip
+ MyGUI::Widget* w;
+ std::string valStr = boost::lexical_cast<std::string>(current) + "/" + boost::lexical_cast<std::string>(modified);
+ if (id == "HBar")
+ {
+ getWidget(w, "Health");
+ w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
+ }
+ else if (id == "MBar")
+ {
+ getWidget(w, "Magicka");
+ w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr);
+ }
+ else if (id == "FBar")
+ {
+ getWidget(w, "Fatigue");
+ w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
}
}
@@ -420,8 +410,6 @@ namespace MWGui
}
mSkillWidgets.clear();
- mSkillView->setViewOffset (MyGUI::IntPoint(0,0));
-
const int valueSize = 40;
MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18);
MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height);
diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp
index ac8319bdcd..49b44a2e16 100644
--- a/apps/openmw/mwgui/statswindow.hpp
+++ b/apps/openmw/mwgui/statswindow.hpp
@@ -37,6 +37,8 @@ namespace MWGui
void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; }
void updateSkillArea();
+ virtual void open() { onWindowResize(static_cast<MyGUI::Window*>(mMainWidget)); }
+
private:
void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp
index 1f53263ecd..1ed80fc1e9 100644
--- a/apps/openmw/mwgui/textinput.hpp
+++ b/apps/openmw/mwgui/textinput.hpp
@@ -15,8 +15,8 @@ namespace MWGui
public:
TextInputDialog();
- std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; }
- void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); }
+ std::string getTextInput() const { return mTextEdit->getCaption(); }
+ void setTextInput(const std::string &text) { mTextEdit->setCaption(text); }
void setNextButtonShow(bool shown);
void setTextLabel(const std::string &label);
diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp
index 3a609aa91f..b52c8e3ddb 100644
--- a/apps/openmw/mwgui/tooltips.cpp
+++ b/apps/openmw/mwgui/tooltips.cpp
@@ -110,11 +110,6 @@ namespace MWGui
else
{
- const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left);
-
- if (mousePos == lastPressed) // mouseclick makes tooltip disappear
- return;
-
if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY)
{
mRemainingDelay -= frameDuration;
@@ -467,7 +462,7 @@ namespace MWGui
}
Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget<Widgets::MWDynamicStat>
("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge");
- chargeWidget->setValue(charge, charge);
+ chargeWidget->setValue(charge, maxCharge);
totalSize.height += 24;
}
}
diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp
index e836355d3e..5c12843da0 100644
--- a/apps/openmw/mwgui/tradeitemmodel.cpp
+++ b/apps/openmw/mwgui/tradeitemmodel.cpp
@@ -154,6 +154,13 @@ namespace MWGui
if(!MWWorld::Class::get(base).canSell(base, services))
continue;
+ // Bound items may not be bought
+ if (item.mBase.getCellRef().mRefID.size() > 6
+ && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_")
+ {
+ continue;
+ }
+
// don't show equipped items
if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
{
diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp
index 94141b1a0a..d544b83cf4 100644
--- a/apps/openmw/mwgui/tradewindow.cpp
+++ b/apps/openmw/mwgui/tradewindow.cpp
@@ -80,9 +80,8 @@ namespace MWGui
}
void TradeWindow::startTrade(const MWWorld::Ptr& actor)
- {
+ {
mPtr = actor;
- setTitle(MWWorld::Class::get(actor).getName(actor));
mCurrentBalance = 0;
mCurrentMerchantOffer = 0;
@@ -99,6 +98,12 @@ namespace MWGui
mItemView->setModel (mSortModel);
updateLabels();
+
+ // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last
+ // or we end up using a possibly invalid model.
+ setTitle(MWWorld::Class::get(actor).getName(actor));
+
+ onFilterChanged(mFilterAll);
}
void TradeWindow::onFilterChanged(MyGUI::Widget* _sender)
@@ -200,32 +205,17 @@ namespace MWGui
sellToNpc(item.mBase, count, true);
}
- void TradeWindow::addOrRemoveGold(int amount)
+ void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor)
{
- bool goldFound = false;
- MWWorld::Ptr gold;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player);
+ MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor);
- for (MWWorld::ContainerStoreIterator it = playerStore.begin();
- it != playerStore.end(); ++it)
- {
- if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
- {
- goldFound = true;
- gold = *it;
- }
- }
- if (goldFound)
+ if (amount > 0)
{
- gold.getRefData().setCount(gold.getRefData().getCount() + amount);
+ store.add("gold_001", amount, actor);
}
else
{
- assert(amount > 0);
- MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001");
- ref.getPtr().getRefData().setCount(amount);
- playerStore.add(ref.getPtr(), player);
+ store.remove("gold_001", - amount, actor);
}
}
@@ -280,6 +270,8 @@ namespace MWGui
return;
}
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
if(mCurrentBalance > mCurrentMerchantOffer)
{
//if npc is a creature: reject (no haggle)
@@ -302,7 +294,6 @@ namespace MWGui
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr);
- MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
@@ -342,9 +333,12 @@ namespace MWGui
mTradeModel->transferItems();
playerItemModel->transferItems();
- // add or remove gold from the player.
+ // transfer the gold
if (mCurrentBalance != 0)
- addOrRemoveGold(mCurrentBalance);
+ {
+ addOrRemoveGold(mCurrentBalance, playerPtr);
+ addOrRemoveGold(-mCurrentBalance, mPtr);
+ }
std::string sound = "Item Gold Up";
MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
@@ -445,22 +439,13 @@ namespace MWGui
int TradeWindow::getMerchantGold()
{
- int merchantGold;
-
- if (mPtr.getTypeName() == typeid(ESM::NPC).name())
+ int merchantGold = 0;
+ MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
- MWWorld::LiveCellRef<ESM::NPC>* ref = mPtr.get<ESM::NPC>();
- if (ref->mBase->mNpdt52.mGold == -10)
- merchantGold = ref->mBase->mNpdt12.mGold;
- else
- merchantGold = ref->mBase->mNpdt52.mGold;
- }
- else // ESM::Creature
- {
- MWWorld::LiveCellRef<ESM::Creature>* ref = mPtr.get<ESM::Creature>();
- merchantGold = ref->mBase->mData.mGold;
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
+ merchantGold += it->getRefData().getCount();
}
-
return merchantGold;
}
}
diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp
index 4e905915a0..7c11bd5394 100644
--- a/apps/openmw/mwgui/tradewindow.hpp
+++ b/apps/openmw/mwgui/tradewindow.hpp
@@ -28,7 +28,7 @@ namespace MWGui
void startTrade(const MWWorld::Ptr& actor);
- void addOrRemoveGold(int gold);
+ void addOrRemoveGold(int gold, const MWWorld::Ptr& actor);
void onFrame(float frameDuration);
diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp
index 7ddac38f54..04eddcb173 100644
--- a/apps/openmw/mwgui/trainingwindow.cpp
+++ b/apps/openmw/mwgui/trainingwindow.cpp
@@ -11,11 +11,11 @@
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "inventorywindow.hpp"
-#include "tradewindow.hpp"
#include "tooltips.hpp"
namespace MWGui
@@ -142,7 +142,7 @@ namespace MWGui
pcStats.increaseSkill (skillId, *class_, true);
// remove gold
- MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+ player.getClass().getContainerStore(player).remove("gold_001", price, player);
// go back to game mode
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp
index 93ac8299d9..dd5da4522f 100644
--- a/apps/openmw/mwgui/travelwindow.cpp
+++ b/apps/openmw/mwgui/travelwindow.cpp
@@ -11,9 +11,9 @@
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
#include "inventorywindow.hpp"
-#include "tradewindow.hpp"
namespace MWGui
{
@@ -121,13 +121,15 @@ namespace MWGui
int price;
iss >> price;
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()<price)
return;
- MWBase::Environment::get().getWindowManager()->getTradeWindow ()->addOrRemoveGold (-price);
+
+ player.getClass().getContainerStore(player).remove("gold_001", price, player);
MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
ESM::Position pos = *_sender->getUserData<ESM::Position>();
std::string cellname = _sender->getUserString("Destination");
int x,y;
diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp
index 3fc3187e80..c37ae15fa1 100644
--- a/apps/openmw/mwgui/widgets.cpp
+++ b/apps/openmw/mwgui/widgets.cpp
@@ -689,22 +689,26 @@ namespace MWGui
int total_width = 0;
int total_height = 0;
std::vector< std::pair<MyGUI::IntSize, bool> > sizes;
+ sizes.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
MyGUI::Widget* w = getChildAt(i);
bool hstretch = w->getUserString ("HStretch") == "true";
+ bool hidden = w->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
h_stretched_count += hstretch;
AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
if (aw)
{
- sizes.push_back(std::make_pair(aw->getRequestedSize (), hstretch));
+ sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch);
total_width += aw->getRequestedSize ().width;
total_height = std::max(total_height, aw->getRequestedSize ().height);
}
else
{
- sizes.push_back (std::make_pair(w->getSize(), hstretch));
+ sizes[i] = std::make_pair(w->getSize(), hstretch);
total_width += w->getSize().width;
if (!(w->getUserString("VStretch") == "true"))
total_height = std::max(total_height, w->getSize().height);
@@ -729,8 +733,13 @@ namespace MWGui
MyGUI::Widget* w = getChildAt(i);
+ bool hidden = w->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
+
bool vstretch = w->getUserString ("VStretch") == "true";
- int height = vstretch ? total_height : sizes[i].first.height;
+ int max_height = getSize().height - mPadding*2;
+ int height = vstretch ? max_height : sizes[i].first.height;
MyGUI::IntCoord widgetCoord;
widgetCoord.left = curX;
@@ -774,6 +783,10 @@ namespace MWGui
MyGUI::IntSize size(0,0);
for (unsigned int i = 0; i < getChildCount (); ++i)
{
+ bool hidden = getChildAt(i)->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
+
AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
if (w)
{
@@ -810,21 +823,27 @@ namespace MWGui
int total_height = 0;
int total_width = 0;
std::vector< std::pair<MyGUI::IntSize, bool> > sizes;
+ sizes.resize(count);
for (unsigned int i = 0; i < count; ++i)
{
MyGUI::Widget* w = getChildAt(i);
+
+ bool hidden = w->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
+
bool vstretch = w->getUserString ("VStretch") == "true";
v_stretched_count += vstretch;
AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
if (aw)
{
- sizes.push_back(std::make_pair(aw->getRequestedSize (), vstretch));
+ sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch);
total_height += aw->getRequestedSize ().height;
total_width = std::max(total_width, aw->getRequestedSize ().width);
}
else
{
- sizes.push_back (std::make_pair(w->getSize(), vstretch));
+ sizes[i] = std::make_pair(w->getSize(), vstretch);
total_height += w->getSize().height;
if (!(w->getUserString("HStretch") == "true"))
@@ -850,8 +869,13 @@ namespace MWGui
MyGUI::Widget* w = getChildAt(i);
+ bool hidden = w->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
+
bool hstretch = w->getUserString ("HStretch") == "true";
- int width = hstretch ? total_width : sizes[i].first.width;
+ int maxWidth = getSize().width - mPadding*2;
+ int width = hstretch ? maxWidth : sizes[i].first.width;
MyGUI::IntCoord widgetCoord;
widgetCoord.top = curY;
@@ -890,6 +914,10 @@ namespace MWGui
MyGUI::IntSize size(0,0);
for (unsigned int i = 0; i < getChildCount (); ++i)
{
+ bool hidden = getChildAt(i)->getUserString("Hidden") == "true";
+ if (hidden)
+ continue;
+
AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
if (w)
{
diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp
index 4b4d2dfd19..90ced66d3c 100644
--- a/apps/openmw/mwgui/windowmanagerimp.cpp
+++ b/apps/openmw/mwgui/windowmanagerimp.cpp
@@ -16,6 +16,7 @@
#include "../mwbase/inputmanager.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
#include "console.hpp"
#include "journalwindow.hpp"
@@ -43,6 +44,7 @@
#include "waitdialog.hpp"
#include "enchantingdialog.hpp"
#include "trainingwindow.hpp"
+#include "recharge.hpp"
#include "exposedwindow.hpp"
#include "cursor.hpp"
#include "merchantrepair.hpp"
@@ -95,10 +97,10 @@ namespace MWGui
, mTrainingWindow(NULL)
, mMerchantRepair(NULL)
, mSoulgemDialog(NULL)
+ , mRecharge(NULL)
, mRepair(NULL)
, mCompanionWindow(NULL)
, mTranslationDataStorage (translationDataStorage)
- , mSoftwareCursor(NULL)
, mCharGen(NULL)
, mInputBlocker(NULL)
, mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD"))
@@ -111,9 +113,6 @@ namespace MWGui
, mPlayerMinorSkills()
, mPlayerMajorSkills()
, mPlayerSkillValues()
- , mPlayerHealth()
- , mPlayerMagicka()
- , mPlayerFatigue()
, mGui(NULL)
, mGuiModes()
, mCursorManager(NULL)
@@ -126,7 +125,6 @@ namespace MWGui
, mFPS(0.0f)
, mTriangleCount(0)
, mBatchCount(0)
- , mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI"))
{
// Set up the GUI system
mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath);
@@ -171,16 +169,19 @@ namespace MWGui
mLoadingScreen->onResChange (w,h);
//set up the hardware cursor manager
- mSoftwareCursor = new Cursor();
mCursorManager = new SFO::SDLCursorManager();
MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange);
MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged);
- setUseHardwareCursors(mUseHardwareCursors);
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
- mCursorManager->cursorVisibilityChange(false);
+ SDL_ShowCursor(false);
+
+ mCursorManager->setEnabled(true);
+
+ // hide mygui's pointer
+ MyGUI::PointerManager::getInstance().setVisible(false);
}
void WindowManager::initUI()
@@ -197,18 +198,25 @@ namespace MWGui
mDragAndDrop->mDraggedWidget = 0;
mDragAndDrop->mDragAndDropWidget = dragAndDropWidget;
+ mRecharge = new Recharge();
mMenu = new MainMenu(w,h);
mMap = new MapWindow("");
+ trackWindow(mMap, "map");
mStatsWindow = new StatsWindow();
+ trackWindow(mStatsWindow, "stats");
mConsole = new Console(w,h, mConsoleOnlyScripts);
+ trackWindow(mConsole, "console");
mJournal = JournalWindow::create(JournalViewModel::create ());
mMessageBoxManager = new MessageBoxManager();
mInventoryWindow = new InventoryWindow(mDragAndDrop);
mTradeWindow = new TradeWindow();
+ trackWindow(mTradeWindow, "barter");
mSpellBuyingWindow = new SpellBuyingWindow();
mTravelWindow = new TravelWindow();
mDialogueWindow = new DialogueWindow();
+ trackWindow(mDialogueWindow, "dialogue");
mContainerWindow = new ContainerWindow(mDragAndDrop);
+ trackWindow(mContainerWindow, "container");
mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop);
mToolTips = new ToolTips();
mScrollWindow = new ScrollWindow();
@@ -217,7 +225,9 @@ namespace MWGui
mSettingsWindow = new SettingsWindow();
mConfirmationDialog = new ConfirmationDialog();
mAlchemyWindow = new AlchemyWindow();
+ trackWindow(mAlchemyWindow, "alchemy");
mSpellWindow = new SpellWindow();
+ trackWindow(mSpellWindow, "spells");
mQuickKeysMenu = new QuickKeysMenu();
mLevelupDialog = new LevelupDialog();
mWaitDialog = new WaitDialog();
@@ -228,6 +238,7 @@ namespace MWGui
mRepair = new Repair();
mSoulgemDialog = new SoulgemDialog(mMessageBoxManager);
mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager);
+ trackWindow(mCompanionWindow, "companion");
mInputBlocker = mGui->createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Default,"Windows","");
@@ -246,9 +257,6 @@ namespace MWGui
mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat<float>()));
}
- unsetSelectedSpell();
- unsetSelectedWeapon();
-
// Set up visibility
updateVisible();
@@ -313,8 +321,9 @@ namespace MWGui
delete mMerchantRepair;
delete mRepair;
delete mSoulgemDialog;
- delete mSoftwareCursor;
delete mCursorManager;
+ delete mRecharge;
+ delete mCompanionWindow;
cleanupGarbage();
@@ -343,8 +352,6 @@ namespace MWGui
mHud->setBatchCount(mBatchCount);
mHud->update();
-
- mSoftwareCursor->update();
}
void WindowManager::updateVisible()
@@ -378,6 +385,7 @@ namespace MWGui
mRepair->setVisible(false);
mCompanionWindow->setVisible(false);
mInventoryWindow->setTrading(false);
+ mRecharge->setVisible(false);
mHud->setVisible(mHudEnabled);
@@ -495,6 +503,9 @@ namespace MWGui
case GM_SpellCreation:
mSpellCreationDialog->setVisible(true);
break;
+ case GM_Recharge:
+ mRecharge->setVisible(true);
+ break;
case GM_Enchanting:
mEnchantingDialog->setVisible(true);
break;
@@ -578,34 +589,7 @@ namespace MWGui
mStatsWindow->setValue (id, value);
mHud->setValue (id, value);
mCharGen->setValue(id, value);
- if (id == "HBar")
- {
- mPlayerHealth = value;
- mCharGen->setPlayerHealth (value);
- }
- else if (id == "MBar")
- {
- mPlayerMagicka = value;
- mCharGen->setPlayerMagicka (value);
- }
- else if (id == "FBar")
- {
- mPlayerFatigue = value;
- mCharGen->setPlayerFatigue (value);
- }
- }
-
- #if 0
- MWMechanics::DynamicStat<int> WindowManager::getValue(const std::string& id)
- {
- if(id == "HBar")
- return layerHealth;
- else if (id == "MBar")
- return mPlayerMagicka;
- else if (id == "FBar")
- return mPlayerFatigue;
}
- #endif
void WindowManager::setValue (const std::string& id, const std::string& value)
{
@@ -792,6 +776,13 @@ namespace MWGui
mHud->setCellName( cell->mCell->mName );
mMap->setCellPrefix( cell->mCell->mName );
mHud->setCellPrefix( cell->mCell->mName );
+
+ Ogre::Vector3 worldPos;
+ if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos))
+ worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition();
+ else
+ MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos);
+ mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y);
}
}
@@ -877,21 +868,9 @@ namespace MWGui
MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop);
}
- void WindowManager::setUseHardwareCursors(bool use)
- {
- mCursorManager->setEnabled(use);
- mSoftwareCursor->setVisible(!use && mCursorVisible);
- }
-
void WindowManager::setCursorVisible(bool visible)
{
- if(mCursorVisible == visible)
- return;
-
mCursorVisible = visible;
- mCursorManager->cursorVisibilityChange(visible);
-
- mSoftwareCursor->setVisible(!mUseHardwareCursors && visible);
}
void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result)
@@ -922,8 +901,6 @@ namespace MWGui
mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD"));
mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI"));
- setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI"));
-
for (Settings::CategorySettingVector::const_iterator it = changed.begin();
it != changed.end(); ++it)
{
@@ -940,6 +917,17 @@ namespace MWGui
mLoadingScreen->onResChange (x,y);
if (!mHud)
return; // UI not initialized yet
+
+ for (std::map<MyGUI::Window*, std::string>::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it)
+ {
+ MyGUI::IntPoint pos (Settings::Manager::getFloat(it->second + " x", "Windows") * x,
+ Settings::Manager::getFloat(it->second+ " y", "Windows") * y);
+ MyGUI::IntSize size (Settings::Manager::getFloat(it->second + " w", "Windows") * x,
+ Settings::Manager::getFloat(it->second + " h", "Windows") * y);
+ it->first->setPosition(pos);
+ it->first->setSize(size);
+ }
+
mHud->onResChange(x, y);
mConsole->onResChange(x, y);
mMenu->onResChange(x, y);
@@ -975,8 +963,6 @@ namespace MWGui
void WindowManager::onCursorChange(const std::string &name)
{
- mSoftwareCursor->onCursorChange(name);
-
if(!mCursorManager->cursorChanged(name))
return; //the cursor manager doesn't want any more info about this cursor
//See if we can get the information we need out of the cursor resource
@@ -1033,6 +1019,7 @@ namespace MWGui
void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent)
{
+ mSelectedSpell = spellId;
mHud->setSelectedSpell(spellId, successChancePercent);
const ESM::Spell* spell =
@@ -1043,6 +1030,7 @@ namespace MWGui
void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item)
{
+ mSelectedSpell = "";
const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>()
.find(MWWorld::Class::get(item).getEnchantment(item));
@@ -1062,6 +1050,7 @@ namespace MWGui
void WindowManager::unsetSelectedSpell()
{
+ mSelectedSpell = "";
mHud->unsetSelectedSpell();
mSpellWindow->setTitle("#{sNone}");
}
@@ -1320,6 +1309,7 @@ namespace MWGui
void WindowManager::changePointer(const std::string &name)
{
+ MyGUI::PointerManager::getInstance().setPointer(name);
onCursorChange(name);
}
@@ -1336,6 +1326,9 @@ namespace MWGui
void WindowManager::updatePlayer()
{
+ unsetSelectedSpell();
+ unsetSelectedWeapon();
+
mInventoryWindow->updatePlayer();
}
@@ -1366,4 +1359,45 @@ namespace MWGui
return mLoadingScreen;
}
+ void WindowManager::startRecharge(MWWorld::Ptr soulgem)
+ {
+ mRecharge->start(soulgem);
+ }
+
+ bool WindowManager::getCursorVisible()
+ {
+ return mCursorVisible;
+ }
+
+ void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name)
+ {
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ MyGUI::IntPoint pos (Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width,
+ Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height);
+ MyGUI::IntSize size (Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width,
+ Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height);
+ layout->mMainWidget->setPosition(pos);
+ layout->mMainWidget->setSize(size);
+
+ MyGUI::Window* window = dynamic_cast<MyGUI::Window*>(layout->mMainWidget);
+ if (!window)
+ throw std::runtime_error("Attempting to track size of a non-resizable window");
+ window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord);
+ mTrackedWindows[window] = name;
+ }
+
+ void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender)
+ {
+ std::string setting = mTrackedWindows[_sender];
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ float x = _sender->getPosition().left / float(viewSize.width);
+ float y = _sender->getPosition().top / float(viewSize.height);
+ float w = _sender->getSize().width / float(viewSize.width);
+ float h = _sender->getSize().height / float(viewSize.height);
+ Settings::Manager::setFloat(setting + " x", "Windows", x);
+ Settings::Manager::setFloat(setting + " y", "Windows", y);
+ Settings::Manager::setFloat(setting + " w", "Windows", w);
+ Settings::Manager::setFloat(setting + " h", "Windows", h);
+ }
+
}
diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp
index badb333a7f..b332ffc364 100644
--- a/apps/openmw/mwgui/windowmanagerimp.hpp
+++ b/apps/openmw/mwgui/windowmanagerimp.hpp
@@ -16,6 +16,7 @@ namespace MyGUI
{
class Gui;
class Widget;
+ class Window;
class UString;
}
@@ -77,6 +78,7 @@ namespace MWGui
class MerchantRepair;
class Repair;
class SoulgemDialog;
+ class Recharge;
class CompanionWindow;
class WindowManager : public MWBase::WindowManager
@@ -198,6 +200,7 @@ namespace MWGui
virtual void activateQuickKey (int index);
+ virtual std::string getSelectedSpell() { return mSelectedSpell; }
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent);
virtual void setSelectedEnchantItem(const MWWorld::Ptr& item);
virtual void setSelectedWeapon(const MWWorld::Ptr& item);
@@ -263,6 +266,7 @@ namespace MWGui
virtual void startTraining(MWWorld::Ptr actor);
virtual void startRepair(MWWorld::Ptr actor);
virtual void startRepairItem(MWWorld::Ptr item);
+ virtual void startRecharge(MWWorld::Ptr soulgem);
virtual void frameStarted(float dt);
@@ -276,9 +280,17 @@ namespace MWGui
void onSoulgemDialogButtonPressed (int button);
+ virtual bool getCursorVisible();
+
private:
bool mConsoleOnlyScripts;
+ std::map<MyGUI::Window*, std::string> mTrackedWindows;
+ void trackWindow(OEngine::GUI::Layout* layout, const std::string& name);
+ void onWindowChangeCoord(MyGUI::Window* _sender);
+
+ std::string mSelectedSpell;
+
OEngine::GUI::MyGUIManager *mGuiManager;
OEngine::Render::OgreRenderer *mRendering;
HUD *mHud;
@@ -313,6 +325,7 @@ namespace MWGui
MerchantRepair* mMerchantRepair;
SoulgemDialog* mSoulgemDialog;
Repair* mRepair;
+ Recharge* mRecharge;
CompanionWindow* mCompanionWindow;
Translation::Storage& mTranslationDataStorage;
@@ -336,8 +349,6 @@ namespace MWGui
std::map<int, MWMechanics::Stat<int> > mPlayerAttributes;
SkillList mPlayerMajorSkills, mPlayerMinorSkills;
std::map<int, MWMechanics::Stat<float> > mPlayerSkillValues;
- MWMechanics::DynamicStat<float> mPlayerHealth, mPlayerMagicka, mPlayerFatigue;
-
MyGUI::Gui *mGui; // Gui
std::vector<GuiMode> mGuiModes;
@@ -366,9 +377,6 @@ namespace MWGui
unsigned int mTriangleCount;
unsigned int mBatchCount;
- bool mUseHardwareCursors;
- void setUseHardwareCursors(bool use);
-
/**
* Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string,
* so this method will retrieve the GMST with the name \a _tag and place the result in \a _result
diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp
index 4746260edf..1ad4095155 100644
--- a/apps/openmw/mwinput/inputmanagerimp.cpp
+++ b/apps/openmw/mwinput/inputmanagerimp.cpp
@@ -87,7 +87,7 @@ namespace MWInput
{
InputManager::InputManager(OEngine::Render::OgreRenderer &ogre,
OMW::Engine& engine,
- const std::string& userFile, bool userFileExists)
+ const std::string& userFile, bool userFileExists, bool grab)
: mOgre(ogre)
, mPlayer(NULL)
, mEngine(engine)
@@ -103,6 +103,7 @@ namespace MWInput
, mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input"))
, mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input"))
, mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input"))
+ , mGrabCursor (Settings::Manager::getBool("grab cursor", "Input"))
, mPreviewPOVDelay(0.f)
, mTimeIdle(0.f)
, mOverencumberedMessageDelay(0.f)
@@ -111,7 +112,7 @@ namespace MWInput
Ogre::RenderWindow* window = ogre.getWindow ();
- mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow());
+ mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab);
mInputManager->setMouseEventCallback (this);
mInputManager->setKeyboardEventCallback (this);
mInputManager->setWindowEventCallback(this);
@@ -182,9 +183,6 @@ namespace MWInput
case A_GameMenu:
toggleMainMenu ();
break;
- case A_Quit:
- exitNow();
- break;
case A_Screenshot:
screenshot();
break;
@@ -267,6 +265,8 @@ namespace MWInput
void InputManager::update(float dt, bool loading)
{
+ mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible());
+
mInputManager->capture(loading);
// inject some fake mouse movement to force updating MyGUI's widget states
// this shouldn't do any harm since we're moving back to the original position afterwards
@@ -277,7 +277,8 @@ namespace MWInput
if (!loading)
mInputBinder->update(dt);
- bool main_menu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu);
+ bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)
+ && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console;
bool was_relative = mInputManager->getMouseRelative();
bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode();
@@ -287,7 +288,7 @@ namespace MWInput
mInputManager->setMouseRelative(is_relative);
//we let the mouse escape in the main menu
- mInputManager->setGrabPointer(!main_menu);
+ mInputManager->setGrabPointer(grab && (mGrabCursor || is_relative));
//we switched to non-relative mode, move our cursor to where the in-game
//cursor is
@@ -378,10 +379,9 @@ namespace MWInput
MWBase::Environment::get().getWorld()->togglePreviewMode(true);
}
} else {
- if (mPreviewPOVDelay > 0.5) {
- //disable preview mode
- MWBase::Environment::get().getWorld()->togglePreviewMode(false);
- } else if (mPreviewPOVDelay > 0.f) {
+ //disable preview mode
+ MWBase::Environment::get().getWorld()->togglePreviewMode(false);
+ if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) {
MWBase::Environment::get().getWorld()->togglePOV();
}
mPreviewPOVDelay = 0.f;
@@ -431,6 +431,9 @@ namespace MWInput
if (it->first == "Input" && it->second == "ui sensitivity")
mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input");
+ if (it->first == "Input" && it->second == "grab cursor")
+ mGrabCursor = Settings::Manager::getBool("grab cursor", "Input");
+
}
}
@@ -505,7 +508,7 @@ namespace MWInput
mInputBinder->keyPressed (arg);
- if(arg.keysym.sym == SDLK_RETURN
+ if((arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER)
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
{
// Pressing enter when a messagebox is prompting for "ok" will activate the ok button
@@ -644,6 +647,11 @@ namespace MWInput
mOgre.windowResized(x,y);
}
+ void InputManager::windowClosed()
+ {
+ MWBase::Environment::setRequestExit();
+ }
+
void InputManager::toggleMainMenu()
{
if (MyGUI::InputManager::getInstance ().isModalAny())
@@ -814,13 +822,6 @@ namespace MWInput
mAlwaysRunActive = !mAlwaysRunActive;
}
- // Exit program now button (which is disabled in GUI mode)
- void InputManager::exitNow()
- {
- if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
- Ogre::Root::getSingleton().queueEndRendering ();
- }
-
void InputManager::resetIdleTime()
{
if (mTimeIdle < 0)
@@ -830,9 +831,11 @@ namespace MWInput
void InputManager::updateIdleTime(float dt)
{
+ static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("fVanityDelay")->getFloat();
if (mTimeIdle >= 0.f)
mTimeIdle += dt;
- if (mTimeIdle > 30.f) {
+ if (mTimeIdle > vanityDelay) {
MWBase::Environment::get().getWorld()->toggleVanityMode(true);
mTimeIdle = -1.f;
}
diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp
index 5f9a752d72..d4693ff28c 100644
--- a/apps/openmw/mwinput/inputmanagerimp.hpp
+++ b/apps/openmw/mwinput/inputmanagerimp.hpp
@@ -61,7 +61,7 @@ namespace MWInput
public:
InputManager(OEngine::Render::OgreRenderer &_ogre,
OMW::Engine& engine,
- const std::string& userFile, bool userFileExists);
+ const std::string& userFile, bool userFileExists, bool grab);
virtual ~InputManager();
@@ -97,6 +97,7 @@ namespace MWInput
virtual void windowVisibilityChange( bool visible );
virtual void windowFocusChange( bool have_focus );
virtual void windowResized (int x, int y);
+ virtual void windowClosed ();
virtual void channelChanged(ICS::Channel* channel, float currentValue, float previousValue);
@@ -137,6 +138,8 @@ namespace MWInput
bool mDragDrop;
+ bool mGrabCursor;
+
bool mInvertY;
float mCameraSensitivity;
@@ -177,7 +180,6 @@ namespace MWInput
void activate();
void toggleWalking();
void toggleAutoMove();
- void exitNow();
void rest();
void quickKey (int index);
@@ -194,7 +196,7 @@ namespace MWInput
A_GameMenu,
- A_Quit, // Exit the program
+ A_Unused,
A_Screenshot, // Take a screenshot
diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp
index 9aca6b7b79..05877e454f 100644
--- a/apps/openmw/mwmechanics/activespells.cpp
+++ b/apps/openmw/mwmechanics/activespells.cpp
@@ -1,26 +1,8 @@
-
#include "activespells.hpp"
-#include <cstdlib>
-
-#include <boost/algorithm/string.hpp>
-
-#include <components/esm/loadalch.hpp>
-#include <components/esm/loadspel.hpp>
-#include <components/esm/loadingr.hpp>
-#include <components/esm/loadmgef.hpp>
-#include <components/esm/loadskil.hpp>
-
-#include "../mwworld/esmstore.hpp"
-
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwworld/class.hpp"
-
-#include "creaturestats.hpp"
-#include "npcstats.hpp"
-
namespace MWMechanics
{
void ActiveSpells::update() const
@@ -29,6 +11,7 @@ namespace MWMechanics
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
+ // Erase no longer active spells
if (mLastUpdate!=now)
{
TContainer::iterator iter (mSpells.begin());
@@ -62,162 +45,28 @@ namespace MWMechanics
for (TIterator iter (begin()); iter!=end(); ++iter)
{
- std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first);
+ const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
- const MWWorld::TimeStamp& start = iter->second.first;
- float magnitude = iter->second.second;
+ const std::vector<Effect>& effects = iter->second.mEffects;
- for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
- iter!=effects.first.mList.end(); ++iter)
+ for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
{
- if (iter->mDuration)
- {
- int duration = iter->mDuration;
-
- if (effects.second.first)
- duration *= magnitude;
-
- MWWorld::TimeStamp end = start;
- end += static_cast<double> (duration)*
- MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
-
- if (end>now)
- {
- EffectParam param;
-
- if (effects.second.first)
- {
- const ESM::MagicEffect *magicEffect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
- iter->mEffectID);
-
- if (iter->mDuration==0)
- {
- param.mMagnitude =
- static_cast<int> (magnitude / (0.1 * magicEffect->mData.mBaseCost));
- }
- else
- {
- param.mMagnitude =
- static_cast<int> (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost));
- }
- }
- else
- param.mMagnitude = static_cast<int> (
- (iter->mMagnMax-iter->mMagnMin)*magnitude + iter->mMagnMin);
-
- mEffects.add (*iter, param);
- }
- }
+ int duration = effectIt->mDuration;
+ MWWorld::TimeStamp end = start;
+ end += static_cast<double> (duration)*
+ MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
+
+ if (end>now)
+ mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude));
}
}
}
- std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const
- {
- if (const ESM::Spell *spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
- return std::make_pair (spell->mEffects, std::make_pair(false, false));
-
- if (const ESM::Potion *potion =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
- return std::make_pair (potion->mEffects, std::make_pair(false, true));
-
- if (const ESM::Ingredient *ingredient =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
- {
- const ESM::MagicEffect *magicEffect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
- ingredient->mData.mEffectID[0]);
-
- ESM::ENAMstruct effect;
- effect.mEffectID = ingredient->mData.mEffectID[0];
- effect.mSkill = ingredient->mData.mSkills[0];
- effect.mAttribute = ingredient->mData.mAttributes[0];
- effect.mRange = 0;
- effect.mArea = 0;
- effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1;
- effect.mMagnMin = 1;
- effect.mMagnMax = 1;
-
- std::pair<ESM::EffectList, std::pair<bool, bool> > result;
- result.second.second = true;
- result.second.first = true;
-
- result.first.mList.push_back (effect);
-
- return result;
- }
-
- throw std::runtime_error ("ID " + id + " can not produce lasting effects");
- }
-
ActiveSpells::ActiveSpells()
- : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
+ : mSpellsChanged (false)
+ , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
{}
- bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor)
- {
- std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id);
- bool stacks = effects.second.second;
-
- bool found = false;
-
- for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
- iter!=effects.first.mList.end(); ++iter)
- {
- if (iter->mDuration)
- {
- found = true;
- break;
- }
- }
-
- if (!found)
- return false;
-
- TContainer::iterator iter = mSpells.find (id);
-
- float random = static_cast<float> (std::rand()) / RAND_MAX;
-
- if (effects.second.first)
- {
- // ingredient -> special treatment required.
- const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
- const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
-
- float x =
- (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
- 0.2 * creatureStats.getAttribute (1).getModified()
- + 0.1 * creatureStats.getAttribute (7).getModified())
- * creatureStats.getFatigueTerm();
- random *= 100;
- random = random / std::min (x, 100.0f);
- random *= 0.25 * x;
- }
-
- if (iter==mSpells.end() || stacks)
- mSpells.insert (std::make_pair (id,
- std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random)));
- else
- iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random);
-
- mSpellsChanged = true;
-
- return true;
- }
-
- void ActiveSpells::removeSpell (const std::string& id)
- {
- TContainer::iterator iter = mSpells.find (id);
-
- if (iter!=mSpells.end())
- {
- mSpells.erase (iter);
- mSpellsChanged = true;
- }
- }
-
const MagicEffects& ActiveSpells::getMagicEffects() const
{
update();
@@ -226,36 +75,31 @@ namespace MWMechanics
ActiveSpells::TIterator ActiveSpells::begin() const
{
- update();
return mSpells.begin();
}
ActiveSpells::TIterator ActiveSpells::end() const
{
- update();
return mSpells.end();
}
double ActiveSpells::timeToExpire (const TIterator& iterator) const
{
- std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iterator->first);
+ const std::vector<Effect>& effects = iterator->second.mEffects;
int duration = 0;
- for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
- iter!=effects.first.mList.end(); ++iter)
+ for (std::vector<Effect>::const_iterator iter (effects.begin());
+ iter!=effects.end(); ++iter)
{
if (iter->mDuration > duration)
duration = iter->mDuration;
}
- if (effects.second.first)
- duration *= iterator->second.second;
-
double scaledDuration = duration *
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
- double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first;
+ double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp;
if (usedUp>=scaledDuration)
return 0;
@@ -281,4 +125,76 @@ namespace MWMechanics
{
return mSpells;
}
+
+ void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects, const std::string &displayName)
+ {
+ bool exists = false;
+ for (TContainer::const_iterator it = begin(); it != end(); ++it)
+ {
+ if (id == it->first)
+ exists = true;
+ }
+
+ ActiveSpellParams params;
+ params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
+ params.mEffects = effects;
+ params.mDisplayName = displayName;
+
+ if (!exists || stack)
+ mSpells.insert (std::make_pair(id, params));
+ else
+ mSpells.find(id)->second = params;
+
+ mSpellsChanged = true;
+ }
+
+ void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
+ {
+ for (TContainer::const_iterator it = begin(); it != end(); ++it)
+ {
+ float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
+
+ for (std::vector<Effect>::const_iterator effectIt = it->second.mEffects.begin();
+ effectIt != it->second.mEffects.end(); ++effectIt)
+ {
+ std::string name = it->second.mDisplayName;
+
+ float remainingTime = effectIt->mDuration +
+ (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
+ float magnitude = effectIt->mMagnitude;
+
+ if (magnitude)
+ visitor.visit(effectIt->mKey, name, magnitude, remainingTime);
+ }
+ }
+ }
+
+ void ActiveSpells::purgeAll(float chance)
+ {
+ for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); )
+ {
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < chance)
+ mSpells.erase(it++);
+ else
+ ++it;
+ }
+ mSpellsChanged = true;
+ }
+
+ void ActiveSpells::purgeEffect(short effectId)
+ {
+ for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
+ {
+ for (std::vector<Effect>::iterator effectIt = it->second.mEffects.begin();
+ effectIt != it->second.mEffects.end();)
+ {
+ if (effectIt->mKey.mId == effectId)
+ effectIt = it->second.mEffects.erase(effectIt);
+ else
+ effectIt++;
+ }
+ }
+ mSpellsChanged = true;
+ }
}
diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp
index 8c859b2cb3..92faf790f2 100644
--- a/apps/openmw/mwmechanics/activespells.hpp
+++ b/apps/openmw/mwmechanics/activespells.hpp
@@ -9,16 +9,7 @@
#include "magiceffects.hpp"
-namespace ESM
-{
- struct Spell;
- struct EffectList;
-}
-
-namespace MWWorld
-{
- class Ptr;
-}
+#include <components/esm/defs.hpp>
namespace MWMechanics
{
@@ -30,12 +21,32 @@ namespace MWMechanics
{
public:
- typedef std::multimap<std::string, std::pair<MWWorld::TimeStamp, float> > TContainer;
+ // Parameters of an effect concerning lasting effects.
+ // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
+ // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster.
+ struct Effect
+ {
+ float mMagnitude;
+ EffectKey mKey;
+ float mDuration;
+ };
+
+ struct ActiveSpellParams
+ {
+ std::vector<Effect> mEffects;
+ MWWorld::TimeStamp mTimeStamp;
+ std::string mDisplayName;
+
+ // TODO: To handle CASTER_LINKED flag (spell is purged when caster dies),
+ // we should probably store a handle to the caster here.
+ };
+
+ typedef std::multimap<std::string, ActiveSpellParams > TContainer;
typedef TContainer::const_iterator TIterator;
private:
- mutable TContainer mSpells; // spellId, (time of casting, relative magnitude)
+ mutable TContainer mSpells;
mutable MagicEffects mEffects;
mutable bool mSpellsChanged;
mutable MWWorld::TimeStamp mLastUpdate;
@@ -44,35 +55,43 @@ namespace MWMechanics
void rebuildEffects() const;
- std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const;
- ///< @return (EffectList, (isIngredient, stacks))
+ double timeToExpire (const TIterator& iterator) const;
+ ///< Returns time (in in-game hours) until the spell pointed to by \a iterator
+ /// expires.
+
+ const TContainer& getActiveSpells() const;
+
+ TIterator begin() const;
+
+ TIterator end() const;
public:
ActiveSpells();
- bool addSpell (const std::string& id, const MWWorld::Ptr& actor);
- ///< Overwrites an existing spell with the same ID. If the spell does not have any
- /// non-instant effects, it is ignored.
+ /// Add lasting effects
+ ///
+ /// \brief addSpell
+ /// \param id ID for stacking purposes.
+ /// \param stack If false, the spell is not added if one with the same ID exists already.
+ /// \param effects
+ /// \param displayName Name for display in magic menu.
///
- /// \return Has the spell been added?
+ void addSpell (const std::string& id, bool stack, std::vector<Effect> effects, const std::string& displayName);
- void removeSpell (const std::string& id);
+ /// Remove all active effects with this id
+ void purgeEffect (short effectId);
+
+ /// Remove all active effects, if roll succeeds (for each effect)
+ void purgeAll (float chance);
bool isSpellActive (std::string id) const;
///< case insensitive
const MagicEffects& getMagicEffects() const;
- const TContainer& getActiveSpells() const;
-
- TIterator begin() const;
-
- TIterator end() const;
+ void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
- double timeToExpire (const TIterator& iterator) const;
- ///< Returns time (in in-game hours) until the spell pointed to by \a iterator
- /// expires.
};
}
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 42851dea39..79dd985628 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -12,6 +12,8 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/actionequip.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@@ -22,20 +24,118 @@
#include "creaturestats.hpp"
#include "movement.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "aicombat.hpp"
+
+namespace
+{
+
+void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor)
+{
+ if (bound)
+ {
+ MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor);
+ MWWorld::ActionEquip action(newPtr);
+ action.execute(actor);
+ }
+ else
+ {
+ actor.getClass().getContainerStore(actor).remove(item, 1, actor);
+ }
+}
+
+bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
+{
+ // TODO: remove this check once creatures support inventory store
+ if (ptr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator item =
+ inv.getSlot(slot);
+ if (item != inv.end())
+ {
+ if (!item->getClass().hasItemHealth(*item))
+ return false;
+ if (item->getCellRef().mCharge == -1)
+ item->getCellRef().mCharge = item->getClass().getItemMaxHealth(*item);
+
+ if (item->getCellRef().mCharge == 0)
+ return false;
+
+ item->getCellRef().mCharge -=
+ std::min(disintegrate,
+ static_cast<float>(item->getCellRef().mCharge));
+
+ if (item->getCellRef().mCharge == 0)
+ {
+ // Will unequip the broken item and try to find a replacement
+ if (ptr.getRefData().getHandle() != "player")
+ inv.autoEquip(ptr);
+ else
+ inv.unequipItem(*item, ptr);
+ }
+
+ return true;
+ }
+ }
+ return true;
+}
+
+
+}
+
namespace MWMechanics
{
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{
// magic effects
adjustMagicEffects (ptr);
- calculateDynamicStats (ptr);
- calculateCreatureStatModifiers (ptr);
+ if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
+ calculateDynamicStats (ptr);
+ calculateCreatureStatModifiers (ptr, duration);
if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
{
// AI
- CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
- creatureStats.getAiSequence().execute (ptr);
+ if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
+ {
+ CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+ //engage combat or not?
+ if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile())
+ {
+ ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition();
+ ESM::Position actorpos = ptr.getRefData().getPosition();
+ float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0])
+ +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
+ +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
+ float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1);
+ float disp = 100; //creatures don't have disposition, so set it to 100 by default
+ if(ptr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
+ }
+ bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ if( ( (fight == 100 )
+ || (fight >= 95 && d <= 3000)
+ || (fight >= 90 && d <= 2000)
+ || (fight >= 80 && d <= 1000)
+ || (fight >= 80 && disp <= 40)
+ || (fight >= 70 && disp <= 35 && d <= 1000)
+ || (fight >= 60 && disp <= 30 && d <= 1000)
+ || (fight >= 50 && disp == 0)
+ || (fight >= 40 && disp <= 10 && d <= 500) )
+ && LOS
+ )
+ {
+ creatureStats.getAiSequence().stack(AiCombat("player"));
+ creatureStats.setHostile(true);
+ }
+ }
+
+ creatureStats.getAiSequence().execute (ptr,duration);
+ }
// fatigue restoration
calculateRestoration(ptr, duration);
@@ -47,6 +147,7 @@ namespace MWMechanics
if(!paused)
{
updateDrowning(ptr, duration);
+ calculateNpcStatModifiers(ptr);
updateEquippedLight(ptr, duration);
}
}
@@ -98,6 +199,8 @@ namespace MWMechanics
void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration)
{
+ if (ptr.getClass().getCreatureStats(ptr).isDead())
+ return;
CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
@@ -144,7 +247,7 @@ namespace MWMechanics
stats.setFatigue (fatigue);
}
- void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr)
+ void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
{
CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects();
@@ -154,7 +257,8 @@ namespace MWMechanics
{
Stat<int> stat = creatureStats.getAttribute(i);
stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude -
- effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude);
+ effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude -
+ effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude);
creatureStats.setAttribute(i, stat);
}
@@ -163,11 +267,205 @@ namespace MWMechanics
for(int i = 0;i < 3;++i)
{
DynamicStat<float> stat = creatureStats.getDynamic(i);
- stat.setModifier(effects.get(EffectKey(80+i)).mMagnitude -
- effects.get(EffectKey(18+i)).mMagnitude);
+ stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude -
+ effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude);
+
+
+ float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude
+ - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude
+ - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude;
+ stat.setCurrent(stat.getCurrent() + currentDiff * duration);
creatureStats.setDynamic(i, stat);
}
+
+ // Apply disintegration (reduces item health)
+ float disintegrateWeapon = effects.get(EffectKey(ESM::MagicEffect::DisintegrateWeapon)).mMagnitude;
+ if (disintegrateWeapon > 0)
+ disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration);
+ float disintegrateArmor = effects.get(EffectKey(ESM::MagicEffect::DisintegrateArmor)).mMagnitude;
+ if (disintegrateArmor > 0)
+ {
+ // According to UESP
+ int priorities[] = {
+ MWWorld::InventoryStore::Slot_CarriedLeft,
+ MWWorld::InventoryStore::Slot_Cuirass,
+ MWWorld::InventoryStore::Slot_LeftPauldron,
+ MWWorld::InventoryStore::Slot_RightPauldron,
+ MWWorld::InventoryStore::Slot_LeftGauntlet,
+ MWWorld::InventoryStore::Slot_RightGauntlet,
+ MWWorld::InventoryStore::Slot_Helmet,
+ MWWorld::InventoryStore::Slot_Greaves,
+ MWWorld::InventoryStore::Slot_Boots
+ };
+
+ for (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
+ {
+ if (disintegrateSlot(ptr, priorities[i], disintegrateArmor*duration))
+ break;
+ }
+ }
+
+ // Apply damage ticks
+ int damageEffects[] = {
+ ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison,
+ ESM::MagicEffect::SunDamage
+ };
+
+ DynamicStat<float> health = creatureStats.getHealth();
+ for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
+ {
+ float magnitude = creatureStats.getMagicEffects().get(EffectKey(damageEffects[i])).mMagnitude;
+
+ if (damageEffects[i] == ESM::MagicEffect::SunDamage)
+ {
+ // isInCell shouldn't be needed, but updateActor called during game start
+ if (!ptr.isInCell() || !ptr.getCell()->isExterior())
+ continue;
+ float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
+ float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
+ float damageScale = 1.f - timeDiff / 7.f;
+ // When cloudy, the sun damage effect is halved
+ static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ "fMagicSunBlockedMult")->getFloat();
+
+ int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
+ if (weather > 1)
+ damageScale *= fMagicSunBlockedMult;
+ health.setCurrent(health.getCurrent() - magnitude * duration * damageScale);
+ }
+ else
+ health.setCurrent(health.getCurrent() - magnitude * duration);
+
+ }
+ creatureStats.setHealth(health);
+
+ // TODO: dirty flag for magic effects to avoid some unnecessary work below?
+
+ // Update bound effects
+ static std::map<int, std::string> boundItemsMap;
+ if (boundItemsMap.empty())
+ {
+ boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe";
+ boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots";
+ boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass";
+ boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger";
+ boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below
+ boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm";
+ boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow";
+ boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword";
+ boundItemsMap[ESM::MagicEffect::BoundMace] = "mace";
+ boundItemsMap[ESM::MagicEffect::BoundShield] = "shield";
+ boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear";
+ }
+
+ for (std::map<int, std::string>::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it)
+ {
+ bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end();
+ int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
+ if (found != (magnitude > 0))
+ {
+ std::string item = "bound_" + it->second;
+ if (it->first == ESM::MagicEffect::BoundGloves)
+ {
+ adjustBoundItem(item + "_left", magnitude > 0, ptr);
+ adjustBoundItem(item + "_right", magnitude > 0, ptr);
+ }
+ else
+ adjustBoundItem(item, magnitude > 0, ptr);
+
+ if (magnitude > 0)
+ creatureStats.mBoundItems.insert(it->first);
+ else
+ creatureStats.mBoundItems.erase(it->first);
+ }
+ }
+
+ // Update summon effects
+ static std::map<int, std::string> summonMap;
+ if (summonMap.empty())
+ {
+ summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon";
+ summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon";
+ summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon";
+ summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon";
+ summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon";
+ summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon";
+ summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon";
+ summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon";
+ summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon";
+ summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon";
+ summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon";
+ summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon";
+ summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon";
+ summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ";
+ summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon";
+ summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon";
+ summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon";
+ summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon";
+ summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon";
+ summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon";
+ }
+
+ for (std::map<int, std::string>::iterator it = summonMap.begin(); it != summonMap.end(); ++it)
+ {
+ bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end();
+ int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
+ if (found != (magnitude > 0))
+ {
+ if (magnitude > 0)
+ {
+ ESM::Position ipos = ptr.getRefData().getPosition();
+ Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]);
+ Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
+ const float distance = 50;
+ pos = pos + distance*rot.yAxis();
+ ipos.pos[0] = pos.x;
+ ipos.pos[1] = pos.y;
+ ipos.pos[2] = pos.z;
+ ipos.rot[0] = 0;
+ ipos.rot[1] = 0;
+ ipos.rot[2] = 0;
+
+ MWWorld::CellStore* store = ptr.getCell();
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1);
+ ref.getPtr().getCellRef().mPos = ipos;
+
+ // TODO: Add AI to follow player and fight for him
+
+ creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle()));
+
+ }
+ else
+ {
+ std::string handle = creatureStats.mSummonedCreatures[it->first];
+ // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
+ // plays though, which is a rather lame exploit in vanilla.
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle);
+ if (!ptr.isEmpty())
+ {
+ MWBase::Environment::get().getWorld()->deleteObject(ptr);
+ creatureStats.mSummonedCreatures.erase(it->first);
+ }
+ }
+ }
+ }
+ }
+
+ void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr)
+ {
+ NpcStats &npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr);
+ const MagicEffects &effects = npcStats.getMagicEffects();
+
+ // skills
+ for(int i = 0;i < ESM::Skill::Length;++i)
+ {
+ Stat<float>& skill = npcStats.getSkill(i);
+ skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude -
+ effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude -
+ effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude);
+ }
}
void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration)
@@ -207,15 +505,69 @@ namespace MWMechanics
void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration)
{
- //If holding a light...
+ bool isPlayer = ptr.getRefData().getHandle()=="player";
+
MWWorld::InventoryStore &inventoryStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
MWWorld::ContainerStoreIterator heldIter =
inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ /**
+ * Automatically equip NPCs torches at night and unequip them at day
+ */
+ if (!isPlayer)
+ {
+ MWWorld::ContainerStoreIterator torch = inventoryStore.end();
+ for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it)
+ {
+ if (it->getTypeName() == typeid(ESM::Light).name())
+ {
+ torch = it;
+ break;
+ }
+ }
+
+ if (MWBase::Environment::get().getWorld()->isDark())
+ {
+ if (torch != inventoryStore.end())
+ {
+ if (!MWWorld::Class::get (ptr).getCreatureStats (ptr).isHostile())
+ {
+ // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light.
+ if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
+ inventoryStore.unequipItem(*heldIter, ptr);
+
+ // Also unequip twohanded weapons which conflict with anything in CarriedLeft
+ if (torch->getClass().canBeEquipped(*torch, ptr).first == 3)
+ inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, ptr);
+ }
+
+ heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+
+ // If we have a torch and can equip it (left slot free, no
+ // twohanded weapon in right slot), then equip it now.
+ if (heldIter == inventoryStore.end()
+ && torch->getClass().canBeEquipped(*torch, ptr).first == 1)
+ {
+ inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr);
+ }
+ }
+ }
+ else
+ {
+ if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name())
+ {
+ // At day, unequip lights and auto equip shields or other suitable items
+ // (Note: autoEquip will ignore lights)
+ inventoryStore.autoEquip(ptr);
+ }
+ }
+ }
+ heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+
+ //If holding a light...
if(heldIter.getType() == MWWorld::ContainerStore::Type_Light)
{
// Use time from the player's light
- bool isPlayer = ptr.getRefData().getHandle()=="player";
if(isPlayer)
{
float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter);
@@ -229,7 +581,7 @@ namespace MWMechanics
heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining);
else
{
- heldIter->getRefData().setCount(0); // remove it
+ inventoryStore.remove(*heldIter, 1, ptr); // remove it
return;
}
}
@@ -238,7 +590,7 @@ namespace MWMechanics
// Both NPC and player lights extinguish in water.
if(MWBase::Environment::get().getWorld()->isSwimming(ptr))
{
- heldIter->getRefData().setCount(0); // remove it
+ inventoryStore.remove(*heldIter, 1, ptr); // remove it
// ...But, only the player makes a sound.
if(isPlayer)
@@ -248,7 +600,17 @@ namespace MWMechanics
}
}
- Actors::Actors() : mDuration (0) {}
+ Actors::Actors() {}
+
+ Actors::~Actors()
+ {
+ PtrControllerMap::iterator it(mActors.begin());
+ for (; it != mActors.end(); ++it)
+ {
+ delete it->second;
+ it->second = NULL;
+ }
+ }
void Actors::addActor (const MWWorld::Ptr& ptr)
{
@@ -284,12 +646,12 @@ namespace MWMechanics
}
}
- void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore)
+ void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore, const MWWorld::Ptr& ignore)
{
PtrControllerMap::iterator iter = mActors.begin();
while(iter != mActors.end())
{
- if(iter->first.getCell()==cellStore)
+ if(iter->first.getCell()==cellStore && iter->first != ignore)
{
delete iter->second;
mActors.erase(iter++);
@@ -301,13 +663,8 @@ namespace MWMechanics
void Actors::update (float duration, bool paused)
{
- mDuration += duration;
-
- //if (mDuration>=0.25)
+ if (!paused)
{
- float totalDuration = mDuration;
- mDuration = 0;
-
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
{
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
@@ -319,9 +676,9 @@ namespace MWMechanics
if(iter->second->isDead())
iter->second->resurrect();
- updateActor(iter->first, totalDuration);
+ updateActor(iter->first, duration);
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
- updateNpc(iter->first, totalDuration, paused);
+ updateNpc(iter->first, duration, paused);
if(!stats.isDead())
continue;
@@ -348,6 +705,11 @@ namespace MWMechanics
iter->second->kill();
+ // Reset magic effects and recalculate derived effects
+ // One case where we need this is to make sure bound items are removed upon death
+ stats.setMagicEffects(MWMechanics::MagicEffects());
+ calculateCreatureStatModifiers(iter->first, 0);
+
++mDeathCount[cls.getId(iter->first)];
if(cls.isEssential(iter->first))
@@ -357,11 +719,17 @@ namespace MWMechanics
if(!paused)
{
+ // Note: we need to do this before any of the animations are updated.
+ // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
+ // so updating VFX immediately after that would just remove the particle effects instantly.
+ // There needs to be a magic effect update in between.
+ for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ iter->second->updateContinuousVfx();
+
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
iter->second->update(duration);
}
}
-
void Actors::restoreDynamicStats()
{
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
index a77e52ba30..83aff63e3c 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -25,20 +25,19 @@ namespace MWMechanics
{
class Actors
{
- typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
- PtrControllerMap mActors;
+ typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
+ PtrControllerMap mActors;
- std::map<std::string, int> mDeathCount;
+ std::map<std::string, int> mDeathCount;
- float mDuration;
-
- void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused);
+ void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused);
void adjustMagicEffects (const MWWorld::Ptr& creature);
void calculateDynamicStats (const MWWorld::Ptr& ptr);
- void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr);
+ void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
+ void calculateNpcStatModifiers (const MWWorld::Ptr& ptr);
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
@@ -49,6 +48,11 @@ namespace MWMechanics
public:
Actors();
+ ~Actors();
+
+ /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
+ /// paused we may want to do it manually (after equipping permanent enchantment)
+ void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); }
void addActor (const MWWorld::Ptr& ptr);
///< Register an actor for stats management
@@ -63,8 +67,8 @@ namespace MWMechanics
void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr);
///< Updates an actor with a new Ptr
- void dropActors (const MWWorld::CellStore *cellStore);
- ///< Deregister all actors in the given cell.
+ void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore);
+ ///< Deregister all actors (except for \a ignore) in the given cell.
void update (float duration, bool paused);
///< Update actor stats and store desired velocity vectors in \a movement
diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp
index b94c8c2599..ee0dcf96e5 100644
--- a/apps/openmw/mwmechanics/aiactivate.cpp
+++ b/apps/openmw/mwmechanics/aiactivate.cpp
@@ -9,7 +9,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
{
return new AiActivate(*this);
}
-bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor)
+bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
{
std::cout << "AiActivate completed.\n";
return true;
diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp
index 7f3d4016dc..f922e238c2 100644
--- a/apps/openmw/mwmechanics/aiactivate.hpp
+++ b/apps/openmw/mwmechanics/aiactivate.hpp
@@ -12,7 +12,7 @@ namespace MWMechanics
public:
AiActivate(const std::string &objectId);
virtual AiActivate *clone() const;
- virtual bool execute (const MWWorld::Ptr& actor);
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp
new file mode 100644
index 0000000000..39a97df6c1
--- /dev/null
+++ b/apps/openmw/mwmechanics/aicombat.cpp
@@ -0,0 +1,148 @@
+#include "aicombat.hpp"
+
+#include "movement.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/timestamp.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "creaturestats.hpp"
+#include "npcstats.hpp"
+
+#include "OgreMath.h"
+
+namespace
+{
+ static float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+}
+
+namespace MWMechanics
+{
+
+ AiCombat::AiCombat(const std::string &targetId)
+ :mTargetId(targetId),mTimer(0),mTimer2(0)
+ {
+ }
+
+ bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
+ {
+ if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true;
+
+ const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false);
+
+ if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true;
+
+ if(actor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWWorld::Class::get(actor).
+ MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true);
+ MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState();
+ if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
+ MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
+ //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
+ }
+ ESM::Position pos = actor.getRefData().getPosition();
+ const ESM::Pathgrid *pathgrid =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
+
+ float xCell = 0;
+ float yCell = 0;
+
+ if (actor.getCell()->mCell->isExterior())
+ {
+ xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE;
+ yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE;
+ }
+
+ ESM::Pathgrid::Point dest;
+ dest.mX = target.getRefData().getPosition().pos[0];
+ dest.mY = target.getRefData().getPosition().pos[1];
+ dest.mZ = target.getRefData().getPosition().pos[2];
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ mTimer2 = mTimer2 + duration;
+
+ if(!mPathFinder.isPathConstructed())
+ mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ else
+ {
+ mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
+ if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() ||
+ (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200))
+ {
+ mTimer2 = 0;
+ mPathFinder = mPathFinder2;
+ }
+ }
+
+ mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
+
+ float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
+ MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
+
+
+ float range = 100;
+ MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
+ if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ)
+ < range*range)
+ {
+ float directionX = dest.mX - start.mX;
+ float directionY = dest.mY - start.mY;
+ float directionResult = sqrt(directionX * directionX + directionY * directionY);
+
+ zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees();
+ MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
+
+ mPathFinder.clearPath();
+
+ if(mTimer == 0)
+ {
+ MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
+ //mTimer = mTimer + duration;
+ }
+ if( mTimer > 1)
+ {
+ MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
+ mTimer = 0;
+ }
+ else
+ {
+ mTimer = mTimer + duration;
+ }
+
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell());
+ }
+ return false;
+ }
+
+ int AiCombat::getTypeId() const
+ {
+ return 5;
+ }
+
+ unsigned int AiCombat::getPriority() const
+ {
+ return 1;
+ }
+
+ AiCombat *MWMechanics::AiCombat::clone() const
+ {
+ return new AiCombat(*this);
+ }
+}
+
diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp
new file mode 100644
index 0000000000..fa71e261fc
--- /dev/null
+++ b/apps/openmw/mwmechanics/aicombat.hpp
@@ -0,0 +1,36 @@
+#ifndef GAME_MWMECHANICS_AICOMBAT_H
+#define GAME_MWMECHANICS_AICOMBAT_H
+
+#include "aipackage.hpp"
+
+#include "pathfinding.hpp"
+
+#include "movement.hpp"
+
+namespace MWMechanics
+{
+ class AiCombat : public AiPackage
+ {
+ public:
+ AiCombat(const std::string &targetId);
+
+ virtual AiCombat *clone() const;
+
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
+ ///< \return Package completed?
+
+ virtual int getTypeId() const;
+
+ virtual unsigned int getPriority() const;
+
+ private:
+ std::string mTargetId;
+
+ PathFinder mPathFinder;
+ PathFinder mPathFinder2;
+ float mTimer;
+ float mTimer2;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp
index 556e0b1267..3615c8546e 100644
--- a/apps/openmw/mwmechanics/aiescort.cpp
+++ b/apps/openmw/mwmechanics/aiescort.cpp
@@ -71,7 +71,7 @@ namespace MWMechanics
return new AiEscort(*this);
}
- bool AiEscort::execute (const MWWorld::Ptr& actor)
+ bool AiEscort::execute (const MWWorld::Ptr& actor,float duration)
{
// If AiEscort has ran for as long or longer then the duration specified
// and the duration is not infinite, the package is complete.
diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp
index 3ae604035a..f3f6d2bd9b 100644
--- a/apps/openmw/mwmechanics/aiescort.hpp
+++ b/apps/openmw/mwmechanics/aiescort.hpp
@@ -18,7 +18,7 @@ namespace MWMechanics
virtual AiEscort *clone() const;
- virtual bool execute (const MWWorld::Ptr& actor);
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp
index dab9e02839..73bf9259af 100644
--- a/apps/openmw/mwmechanics/aifollow.cpp
+++ b/apps/openmw/mwmechanics/aifollow.cpp
@@ -15,7 +15,7 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const
return new AiFollow(*this);
}
- bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor)
+ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
{
std::cout << "AiFollow completed.\n";
return true;
diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp
index 0b37b0a2d9..39df024e4b 100644
--- a/apps/openmw/mwmechanics/aifollow.hpp
+++ b/apps/openmw/mwmechanics/aifollow.hpp
@@ -13,7 +13,7 @@ namespace MWMechanics
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z);
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
virtual AiFollow *clone() const;
- virtual bool execute (const MWWorld::Ptr& actor);
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp
index d286fbba89..5832198dad 100644
--- a/apps/openmw/mwmechanics/aipackage.hpp
+++ b/apps/openmw/mwmechanics/aipackage.hpp
@@ -17,11 +17,14 @@ namespace MWMechanics
virtual AiPackage *clone() const = 0;
- virtual bool execute (const MWWorld::Ptr& actor) = 0;
+ virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0;
///< \return Package completed?
virtual int getTypeId() const = 0;
///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
+
+ virtual unsigned int getPriority() const {return 0;}
+ ///< higher number is higher priority (0 beeing the lowest)
};
}
diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp
index 2f06b849a3..6d461e5f63 100644
--- a/apps/openmw/mwmechanics/aisequence.cpp
+++ b/apps/openmw/mwmechanics/aisequence.cpp
@@ -8,6 +8,14 @@
#include "aitravel.hpp"
#include "aifollow.hpp"
#include "aiactivate.hpp"
+#include "aicombat.hpp"
+
+#include "../mwworld/class.hpp"
+#include "creaturestats.hpp"
+#include "npcstats.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwworld/player.hpp"
void MWMechanics::AiSequence::copy (const AiSequence& sequence)
{
@@ -29,6 +37,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s
{
clear();
copy (sequence);
+ mDone = sequence.mDone;
}
return *this;
@@ -52,17 +61,20 @@ bool MWMechanics::AiSequence::isPackageDone() const
return mDone;
}
-void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor)
+void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
{
- if (!mPackages.empty())
+ if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
{
- if (mPackages.front()->execute (actor))
+ if (!mPackages.empty())
{
- mPackages.erase (mPackages.begin());
- mDone = true;
+ if (mPackages.front()->execute (actor,duration))
+ {
+ mPackages.erase (mPackages.begin());
+ mDone = true;
+ }
+ else
+ mDone = false;
}
- else
- mDone = false;
}
}
@@ -76,7 +88,14 @@ void MWMechanics::AiSequence::clear()
void MWMechanics::AiSequence::stack (const AiPackage& package)
{
- mPackages.push_front (package.clone());
+ for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); it++)
+ {
+ if(mPackages.front()->getPriority() <= package.getPriority())
+ mPackages.insert(it,package.clone());
+ }
+
+ if(mPackages.empty())
+ mPackages.push_front (package.clone());
}
void MWMechanics::AiSequence::queue (const AiPackage& package)
diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp
index 9f70daeb83..0976ef0995 100644
--- a/apps/openmw/mwmechanics/aisequence.hpp
+++ b/apps/openmw/mwmechanics/aisequence.hpp
@@ -18,6 +18,7 @@ namespace MWMechanics
class AiSequence
{
std::list<AiPackage *> mPackages;
+
bool mDone;
void copy (const AiSequence& sequence);
@@ -33,17 +34,17 @@ namespace MWMechanics
virtual ~AiSequence();
int getTypeId() const;
- ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
+ ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate, 5 Combat
bool isPackageDone() const;
///< Has a package been completed during the last update?
- void execute (const MWWorld::Ptr& actor);
+ void execute (const MWWorld::Ptr& actor,float duration);
///< Execute package.
void clear();
///< Remove all packages.
-
+
void stack (const AiPackage& package);
///< Add \a package to the front of the sequence (suspends current package)
diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp
index d47a49c702..08d7586388 100644
--- a/apps/openmw/mwmechanics/aitravel.cpp
+++ b/apps/openmw/mwmechanics/aitravel.cpp
@@ -31,7 +31,7 @@ namespace MWMechanics
return new AiTravel(*this);
}
- bool AiTravel::execute (const MWWorld::Ptr& actor)
+ bool AiTravel::execute (const MWWorld::Ptr& actor,float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp
index 6eb9af8cec..b479dfd431 100644
--- a/apps/openmw/mwmechanics/aitravel.hpp
+++ b/apps/openmw/mwmechanics/aitravel.hpp
@@ -13,7 +13,7 @@ namespace MWMechanics
AiTravel(float x, float y, float z);
virtual AiTravel *clone() const;
- virtual bool execute (const MWWorld::Ptr& actor);
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp
index 96a41883b9..f66f7ca620 100644
--- a/apps/openmw/mwmechanics/aiwander.cpp
+++ b/apps/openmw/mwmechanics/aiwander.cpp
@@ -63,7 +63,7 @@ namespace MWMechanics
return new AiWander(*this);
}
- bool AiWander::execute (const MWWorld::Ptr& actor)
+ bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
if(mDuration)
@@ -144,12 +144,12 @@ namespace MWMechanics
mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index);
}
-
- if(mAllowedNodes.empty())
- mDistance = 0;
}
}
+ if(mAllowedNodes.empty())
+ mDistance = 0;
+
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY))
mDistance = 0;
@@ -200,6 +200,7 @@ namespace MWMechanics
{
if(!mPathFinder.isPathConstructed())
{
+ assert(mAllowedNodes.size());
unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size());
Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ);
diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp
index c82ccc2155..48bc62c625 100644
--- a/apps/openmw/mwmechanics/aiwander.hpp
+++ b/apps/openmw/mwmechanics/aiwander.hpp
@@ -16,7 +16,7 @@ namespace MWMechanics
AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat);
virtual AiPackage *clone() const;
- virtual bool execute (const MWWorld::Ptr& actor);
+ virtual bool execute (const MWWorld::Ptr& actor,float duration);
///< \return Package completed?
virtual int getTypeId() const;
///< 0: Wander
diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp
index 1d992be413..f994c28b84 100644
--- a/apps/openmw/mwmechanics/alchemy.cpp
+++ b/apps/openmw/mwmechanics/alchemy.cpp
@@ -30,13 +30,13 @@
std::set<MWMechanics::EffectKey> MWMechanics::Alchemy::listEffects() const
{
std::map<EffectKey, int> effects;
-
+
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
{
if (!iter->isEmpty())
{
const MWWorld::LiveCellRef<ESM::Ingredient> *ingredient = iter->get<ESM::Ingredient>();
-
+
for (int i=0; i<4; ++i)
if (ingredient->mBase->mData.mEffectID[i]!=-1)
{
@@ -48,13 +48,13 @@ std::set<MWMechanics::EffectKey> MWMechanics::Alchemy::listEffects() const
}
}
}
-
+
std::set<EffectKey> effects2;
-
+
for (std::map<EffectKey, int>::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
if (iter->second>1)
effects2.insert (iter->first);
-
+
return effects2;
}
@@ -67,7 +67,7 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const
int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic;
int setup = 0;
-
+
if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty())
setup = 1;
else if (!mTools[tool].isEmpty())
@@ -82,23 +82,23 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const
mTools[ESM::Apparatus::Calcinator].get<ESM::Apparatus>()->mBase->mData.mQuality : 0;
float quality = 1;
-
+
switch (setup)
{
case 1:
-
+
quality = negative ? 2 * toolQuality + 3 * calcinatorQuality :
(magnitude && duration ?
2 * toolQuality + calcinatorQuality : 2/3.0 * (toolQuality + calcinatorQuality) + 0.5);
break;
-
+
case 2:
-
+
quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5);
break;
-
+
case 3:
-
+
quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5;
break;
}
@@ -110,8 +110,8 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const
else
{
if (quality==0)
- throw std::runtime_error ("invalid derived alchemy apparatus quality");
-
+ throw std::runtime_error ("invalid derived alchemy apparatus quality");
+
value /= quality;
}
}
@@ -141,21 +141,21 @@ void MWMechanics::Alchemy::updateEffects()
for (std::set<EffectKey>::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
{
const ESM::MagicEffect *magicEffect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (iter->mId);
-
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (iter->mId);
+
if (magicEffect->mData.mBaseCost<=0)
{
std::ostringstream os;
os << "invalid base cost for magic effect " << iter->mId;
throw std::runtime_error (os.str());
}
-
+
float fPotionT1MagMul =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionT1MagMult")->getFloat();
if (fPotionT1MagMul<=0)
throw std::runtime_error ("invalid gmst: fPotionT1MagMul");
-
+
float fPotionT1DurMult =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionT1DurMult")->getFloat();
@@ -172,25 +172,25 @@ void MWMechanics::Alchemy::updateEffects()
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
applyTools (magicEffect->mData.mFlags, duration);
-
- duration = static_cast<int> (duration+0.5);
+
+ duration = static_cast<int> (duration+0.5);
magnitude = static_cast<int> (magnitude+0.5);
if (magnitude>0 && duration>0)
{
ESM::ENAMstruct effect;
effect.mEffectID = iter->mId;
-
+
effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work
-
+
effect.mRange = 0;
- effect.mArea = 0;
-
+ effect.mArea = 0;
+
effect.mDuration = duration;
effect.mMagnMin = effect.mMagnMax = magnitude;
mEffects.push_back (effect);
- }
+ }
}
}
@@ -204,14 +204,14 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const
{
if (iter->mEffects.mList.size() != mEffects.size())
continue;
-
- bool mismatch = false;
+
+ bool mismatch = false;
for (int i=0; i<static_cast<int> (iter->mEffects.mList.size()); ++i)
{
const ESM::ENAMstruct& first = iter->mEffects.mList[i];
const ESM::ENAMstruct& second = mEffects[i];
-
+
if (first.mEffectID!=second.mEffectID ||
first.mArea!=second.mArea ||
first.mRange!=second.mRange ||
@@ -225,29 +225,30 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const
break;
}
}
-
+
if (!mismatch)
return &(*iter);
}
-
+
return 0;
}
void MWMechanics::Alchemy::removeIngredients()
{
bool needsUpdate = false;
-
+
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
if (!iter->isEmpty())
{
- iter->getRefData().setCount (iter->getRefData().getCount()-1);
+ iter->getContainerStore()->remove(*iter, 1, mAlchemist);
+
if (iter->getRefData().getCount()<1)
{
needsUpdate = true;
*iter = MWWorld::Ptr();
}
}
-
+
if (needsUpdate)
updateEffects();
}
@@ -255,39 +256,38 @@ void MWMechanics::Alchemy::removeIngredients()
void MWMechanics::Alchemy::addPotion (const std::string& name)
{
const ESM::Potion *record = getRecord();
-
+
if (!record)
{
ESM::Potion newRecord;
-
+
newRecord.mData.mWeight = 0;
-
+
for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
if (!iter->isEmpty())
newRecord.mData.mWeight += iter->get<ESM::Ingredient>()->mBase->mData.mWeight;
-
+
newRecord.mData.mWeight /= countIngredients();
-
+
newRecord.mData.mValue = mValue;
newRecord.mData.mAutoCalc = 0;
-
+
newRecord.mName = name;
- int index = static_cast<int> (std::rand()/static_cast<double> (RAND_MAX)*6);
+ int index = static_cast<int> (std::rand()/(static_cast<double> (RAND_MAX)+1)*6);
assert (index>=0 && index<6);
-
+
static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" };
-
+
newRecord.mModel = "m\\misc_potion_" + std::string (name[index]) + "_01.nif";
newRecord.mIcon = "m\\tx_potion_" + std::string (name[index]) + "_01.dds";
-
+
newRecord.mEffects.mList = mEffects;
-
+
record = MWBase::Environment::get().getWorld()->createRecord (newRecord);
}
-
- MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId);
- MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist);
+
+ mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist);
}
void MWMechanics::Alchemy::increaseSkill()
@@ -299,7 +299,7 @@ float MWMechanics::Alchemy::getChance() const
{
const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist);
const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist);
-
+
return
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
0.1 * creatureStats.getAttribute (1).getModified()
@@ -320,34 +320,34 @@ int MWMechanics::Alchemy::countIngredients() const
void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc)
{
mAlchemist = npc;
-
+
mIngredients.resize (4);
std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr());
-
+
mTools.resize (4);
-
+
std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr());
-
+
mEffects.clear();
-
+
MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc);
-
+
for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus));
iter!=store.end(); ++iter)
- {
+ {
MWWorld::LiveCellRef<ESM::Apparatus>* ref = iter->get<ESM::Apparatus>();
-
+
int type = ref->mBase->mData.mType;
-
+
if (type<0 || type>=static_cast<int> (mTools.size()))
throw std::runtime_error ("invalid apparatus type");
-
+
if (!mTools[type].isEmpty())
if (ref->mBase->mData.mQuality<=mTools[type].get<ESM::Apparatus>()->mBase->mData.mQuality)
continue;
-
- mTools[type] = *iter;
+
+ mTools[type] = *iter;
}
}
@@ -389,19 +389,19 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
{
slot = i;
break;
- }
-
+ }
+
if (slot==-1)
return -1;
-
+
for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
if (!iter->isEmpty() && ingredient.get<ESM::Ingredient>()==iter->get<ESM::Ingredient>())
return -1;
-
+
mIngredients[slot] = ingredient;
-
+
updateEffects();
-
+
return slot;
}
@@ -428,7 +428,7 @@ std::string MWMechanics::Alchemy::getPotionName() const
{
if (const ESM::Potion *potion = getRecord())
return potion->mName;
-
+
return "";
}
@@ -436,13 +436,13 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na
{
if (mTools[ESM::Apparatus::MortarPestle].isEmpty())
return Result_NoMortarAndPestle;
-
+
if (countIngredients()<2)
return Result_LessThanTwoIngredients;
if (name.empty() && getPotionName().empty())
return Result_NoName;
-
+
if (beginEffects()==endEffects())
return Result_NoEffects;
@@ -455,7 +455,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na
addPotion (name);
removeIngredients();
-
+
increaseSkill();
return Result_Success;
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
index c4260d907d..351e33f13e 100644
--- a/apps/openmw/mwmechanics/character.cpp
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -425,6 +425,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
{
forcestateupdate = true;
+ // Shields/torches shouldn't be visible during spellcasting or hand-to-hand
+ // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop",
+ // but they are also present in weapon drawing animation.
+ mAnimation->showCarriedLeft(weaptype != WeapType_Spell && weaptype != WeapType_HandToHand);
+
std::string weapgroup;
if(weaptype == WeapType_None)
{
@@ -442,6 +447,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
MWRender::Animation::Group_UpperBody, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
+
if(isWerewolf)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@@ -494,13 +500,25 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
{
if(mUpperBodyState == UpperCharState_WeapEquiped)
{
+ MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackType.clear();
if(mWeaponType == WeapType_Spell)
{
+ // Unset casting flag, otherwise pressing the mouse button down would
+ // continue casting every frame if there is no animation
+ mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
+
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
- const std::string spellid = stats.getSpells().getSelectedSpell();
- if(!spellid.empty())
+ // 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.getRefData().getHandle() == "player")
+ stats.getSpells().setSelectedSpell(MWBase::Environment::get().getWindowManager()->getSelectedSpell());
+
+ std::string spellid = stats.getSpells().getSelectedSpell();
+
+ if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
{
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
@@ -512,6 +530,15 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
const ESM::MagicEffect *effect;
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
+ const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
+ mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
+
+ castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
+ //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle);
+ //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle);
+ mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle);
+ mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle);
+
switch(effectentry.mRange)
{
case 0: mAttackType = "self"; break;
@@ -531,6 +558,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
else
sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
}
+ if (inv.getSelectedEnchantItem() != inv.end())
+ {
+ // Enchanted items cast immediately (no animation)
+ MWBase::Environment::get().getWorld()->castSpell(mPtr);
+ }
}
else if(mWeaponType == WeapType_PickProbe)
{
@@ -555,10 +587,8 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
if(!resultSound.empty())
MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f);
- // tool used up?
- if(!item.getRefData().getCount())
- MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon();
- else
+ // Set again, just to update the charge bar
+ if(item.getRefData().getCount())
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
}
else
@@ -687,17 +717,18 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
}
}
-
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
- if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name())
+ if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()
+ && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand)
+
{
- if(!mAnimation->isPlaying("torch"))
- mAnimation->play("torch", Priority_Torch,
- MWRender::Animation::Group_LeftArm, false,
- 1.0f, "start", "stop", 0.0f, (~(size_t)0));
+ mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm,
+ false, 1.0f, "start", "stop", 0.0f, (~(size_t)0));
}
- else if(mAnimation->isPlaying("torch"))
+ else if (mAnimation->isPlaying("torch"))
+ {
mAnimation->disable("torch");
+ }
return forcestateupdate;
}
@@ -708,6 +739,8 @@ void CharacterController::update(float duration)
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
Ogre::Vector3 movement(0.0f);
+ updateVisibility();
+
if(!cls.isActor())
{
if(mAnimQueue.size() > 1)
@@ -767,23 +800,19 @@ void CharacterController::update(float duration)
}
if(sneak || inwater || flying)
- {
vec.z = 0.0f;
- mFallHeight = mPtr.getRefData().getPosition().pos[2];
- }
+
+ if (inwater || flying)
+ cls.getCreatureStats(mPtr).land();
if(!onground && !flying && !inwater)
{
- // The player is in the air (either getting up ā€”ascending part of jumpā€” or falling).
+ // In the air (either getting up ā€”ascending part of jumpā€” or falling).
if (world->isSlowFalling(mPtr))
{
// SlowFalling spell effect is active, do not keep previous fall height
- mFallHeight = mPtr.getRefData().getPosition().pos[2];
- }
- else
- {
- mFallHeight = std::max(mFallHeight, mPtr.getRefData().getPosition().pos[2]);
+ cls.getCreatureStats(mPtr).land();
}
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
@@ -809,8 +838,7 @@ void CharacterController::update(float duration)
}
else if(vec.z > 0.0f && mJumpState == JumpState_None)
{
- // The player has started a jump.
-
+ // Started a jump.
float z = cls.getJump(mPtr);
if(vec.x == 0 && vec.y == 0)
vec = Ogre::Vector3(0.0f, 0.0f, z);
@@ -821,7 +849,8 @@ void CharacterController::update(float duration)
}
// advance acrobatics
- cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0);
+ if (mPtr.getRefData().getHandle() == "player")
+ cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0);
// decrease fatigue
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
@@ -835,13 +864,12 @@ void CharacterController::update(float duration)
}
else if(mJumpState == JumpState_Falling)
{
- // The player is landing.
-
forcestateupdate = true;
mJumpState = JumpState_Landing;
vec.z = 0.0f;
- float healthLost = cls.getFallDamage(mPtr, mFallHeight - mPtr.getRefData().getPosition().pos[2]);
+ float height = cls.getCreatureStats(mPtr).land();
+ float healthLost = cls.getFallDamage(mPtr, height);
if (healthLost > 0.0f)
{
const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm();
@@ -853,7 +881,8 @@ void CharacterController::update(float duration)
cls.getCreatureStats(mPtr).setHealth(health);
// report acrobatics progression
- cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
+ if (mPtr.getRefData().getHandle() == "player")
+ cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified();
if (healthLost > (acrobaticsSkill * fatigueTerm))
@@ -861,8 +890,6 @@ void CharacterController::update(float duration)
//TODO: actor falls over
}
}
-
- mFallHeight = mPtr.getRefData().getPosition().pos[2];
}
else
{
@@ -901,6 +928,9 @@ void CharacterController::update(float duration)
}
}
+ if (onground)
+ cls.getCreatureStats(mPtr).land();
+
if(movestate != CharState_None)
clearAnimQueue();
@@ -1091,8 +1121,46 @@ void CharacterController::resurrect()
if(mAnimation)
mAnimation->disable(mCurrentDeath);
- mCurrentDeath.empty();
+ mCurrentDeath.clear();
mDeathState = CharState_None;
}
+void CharacterController::updateContinuousVfx()
+{
+ // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code,
+ // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here.
+
+ // Stop any effects that are no longer active
+ std::vector<int> effects;
+ mAnimation->getLoopingEffects(effects);
+
+ for (std::vector<int>::iterator it = effects.begin(); it != effects.end(); ++it)
+ {
+ if (mPtr.getClass().getCreatureStats(mPtr).isDead()
+ || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0)
+ mAnimation->removeEffect(*it);
+ }
+}
+
+void CharacterController::updateVisibility()
+{
+ if (!mPtr.getClass().isActor())
+ return;
+ float alpha = 1.f;
+ if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude)
+ {
+ if (mPtr.getRefData().getHandle() == "player")
+ alpha = 0.4f;
+ else
+ alpha = 0.f;
+ }
+ float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude;
+ if (chameleon)
+ {
+ alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
+ }
+
+ mAnimation->setAlpha(alpha);
+}
+
}
diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp
index 8670b385e3..817fa2fd51 100644
--- a/apps/openmw/mwmechanics/character.hpp
+++ b/apps/openmw/mwmechanics/character.hpp
@@ -156,9 +156,6 @@ class CharacterController
float mSecondsOfSwimming;
float mSecondsOfRunning;
- // used for acrobatics progress and fall damages
- float mFallHeight;
-
std::string mAttackType; // slash, chop or thrust
void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false);
@@ -173,10 +170,15 @@ class CharacterController
bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak);
+ void updateVisibility();
+
public:
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
virtual ~CharacterController();
+ // Be careful when to call this, see comment in Actors
+ void updateContinuousVfx();
+
void updatePtr(const MWWorld::Ptr &ptr);
void update(float duration);
diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp
index 52ed43be37..95bd405b1d 100644
--- a/apps/openmw/mwmechanics/creaturestats.cpp
+++ b/apps/openmw/mwmechanics/creaturestats.cpp
@@ -14,7 +14,8 @@ namespace MWMechanics
mTalkedTo (false), mAlarmed (false),
mAttacked (false), mHostile (false),
mAttackingOrSpell(false), mAttackType(AT_Chop),
- mIsWerewolf(false)
+ mIsWerewolf(false),
+ mFallHeight(0), mRecalcDynamicStats(false)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;
@@ -127,14 +128,6 @@ namespace MWMechanics
return mAiSettings[index];
}
- Stat<int> &CreatureStats::getAttribute(int index)
- {
- if (index < 0 || index > 7) {
- throw std::runtime_error("attribute index is out of range");
- }
- return (!mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]);
- }
-
const DynamicStat<float> &CreatureStats::getDynamic(int index) const
{
if (index < 0 || index > 2) {
@@ -163,11 +156,29 @@ namespace MWMechanics
return mMagicEffects;
}
+ void CreatureStats::setAttribute(int index, int base)
+ {
+ MWMechanics::Stat<int> current = getAttribute(index);
+ current.setBase(base);
+ setAttribute(index, current);
+ }
+
void CreatureStats::setAttribute(int index, const Stat<int> &value)
{
if (index < 0 || index > 7) {
throw std::runtime_error("attribute index is out of range");
}
+
+ const Stat<int>& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index];
+
+ if (value.getModified() != currentValue.getModified())
+ {
+ if (index != ESM::Attribute::Luck
+ && index != ESM::Attribute::Personality
+ && index != ESM::Attribute::Speed)
+ mRecalcDynamicStats = true;
+ }
+
if(!mIsWerewolf)
mAttributes[index] = value;
else
@@ -217,6 +228,10 @@ namespace MWMechanics
void CreatureStats::setMagicEffects(const MagicEffects &effects)
{
+ if (effects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude
+ != mMagicEffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude)
+ mRecalcDynamicStats = true;
+
mMagicEffects = effects;
}
@@ -342,4 +357,40 @@ namespace MWMechanics
{
return mLastHitObject;
}
+
+ bool CreatureStats::canUsePower(const std::string &power) const
+ {
+ std::map<std::string, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(power);
+ if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp())
+ return true;
+ else
+ return false;
+ }
+
+ void CreatureStats::usePower(const std::string &power)
+ {
+ mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp();
+ }
+
+ void CreatureStats::addToFallHeight(float height)
+ {
+ mFallHeight += height;
+ }
+
+ float CreatureStats::land()
+ {
+ float height = mFallHeight;
+ mFallHeight = 0;
+ return height;
+ }
+
+ bool CreatureStats::needToRecalcDynamicStats()
+ {
+ if (mRecalcDynamicStats)
+ {
+ mRecalcDynamicStats = false;
+ return true;
+ }
+ return false;
+ }
}
diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp
index 1a7cb5d698..4d9be01324 100644
--- a/apps/openmw/mwmechanics/creaturestats.hpp
+++ b/apps/openmw/mwmechanics/creaturestats.hpp
@@ -36,10 +36,17 @@ namespace MWMechanics
bool mHostile;
bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not.
+ float mFallHeight;
+
int mAttackType;
std::string mLastHitObject; // The last object to hit this actor
+ // Do we need to recalculate stats derived from attributes or other factors?
+ bool mRecalcDynamicStats;
+
+ std::map<std::string, MWWorld::TimeStamp> mUsedPowers;
+
protected:
bool mIsWerewolf;
Stat<int> mWerewolfAttributes[8];
@@ -47,6 +54,17 @@ namespace MWMechanics
public:
CreatureStats();
+ bool needToRecalcDynamicStats();
+
+ void addToFallHeight(float height);
+
+ /// Reset the fall height
+ /// @return total fall height
+ float land();
+
+ bool canUsePower (const std::string& power) const;
+ void usePower (const std::string& power);
+
const Stat<int> & getAttribute(int index) const;
const DynamicStat<float> & getHealth() const;
@@ -70,8 +88,6 @@ namespace MWMechanics
int getAiSetting (int index) const;
///< 0: hello, 1 fight, 2 flee, 3 alarm
- Stat<int> & getAttribute(int index);
-
Spells & getSpells();
ActiveSpells & getActiveSpells();
@@ -79,6 +95,8 @@ namespace MWMechanics
MagicEffects & getMagicEffects();
void setAttribute(int index, const Stat<int> &value);
+ // Shortcut to set only the base
+ void setAttribute(int index, int base);
void setHealth(const DynamicStat<float> &value);
@@ -166,6 +184,11 @@ namespace MWMechanics
void setLastHitObject(const std::string &objectid);
const std::string &getLastHitObject() const;
+
+ // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else?
+ std::set<int> mBoundItems;
+ // Same as above
+ std::map<int, std::string> mSummonedCreatures;
};
}
diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp
index 4e26b5027c..7e11acdb0c 100644
--- a/apps/openmw/mwmechanics/enchanting.cpp
+++ b/apps/openmw/mwmechanics/enchanting.cpp
@@ -14,7 +14,6 @@ namespace MWMechanics
Enchanting::Enchanting()
: mCastStyle(ESM::Enchantment::CastOnce)
, mSelfEnchanting(false)
- , mOldItemCount(0)
{}
void Enchanting::setOldItem(MWWorld::Ptr oldItem)
@@ -24,7 +23,6 @@ namespace MWMechanics
{
mObjectType = mOldItemPtr.getTypeName();
mOldItemId = mOldItemPtr.getCellRef().mRefID;
- mOldItemCount = mOldItemPtr.getRefData().getCount();
}
else
{
@@ -55,25 +53,23 @@ namespace MWMechanics
bool Enchanting::create()
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
ESM::Enchantment enchantment;
enchantment.mData.mCharge = getGemCharge();
- mSoulGemPtr.getRefData().setCount (mSoulGemPtr.getRefData().getCount()-1);
+ store.remove(mSoulGemPtr, 1, player);
//Exception for Azura Star, new one will be added after enchanting
if(boost::iequals(mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
- {
- MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura");
- MWWorld::Class::get (player).getContainerStore (player).add (azura.getPtr(), player);
- }
+ store.add("Misc_SoulGem_Azura", 1, player);
if(mSelfEnchanting)
{
if(getEnchantChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
return false;
- MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1);
+ MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);
}
if(mCastStyle==ESM::Enchantment::ConstantEffect)
@@ -84,16 +80,18 @@ namespace MWMechanics
enchantment.mData.mCost = getEnchantPoints();
enchantment.mEffects = mEffectList;
- const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment);
-
- MWWorld::Class::get(mOldItemPtr).applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
+ // Create a new item
+ MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId, 1);
+ const MWWorld::Ptr& newItemPtr = ref.getPtr();
- mOldItemPtr.getRefData().setCount(1);
+ // Apply the enchantment
+ const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment);
+ MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
- MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId);
- ref.getPtr().getRefData().setCount (mOldItemCount-1);
+ // Add the new item to player inventory and remove the old one
+ store.remove(mOldItemPtr, 1, player);
+ store.add(newItemPtr, player);
- MWWorld::Class::get (player).getContainerStore (player).add (ref.getPtr(), player);
if(!mSelfEnchanting)
payForEnchantment();
@@ -299,20 +297,9 @@ namespace MWMechanics
void Enchanting::payForEnchantment() const
{
- MWWorld::Ptr gold;
-
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
- for (MWWorld::ContainerStoreIterator it = store.begin();
- it != store.end(); ++it)
- {
- if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
- {
- gold = *it;
- }
- }
-
- gold.getRefData().setCount(gold.getRefData().getCount() - getEnchantPrice());
+ store.remove("gold_001", getEnchantPrice(), player);
}
}
diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp
index a25fd43abc..988ce41fc7 100644
--- a/apps/openmw/mwmechanics/enchanting.hpp
+++ b/apps/openmw/mwmechanics/enchanting.hpp
@@ -22,7 +22,6 @@ namespace MWMechanics
std::string mNewItemName;
std::string mObjectType;
std::string mOldItemId;
- int mOldItemCount;
public:
Enchanting();
diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp
index 3ed458c3fa..5be0854aba 100644
--- a/apps/openmw/mwmechanics/magiceffects.cpp
+++ b/apps/openmw/mwmechanics/magiceffects.cpp
@@ -68,28 +68,6 @@ namespace MWMechanics
}
}
- void MagicEffects::add (const ESM::EffectList& list, float magnitude)
- {
- for (std::vector<ESM::ENAMstruct>::const_iterator iter (list.mList.begin()); iter!=list.mList.end();
- ++iter)
- {
- EffectParam param;
-
- if (iter->mMagnMin>=iter->mMagnMax)
- param.mMagnitude = iter->mMagnMin;
- else
- {
- if (magnitude==-1)
- magnitude = static_cast<float> (std::rand()) / RAND_MAX;
-
- param.mMagnitude = static_cast<int> (
- (iter->mMagnMax-iter->mMagnMin+1)*magnitude + iter->mMagnMin);
- }
-
- add (*iter, param);
- }
- }
-
MagicEffects& MagicEffects::operator+= (const MagicEffects& effects)
{
if (this==&effects)
diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp
index b80b5a863d..2c1b363b7d 100644
--- a/apps/openmw/mwmechanics/magiceffects.hpp
+++ b/apps/openmw/mwmechanics/magiceffects.hpp
@@ -2,6 +2,7 @@
#define GAME_MWMECHANICS_MAGICEFFECTS_H
#include <map>
+#include <string>
namespace ESM
{
@@ -27,10 +28,13 @@ namespace MWMechanics
struct EffectParam
{
- int mMagnitude;
+ // Note usually this would be int, but applying partial resistance might introduce decimal point.
+ float mMagnitude;
EffectParam();
+ EffectParam(float magnitude) : mMagnitude(magnitude) {}
+
EffectParam& operator+= (const EffectParam& param);
EffectParam& operator-= (const EffectParam& param);
@@ -48,6 +52,13 @@ namespace MWMechanics
return param -= right;
}
+ // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
+ struct EffectSourceVisitor
+ {
+ virtual void visit (MWMechanics::EffectKey key,
+ const std::string& sourceName, float magnitude, float remainingTime = -1) = 0;
+ };
+
/// \brief Effects currently affecting a NPC or creature
class MagicEffects
{
@@ -67,9 +78,6 @@ namespace MWMechanics
void add (const EffectKey& key, const EffectParam& param);
- void add (const ESM::EffectList& list, float magnitude = -1);
- ///< \param magnitude normalised magnitude (-1: random)
-
MagicEffects& operator+= (const MagicEffects& effects);
EffectParam get (const EffectKey& key) const;
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
index 8c13db7900..7740240b6b 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
@@ -12,6 +12,8 @@
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
+#include "spellcasting.hpp"
+
namespace MWMechanics
{
void MechanicsManager::buildPlayer()
@@ -31,15 +33,14 @@ namespace MWMechanics
for (int i=0; i<27; ++i)
npcStats.getSkill (i).setBase (player->mNpdt52.mSkills[i]);
- creatureStats.getAttribute(0).setBase (player->mNpdt52.mStrength);
- creatureStats.getAttribute(1).setBase (player->mNpdt52.mIntelligence);
- creatureStats.getAttribute(2).setBase (player->mNpdt52.mWillpower);
- creatureStats.getAttribute(3).setBase (player->mNpdt52.mAgility);
- creatureStats.getAttribute(4).setBase (player->mNpdt52.mSpeed);
- creatureStats.getAttribute(5).setBase (player->mNpdt52.mEndurance);
- creatureStats.getAttribute(6).setBase (player->mNpdt52.mPersonality);
- creatureStats.getAttribute(7).setBase (player->mNpdt52.mLuck);
-
+ creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt52.mStrength);
+ creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt52.mIntelligence);
+ creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt52.mWillpower);
+ creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt52.mAgility);
+ creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt52.mSpeed);
+ creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt52.mEndurance);
+ creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt52.mPersonality);
+ creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt52.mLuck);
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
@@ -55,7 +56,7 @@ namespace MWMechanics
{
const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
- creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale);
+ creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
}
for (int i=0; i<27; ++i)
@@ -106,7 +107,7 @@ namespace MWMechanics
int attribute = class_->mData.mAttribute[i];
if (attribute>=0 && attribute<8)
{
- creatureStats.getAttribute(attribute).setBase (
+ creatureStats.setAttribute(attribute,
creatureStats.getAttribute(attribute).getBase() + 10);
}
}
@@ -124,6 +125,19 @@ namespace MWMechanics
npcStats.getSkill (index).setBase (
npcStats.getSkill (index).getBase() + bonus);
}
+
+ if (i==1)
+ {
+ // Major skill - add starting spells for this skill if existing
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
+ for (; it != store.get<ESM::Spell>().end(); ++it)
+ {
+ if (it->mData.mFlags & ESM::Spell::F_PCStart
+ && spellSchoolToSkill(getSpellSchool(&*it, ptr)) == index)
+ creatureStats.getSpells().add(it->mId);
+ }
+ }
}
}
@@ -159,15 +173,15 @@ namespace MWMechanics
// auto-equip again. we need this for when the race is changed to a beast race
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
- invStore.equip(i, invStore.end());
+ invStore.unequipAll(ptr);
invStore.autoEquip(ptr);
}
MechanicsManager::MechanicsManager()
: mUpdatePlayer (true), mClassSelected (false),
- mRaceSelected (false)
+ mRaceSelected (false), mAI(true)
{
- buildPlayer();
+ //buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running
}
void MechanicsManager::add(const MWWorld::Ptr& ptr)
@@ -200,10 +214,7 @@ namespace MWMechanics
void MechanicsManager::drop(const MWWorld::CellStore *cellStore)
{
- if(!mWatched.isEmpty() && mWatched.getCell() == cellStore)
- mWatched = MWWorld::Ptr();
-
- mActors.dropActors(cellStore);
+ mActors.dropActors(cellStore, mWatched);
mObjects.dropObjects(cellStore);
}
@@ -213,6 +224,14 @@ namespace MWMechanics
mWatched = ptr;
}
+ void MechanicsManager::advanceTime (float duration)
+ {
+ // Uses ingame time, but scaled to real time
+ duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ player.getClass().getInventoryStore(player).rechargeItems(duration);
+ }
+
void MechanicsManager::update(float duration, bool paused)
{
if(!mWatched.isEmpty())
@@ -411,7 +430,7 @@ namespace MWMechanics
MWWorld::LiveCellRef<ESM::NPC>* player = playerPtr.get<ESM::NPC>();
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
- if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace))
+ if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace))
x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispRaceMod")->getFloat();
x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispPersonalityMult")->getFloat()
@@ -427,7 +446,9 @@ namespace MWMechanics
for(std::vector<ESM::Faction::Reaction>::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin();
it != MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end(); ++it)
{
- if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction;
+ if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)
+ && playerStats.getExpelled().find(Misc::StringUtils::lowerCase(it->mFaction)) == playerStats.getExpelled().end())
+ reaction = it->mReaction;
}
rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second;
}
@@ -438,7 +459,8 @@ namespace MWMechanics
{
if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() )
{
- if(it->mReaction<reaction) reaction = it->mReaction;
+ if(it->mReaction < reaction)
+ reaction = it->mReaction;
}
}
rank = 0;
@@ -679,4 +701,18 @@ namespace MWMechanics
return false;
}
+ void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr)
+ {
+ mActors.updateMagicEffects(ptr);
+ }
+
+ void MechanicsManager::toggleAI()
+ {
+ mAI = !mAI;
+ }
+
+ bool MechanicsManager::isAIActive()
+ {
+ return mAI;
+ }
}
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
index ad07562c7b..ec03b457b2 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
@@ -29,16 +29,17 @@ namespace MWMechanics
bool mUpdatePlayer;
bool mClassSelected;
bool mRaceSelected;
+ bool mAI;///< is AI active?
Objects mObjects;
Actors mActors;
+ public:
+
void buildPlayer();
///< build player according to stored class/race/birthsign information. Will
/// default to the values of the ESM::NPC object, if no explicit information is given.
- public:
-
MechanicsManager();
virtual void add (const MWWorld::Ptr& ptr);
@@ -63,6 +64,8 @@ namespace MWMechanics
/// \param paused In game type does not currently advance (this usually means some GUI
/// component is up).
+ virtual void advanceTime (float duration);
+
virtual void setPlayerName (const std::string& name);
///< Set player name.
@@ -89,7 +92,7 @@ namespace MWMechanics
virtual int countDeaths (const std::string& id) const;
///< Return the number of deaths for actors with the given ID.
-
+
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type,
float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange);
void toLower(std::string npcFaction);
@@ -100,6 +103,13 @@ namespace MWMechanics
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
virtual void skipAnimation(const MWWorld::Ptr& ptr);
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
+
+ /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
+ /// paused we may want to do it manually (after equipping permanent enchantment)
+ virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
+
+ virtual void toggleAI();
+ virtual bool isAIActive();
};
}
diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp
index 0b36982890..1fdefc84f9 100644
--- a/apps/openmw/mwmechanics/npcstats.cpp
+++ b/apps/openmw/mwmechanics/npcstats.cpp
@@ -86,7 +86,7 @@ void MWMechanics::NpcStats::setMovementFlag (Flag flag, bool state)
const MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index) const
{
- if (index<0 || index>=27)
+ if (index<0 || index>=ESM::Skill::Length)
throw std::runtime_error ("skill index out of range");
return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]);
@@ -94,7 +94,7 @@ const MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index) cons
MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index)
{
- if (index<0 || index>=27)
+ if (index<0 || index>=ESM::Skill::Length)
throw std::runtime_error ("skill index out of range");
return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]);
diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp
index 694987855e..41d6b4ffad 100644
--- a/apps/openmw/mwmechanics/objects.cpp
+++ b/apps/openmw/mwmechanics/objects.cpp
@@ -14,6 +14,16 @@ Objects::Objects()
{
}
+Objects::~Objects()
+{
+ PtrControllerMap::iterator it(mObjects.begin());
+ for (; it != mObjects.end();++it)
+ {
+ delete it->second;
+ it->second = NULL;
+ }
+}
+
void Objects::addObject(const MWWorld::Ptr& ptr)
{
removeObject(ptr);
diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp
index 5cdcdaa0af..32432c130a 100644
--- a/apps/openmw/mwmechanics/objects.hpp
+++ b/apps/openmw/mwmechanics/objects.hpp
@@ -21,6 +21,7 @@ namespace MWMechanics
public:
Objects();
+ ~Objects();
void addObject (const MWWorld::Ptr& ptr);
///< Register an animated object
diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp
index 8ef0edab83..ff266f9ae7 100644
--- a/apps/openmw/mwmechanics/pathfinding.cpp
+++ b/apps/openmw/mwmechanics/pathfinding.cpp
@@ -189,7 +189,7 @@ namespace MWMechanics
// This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call
// if otherwise).
if(mPath.empty())
- return 0;
+ return 0.;
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
float directionX = nextPoint.mX - x;
@@ -199,6 +199,21 @@ namespace MWMechanics
return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees();
}
+ bool PathFinder::checkWaypoint(float x, float y, float z)
+ {
+ if(mPath.empty())
+ return true;
+
+ ESM::Pathgrid::Point nextPoint = *mPath.begin();
+ if(distanceZCorrected(nextPoint, x, y, z) < 64)
+ {
+ mPath.pop_front();
+ if(mPath.empty()) mIsPathConstructed = false;
+ return true;
+ }
+ return false;
+ }
+
bool PathFinder::checkPathCompleted(float x, float y, float z)
{
if(mPath.empty())
diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp
index 35e0fa9087..916df850b5 100644
--- a/apps/openmw/mwmechanics/pathfinding.hpp
+++ b/apps/openmw/mwmechanics/pathfinding.hpp
@@ -18,6 +18,8 @@ namespace MWMechanics
bool checkPathCompleted(float x, float y, float z);
///< \Returns true if the last point of the path has been reached.
+ bool checkWaypoint(float x, float y, float z);
+ ///< \Returns true if a way point was reached
float getZAngleToNext(float x, float y) const;
bool isPathConstructed() const
@@ -25,6 +27,16 @@ namespace MWMechanics
return mIsPathConstructed;
}
+ int getPathSize() const
+ {
+ return mPath.size();
+ }
+
+ std::list<ESM::Pathgrid::Point> getPath() const
+ {
+ return mPath;
+ }
+
private:
std::list<ESM::Pathgrid::Point> mPath;
bool mIsPathConstructed;
diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp
index 66c492bf8b..38b2a48d7b 100644
--- a/apps/openmw/mwmechanics/repair.cpp
+++ b/apps/openmw/mwmechanics/repair.cpp
@@ -85,7 +85,10 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
// tool used up?
if (mTool.getCellRef().mCharge == 0)
{
- mTool.getRefData().setCount(0);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
+
+ store.remove(mTool, 1, player);
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("sNotifyMessage51")->getString();
@@ -93,8 +96,6 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % MWWorld::Class::get(mTool).getName(mTool)).str());
// try to find a new tool of the same ID
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
for (MWWorld::ContainerStoreIterator iter (store.begin());
iter!=store.end(); ++iter)
{
diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp
index d19da6e2af..c373e83f51 100644
--- a/apps/openmw/mwmechanics/security.cpp
+++ b/apps/openmw/mwmechanics/security.cpp
@@ -2,6 +2,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@@ -61,7 +62,7 @@ namespace MWMechanics
lockpick.getCellRef().mCharge = lockpick.get<ESM::Lockpick>()->mBase->mData.mUses;
--lockpick.getCellRef().mCharge;
if (!lockpick.getCellRef().mCharge)
- lockpick.getRefData().setCount(0);
+ lockpick.getContainerStore()->remove(lockpick, 1, mActor);
}
void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe,
@@ -103,7 +104,7 @@ namespace MWMechanics
probe.getCellRef().mCharge = probe.get<ESM::Probe>()->mBase->mData.mUses;
--probe.getCellRef().mCharge;
if (!probe.getCellRef().mCharge)
- probe.getRefData().setCount(0);
+ probe.getContainerStore()->remove(probe, 1, mActor);
}
}
diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp
new file mode 100644
index 0000000000..735652163e
--- /dev/null
+++ b/apps/openmw/mwmechanics/spellcasting.cpp
@@ -0,0 +1,500 @@
+#include "spellcasting.hpp"
+
+#include <boost/format.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/actionteleport.hpp"
+
+#include "../mwrender/animation.hpp"
+
+namespace MWMechanics
+{
+
+ CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
+ : mCaster(caster)
+ , mTarget(target)
+ , mStack(false)
+ {
+ }
+
+ void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
+ const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
+ {
+ // If none of the effects need to apply, we can early-out
+ bool found = false;
+ for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
+ iter!=effects.mList.end(); ++iter)
+ {
+ if (iter->mRange != range)
+ continue;
+ found = true;
+ }
+ if (!found)
+ return;
+
+ const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
+ if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
+ {
+ float x = (spell->mData.mType == ESM::Spell::ST_Disease) ?
+ target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude
+ : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude;
+
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll <= x)
+ {
+ // Fully resisted, show message
+ if (target.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
+ return;
+ }
+ }
+
+ ESM::EffectList reflectedEffects;
+ std::vector<ActiveSpells::Effect> appliedLastingEffects;
+ bool firstAppliedEffect = true;
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
+ effectIt!=effects.mList.end(); ++effectIt)
+ {
+ if (effectIt->mRange != range)
+ continue;
+
+ const ESM::MagicEffect *magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
+ effectIt->mEffectID);
+
+ float magnitudeMult = 1;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
+ {
+ // If player is attempting to cast a harmful spell, show the target's HP bar
+ if (caster.getRefData().getHandle() == "player" && target != caster)
+ MWBase::Environment::get().getWindowManager()->setEnemy(target);
+
+ // Try absorbing if it's a spell
+ // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure
+ // if that is worth replicating.
+ if (spell && caster != target)
+ {
+ int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude;
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ bool isAbsorbed = (roll < absorb);
+ if (isAbsorbed)
+ {
+ const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
+ MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
+ "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, "");
+ // Magicka is increased by cost of spell
+ DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
+ magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
+ target.getClass().getCreatureStats(target).setMagicka(magicka);
+ magnitudeMult = 0;
+ }
+ }
+
+ // Try reflecting
+ if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
+ {
+ int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude;
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ bool isReflected = (roll < reflect);
+ if (isReflected)
+ {
+ const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
+ MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
+ "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
+ reflectedEffects.mList.push_back(*effectIt);
+ magnitudeMult = 0;
+ }
+ }
+
+ // Try resisting
+ if (magnitudeMult > 0 && target.getClass().isActor())
+ {
+
+ magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell);
+ if (magnitudeMult == 0)
+ {
+ // Fully resisted, show message
+ if (target.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
+ else
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
+ }
+ }
+ }
+
+
+ if (magnitudeMult > 0)
+ {
+ float random = std::rand() / static_cast<float>(RAND_MAX);
+ float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
+ magnitude *= magnitudeMult;
+
+ if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
+ {
+ ActiveSpells::Effect effect;
+ effect.mKey = MWMechanics::EffectKey(*effectIt);
+ effect.mDuration = effectIt->mDuration;
+ effect.mMagnitude = magnitude;
+
+ appliedLastingEffects.push_back(effect);
+
+ // For absorb effects, also apply the effect to the caster - but with a negative
+ // magnitude, since we're transfering stats from the target to the caster
+ for (int i=0; i<5; ++i)
+ {
+ if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i)
+ {
+ std::vector<ActiveSpells::Effect> effects;
+ ActiveSpells::Effect effect_ = effect;
+ effect_.mMagnitude *= -1;
+ effects.push_back(effect_);
+ caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName);
+ }
+ }
+ }
+ else
+ applyInstantEffect(target, effectIt->mEffectID, magnitude);
+
+ if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
+ {
+ // Play sound, only for the first effect
+ if (firstAppliedEffect)
+ {
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(!magicEffect->mHitSound.empty())
+ sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f);
+ else
+ sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
+ firstAppliedEffect = false;
+ }
+
+ // Add VFX
+ if (!magicEffect->mHit.empty())
+ {
+ const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
+ bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
+ // Note: in case of non actor, a free effect should be fine as well
+ MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
+ if (anim)
+ anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
+ }
+ }
+
+ // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
+ }
+ }
+
+ if (reflectedEffects.mList.size())
+ inflict(caster, target, reflectedEffects, range, true);
+
+ if (appliedLastingEffects.size())
+ target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName);
+ }
+
+ void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude)
+ {
+ if (!target.getClass().isActor())
+ {
+ if (effectId == ESM::MagicEffect::Lock)
+ {
+ if (target.getCellRef().mLockLevel < magnitude)
+ target.getCellRef().mLockLevel = magnitude;
+ }
+ else if (effectId == ESM::MagicEffect::Open)
+ {
+ // TODO: This is a crime
+ if (target.getCellRef().mLockLevel <= magnitude)
+ {
+ if (target.getCellRef().mLockLevel > 0)
+ MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
+ target.getCellRef().mLockLevel = 0;
+ }
+ else
+ MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
+ }
+ }
+ else
+ {
+ if (effectId == ESM::MagicEffect::CurePoison)
+ target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
+ else if (effectId == ESM::MagicEffect::CureParalyzation)
+ target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
+ else if (effectId == ESM::MagicEffect::CureCommonDisease)
+ target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
+ else if (effectId == ESM::MagicEffect::CureBlightDisease)
+ target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
+ else if (effectId == ESM::MagicEffect::CureCorprusDisease)
+ target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease();
+ else if (effectId == ESM::MagicEffect::Dispel)
+ target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude);
+ else if (effectId == ESM::MagicEffect::RemoveCurse)
+ target.getClass().getCreatureStats(target).getSpells().purgeCurses();
+
+ if (target.getRefData().getHandle() != "player")
+ return;
+ if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled())
+ return;
+
+ Ogre::Vector3 worldPos;
+ if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(target.getCell(), worldPos))
+ worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition();
+
+ if (effectId == ESM::MagicEffect::DivineIntervention)
+ {
+ MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker", worldPos);
+ }
+ else if (effectId == ESM::MagicEffect::AlmsiviIntervention)
+ {
+ MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker", worldPos);
+ }
+
+ else if (effectId == ESM::MagicEffect::Mark)
+ {
+ MWBase::Environment::get().getWorld()->getPlayer().markPosition(
+ target.getCell(), target.getRefData().getPosition());
+ }
+ else if (effectId == ESM::MagicEffect::Recall)
+ {
+ MWWorld::CellStore* markedCell = NULL;
+ ESM::Position markedPosition;
+
+ MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition);
+ if (markedCell)
+ {
+ MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->mCell->mName,
+ markedPosition);
+ action.execute(target);
+ }
+ }
+ }
+ }
+
+ bool CastSpell::cast(const std::string &id)
+ {
+ if (const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
+ return cast(spell);
+
+ if (const ESM::Potion *potion =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
+ return cast(potion);
+
+ if (const ESM::Ingredient *ingredient =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
+ return cast(ingredient);
+
+ throw std::runtime_error("ID type cannot be casted");
+ }
+
+ bool CastSpell::cast(const MWWorld::Ptr &item)
+ {
+ std::string enchantmentName = item.getClass().getEnchantment(item);
+ if (enchantmentName.empty())
+ throw std::runtime_error("can't cast an item without an enchantment");
+
+ mSourceName = item.getClass().getName(item);
+ mId = item.getCellRef().mRefID;
+
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
+
+ mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
+
+ if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
+ {
+ // Check if there's enough charge left
+ const float enchantCost = enchantment->mData.mCost;
+ MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
+ int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+ const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
+
+ if (item.getCellRef().mEnchantmentCharge == -1)
+ item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
+
+ if (item.getCellRef().mEnchantmentCharge < castCost)
+ {
+ // TODO: Should there be a sound here?
+ if (mCaster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
+ return false;
+ }
+
+ // Reduce charge
+ item.getCellRef().mEnchantmentCharge -= castCost;
+ }
+ if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
+ item.getContainerStore()->remove(item, 1, mCaster);
+ else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
+ {
+ if (mCaster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
+ }
+
+ if (mCaster.getRefData().getHandle() == "player")
+ mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
+
+ inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
+
+ if (!mTarget.isEmpty())
+ {
+ if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
+ inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
+ }
+
+ MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName);
+
+ return true;
+ }
+
+ bool CastSpell::cast(const ESM::Potion* potion)
+ {
+ mSourceName = potion->mName;
+ mId = potion->mId;
+ mStack = true;
+
+ inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
+
+ return true;
+ }
+
+ bool CastSpell::cast(const ESM::Spell* spell)
+ {
+ mSourceName = spell->mName;
+ mId = spell->mId;
+ mStack = false;
+
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ int school = 0;
+
+ if (mCaster.getClass().isActor())
+ {
+ school = getSpellSchool(spell, mCaster);
+
+ CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
+
+ // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
+ static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat();
+ static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat();
+ DynamicStat<float> fatigue = stats.getFatigue();
+ const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster);
+ float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
+ fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
+ stats.setFatigue(fatigue);
+
+ bool fail = false;
+
+ // Check success
+ int successChance = getSpellSuccessChance(spell, mCaster);
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (!fail && roll >= successChance)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
+ fail = true;
+ }
+
+ if (fail)
+ {
+ // Failure sound
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
+ return false;
+ }
+ }
+
+ if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell)
+ mCaster.getClass().skillUsageSucceeded(mCaster,
+ spellSchoolToSkill(school), 0);
+
+ inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
+
+ if (!mTarget.isEmpty())
+ {
+ if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
+ {
+ inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
+ }
+ }
+
+ MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName);
+ return true;
+ }
+
+ bool CastSpell::cast (const ESM::Ingredient* ingredient)
+ {
+ mId = ingredient->mId;
+ mStack = true;
+ mSourceName = ingredient->mName;
+
+ ESM::ENAMstruct effect;
+ effect.mEffectID = ingredient->mData.mEffectID[0];
+ effect.mSkill = ingredient->mData.mSkills[0];
+ effect.mAttribute = ingredient->mData.mAttributes[0];
+ effect.mRange = ESM::RT_Self;
+ effect.mArea = 0;
+
+ const ESM::MagicEffect *magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
+ effect.mEffectID);
+
+ const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster);
+ const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
+
+ float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
+ 0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
+ + 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
+ * creatureStats.getFatigueTerm();
+
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll > x)
+ {
+ // "X has no effect on you"
+ std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->getString();
+ message = boost::str(boost::format(message) % ingredient->mName);
+ MWBase::Environment::get().getWindowManager()->messageBox(message);
+ return false;
+ }
+
+ float magnitude = 0;
+ float y = roll / std::min(x, 100.f);
+ y *= 0.25 * x;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
+ effect.mDuration = int(y);
+ else
+ effect.mDuration = 1;
+ if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
+ {
+ if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
+ magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost));
+ else
+ magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost));
+ magnitude = std::max(1.f, magnitude);
+ }
+ else
+ magnitude = 1;
+
+ effect.mMagnMax = magnitude;
+ effect.mMagnMin = magnitude;
+
+ ESM::EffectList effects;
+ effects.mList.push_back(effect);
+
+ inflict(mCaster, mCaster, effects, ESM::RT_Self);
+
+ return true;
+ }
+
+}
diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp
new file mode 100644
index 0000000000..e2efaa140b
--- /dev/null
+++ b/apps/openmw/mwmechanics/spellcasting.hpp
@@ -0,0 +1,211 @@
+#ifndef MWMECHANICS_SPELLSUCCESS_H
+#define MWMECHANICS_SPELLSUCCESS_H
+
+#include <cfloat>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include "npcstats.hpp"
+
+namespace MWMechanics
+{
+ inline int spellSchoolToSkill(int school)
+ {
+ std::map<int, int> schoolSkillMap; // maps spell school to skill id
+ schoolSkillMap[0] = 11; // alteration
+ schoolSkillMap[1] = 13; // conjuration
+ schoolSkillMap[3] = 12; // illusion
+ schoolSkillMap[2] = 10; // destruction
+ schoolSkillMap[4] = 14; // mysticism
+ schoolSkillMap[5] = 15; // restoration
+ assert(schoolSkillMap.find(school) != schoolSkillMap.end());
+ return schoolSkillMap[school];
+ }
+
+ /**
+ * @param spell spell to cast
+ * @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
+ * @attention actor has to be an NPC and not a creature!
+ * @return success chance from 0 to 100 (in percent)
+ */
+ inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
+ {
+ NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
+ CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
+
+ if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
+ return 0;
+
+ float y = FLT_MAX;
+ float lowestSkill = 0;
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
+ {
+ float x = it->mDuration;
+ const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
+ it->mEffectID);
+ if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
+ x = std::max(1.f, x);
+ x *= 0.1 * magicEffect->mData.mBaseCost;
+ x *= 0.5 * (it->mMagnMin + it->mMagnMax);
+ x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
+ x *= 1.5;
+ static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ "fEffectCostMult")->getFloat();
+ x *= fEffectCostMult;
+
+ float s = 2 * stats.getSkill(spellSchoolToSkill(magicEffect->mData.mSchool)).getModified();
+ if (s - x < y)
+ {
+ y = s - x;
+ if (effectiveSchool)
+ *effectiveSchool = magicEffect->mData.mSchool;
+ lowestSkill = s;
+ }
+ }
+
+ if (spell->mData.mType != ESM::Spell::ST_Spell)
+ return 100;
+
+ if (spell->mData.mFlags & ESM::Spell::F_Always)
+ return 100;
+
+ int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
+
+ int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
+ int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+
+ float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
+ if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
+ castChance = 100;
+
+ return std::max(0.f, std::min(100.f, castChance));
+ }
+
+ inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
+ {
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+ return getSpellSuccessChance(spell, actor, effectiveSchool);
+ }
+
+ inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
+ {
+ int school = 0;
+ getSpellSuccessChance(spellId, actor, &school);
+ return school;
+ }
+
+ inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
+ {
+ int school = 0;
+ getSpellSuccessChance(spell, actor, &school);
+ return school;
+ }
+
+ /// @return >=100 for fully resisted. can also return negative value for damage amplification.
+ inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
+ {
+ const ESM::MagicEffect *magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
+ effectId);
+
+ const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+
+ float resisted = 0;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
+ {
+
+ short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
+ short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
+
+ float resistance = 0;
+ if (resistanceEffect != -1)
+ resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
+ if (weaknessEffect != -1)
+ resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
+
+
+ float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
+ float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+ float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
+
+ // This makes spells that are easy to cast harder to resist and vice versa
+ if (spell != NULL && caster.getClass().isActor())
+ {
+ float castChance = getSpellSuccessChance(spell, caster);
+ if (castChance > 0)
+ x *= 50 / castChance;
+ }
+
+ float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
+ roll -= resistance;
+
+ if (x <= roll)
+ x = 0;
+ else
+ {
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
+ x = 100;
+ else
+ x = roll / std::min(x, 100.f);
+ }
+
+ x = std::min(x + resistance, 100.f);
+
+ resisted = x;
+ }
+
+ return resisted;
+ }
+
+ inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
+ {
+ float resistance = getEffectResistance(effectId, actor, caster, spell);
+ if (resistance >= 0)
+ return 1 - resistance / 100.f;
+ else
+ return -(resistance-100) / 100.f;
+ }
+
+
+ class CastSpell
+ {
+ private:
+ MWWorld::Ptr mCaster;
+ MWWorld::Ptr mTarget;
+ public:
+ bool mStack;
+ std::string mId; // ID of spell, potion, item etc
+ std::string mSourceName; // Display name for spell, potion, etc
+
+ public:
+ CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
+
+ bool cast (const ESM::Spell* spell);
+ bool cast (const MWWorld::Ptr& item);
+ bool cast (const ESM::Ingredient* ingredient);
+ bool cast (const ESM::Potion* potion);
+
+ /// @note Auto detects if spell, ingredient or potion
+ bool cast (const std::string& id);
+
+ void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
+ const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false);
+
+ void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp
index e10dcdc93d..0088bcb603 100644
--- a/apps/openmw/mwmechanics/spells.cpp
+++ b/apps/openmw/mwmechanics/spells.cpp
@@ -27,7 +27,15 @@ namespace MWMechanics
void Spells::add (const std::string& spellId)
{
if (mSpells.find (spellId)==mSpells.end())
- mSpells.insert (std::make_pair (spellId, static_cast<float> (std::rand()) / RAND_MAX));
+ {
+ const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+
+ std::vector<float> random;
+ random.resize(spell->mEffects.mList.size());
+ for (unsigned int i=0; i<random.size();++i)
+ random[i] = static_cast<float> (std::rand()) / RAND_MAX;
+ mSpells.insert (std::make_pair (spellId, random));
+ }
}
void Spells::remove (const std::string& spellId)
@@ -43,6 +51,8 @@ namespace MWMechanics
MagicEffects Spells::getMagicEffects() const
{
+ // TODO: These are recalculated every frame, no need to do that
+
MagicEffects effects;
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
@@ -52,7 +62,14 @@ namespace MWMechanics
if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight ||
spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse)
- effects.add (spell->mEffects, iter->second);
+ {
+ int i=0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
+ {
+ effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]);
+ ++i;
+ }
+ }
}
return effects;
@@ -100,4 +117,83 @@ namespace MWMechanics
return false;
}
+
+ void Spells::purgeCommonDisease()
+ {
+ for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType == ESM::Spell::ST_Disease)
+ mSpells.erase(iter++);
+ else
+ iter++;
+ }
+ }
+
+ void Spells::purgeBlightDisease()
+ {
+ for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType == ESM::Spell::ST_Blight)
+ mSpells.erase(iter++);
+ else
+ iter++;
+ }
+ }
+
+ void Spells::purgeCorprusDisease()
+ {
+ for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (Misc::StringUtils::ciEqual(spell->mId, "corprus"))
+ mSpells.erase(iter++);
+ else
+ iter++;
+ }
+ }
+
+ void Spells::purgeCurses()
+ {
+ for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType == ESM::Spell::ST_Curse)
+ mSpells.erase(iter++);
+ else
+ iter++;
+ }
+ }
+
+ void Spells::visitEffectSources(EffectSourceVisitor &visitor) const
+ {
+ for (TIterator it = begin(); it != end(); ++it)
+ {
+ const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(it->first);
+
+ // these are the spell types that are permanently in effect
+ if (!(spell->mData.mType == ESM::Spell::ST_Ability)
+ && !(spell->mData.mType == ESM::Spell::ST_Disease)
+ && !(spell->mData.mType == ESM::Spell::ST_Curse)
+ && !(spell->mData.mType == ESM::Spell::ST_Blight))
+ continue;
+ const ESM::EffectList& list = spell->mEffects;
+ int i=0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
+ effectIt != list.mList.end(); ++effectIt, ++i)
+ {
+ float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i];
+ visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude);
+ }
+ }
+ }
}
diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp
index e00ac259f1..cf9b660915 100644
--- a/apps/openmw/mwmechanics/spells.hpp
+++ b/apps/openmw/mwmechanics/spells.hpp
@@ -4,6 +4,10 @@
#include <map>
#include <string>
+#include "../mwworld/ptr.hpp"
+
+#include "magiceffects.hpp"
+
namespace ESM
{
struct Spell;
@@ -21,7 +25,7 @@ namespace MWMechanics
{
public:
- typedef std::map<std::string, float> TContainer; // ID, normalised magnitude
+ typedef std::map<std::string, std::vector<float> > TContainer; // ID, normalised magnitudes
typedef TContainer::const_iterator TIterator;
private:
@@ -31,6 +35,11 @@ namespace MWMechanics
public:
+ void purgeCommonDisease();
+ void purgeBlightDisease();
+ void purgeCorprusDisease();
+ void purgeCurses();
+
TIterator begin() const;
TIterator end() const;
@@ -57,6 +66,8 @@ namespace MWMechanics
bool hasCommonDisease() const;
bool hasBlightDisease() const;
+
+ void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
};
}
diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp
deleted file mode 100644
index 57c600df50..0000000000
--- a/apps/openmw/mwmechanics/spellsuccess.hpp
+++ /dev/null
@@ -1,118 +0,0 @@
-#ifndef MWMECHANICS_SPELLSUCCESS_H
-#define MWMECHANICS_SPELLSUCCESS_H
-
-#include "../mwbase/world.hpp"
-#include "../mwbase/environment.hpp"
-
-#include "../mwworld/ptr.hpp"
-#include "../mwworld/class.hpp"
-#include "../mwmechanics/creaturestats.hpp"
-
-#include "../mwworld/esmstore.hpp"
-
-#include "npcstats.hpp"
-
-namespace MWMechanics
-{
- inline int spellSchoolToSkill(int school)
- {
- std::map<int, int> schoolSkillMap; // maps spell school to skill id
- schoolSkillMap[0] = 11; // alteration
- schoolSkillMap[1] = 13; // conjuration
- schoolSkillMap[3] = 12; // illusion
- schoolSkillMap[2] = 10; // destruction
- schoolSkillMap[4] = 14; // mysticism
- schoolSkillMap[5] = 15; // restoration
- assert(schoolSkillMap.find(school) != schoolSkillMap.end());
- return schoolSkillMap[school];
- }
-
- inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
- {
- NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
-
- // determine the spell's school
- // this is always the school where the player's respective skill is the least advanced
- // out of all the magic effects' schools
- const std::vector<ESM::ENAMstruct>& effects = spell->mEffects.mList;
- int school = -1;
- int skillLevel = -1;
- for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.begin();
- it != effects.end(); ++it)
- {
- const ESM::MagicEffect* effect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(it->mEffectID);
- int _school = effect->mData.mSchool;
- int _skillLevel = stats.getSkill (spellSchoolToSkill(_school)).getModified();
-
- if (school == -1)
- {
- school = _school;
- skillLevel = _skillLevel;
- }
- else if (_skillLevel < skillLevel)
- {
- school = _school;
- skillLevel = _skillLevel;
- }
- }
-
- return school;
- }
-
- inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
- {
- const ESM::Spell* spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
- return getSpellSchool(spell, actor);
- }
-
-
- // UESP wiki / Morrowind/Spells:
- // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2
- /**
- * @param spell spell to cast
- * @param actor calculate spell success chance for this actor (depends on actor's skills)
- * @attention actor has to be an NPC and not a creature!
- * @return success chance from 0 to 100 (in percent)
- */
- inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor)
- {
- if (spell->mData.mFlags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells)
- || spell->mData.mType == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day
- return 100.0;
-
- if (spell->mEffects.mList.size() == 0)
- return 0.0;
-
- NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
- CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
-
- int skillLevel = stats.getSkill (getSpellSchool(spell, actor)).getModified();
-
- // Sound magic effect (reduces spell casting chance)
- int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude;
-
- int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified();
- int luck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified();
- int currentFatigue = creatureStats.getFatigue().getCurrent();
- int maxFatigue = creatureStats.getFatigue().getModified();
- int spellCost = spell->mData.mCost;
-
- // There we go, all needed variables are there, lets go
- float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0);
-
- chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100
-
- return chance;
- }
-
- inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor)
- {
- const ESM::Spell* spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
- return getSpellSuccessChance(spell, actor);
- }
-}
-
-#endif
diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp
index 65d47c9c08..cb6c09014b 100644
--- a/apps/openmw/mwmechanics/stat.hpp
+++ b/apps/openmw/mwmechanics/stat.hpp
@@ -162,14 +162,26 @@ namespace MWMechanics
setCurrent (getCurrent()+diff);
}
- void setCurrent (const T& value)
+ void setCurrent (const T& value, bool allowDecreaseBelowZero = false)
{
- mCurrent = value;
+ if (value > mCurrent)
+ {
+ // increase
+ mCurrent = value;
- if (mCurrent<0)
+ if (mCurrent > getModified())
+ mCurrent = getModified();
+ }
+ else if (value > 0 || allowDecreaseBelowZero)
+ {
+ // allowed decrease
+ mCurrent = value;
+ }
+ else if (mCurrent > 0)
+ {
+ // capped decrease
mCurrent = 0;
- else if (mCurrent>getModified())
- mCurrent = getModified();
+ }
}
void setModifier (const T& modifier)
diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp
index 566b6fa81e..639045bbe9 100644
--- a/apps/openmw/mwrender/actors.cpp
+++ b/apps/openmw/mwrender/actors.cpp
@@ -68,13 +68,16 @@ void Actors::insertBegin(const MWWorld::Ptr &ptr)
ptr.getRefData().setBaseNode(insert);
}
-void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv)
+void Actors::insertNPC(const MWWorld::Ptr& ptr)
{
insertBegin(ptr);
- NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), inv, RV_Actors);
+ NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
delete mAllActors[ptr];
mAllActors[ptr] = anim;
mRendering->addWaterRippleEmitter (ptr);
+
+ // Create CustomData, will do autoEquip and trigger animation parts update
+ ptr.getClass().getInventoryStore(ptr);
}
void Actors::insertCreature (const MWWorld::Ptr& ptr)
{
@@ -147,9 +150,12 @@ void Actors::removeCell(MWWorld::Ptr::CellStore* store)
}
}
-void Actors::update (float duration)
+void Actors::update (Ogre::Camera* camera)
{
- // Nothing to do
+ for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end(); ++iter)
+ {
+ iter->second->preRender(camera);
+ }
}
Animation* Actors::getAnimation(const MWWorld::Ptr &ptr)
diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp
index 4547db9ad2..d91321843b 100644
--- a/apps/openmw/mwrender/actors.hpp
+++ b/apps/openmw/mwrender/actors.hpp
@@ -39,7 +39,7 @@ namespace MWRender
void setRootNode(Ogre::SceneNode* root);
- void insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv);
+ void insertNPC(const MWWorld::Ptr& ptr);
void insertCreature (const MWWorld::Ptr& ptr);
void insertActivator (const MWWorld::Ptr& ptr);
bool deleteObject (const MWWorld::Ptr& ptr);
@@ -47,7 +47,7 @@ namespace MWRender
void removeCell(MWWorld::CellStore* store);
- void update (float duration);
+ void update (Ogre::Camera* camera);
/// Updates containing cell for object rendering data
void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur);
diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp
index 545060fe3a..faf9d979ae 100644
--- a/apps/openmw/mwrender/animation.cpp
+++ b/apps/openmw/mwrender/animation.cpp
@@ -17,6 +17,8 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
+#include <extern/shiny/Main/Factory.hpp>
+
#include "../mwmechanics/character.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwworld/class.hpp"
@@ -24,6 +26,7 @@
#include "renderconst.hpp"
+
namespace MWRender
{
@@ -39,26 +42,13 @@ void Animation::AnimationValue::setValue(Ogre::Real)
{
}
+Ogre::Real Animation::EffectAnimationValue::getValue() const
+{
+ return mTime;
+}
-void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects)
+void Animation::EffectAnimationValue::setValue(Ogre::Real)
{
- for(size_t i = 0;i < objects.mLights.size();i++)
- {
- Ogre::Light *light = objects.mLights[i];
- // If parent is a scene node, it was created specifically for this light. Destroy it now.
- if(light->isAttached() && !light->isParentTagPoint())
- sceneMgr->destroySceneNode(light->getParentSceneNode());
- sceneMgr->destroyLight(light);
- }
- for(size_t i = 0;i < objects.mParticles.size();i++)
- sceneMgr->destroyParticleSystem(objects.mParticles[i]);
- for(size_t i = 0;i < objects.mEntities.size();i++)
- sceneMgr->destroyEntity(objects.mEntities[i]);
- objects.mControllers.clear();
- objects.mLights.clear();
- objects.mParticles.clear();
- objects.mEntities.clear();
- objects.mSkelBase = NULL;
}
Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
@@ -78,10 +68,9 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
Animation::~Animation()
{
- mAnimSources.clear();
+ mEffects.clear();
- Ogre::SceneManager *sceneMgr = mInsert->getCreator();
- destroyObjectList(sceneMgr, mObjectRoot);
+ mAnimSources.clear();
}
@@ -90,7 +79,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!");
mSkelBase = NULL;
- destroyObjectList(mInsert->getCreator(), mObjectRoot);
+ mObjectRoot.setNull();
if(model.empty())
return;
@@ -111,11 +100,11 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) :
NifOgre::Loader::createObjectBase(mInsert, mdlname));
- if(mObjectRoot.mSkelBase)
+ if(mObjectRoot->mSkelBase)
{
- mSkelBase = mObjectRoot.mSkelBase;
+ mSkelBase = mObjectRoot->mSkelBase;
- Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates();
+ Ogre::AnimationStateSet *aset = mObjectRoot->mSkelBase->getAllAnimationStates();
Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator();
while(asiter.hasMoreElements())
{
@@ -126,7 +115,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
// Set the bones as manually controlled since we're applying the
// transformations manually
- Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton();
+ Ogre::SkeletonInstance *skelinst = mObjectRoot->mSkelBase->getSkeleton();
Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
while(boneiter.hasMoreElements())
boneiter.getNext()->setManuallyControlled(true);
@@ -147,15 +136,36 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
else
mAttachedObjects.clear();
- for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
+ for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
{
- if(mObjectRoot.mControllers[i].getSource().isNull())
- mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]);
+ if(mObjectRoot->mControllers[i].getSource().isNull())
+ mObjectRoot->mControllers[i].setSource(mAnimationValuePtr[0]);
}
}
+struct AddGlow
+{
+ Ogre::Vector3* mColor;
+ NifOgre::MaterialControllerManager* mMaterialControllerMgr;
+ AddGlow(Ogre::Vector3* col, NifOgre::MaterialControllerManager* materialControllerMgr)
+ : mColor(col)
+ , mMaterialControllerMgr(materialControllerMgr)
+ {}
+
+ void operator()(Ogre::Entity* entity) const
+ {
+ if (!entity->getNumSubEntities())
+ return;
+ Ogre::MaterialPtr writableMaterial = mMaterialControllerMgr->getWritableMaterial(entity);
+ sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(writableMaterial->getName());
-class VisQueueSet {
+ instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true)));
+ instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z)));
+ }
+};
+
+class VisQueueSet
+{
Ogre::uint32 mVisFlags;
Ogre::uint8 mSolidQueue, mTransQueue;
Ogre::Real mDist;
@@ -189,12 +199,16 @@ public:
}
};
-void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist)
+void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor)
{
- std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(),
+ std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(),
VisQueueSet(visflags, solidqueue, transqueue, dist));
- std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(),
+ std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(),
VisQueueSet(visflags, solidqueue, transqueue, dist));
+
+ if (enchantedGlow)
+ std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(),
+ AddGlow(glowColor, &objlist->mMaterialControllerMgr));
}
@@ -292,7 +306,7 @@ void Animation::clearAnimSources()
}
-void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light)
+void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light)
{
const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback();
@@ -305,8 +319,8 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList
if((light->mData.mFlags&ESM::Light::Negative))
color *= -1;
- objlist.mLights.push_back(sceneMgr->createLight());
- Ogre::Light *olight = objlist.mLights.back();
+ objlist->mLights.push_back(sceneMgr->createLight());
+ Ogre::Light *olight = objlist->mLights.back();
olight->setDiffuseColour(color);
Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource());
@@ -318,7 +332,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList
(light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow :
OEngine::Render::LT_Normal
));
- objlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(src, dest, func));
+ objlist->mControllers.push_back(Ogre::Controller<Ogre::Real>(src, dest, func));
bool interior = !(mPtr.isInCell() && mPtr.getCell()->mCell->isExterior());
bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ?
@@ -344,14 +358,14 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList
}
// If there's an AttachLight bone, attach the light to that, otherwise put it in the center,
- if(objlist.mSkelBase && objlist.mSkelBase->getSkeleton()->hasBone("AttachLight"))
- objlist.mSkelBase->attachObjectToBone("AttachLight", olight);
+ if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight"))
+ objlist->mSkelBase->attachObjectToBone("AttachLight", olight);
else
{
Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL;
- for(size_t i = 0;i < objlist.mEntities.size();i++)
+ for(size_t i = 0;i < objlist->mEntities.size();i++)
{
- Ogre::Entity *ent = objlist.mEntities[i];
+ Ogre::Entity *ent = objlist->mEntities[i];
bounds.merge(ent->getBoundingBox());
}
@@ -653,6 +667,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
else if(evt.compare(off, len, "hit") == 0)
MWWorld::Class::get(mPtr).hit(mPtr);
+
+ else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
+ MWBase::Environment::get().getWorld()->castSpell(mPtr);
}
@@ -891,8 +908,8 @@ Ogre::Vector3 Animation::runAnimation(float duration)
++stateiter;
}
- for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
- mObjectRoot.mControllers[i].update();
+ for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
+ mObjectRoot->mControllers[i].update();
// Apply group controllers
for(size_t grp = 0;grp < sNumGroups;grp++)
@@ -913,6 +930,8 @@ Ogre::Vector3 Animation::runAnimation(float duration)
mSkelBase->getAllAnimationStates()->_notifyDirty();
}
+ updateEffects(duration);
+
return movement;
}
@@ -933,7 +952,7 @@ public:
void Animation::enableLights(bool enable)
{
- std::for_each(mObjectRoot.mLights.begin(), mObjectRoot.mLights.end(), ToggleLight(enable));
+ std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable));
}
@@ -952,7 +971,7 @@ public:
Ogre::AxisAlignedBox Animation::getWorldBounds()
{
Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL;
- std::for_each(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), MergeBounds(&bounds));
+ std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds));
return bounds;
}
@@ -977,6 +996,163 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj)
mSkelBase->detachObjectFromBone(obj);
}
+bool Animation::isPlaying(Group group) const
+{
+ for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
+ {
+ if(stateiter->second.mGroups == group)
+ return true;
+ }
+ return false;
+}
+
+void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture)
+{
+ // Early out if we already have this effect
+ for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
+ return;
+
+ // fix texture extension to .dds
+ if (texture.size() > 4)
+ {
+ texture[texture.size()-3] = 'd';
+ texture[texture.size()-2] = 'd';
+ texture[texture.size()-1] = 's';
+ }
+
+ EffectParams params;
+ params.mModelName = model;
+ if (bonename.empty())
+ params.mObjects = NifOgre::Loader::createObjects(mInsert, model);
+ else
+ params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
+
+ setRenderProperties(params.mObjects, RV_Misc,
+ RQG_Main, RQG_Alpha, 0.f, false, NULL);
+
+ params.mLoop = loop;
+ params.mEffectId = effectId;
+ params.mBoneName = bonename;
+
+ for(size_t i = 0;i < params.mObjects->mControllers.size();i++)
+ {
+ if(params.mObjects->mControllers[i].getSource().isNull())
+ params.mObjects->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
+ }
+
+ if (!texture.empty())
+ {
+ for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i)
+ {
+ Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i];
+
+ Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys);
+
+ for (int t=0; t<mat->getNumTechniques(); ++t)
+ {
+ Ogre::Technique* tech = mat->getTechnique(t);
+ for (int p=0; p<tech->getNumPasses(); ++p)
+ {
+ Ogre::Pass* pass = tech->getPass(p);
+ for (int tex=0; tex<pass->getNumTextureUnitStates(); ++tex)
+ {
+ Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex);
+ tus->setTextureName("textures\\" + texture);
+ }
+ }
+ }
+ }
+ }
+
+ mEffects.push_back(params);
+}
+
+void Animation::removeEffect(int effectId)
+{
+ for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ if (it->mEffectId == effectId)
+ {
+ mEffects.erase(it);
+ return;
+ }
+ }
+}
+
+void Animation::getLoopingEffects(std::vector<int> &out)
+{
+ for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ if (it->mLoop)
+ out.push_back(it->mEffectId);
+ }
+}
+
+void Animation::updateEffects(float duration)
+{
+ for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); )
+ {
+ NifOgre::ObjectScenePtr objects = it->mObjects;
+ for(size_t i = 0; i < objects->mControllers.size() ;i++)
+ {
+ EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
+ if (value)
+ value->addTime(duration);
+
+ objects->mControllers[i].update();
+ }
+
+ if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
+ {
+ if (it->mLoop)
+ {
+ // Start from the beginning again; carry over the remainder
+ float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength;
+ for(size_t i = 0; i < objects->mControllers.size() ;i++)
+ {
+ EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
+ if (value)
+ value->resetTime(remainder);
+ }
+ }
+ else
+ {
+ it = mEffects.erase(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+}
+
+void Animation::preRender(Ogre::Camera *camera)
+{
+ for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ NifOgre::ObjectScenePtr objects = it->mObjects;
+ objects->rotateBillboardNodes(camera);
+ }
+ mObjectRoot->rotateBillboardNodes(camera);
+}
+
+// TODO: Should not be here
+Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item)
+{
+ Ogre::Vector3 result(1,1,1);
+ std::string enchantmentName = item.getClass().getEnchantment(item);
+ if (enchantmentName.empty())
+ return result;
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
+ assert (enchantment->mEffects.mList.size());
+ const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
+ enchantment->mEffects.mList.front().mEffectID);
+ result.x = magicEffect->mData.mRed / 255.f;
+ result.y = magicEffect->mData.mGreen / 255.f;
+ result.z = magicEffect->mData.mBlue / 255.f;
+ return result;
+}
+
ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model)
: Animation(ptr, ptr.getRefData().getBaseNode())
@@ -993,9 +1169,10 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod
small = false;
float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f;
+ Ogre::Vector3 col = getEnchantmentColor(ptr);
setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ?
(small ? RV_StaticsSmall : RV_Statics) : RV_Misc,
- RQG_Main, RQG_Alpha, dist);
+ RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col);
}
void ObjectAnimation::addLight(const ESM::Light *light)
@@ -1020,19 +1197,22 @@ public:
bool ObjectAnimation::canBatch() const
{
- if(!mObjectRoot.mParticles.empty() || !mObjectRoot.mLights.empty() || !mObjectRoot.mControllers.empty())
+ if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty())
+ return false;
+ if (!mObjectRoot->mBillboardNodes.empty())
return false;
- return std::find_if(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(),
- FindEntityTransparency()) == mObjectRoot.mEntities.end();
+ return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(),
+ FindEntityTransparency()) == mObjectRoot->mEntities.end();
}
void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg)
{
- std::vector<Ogre::Entity*>::reverse_iterator iter = mObjectRoot.mEntities.rbegin();
- for(;iter != mObjectRoot.mEntities.rend();++iter)
+ std::vector<Ogre::Entity*>::reverse_iterator iter = mObjectRoot->mEntities.rbegin();
+ for(;iter != mObjectRoot->mEntities.rend();++iter)
{
Ogre::Node *node = (*iter)->getParentNode();
- sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale());
+ if ((*iter)->isVisible())
+ sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale());
}
}
diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp
index 2215fc5827..72d1c100ef 100644
--- a/apps/openmw/mwrender/animation.hpp
+++ b/apps/openmw/mwrender/animation.hpp
@@ -52,6 +52,20 @@ protected:
virtual void setValue(Ogre::Real value);
};
+ class EffectAnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ {
+ private:
+ float mTime;
+ public:
+ EffectAnimationValue() : mTime(0) { }
+ void addTime(float time) { mTime += time; }
+ void resetTime(float value) { mTime = value; }
+
+ virtual Ogre::Real getValue() const;
+ virtual void setValue(Ogre::Real value);
+ };
+
+
class NullAnimationValue : public Ogre::ControllerValue<Ogre::Real>
{
@@ -95,12 +109,23 @@ protected:
typedef std::map<Ogre::MovableObject*,std::string> ObjectAttachMap;
+ struct EffectParams
+ {
+ std::string mModelName; // Just here so we don't add the same effect twice
+ NifOgre::ObjectScenePtr mObjects;
+ int mEffectId;
+ bool mLoop;
+ std::string mBoneName;
+ };
+
+ std::vector<EffectParams> mEffects;
+
MWWorld::Ptr mPtr;
Camera *mCamera;
Ogre::SceneNode *mInsert;
Ogre::Entity *mSkelBase;
- NifOgre::ObjectList mObjectRoot;
+ NifOgre::ObjectScenePtr mObjectRoot;
AnimSourceList mAnimSources;
Ogre::Node *mAccumRoot;
Ogre::Node *mNonAccumRoot;
@@ -114,6 +139,7 @@ protected:
ObjectAttachMap mAttachedObjects;
+
/* Sets the appropriate animations on the bone groups based on priority.
*/
void resetActiveGroups();
@@ -161,18 +187,44 @@ protected:
void addAnimSource(const std::string &model);
/** Adds an additional light to the given object list using the specified ESM record. */
- void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light);
+ void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light);
- static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects);
-
- static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f);
+ static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue,
+ Ogre::uint8 transqueue, Ogre::Real dist=0.0f,
+ bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
void clearAnimSources();
+ // TODO: Should not be here
+ Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item);
+
public:
Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node);
virtual ~Animation();
+ /**
+ * @brief Add an effect mesh attached to a bone or the insert scene node
+ * @param model
+ * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect.
+ * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true,
+ * you need to remove it manually using removeEffect when the effect should end.
+ * @param bonename Bone to attach to, or empty string to use the scene node instead
+ * @param texture override the texture specified in the model's materials
+ * @note Will not add an effect twice.
+ */
+ void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = "");
+ void removeEffect (int effectId);
+ void getLoopingEffects (std::vector<int>& out);
+
+ /// Prepare this animation for being rendered with \a camera (rotates billboard nodes)
+ virtual void preRender (Ogre::Camera* camera);
+
+ virtual void setAlpha(float alpha) {}
+private:
+ void updateEffects(float duration);
+
+
+public:
void updatePtr(const MWWorld::Ptr &ptr);
bool hasAnimation(const std::string &anim);
@@ -206,6 +258,8 @@ public:
/** Returns true if the named animation group is playing. */
bool isPlaying(const std::string &groupname) const;
+ bool isPlaying(Group group) const;
+
/** Gets info about the given animation group.
* \param groupname Animation group to check.
* \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc.
@@ -225,6 +279,7 @@ public:
virtual Ogre::Vector3 runAnimation(float duration);
virtual void showWeapons(bool showWeapon);
+ virtual void showCarriedLeft(bool show) {}
void enableLights(bool enable);
diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp
index 9af3987a8e..9a35725ee3 100644
--- a/apps/openmw/mwrender/camera.cpp
+++ b/apps/openmw/mwrender/camera.cpp
@@ -19,23 +19,27 @@ namespace MWRender
Camera::Camera (Ogre::Camera *camera)
: mCamera(camera),
mCameraNode(NULL),
+ mAnimation(NULL),
mFirstPersonView(true),
mPreviewMode(false),
mFreeLook(true),
- mHeight(128.f),
- mCameraDistance(300.f),
- mDistanceAdjusted(false),
- mAnimation(NULL),
mNearest(30.f),
mFurthest(800.f),
mIsNearest(false),
- mIsFurthest(false)
+ mIsFurthest(false),
+ mHeight(128.f),
+ mCameraDistance(300.f),
+ mDistanceAdjusted(false),
+ mVanityToggleQueued(false),
+ mViewModeToggleQueued(false)
{
mVanity.enabled = false;
mVanity.allowed = true;
+ mPreviewCam.pitch = 0.f;
mPreviewCam.yaw = 0.f;
mPreviewCam.offset = 400.f;
+ mMainCam.pitch = 0.f;
mMainCam.yaw = 0.f;
mMainCam.offset = 400.f;
}
@@ -103,6 +107,23 @@ namespace MWRender
void Camera::update(float duration, bool paused)
{
+ if (!mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ {
+ // Now process the view changes we queued earlier
+ if (mVanityToggleQueued)
+ {
+ toggleVanityMode(!mVanity.enabled);
+ mVanityToggleQueued = false;
+ }
+ if (mViewModeToggleQueued)
+ {
+
+ togglePreviewMode(false);
+ toggleViewMode();
+ mViewModeToggleQueued = false;
+ }
+ }
+
updateListener();
if (paused)
return;
@@ -121,6 +142,14 @@ namespace MWRender
void Camera::toggleViewMode()
{
+ // Changing the view will stop all playing animations, so if we are playing
+ // anything important, queue the view change for later
+ if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ {
+ mViewModeToggleQueued = true;
+ return;
+ }
+
mFirstPersonView = !mFirstPersonView;
processViewChange();
@@ -140,6 +169,14 @@ namespace MWRender
bool Camera::toggleVanityMode(bool enable)
{
+ // Changing the view will stop all playing animations, so if we are playing
+ // anything important, queue the view change for later
+ if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ {
+ mVanityToggleQueued = true;
+ return false;
+ }
+
if(!mVanity.allowed && enable)
return false;
@@ -168,6 +205,9 @@ namespace MWRender
void Camera::togglePreviewMode(bool enable)
{
+ if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ return;
+
if(mPreviewMode == enable)
return;
@@ -184,7 +224,6 @@ namespace MWRender
}
mCamera->setPosition(0.f, 0.f, offset);
- rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false);
}
void Camera::setSneakOffset()
@@ -241,6 +280,11 @@ namespace MWRender
}
}
+ float Camera::getCameraDistance() const
+ {
+ return mCamera->getPosition().z;
+ }
+
void Camera::setCameraDistance(float dist, bool adjust, bool override)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
@@ -319,6 +363,7 @@ namespace MWRender
mAnimation->setViewMode(NpcAnimation::VM_Normal);
mCameraNode->attachObject(mCamera);
}
+ rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false);
}
void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera)
diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp
index baf2f3685a..d31d9e56c0 100644
--- a/apps/openmw/mwrender/camera.hpp
+++ b/apps/openmw/mwrender/camera.hpp
@@ -47,6 +47,9 @@ namespace MWRender
bool mDistanceAdjusted;
+ bool mVanityToggleQueued;
+ bool mViewModeToggleQueued;
+
/// Updates sound manager listener data
void updateListener();
@@ -77,6 +80,7 @@ namespace MWRender
bool toggleVanityMode(bool enable);
void allowVanityMode(bool allow);
+ /// @note this may be ignored if an important animation is currently playing
void togglePreviewMode(bool enable);
/// \brief Lowers the camera for sneak.
@@ -101,6 +105,8 @@ namespace MWRender
/// Restore default camera distance for current mode.
void setCameraDistance();
+ float getCameraDistance() const;
+
void setAnimation(NpcAnimation *anim);
/// Stores focal and camera world positions in passed arguments
diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp
index e42218bc2d..5e659ca1d7 100644
--- a/apps/openmw/mwrender/characterpreview.cpp
+++ b/apps/openmw/mwrender/characterpreview.cpp
@@ -35,7 +35,7 @@ namespace MWRender
, mCamera(NULL)
, mNode(NULL)
{
-
+ mCharacter.mCell = NULL;
}
void CharacterPreview::onSetup()
@@ -70,8 +70,8 @@ namespace MWRender
mNode = renderRoot->createChildSceneNode();
- mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter),
- 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
+ mAnimation = new NpcAnimation(mCharacter, mNode,
+ 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
Ogre::Vector3 scale = mNode->getScale();
mCamera->setPosition(mPosition * scale);
@@ -101,7 +101,6 @@ namespace MWRender
{
if (mSceneMgr)
{
- //Ogre::TextureManager::getSingleton().remove(mName);
mSceneMgr->destroyAllCameras();
delete mAnimation;
Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
@@ -112,10 +111,8 @@ namespace MWRender
{
assert(mAnimation);
delete mAnimation;
- mAnimation = 0;
-
- mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter),
- 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
+ mAnimation = new NpcAnimation(mCharacter, mNode,
+ 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
float scale=1.f;
MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale);
@@ -143,6 +140,9 @@ namespace MWRender
void InventoryPreview::update(int sizeX, int sizeY)
{
+ // TODO: can we avoid this. Vampire state needs to be updated.
+ mAnimation->rebuild();
+
MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter);
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
std::string groupname;
@@ -177,11 +177,12 @@ namespace MWRender
groupname = "inventoryhandtohand";
}
- if(groupname != mCurrentAnimGroup)
- {
+ // TODO see above
+ //if(groupname != mCurrentAnimGroup)
+ //{
mCurrentAnimGroup = groupname;
mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
- }
+ //}
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name())
@@ -193,7 +194,7 @@ namespace MWRender
else if(mAnimation->getInfo("torch"))
mAnimation->disable("torch");
- mAnimation->updateParts(true);
+ mAnimation->updateParts();
mAnimation->runAnimation(0.0f);
mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024)));
@@ -229,7 +230,7 @@ namespace MWRender
, mRef(&mBase)
{
mBase = *mCharacter.get<ESM::NPC>()->mBase;
- mCharacter = MWWorld::Ptr(&mRef, mCharacter.getCell());
+ mCharacter = MWWorld::Ptr(&mRef, NULL);
}
void RaceSelectionPreview::update(float angle)
diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp
deleted file mode 100644
index b1c98a3067..0000000000
--- a/apps/openmw/mwrender/compositors.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-#include "compositors.hpp"
-
-#include <OgreViewport.h>
-#include <OgreCompositorManager.h>
-#include <OgreCompositorChain.h>
-#include <OgreCompositionTargetPass.h>
-
-using namespace MWRender;
-
-Compositors::Compositors(Ogre::Viewport* vp) :
- mViewport(vp)
- , mEnabled(true)
-{
-}
-
-Compositors::~Compositors()
-{
- Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport);
-}
-
-void Compositors::setEnabled (const bool enabled)
-{
- for (CompositorMap::iterator it=mCompositors.begin();
- it != mCompositors.end(); ++it)
- {
- Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, it->first, enabled && it->second.first);
- }
- mEnabled = enabled;
-}
-
-void Compositors::recreate()
-{
- Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport);
-
- CompositorMap temp = mCompositors;
- mCompositors.clear();
-
- for (CompositorMap::iterator it=temp.begin();
- it != temp.end(); ++it)
- {
- addCompositor(it->first, it->second.second);
- setCompositorEnabled(it->first, mEnabled && it->second.first);
- }
-}
-
-void Compositors::addCompositor (const std::string& name, const int priority)
-{
- int id = 0;
-
- for (CompositorMap::iterator it=mCompositors.begin();
- it != mCompositors.end(); ++it)
- {
- if (it->second.second > priority)
- break;
- ++id;
- }
- Ogre::CompositorManager::getSingleton().addCompositor (mViewport, name, id);
-
- mCompositors[name] = std::make_pair(false, priority);
-}
-
-void Compositors::setCompositorEnabled (const std::string& name, const bool enabled)
-{
- mCompositors[name].first = enabled;
- Ogre::CompositorManager::getSingleton().setCompositorEnabled (mViewport, name, enabled && mEnabled);
-}
-
-void Compositors::removeAll()
-{
- Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport);
-
- mCompositors.clear();
-}
-
-bool Compositors::anyCompositorEnabled()
-{
- for (CompositorMap::iterator it=mCompositors.begin();
- it != mCompositors.end(); ++it)
- {
- if (it->second.first && mEnabled)
- return true;
- }
- return false;
-}
-
-void Compositors::countTrianglesBatches(unsigned int &triangles, unsigned int &batches)
-{
- triangles = 0;
- batches = 0;
-
- Ogre::CompositorInstance* c = NULL;
- Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain (mViewport);
- // accumulate tris & batches from all compositors with all their render targets
- for (unsigned int i=0; i < chain->getNumCompositors(); ++i)
- {
- if (chain->getCompositor(i)->getEnabled())
- {
- c = chain->getCompositor(i);
- for (unsigned int j = 0; j < c->getTechnique()->getNumTargetPasses(); ++j)
- {
- std::string textureName = c->getTechnique()->getTargetPass(j)->getOutputName();
- Ogre::RenderTarget* rt = c->getRenderTarget(textureName);
- triangles += rt->getTriangleCount();
- batches += rt->getBatchCount();
- }
- }
- }
-}
diff --git a/apps/openmw/mwrender/compositors.hpp b/apps/openmw/mwrender/compositors.hpp
deleted file mode 100644
index e5dd7503ce..0000000000
--- a/apps/openmw/mwrender/compositors.hpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef GAME_MWRENDER_COMPOSITORS_H
-#define GAME_MWRENDER_COMPOSITORS_H
-
-#include <map>
-#include <string>
-
-namespace Ogre
-{
- class Viewport;
-}
-
-namespace MWRender
-{
- typedef std::map < std::string, std::pair<bool, int> > CompositorMap;
-
- /// \brief Manages a set of compositors for one viewport
- class Compositors
- {
- public:
- Compositors(Ogre::Viewport* vp);
- virtual ~Compositors();
-
- /**
- * enable or disable all compositors globally
- */
- void setEnabled (const bool enabled);
-
- void setViewport(Ogre::Viewport* vp) { mViewport = vp; }
-
- /// recreate compositors (call this after viewport size changes)
- void recreate();
-
- bool toggle() { setEnabled(!mEnabled); return mEnabled; }
-
- /**
- * enable or disable a specific compositor
- * @note enable has no effect if all compositors are globally disabled
- */
- void setCompositorEnabled (const std::string& name, const bool enabled);
-
- /**
- * @param name of compositor
- * @param priority, lower number will be first in the chain
- */
- void addCompositor (const std::string& name, const int priority);
-
- bool anyCompositorEnabled();
-
- void countTrianglesBatches(unsigned int &triangles, unsigned int &batches);
-
- void removeAll ();
-
- protected:
- /// maps compositor name to its "enabled" state
- CompositorMap mCompositors;
-
- bool mEnabled;
-
- Ogre::Viewport* mViewport;
- };
-
-}
-
-#endif
diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp
index 5f41289788..3ea3380e85 100644
--- a/apps/openmw/mwrender/localmap.cpp
+++ b/apps/openmw/mwrender/localmap.cpp
@@ -225,64 +225,54 @@ void LocalMap::render(const float x, const float y,
tex = TextureManager::getSingleton().getByName(texture);
if (tex.isNull())
{
- // try loading from disk
- //if (boost::filesystem::exists(texture+".jpg"))
- //{
- /// \todo
- //}
- //else
+ // render
+ tex = TextureManager::getSingleton().createManual(
+ texture,
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ xw*sMapResolution/sSize, yw*sMapResolution/sSize,
+ 0,
+ PF_R8G8B8,
+ TU_RENDERTARGET);
+
+ RenderTarget* rtt = tex->getBuffer()->getRenderTarget();
+
+ rtt->setAutoUpdated(false);
+ Viewport* vp = rtt->addViewport(mCellCamera);
+ vp->setOverlaysEnabled(false);
+ vp->setShadowsEnabled(false);
+ vp->setBackgroundColour(ColourValue(0, 0, 0));
+ vp->setVisibilityMask(RV_Map);
+ vp->setMaterialScheme("local_map");
+
+ rtt->update();
+
+ // create "fog of war" texture
+ TexturePtr tex2 = TextureManager::getSingleton().createManual(
+ texture + "_fog",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize,
+ 0,
+ PF_A8R8G8B8,
+ TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
+
+ // create a buffer to use for dynamic operations
+ std::vector<uint32> buffer;
+ buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
+
+ // initialize to (0, 0, 0, 1)
+ for (int p=0; p<sFogOfWarResolution*sFogOfWarResolution; ++p)
{
- // render
- tex = TextureManager::getSingleton().createManual(
- texture,
- ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
- TEX_TYPE_2D,
- xw*sMapResolution/sSize, yw*sMapResolution/sSize,
- 0,
- PF_R8G8B8,
- TU_RENDERTARGET);
-
- RenderTarget* rtt = tex->getBuffer()->getRenderTarget();
-
- rtt->setAutoUpdated(false);
- Viewport* vp = rtt->addViewport(mCellCamera);
- vp->setOverlaysEnabled(false);
- vp->setShadowsEnabled(false);
- vp->setBackgroundColour(ColourValue(0, 0, 0));
- vp->setVisibilityMask(RV_Map);
- vp->setMaterialScheme("local_map");
-
- rtt->update();
-
- // create "fog of war" texture
- TexturePtr tex2 = TextureManager::getSingleton().createManual(
- texture + "_fog",
- ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
- TEX_TYPE_2D,
- xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize,
- 0,
- PF_A8R8G8B8,
- TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
-
- // create a buffer to use for dynamic operations
- std::vector<uint32> buffer;
- buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
-
- // initialize to (0, 0, 0, 1)
- for (int p=0; p<sFogOfWarResolution*sFogOfWarResolution; ++p)
- {
- buffer[p] = (255 << 24);
- }
-
- memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
- tex2->getBuffer()->unlock();
+ buffer[p] = (255 << 24);
+ }
- mBuffers[texture] = buffer;
+ memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
+ tex2->getBuffer()->unlock();
- // save to cache for next time
- //rtt->writeContentsToFile("./" + texture + ".jpg");
- }
+ mBuffers[texture] = buffer;
}
+
mRenderingManager->enableLights(true);
mLight->setVisible(false);
@@ -339,8 +329,6 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni
Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis();
- Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
-
if (!mInterior)
{
x = std::ceil(pos.x / sSize)-1;
diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp
index 9ffe53eabb..b1455f0dc6 100644
--- a/apps/openmw/mwrender/npcanimation.cpp
+++ b/apps/openmw/mwrender/npcanimation.cpp
@@ -5,6 +5,8 @@
#include <OgreParticleSystem.h>
#include <OgreSubEntity.h>
+#include <extern/shiny/Main/Factory.hpp>
+
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
@@ -14,14 +16,60 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
#include "renderconst.hpp"
#include "camera.hpp"
+namespace
+{
+
+std::string getVampireHead(const std::string& race, bool female)
+{
+ static std::map <std::pair<std::string,int>, const ESM::BodyPart* > sVampireMapping;
+
+ std::pair<std::string, int> thisCombination = std::make_pair(race, int(female));
+
+ if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
+ for(MWWorld::Store<ESM::BodyPart>::iterator it = partStore.begin(); it != partStore.end(); ++it)
+ {
+ const ESM::BodyPart& bodypart = *it;
+ if (!bodypart.mData.mVampire)
+ continue;
+ if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
+ continue;
+ if (bodypart.mData.mPart != ESM::BodyPart::MP_Head)
+ continue;
+ if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
+ continue;
+ if (!Misc::StringUtils::ciEqual(bodypart.mRace, race))
+ continue;
+ sVampireMapping[thisCombination] = &*it;
+ }
+ }
+
+ assert(sVampireMapping[thisCombination]);
+ return "meshes\\" + sVampireMapping[thisCombination]->mModel;
+}
+
+}
+
namespace MWRender
{
+float SayAnimationValue::getValue() const
+{
+ if (MWBase::Environment::get().getSoundManager()->sayDone(mReference))
+ return 0;
+ else
+ // TODO: Use the loudness of the currently playing sound
+ return 1;
+}
+
static NpcAnimation::PartBoneMap createPartListMap()
{
NpcAnimation::PartBoneMap result;
@@ -58,43 +106,34 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap();
NpcAnimation::~NpcAnimation()
{
- Ogre::SceneManager *sceneMgr = mInsert->getCreator();
- for(size_t i = 0;i < ESM::PRT_Count;i++)
- destroyObjectList(sceneMgr, mObjectParts[i]);
+ if (!mListenerDisabled)
+ mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr);
}
-NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags, ViewMode viewMode)
+NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, ViewMode viewMode)
: Animation(ptr, node),
- mStateID(-1),
- mTimeToChange(0),
mVisibilityFlags(visibilityFlags),
- mRobe(inv.end()),
- mHelmet(inv.end()),
- mShirt(inv.end()),
- mCuirass(inv.end()),
- mGreaves(inv.end()),
- mPauldronL(inv.end()),
- mPauldronR(inv.end()),
- mBoots(inv.end()),
- mPants(inv.end()),
- mGloveL(inv.end()),
- mGloveR(inv.end()),
- mSkirtIter(inv.end()),
- mWeapon(inv.end()),
- mShield(inv.end()),
+ mListenerDisabled(disableListener),
mViewMode(viewMode),
mShowWeapons(false),
- mFirstPersonOffset(0.f, 0.f, 0.f)
+ mShowCarriedLeft(true),
+ mFirstPersonOffset(0.f, 0.f, 0.f),
+ mAlpha(1.f)
{
mNpc = mPtr.get<ESM::NPC>()->mBase;
+ mSayAnimationValue = Ogre::SharedPtr<SayAnimationValue>(new SayAnimationValue(mPtr));
+
for(size_t i = 0;i < ESM::PRT_Count;i++)
{
mPartslots[i] = -1; //each slot is empty
mPartPriorities[i] = 0;
}
+ if (!disableListener)
+ mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
+
updateNpcBase();
}
@@ -118,17 +157,22 @@ void NpcAnimation::updateNpcBase()
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
- bool isWerewolf = MWWorld::Class::get(mPtr).getNpcStats(mPtr).isWerewolf();
+ bool isWerewolf = mPtr.getClass().getNpcStats(mPtr).isWerewolf();
+ bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude;
- if(!isWerewolf)
+ if (isWerewolf)
{
- mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHead)->mModel;
- mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHair)->mModel;
+ mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
+ mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
}
else
{
- mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
- mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
+ if (vampire)
+ mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female);
+ else
+ mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHead)->mModel;
+
+ mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHair)->mModel;
}
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
@@ -173,73 +217,63 @@ void NpcAnimation::updateNpcBase()
for(size_t i = 0;i < ESM::PRT_Count;i++)
removeIndividualPart((ESM::PartReferenceType)i);
- updateParts(true);
+ updateParts();
}
-void NpcAnimation::updateParts(bool forceupdate)
+void NpcAnimation::updateParts()
{
+ mAlpha = 1.f;
+ const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
+ MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
+
static const struct {
- MWWorld::ContainerStoreIterator NpcAnimation::*mPart;
int mSlot;
int mBasePriority;
} slotlist[] = {
// FIXME: Priority is based on the number of reserved slots. There should be a better way.
- { &NpcAnimation::mRobe, MWWorld::InventoryStore::Slot_Robe, 12 },
- { &NpcAnimation::mSkirtIter, MWWorld::InventoryStore::Slot_Skirt, 3 },
- { &NpcAnimation::mHelmet, MWWorld::InventoryStore::Slot_Helmet, 0 },
- { &NpcAnimation::mCuirass, MWWorld::InventoryStore::Slot_Cuirass, 0 },
- { &NpcAnimation::mGreaves, MWWorld::InventoryStore::Slot_Greaves, 0 },
- { &NpcAnimation::mPauldronL, MWWorld::InventoryStore::Slot_LeftPauldron, 0 },
- { &NpcAnimation::mPauldronR, MWWorld::InventoryStore::Slot_RightPauldron, 0 },
- { &NpcAnimation::mBoots, MWWorld::InventoryStore::Slot_Boots, 0 },
- { &NpcAnimation::mGloveL, MWWorld::InventoryStore::Slot_LeftGauntlet, 0 },
- { &NpcAnimation::mGloveR, MWWorld::InventoryStore::Slot_RightGauntlet, 0 },
- { &NpcAnimation::mShirt, MWWorld::InventoryStore::Slot_Shirt, 0 },
- { &NpcAnimation::mPants, MWWorld::InventoryStore::Slot_Pants, 0 },
- { &NpcAnimation::mShield, MWWorld::InventoryStore::Slot_CarriedLeft, 0 },
- { &NpcAnimation::mWeapon, MWWorld::InventoryStore::Slot_CarriedRight, 0 }
+ { MWWorld::InventoryStore::Slot_Robe, 12 },
+ { MWWorld::InventoryStore::Slot_Skirt, 3 },
+ { MWWorld::InventoryStore::Slot_Helmet, 0 },
+ { MWWorld::InventoryStore::Slot_Cuirass, 0 },
+ { MWWorld::InventoryStore::Slot_Greaves, 0 },
+ { MWWorld::InventoryStore::Slot_LeftPauldron, 0 },
+ { MWWorld::InventoryStore::Slot_RightPauldron, 0 },
+ { MWWorld::InventoryStore::Slot_Boots, 0 },
+ { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 },
+ { MWWorld::InventoryStore::Slot_RightGauntlet, 0 },
+ { MWWorld::InventoryStore::Slot_Shirt, 0 },
+ { MWWorld::InventoryStore::Slot_Pants, 0 },
+ { MWWorld::InventoryStore::Slot_CarriedLeft, 0 },
+ { MWWorld::InventoryStore::Slot_CarriedRight, 0 }
};
static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]);
- const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
- MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
- for(size_t i = 0;!forceupdate && i < slotlistsize;i++)
- {
- MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].mSlot);
- if(this->*slotlist[i].mPart != iter)
- {
- forceupdate = true;
- break;
- }
- }
- if(!forceupdate)
- return;
-
for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++)
{
MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot);
- this->*slotlist[i].mPart = store;
removePartGroup(slotlist[i].mSlot);
- if(this->*slotlist[i].mPart == inv.end())
+ if(store == inv.end())
continue;
if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet)
removeIndividualPart(ESM::PRT_Hair);
int prio = 1;
+ bool enchantedGlow = !store->getClass().getEnchantment(*store).empty();
+ Ogre::Vector3 glowColor = getEnchantmentColor(*store);
if(store->getTypeName() == typeid(ESM::Clothing).name())
{
prio = ((slotlist[i].mBasePriority+1)<<1) + 0;
const ESM::Clothing *clothes = store->get<ESM::Clothing>()->mBase;
- addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts);
+ addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor);
}
else if(store->getTypeName() == typeid(ESM::Armor).name())
{
prio = ((slotlist[i].mBasePriority+1)<<1) + 1;
const ESM::Armor *armor = store->get<ESM::Armor>()->mBase;
- addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts);
+ addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor);
}
if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe)
@@ -285,10 +319,13 @@ void NpcAnimation::updateParts(bool forceupdate)
}
showWeapons(mShowWeapons);
+ showCarriedLeft(mShowCarriedLeft);
// Remember body parts so we only have to search through the store once for each race/gender/viewmode combination
static std::map< std::pair<std::string,int>,std::vector<const ESM::BodyPart*> > sRaceMapping;
+ static std::map <std::pair<std::string,int>, std::vector<const ESM::BodyPart*> > sVampireMapping;
+
static const int Flag_Female = 1<<0;
static const int Flag_FirstPerson = 1<<1;
@@ -410,17 +447,18 @@ public:
}
};
-NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename)
+NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor)
{
- NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
- setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha);
+ NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
+ setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0,
+ enchantedGlow, glowColor);
- std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group));
- std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group));
+ std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group));
+ std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group));
- if(objects.mSkelBase)
+ if(objects->mSkelBase)
{
- Ogre::AnimationStateSet *aset = objects.mSkelBase->getAllAnimationStates();
+ Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates();
Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator();
while(asiter.hasMoreElements())
{
@@ -428,7 +466,7 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in
state->setEnabled(false);
state->setLoop(false);
}
- Ogre::SkeletonInstance *skelinst = objects.mSkelBase->getSkeleton();
+ Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton();
Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
while(boneiter.hasMoreElements())
boneiter.getNext()->setManuallyControlled(true);
@@ -439,13 +477,6 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in
Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
{
- if(mTimeToChange <= 0.0f)
- {
- mTimeToChange = 0.2f;
- updateParts();
- }
- mTimeToChange -= timepassed;
-
Ogre::Vector3 ret = Animation::runAnimation(timepassed);
Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton();
@@ -463,11 +494,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
for(size_t i = 0;i < ESM::PRT_Count;i++)
{
- std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[i].mControllers.begin());
- for(;ctrl != mObjectParts[i].mControllers.end();ctrl++)
+ if (mObjectParts[i].isNull())
+ continue;
+ std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[i]->mControllers.begin());
+ for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++)
ctrl->update();
- Ogre::Entity *ent = mObjectParts[i].mSkelBase;
+ Ogre::Entity *ent = mObjectParts[i]->mSkelBase;
if(!ent) continue;
updateSkeletonInstance(baseinst, ent->getSkeleton());
ent->getAllAnimationStates()->_notifyDirty();
@@ -481,7 +514,7 @@ void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type)
mPartPriorities[type] = 0;
mPartslots[type] = -1;
- destroyObjectList(mInsert->getCreator(), mObjectParts[type]);
+ mObjectParts[type].setNull();
}
void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority)
@@ -503,7 +536,7 @@ void NpcAnimation::removePartGroup(int group)
}
}
-bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh)
+bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor)
{
if(priority <= mPartPriorities[type])
return false;
@@ -512,20 +545,23 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
mPartslots[type] = group;
mPartPriorities[type] = priority;
- mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type));
- if(mObjectParts[type].mSkelBase)
+ mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor);
+ if(mObjectParts[type]->mSkelBase)
{
- Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton();
- if(mObjectParts[type].mSkelBase->isParentTagPoint())
+ Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton();
+ if(mObjectParts[type]->mSkelBase->isParentTagPoint())
{
- Ogre::Node *root = mObjectParts[type].mSkelBase->getParentNode();
+ Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode();
if(skel->hasBone("BoneOffset"))
{
Ogre::Bone *offset = skel->getBone("BoneOffset");
+
root->translate(offset->getPosition());
- root->rotate(offset->getOrientation());
- // HACK: Why an extra -90 degree rotation?
+
+ // It appears that the BoneOffset rotation is completely bogus, at least for light models.
+ //root->rotate(offset->getOrientation());
root->pitch(Ogre::Degree(-90.0f));
+
root->scale(offset->getScale());
root->setInitialState();
}
@@ -535,21 +571,24 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
}
// TODO:
- // type == ESM::PRT_Head should get an animation source based on the current output of
- // the actor's 'say' sound (0 = silent, 1 = loud?).
// type == ESM::PRT_Weapon should get an animation source based on the current offset
// of the weapon attack animation (from its beginning, or start marker?)
- std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[type].mControllers.begin());
- for(;ctrl != mObjectParts[type].mControllers.end();ctrl++)
+ std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[type]->mControllers.begin());
+ for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++)
{
if(ctrl->getSource().isNull())
+ {
ctrl->setSource(mNullAnimationValuePtr);
+
+ if (type == ESM::PRT_Head)
+ ctrl->setSource(mSayAnimationValue);
+ }
}
return true;
}
-void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts)
+void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts, bool enchantedGlow, Ogre::Vector3* glowColor)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
@@ -587,7 +626,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
}
if(bodypart)
- addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel);
+ addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor);
else
reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority);
}
@@ -599,18 +638,132 @@ void NpcAnimation::showWeapons(bool showWeapon)
if(showWeapon)
{
MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
- mWeapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
- if(mWeapon != inv.end()) // special case for weapons
+ MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ if(weapon != inv.end()) // special case for weapons
{
- MWWorld::Ptr weapon = *mWeapon;
- std::string mesh = MWWorld::Class::get(weapon).getModel(weapon);
- addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh);
+ Ogre::Vector3 glowColor = getEnchantmentColor(*weapon);
+ std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon);
+ addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
+ mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
}
}
else
{
removeIndividualPart(ESM::PRT_Weapon);
}
+ mAlpha = 1.f;
+}
+
+void NpcAnimation::showCarriedLeft(bool show)
+{
+ mShowCarriedLeft = show;
+ MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
+ MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+
+ if(show && iter != inv.end())
+ {
+ Ogre::Vector3 glowColor = getEnchantmentColor(*iter);
+ std::string mesh = MWWorld::Class::get(*iter).getModel(*iter);
+ if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
+ mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor))
+ {
+ if (iter->getTypeName() == typeid(ESM::Light).name())
+ addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], iter->get<ESM::Light>()->mBase);
+ }
+ }
+ else
+ removeIndividualPart(ESM::PRT_Shield);
+}
+
+void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
+{
+ // During first auto equip, we don't play any sounds.
+ // Basically we don't want sounds when the actor is first loaded,
+ // the items should appear as if they'd always been equipped.
+ if (playSound)
+ {
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(!magicEffect->mHitSound.empty())
+ sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f);
+ else
+ sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
+ }
+
+ if (!magicEffect->mHit.empty())
+ {
+ const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
+ bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
+ // Don't play particle VFX unless the effect is new or it should be looping.
+ if (isNew || loop)
+ addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
+ }
+}
+
+void NpcAnimation::setAlpha(float alpha)
+{
+ if (alpha == mAlpha)
+ return;
+ mAlpha = alpha;
+
+ for (int i=0; i<ESM::PRT_Count; ++i)
+ {
+ if (mObjectParts[i].isNull())
+ continue;
+
+ for (unsigned int j=0; j<mObjectParts[i]->mEntities.size(); ++j)
+ {
+ Ogre::Entity* ent = mObjectParts[i]->mEntities[j];
+ if (ent != mObjectParts[i]->mSkelBase)
+ applyAlpha(alpha, ent, mObjectParts[i]);
+ }
+ }
+}
+
+void NpcAnimation::preRender(Ogre::Camera *camera)
+{
+ Animation::preRender(camera);
+ for (int i=0; i<ESM::PRT_Count; ++i)
+ {
+ if (mObjectParts[i].isNull())
+ continue;
+ mObjectParts[i]->rotateBillboardNodes(camera);
+ }
+}
+
+void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene)
+{
+ ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent()
+ ? RQG_Alpha : RQG_Main);
+
+
+ Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent);
+ if (mAlpha == 1.f)
+ {
+ // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them.
+ mat->removeAllTechniques();
+ sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default");
+ return;
+ }
+
+ Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
+ while(techs.hasMoreElements())
+ {
+ Ogre::Technique *tech = techs.getNext();
+ Ogre::Technique::PassIterator passes = tech->getPassIterator();
+ while(passes.hasMoreElements())
+ {
+ Ogre::Pass *pass = passes.getNext();
+ pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
+ Ogre::ColourValue diffuse = pass->getDiffuse();
+ diffuse.a = alpha;
+ pass->setDiffuse(diffuse);
+ pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE);
+ }
+ }
}
}
diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp
index b1abf97af0..28bb81dddf 100644
--- a/apps/openmw/mwrender/npcanimation.hpp
+++ b/apps/openmw/mwrender/npcanimation.hpp
@@ -3,24 +3,35 @@
#include "animation.hpp"
-#include "../mwworld/containerstore.hpp"
+#include "../mwworld/inventorystore.hpp"
namespace ESM
{
struct NPC;
}
-namespace MWWorld
+namespace MWRender
{
- class InventoryStore;
-}
-namespace MWRender
+class SayAnimationValue : public Ogre::ControllerValue<Ogre::Real>
{
+private:
+ MWWorld::Ptr mReference;
+public:
+ SayAnimationValue(MWWorld::Ptr reference) : mReference(reference) {}
+
+ virtual Ogre::Real getValue() const;
+ virtual void setValue(Ogre::Real value)
+ { }
+};
-class NpcAnimation : public Animation
+class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener
{
public:
+ virtual void equipmentChanged() { updateParts(); }
+ virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound);
+
+public:
typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap;
enum ViewMode {
@@ -32,32 +43,17 @@ public:
private:
static const PartBoneMap sPartList;
- int mStateID;
+ bool mListenerDisabled;
// Bounded Parts
- NifOgre::ObjectList mObjectParts[ESM::PRT_Count];
+ NifOgre::ObjectScenePtr mObjectParts[ESM::PRT_Count];
const ESM::NPC *mNpc;
std::string mHeadModel;
std::string mHairModel;
ViewMode mViewMode;
bool mShowWeapons;
-
- float mTimeToChange;
- MWWorld::ContainerStoreIterator mRobe;
- MWWorld::ContainerStoreIterator mHelmet;
- MWWorld::ContainerStoreIterator mShirt;
- MWWorld::ContainerStoreIterator mCuirass;
- MWWorld::ContainerStoreIterator mGreaves;
- MWWorld::ContainerStoreIterator mPauldronL;
- MWWorld::ContainerStoreIterator mPauldronR;
- MWWorld::ContainerStoreIterator mBoots;
- MWWorld::ContainerStoreIterator mPants;
- MWWorld::ContainerStoreIterator mGloveL;
- MWWorld::ContainerStoreIterator mGloveR;
- MWWorld::ContainerStoreIterator mSkirtIter;
- MWWorld::ContainerStoreIterator mWeapon;
- MWWorld::ContainerStoreIterator mShield;
+ bool mShowCarriedLeft;
int mVisibilityFlags;
@@ -66,30 +62,49 @@ private:
Ogre::Vector3 mFirstPersonOffset;
+ Ogre::SharedPtr<SayAnimationValue> mSayAnimationValue;
+
+ float mAlpha;
+
void updateNpcBase();
- NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename);
+ NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename,
+ bool enchantedGlow, Ogre::Vector3* glowColor=NULL);
void removeIndividualPart(ESM::PartReferenceType type);
void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority);
- bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh);
+ bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh,
+ bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
void removePartGroup(int group);
- void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts);
+ void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts,
+ bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
+
+ void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene);
public:
- NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node,
- MWWorld::InventoryStore& inv, int visibilityFlags,
+ /**
+ * @param ptr
+ * @param node
+ * @param visibilityFlags
+ * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports
+ * one listener at a time, so you shouldn't do this if creating several NpcAnimations
+ * for the same Ptr, eg preview dolls for the player.
+ * Those need to be manually rendered anyway.
+ * @param viewMode
+ */
+ NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener = false,
ViewMode viewMode=VM_Normal);
virtual ~NpcAnimation();
virtual Ogre::Vector3 runAnimation(float timepassed);
virtual void showWeapons(bool showWeapon);
+ virtual void showCarriedLeft(bool showa);
void setViewMode(ViewMode viewMode);
- void updateParts(bool forceupdate = false);
+ void updateParts();
/// \brief Applies a translation to the arms and hands.
/// This may be called multiple times before the animation
@@ -98,6 +113,12 @@ public:
/// Rebuilds the NPC, updating their root model, animation sources, and equipment.
void rebuild();
+
+ /// Make the NPC only partially visible
+ virtual void setAlpha(float alpha);
+
+ /// Prepare this animation for being rendered with \a camera (rotates billboard nodes)
+ virtual void preRender (Ogre::Camera* camera);
};
}
diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp
index fd81baf6ed..2673207137 100644
--- a/apps/openmw/mwrender/objects.cpp
+++ b/apps/openmw/mwrender/objects.cpp
@@ -245,11 +245,16 @@ void Objects::disableLights()
it->second->enableLights(false);
}
-void Objects::update(const float dt)
+void Objects::update(float dt, Ogre::Camera* camera)
{
PtrAnimationMap::const_iterator it = mObjects.begin();
for(;it != mObjects.end();it++)
it->second->runAnimation(dt);
+
+ it = mObjects.begin();
+ for(;it != mObjects.end();it++)
+ it->second->preRender(camera);
+
}
void Objects::rebuildStaticGeometry()
@@ -278,6 +283,24 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur)
} else {
node = mCellSceneNodes[newCell];
}
+
node->addChild(cur.getRefData().getBaseNode());
+
+ PtrAnimationMap::iterator iter = mObjects.find(old);
+ if(iter != mObjects.end())
+ {
+ ObjectAnimation *anim = iter->second;
+ mObjects.erase(iter);
+ anim->updatePtr(cur);
+ mObjects[cur] = anim;
+ }
+}
+
+ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr)
+{
+ PtrAnimationMap::const_iterator iter = mObjects.find(ptr);
+ if(iter != mObjects.end())
+ return iter->second;
+ return NULL;
}
diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp
index 22dd1e4f5d..8a50745032 100644
--- a/apps/openmw/mwrender/objects.hpp
+++ b/apps/openmw/mwrender/objects.hpp
@@ -33,6 +33,8 @@ class Objects{
void insertBegin(const MWWorld::Ptr& ptr);
+
+
public:
Objects(OEngine::Render::OgreRenderer &renderer)
: mRenderer(renderer)
@@ -41,10 +43,12 @@ public:
~Objects(){}
void insertModel(const MWWorld::Ptr& ptr, const std::string &model);
+ ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr);
+
void enableLights();
void disableLights();
- void update (const float dt);
+ void update (float dt, Ogre::Camera* camera);
///< per-frame update
Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*);
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
index 93425191dd..f6e69b7260 100644
--- a/apps/openmw/mwrender/renderingmanager.cpp
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -8,10 +8,6 @@
#include <OgreViewport.h>
#include <OgreCamera.h>
#include <OgreTextureManager.h>
-#include <OgreCompositorManager.h>
-#include <OgreCompositorChain.h>
-#include <OgreCompositionTargetPass.h>
-#include <OgreCompositionPass.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreControllerManager.h>
#include <OgreMeshManager.h>
@@ -43,7 +39,6 @@
#include "shadows.hpp"
#include "localmap.hpp"
#include "water.hpp"
-#include "compositors.hpp"
#include "npcanimation.hpp"
#include "externalrendering.hpp"
#include "globalmap.hpp"
@@ -60,14 +55,14 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
MWWorld::Fallback* fallback)
: mRendering(_rend)
, mFallback(fallback)
- , mObjects(mRendering)
- , mActors(mRendering, this)
, mPlayerAnimation(NULL)
, mAmbientMode(0)
, mSunEnabled(0)
, mPhysicsEngine(engine)
, mTerrain(NULL)
{
+ mActors = new MWRender::Actors(mRendering, this);
+ mObjects = new MWRender::Objects(mRendering);
// select best shader mode
bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos);
bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos);
@@ -87,8 +82,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
mRendering.getWindow()->addListener(this);
mRendering.setWindowListener(this);
- mCompositors = new Compositors(mRendering.getViewport());
-
mWater = 0;
// material system
@@ -116,10 +109,13 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
mFactory->loadAllFiles();
- // Set default mipmap level (NB some APIs ignore this)
- // Mipmap generation is currently disabled because it causes issues on Intel/AMD
- //TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General"));
+ // Compressed textures with 0 mip maps are bugged in 1.8, so disable mipmap generator in that case
+ // ( https://ogre3d.atlassian.net/browse/OGRE-259 )
+#if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0)
+ TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General"));
+#else
TextureManager::getSingleton().setDefaultNumMipmaps(0);
+#endif
// Set default texture filtering options
TextureFilterOptions tfo;
@@ -157,13 +153,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false");
sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty<sh::Vector4> (new sh::Vector4(0,0,0,0)));
- applyCompositors();
-
mRootNode = mRendering.getScene()->getRootSceneNode();
mRootNode->createChildSceneNode("player");
- mObjects.setRootNode(mRootNode);
- mActors.setRootNode(mRootNode);
+ mObjects->setRootNode(mRootNode);
+ mActors->setRootNode(mRootNode);
mCamera = new MWRender::Camera(mRendering.getCamera());
@@ -198,9 +192,10 @@ RenderingManager::~RenderingManager ()
delete mTerrain;
delete mLocalMap;
delete mOcclusionQuery;
- delete mCompositors;
delete mWater;
delete mVideoPlayer;
+ delete mActors;
+ delete mObjects;
delete mFactory;
}
@@ -210,10 +205,10 @@ MWRender::SkyManager* RenderingManager::getSkyManager()
}
MWRender::Objects& RenderingManager::getObjects(){
- return mObjects;
+ return *mObjects;
}
MWRender::Actors& RenderingManager::getActors(){
- return mActors;
+ return *mActors;
}
OEngine::Render::Fader* RenderingManager::getFader()
@@ -223,8 +218,8 @@ OEngine::Render::Fader* RenderingManager::getFader()
void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store)
{
- mObjects.removeCell(store);
- mActors.removeCell(store);
+ mObjects->removeCell(store);
+ mActors->removeCell(store);
mDebugging->cellRemoved(store);
}
@@ -240,7 +235,7 @@ void RenderingManager::toggleWater()
void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
{
- mObjects.buildStaticGeometry (*store);
+ mObjects->buildStaticGeometry (*store);
sh::Factory::getInstance().unloadUnreferencedMaterials();
mDebugging->cellAdded(store);
waterAdded(store);
@@ -254,8 +249,8 @@ void RenderingManager::addObject (const MWWorld::Ptr& ptr){
void RenderingManager::removeObject (const MWWorld::Ptr& ptr)
{
- if (!mObjects.deleteObject (ptr))
- mActors.deleteObject (ptr);
+ if (!mObjects->deleteObject (ptr))
+ mActors->deleteObject (ptr);
}
void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position)
@@ -295,9 +290,9 @@ RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &
parent->removeChild(child);
if (MWWorld::Class::get(old).isActor()) {
- mActors.updateObjectCell(old, cur);
+ mActors->updateObjectCell(old, cur);
} else {
- mObjects.updateObjectCell(old, cur);
+ mObjects->updateObjectCell(old, cur);
}
}
@@ -315,7 +310,7 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr)
if(ptr.getRefData().getHandle() == "player")
anim = mPlayerAnimation;
else if(MWWorld::Class::get(ptr).isActor())
- anim = dynamic_cast<NpcAnimation*>(mActors.getAnimation(ptr));
+ anim = dynamic_cast<NpcAnimation*>(mActors->getAnimation(ptr));
if(anim)
{
anim->rebuild();
@@ -380,9 +375,9 @@ void RenderingManager::update (float duration, bool paused)
if(paused)
return;
- mActors.update (duration);
- mObjects.update (duration);
-
+ mActors->update (mRendering.getCamera());
+ mPlayerAnimation->preRender(mRendering.getCamera());
+ mObjects->update (duration, mRendering.getCamera());
mSkyManager->update(duration);
@@ -476,29 +471,21 @@ bool RenderingManager::toggleRenderMode(int mode)
{
if (mRendering.getCamera()->getPolygonMode() == PM_SOLID)
{
- mCompositors->setEnabled(false);
-
mRendering.getCamera()->setPolygonMode(PM_WIREFRAME);
return true;
}
else
{
- mCompositors->setEnabled(true);
-
mRendering.getCamera()->setPolygonMode(PM_SOLID);
return false;
}
}
- else if (mode == MWBase::World::Render_BoundingBoxes)
+ else //if (mode == MWBase::World::Render_BoundingBoxes)
{
bool show = !mRendering.getScene()->getShowBoundingBoxes();
mRendering.getScene()->showBoundingBoxes(show);
return show;
}
- else //if (mode == MWBase::World::Render_Compositors)
- {
- return mCompositors->toggle();
- }
}
void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell)
@@ -657,7 +644,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
{
assert(mTerrain);
- Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell);
+ Ogre::AxisAlignedBox dims = mObjects->getDimensions(cell);
Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center));
@@ -667,7 +654,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
}
else
- mLocalMap->requestMap(cell, mObjects.getDimensions(cell));
+ mLocalMap->requestMap(cell, mObjects->getDimensions(cell));
}
void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell)
@@ -677,13 +664,13 @@ void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell)
void RenderingManager::disableLights(bool sun)
{
- mObjects.disableLights();
+ mObjects->disableLights();
sunDisable(sun);
}
void RenderingManager::enableLights(bool sun)
{
- mObjects.enableLights();
+ mObjects->enableLights();
sunEnable(sun);
}
@@ -743,11 +730,6 @@ Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds)
return Vector4(min_x, min_y, max_x, max_y);
}
-Compositors* RenderingManager::getCompositors()
-{
- return mCompositors;
-}
-
void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings)
{
bool changeRes = false;
@@ -793,7 +775,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec
}
else if (it->second == "shader" && it->first == "Water")
{
- applyCompositors();
sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true");
rebuild = true;
mRendering.getViewport ()->setClearEveryFrame (true);
@@ -859,7 +840,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec
if (rebuild)
{
- mObjects.rebuildStaticGeometry();
+ mObjects->rebuildStaticGeometry();
if (mTerrain)
mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
Settings::Manager::getBool("split", "Shadows"));
@@ -878,29 +859,19 @@ void RenderingManager::setMenuTransparency(float val)
void RenderingManager::windowResized(int x, int y)
{
+ Settings::Manager::setInt("resolution x", "Video", x);
+ Settings::Manager::setInt("resolution y", "Video", y);
mRendering.adjustViewport();
- mCompositors->recreate();
mVideoPlayer->setResolution (x, y);
MWBase::Environment::get().getWindowManager()->windowResized(x,y);
}
-void RenderingManager::applyCompositors()
-{
-}
-
void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches)
{
- if (mCompositors->anyCompositorEnabled())
- {
- mCompositors->countTrianglesBatches(triangles, batches);
- }
- else
- {
- triangles = mRendering.getWindow()->getTriangleCount();
- batches = mRendering.getWindow()->getBatchCount();
- }
+ batches = mRendering.getWindow()->getBatchCount();
+ triangles = mRendering.getWindow()->getTriangleCount();
}
void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr)
@@ -913,18 +884,17 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
{
if(!mPlayerAnimation)
{
- mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(),
- MWWorld::Class::get(ptr).getInventoryStore(ptr),
- RV_Actors);
+ mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
}
else
{
// Reconstruct the NpcAnimation in-place
mPlayerAnimation->~NpcAnimation();
- new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(),
- MWWorld::Class::get(ptr).getInventoryStore(ptr),
- RV_Actors);
+ new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
}
+ // Ensure CustomData -> autoEquip -> animation update
+ ptr.getClass().getInventoryStore(ptr);
+
mCamera->setAnimation(mPlayerAnimation);
mWater->removeEmitter(ptr);
mWater->addEmitter(ptr);
@@ -975,9 +945,14 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend
Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
{
- Animation *anim = mActors.getAnimation(ptr);
+ Animation *anim = mActors->getAnimation(ptr);
+
if(!anim && ptr.getRefData().getHandle() == "player")
anim = mPlayerAnimation;
+
+ if (!anim)
+ anim = mObjects->getAnimation(ptr);
+
return anim;
}
@@ -1051,4 +1026,9 @@ void RenderingManager::enableTerrain(bool enable)
mTerrain->setVisible(false);
}
+float RenderingManager::getCameraDistance() const
+{
+ return mCamera->getCameraDistance();
+}
+
} // namespace
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index 2d08139128..b13e546e83 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -48,7 +48,6 @@ namespace MWRender
class Shadows;
class LocalMap;
class Water;
- class Compositors;
class ExternalRendering;
class GlobalMap;
class VideoPlayer;
@@ -91,12 +90,12 @@ public:
bool vanityRotateCamera(const float *rot);
void setCameraDistance(float dist, bool adjust = false, bool override = true);
+ float getCameraDistance() const;
void setupPlayer(const MWWorld::Ptr &ptr);
void renderPlayer(const MWWorld::Ptr &ptr);
SkyManager* getSkyManager();
- Compositors* getCompositors();
void toggleLight();
bool toggleRenderMode(int mode);
@@ -224,8 +223,6 @@ private:
void setMenuTransparency(float val);
- void applyCompositors();
-
bool mSunEnabled;
MWWorld::Fallback* mFallback;
@@ -242,8 +239,8 @@ private:
OEngine::Render::OgreRenderer &mRendering;
- MWRender::Objects mObjects;
- MWRender::Actors mActors;
+ MWRender::Objects* mObjects;
+ MWRender::Actors* mActors;
MWRender::NpcAnimation *mPlayerAnimation;
@@ -269,8 +266,6 @@ private:
MWRender::Shadows* mShadows;
- MWRender::Compositors* mCompositors;
-
VideoPlayer* mVideoPlayer;
};
diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp
index 03e14dc07d..39f7ccc854 100644
--- a/apps/openmw/mwrender/sky.cpp
+++ b/apps/openmw/mwrender/sky.cpp
@@ -285,10 +285,10 @@ void SkyManager::create()
// Stars
mAtmosphereNight = mRootNode->createChildSceneNode();
- NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif");
- for(size_t i = 0, matidx = 0;i < objects.mEntities.size();i++)
+ NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif");
+ for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++)
{
- Entity* night1_ent = objects.mEntities[i];
+ Entity* night1_ent = objects->mEntities[i];
night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1);
night1_ent->setVisibilityFlags(RV_Sky);
night1_ent->setCastShadows(false);
@@ -307,14 +307,14 @@ void SkyManager::create()
night1_ent->getSubEntity(j)->setMaterialName(matName);
}
}
-
+ mObjects.push_back(objects);
// Atmosphere (day)
mAtmosphereDay = mRootNode->createChildSceneNode();
objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif");
- for(size_t i = 0;i < objects.mEntities.size();i++)
+ for(size_t i = 0;i < objects->mEntities.size();i++)
{
- Entity* atmosphere_ent = objects.mEntities[i];
+ Entity* atmosphere_ent = objects->mEntities[i];
atmosphere_ent->setCastShadows(false);
atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly);
atmosphere_ent->setVisibilityFlags(RV_Sky);
@@ -325,14 +325,14 @@ void SkyManager::create()
// Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions
atmosphere_ent->getMesh()->_setBounds (aabInf);
}
-
+ mObjects.push_back(objects);
// Clouds
SceneNode* clouds_node = mRootNode->createChildSceneNode();
objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif");
- for(size_t i = 0;i < objects.mEntities.size();i++)
+ for(size_t i = 0;i < objects->mEntities.size();i++)
{
- Entity* clouds_ent = objects.mEntities[i];
+ Entity* clouds_ent = objects->mEntities[i];
clouds_ent->setVisibilityFlags(RV_Sky);
clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5);
for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++)
@@ -341,6 +341,7 @@ void SkyManager::create()
// Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions
clouds_ent->getMesh()->_setBounds (aabInf);
}
+ mObjects.push_back(objects);
mCreated = true;
}
diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp
index 3df8846cd4..965907a979 100644
--- a/apps/openmw/mwrender/sky.hpp
+++ b/apps/openmw/mwrender/sky.hpp
@@ -11,6 +11,8 @@
#include <extern/shiny/Main/Factory.hpp>
+#include <components/nifogre/ogrenifloader.hpp>
+
#include "../mwworld/weather.hpp"
@@ -196,6 +198,8 @@ namespace MWRender
Ogre::SceneNode* mAtmosphereDay;
Ogre::SceneNode* mAtmosphereNight;
+ std::vector<NifOgre::ObjectScenePtr> mObjects;
+
// remember some settings so we don't have to apply them again if they didnt change
Ogre::String mClouds;
Ogre::String mNextClouds;
diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp
index ee2b80f731..adf20dc633 100644
--- a/apps/openmw/mwrender/videoplayer.cpp
+++ b/apps/openmw/mwrender/videoplayer.cpp
@@ -949,9 +949,25 @@ void VideoState::init(const std::string& resourceName)
this->format_ctx->pb = ioCtx;
// Open video file
- /// \todo leak here, ffmpeg or valgrind bug ?
+ ///
+ /// format_ctx->pb->buffer must be freed by hand,
+ /// if not, valgrind will show memleak, see:
+ ///
+ /// https://trac.ffmpeg.org/ticket/1357
+ ///
if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL))
{
+ if (this->format_ctx != NULL)
+ {
+ if (this->format_ctx->pb != NULL)
+ {
+ av_free(this->format_ctx->pb->buffer);
+ this->format_ctx->pb->buffer = NULL;
+
+ av_free(this->format_ctx->pb);
+ this->format_ctx->pb = NULL;
+ }
+ }
// "Note that a user-supplied AVFormatContext will be freed on failure."
this->format_ctx = NULL;
av_free(ioCtx);
@@ -989,9 +1005,12 @@ void VideoState::deinit()
this->audioq.cond.notify_one();
this->videoq.cond.notify_one();
- this->parse_thread.join();
- this->video_thread.join();
- this->refresh_thread.join();
+ if (this->parse_thread.joinable())
+ this->parse_thread.join();
+ if (this->video_thread.joinable())
+ this->video_thread.join();
+ if (this->refresh_thread.joinable())
+ this->refresh_thread.join();
if(this->audio_st)
avcodec_close((*this->audio_st)->codec);
@@ -1006,9 +1025,21 @@ void VideoState::deinit()
if(this->format_ctx)
{
- AVIOContext *ioContext = this->format_ctx->pb;
+ ///
+ /// format_ctx->pb->buffer must be freed by hand,
+ /// if not, valgrind will show memleak, see:
+ ///
+ /// https://trac.ffmpeg.org/ticket/1357
+ ///
+ if (this->format_ctx->pb != NULL)
+ {
+ av_free(this->format_ctx->pb->buffer);
+ this->format_ctx->pb->buffer = NULL;
+
+ av_free(this->format_ctx->pb);
+ this->format_ctx->pb = NULL;;
+ }
avformat_close_input(&this->format_ctx);
- av_free(ioContext);
}
}
diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp
index 082551f371..0a4db30e9c 100644
--- a/apps/openmw/mwrender/water.cpp
+++ b/apps/openmw/mwrender/water.cpp
@@ -292,6 +292,7 @@ Water::~Water()
delete mReflection;
delete mRefraction;
+ delete mSimulation;
}
void Water::changeCell(const ESM::Cell* cell)
diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp
index bc15b4980a..481a412977 100644
--- a/apps/openmw/mwrender/water.hpp
+++ b/apps/openmw/mwrender/water.hpp
@@ -133,8 +133,6 @@ namespace MWRender {
RenderingManager* mRendering;
SkyManager* mSky;
- std::string mCompositorName;
-
Ogre::MaterialPtr mMaterial;
bool mUnderwaterEffect;
diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp
index fac44c08f3..1cdbaa008d 100644
--- a/apps/openmw/mwscript/aiextensions.cpp
+++ b/apps/openmw/mwscript/aiextensions.cpp
@@ -17,11 +17,16 @@
#include "../mwmechanics/aitravel.hpp"
#include "../mwmechanics/aiwander.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
#include "interpretercontext.hpp"
#include "ref.hpp"
#include <iostream>
+#include "../mwbase/mechanicsmanager.hpp"
+
namespace MWScript
{
namespace Ai
@@ -364,6 +369,39 @@ namespace MWScript
}
};
+ template<class R>
+ class OpGetLineOfSight : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+
+ MWWorld::Ptr source = R()(runtime);
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true);
+ bool value = false;
+ if(dest != MWWorld::Ptr() )
+ {
+ value = MWBase::Environment::get().getWorld()->getLOS(source,dest);
+ }
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpToggleAI : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getMechanicsManager()->toggleAI();
+ }
+ };
void installOpcodes (Interpreter::Interpreter& interpreter)
{
@@ -389,6 +427,11 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage<ExplicitRef>);
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected<ImplicitRef>);
interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI<ExplicitRef>);
+
interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting<ImplicitRef>(0));
interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting<ExplicitRef>(0));
interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting<ImplicitRef>(1));
diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp
index 316f912dad..f26602f7a7 100644
--- a/apps/openmw/mwscript/cellextensions.cpp
+++ b/apps/openmw/mwscript/cellextensions.cpp
@@ -43,10 +43,13 @@ namespace MWScript
ESM::Position pos;
MWBase::World *world = MWBase::Environment::get().getWorld();
- if (world->findExteriorPosition(cell, pos)) {
+ world->getPlayer().setTeleported(true);
+ if (world->findExteriorPosition(cell, pos))
+ {
world->changeToExteriorCell(pos);
}
- else {
+ else
+ {
// Change to interior even if findInteriorPosition()
// yields false. In this case position will be zero-point.
world->findInteriorPosition(cell, pos);
@@ -68,13 +71,14 @@ namespace MWScript
runtime.pop();
ESM::Position pos;
-
- MWBase::Environment::get().getWorld()->indexToPosition (x, y, pos.pos[0], pos.pos[1], true);
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ world->getPlayer().setTeleported(true);
+ world->indexToPosition (x, y, pos.pos[0], pos.pos[1], true);
pos.pos[2] = 0;
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
- MWBase::Environment::get().getWorld()->changeToExteriorCell (pos);
+ world->changeToExteriorCell (pos);
}
};
diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp
index 2f3ef2d792..53f4c23c97 100644
--- a/apps/openmw/mwscript/containerextensions.cpp
+++ b/apps/openmw/mwscript/containerextensions.cpp
@@ -17,7 +17,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/actionequip.hpp"
@@ -53,26 +52,14 @@ namespace MWScript
if (count == 0)
return;
- MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item);
-
- ref.getPtr().getRefData().setCount (count);
-
- // Configure item's script variables
- std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr());
- if (script != "")
- {
- const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (script);
- ref.getPtr().getRefData().setLocals(*esmscript);
- }
-
- MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr);
+ MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr);
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() )
{
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
std::string msgBox;
- std::string itemName = MWWorld::Class::get(ref.getPtr()).getName(ref.getPtr());
+ std::string itemName = itemPtr.getClass().getName(itemPtr);
if (count == 1)
{
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}");
@@ -136,41 +123,21 @@ namespace MWScript
return;
MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
-
- std::string itemName = "";
- // originalCount holds the total number of items to remove, count holds the remaining number of items to remove
- Interpreter::Type_Integer originalCount = count;
-
- for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end() && count;
- ++iter)
- {
+ std::string itemName;
+ for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter)
if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
- {
- itemName = MWWorld::Class::get(*iter).getName(*iter);
-
- if (iter->getRefData().getCount()<=count)
- {
- count -= iter->getRefData().getCount();
- iter->getRefData().setCount (0);
- }
- else
- {
- iter->getRefData().setCount (iter->getRefData().getCount()-count);
- count = 0;
- }
- }
- }
-
+ itemName = iter->getClass().getName(*iter);
+
+ int numRemoved = store.remove(item, count, ptr);
+
// Spawn a messagebox (only for items removed from player's inventory)
- if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer())
+ if ((numRemoved > 0)
+ && (ptr == MWBase::Environment::get().getWorld()->getPlayer().getPlayer()))
{
// The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory
std::string msgBox;
- int numRemoved = (originalCount - count);
- if (numRemoved == 0)
- return;
-
+
if(numRemoved > 1)
{
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}");
diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt
index 7bbb336999..cf533451c6 100644
--- a/apps/openmw/mwscript/docs/vmformat.txt
+++ b/apps/openmw/mwscript/docs/vmformat.txt
@@ -127,7 +127,6 @@ op 0x200007e-0x2000084: Enable Controls
op 0x2000085-0x200008b: Disable Controls
op 0x200008c: Unlock
op 0x200008d: Unlock, explicit reference
-op 0x200008e: COE
op 0x200008e-0x20000a8: GetSkill
op 0x20000a9-0x20000c3: GetSkill, explicit reference
op 0x20000c4-0x20000de: SetSkill
@@ -354,5 +353,10 @@ op 0x200021e: ShowVarsExplicit
op 0x200021f: ToggleGodMode
op 0x2000220: DisableLevitation
op 0x2000221: EnableLevitation
+op 0x2000222: GetLineOfSight
+op 0x2000223: GetLineOfSightExplicit
+op 0x2000224: ToggleAI
+op 0x2000225: ToggleAIExplicit
+op 0x2000226: COE
-opcodes 0x2000222-0x3ffffff unused
+opcodes 0x2000227-0x3ffffff unused
diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp
index 4ae1136e22..8e2a8af8c4 100644
--- a/apps/openmw/mwscript/miscextensions.cpp
+++ b/apps/openmw/mwscript/miscextensions.cpp
@@ -19,7 +19,6 @@
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
-#include "../mwworld/manualref.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/npcstats.hpp"
@@ -348,14 +347,8 @@ namespace MWScript
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
store.get<ESM::Creature>().find(creature); // This line throws an exception if it can't find the creature
- MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem);
-
- ref.getPtr().getRefData().setCount (1);
-
- ref.getPtr().getCellRef().mSoul = creature;
-
- MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr);
-
+ MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr);
+ item.getCellRef().mSoul = creature;
}
};
@@ -374,18 +367,7 @@ namespace MWScript
MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
-
- for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter)
- {
- if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul))
- {
- if (iter->getRefData().getCount() <= 1)
- iter->getRefData().setCount (0);
- else
- iter->getRefData().setCount (iter->getRefData().getCount() - 1);
- break;
- }
- }
+ store.remove(soul, 1, ptr);
}
};
@@ -415,24 +397,18 @@ namespace MWScript
MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
+ int toRemove = amount;
for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter)
{
if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
{
- if(iter->getRefData().getCount() <= amount)
- {
- MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter);
- iter->getRefData().setCount(0);
- }
- else
- {
- int original = iter->getRefData().getCount();
- iter->getRefData().setCount(amount);
- MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter);
- iter->getRefData().setCount(original - amount);
- }
+ int removed = store.remove(*iter, toRemove, ptr);
+ MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed);
- break;
+ toRemove -= removed;
+
+ if (toRemove <= 0)
+ break;
}
}
}
@@ -458,20 +434,8 @@ namespace MWScript
{
if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul))
{
-
- if(iter->getRefData().getCount() <= 1)
- {
- MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter);
- iter->getRefData().setCount(0);
- }
- else
- {
- int original = iter->getRefData().getCount();
- iter->getRefData().setCount(1);
- MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter);
- iter->getRefData().setCount(original - 1);
- }
-
+ MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1);
+ store.remove(*iter, 1, ptr);
break;
}
}
@@ -541,12 +505,7 @@ namespace MWScript
runtime.pop();
if (parameter == 1)
- {
- if (ptr.isInCell())
- MWBase::Environment::get().getWorld()->deleteObject (ptr);
- else
- ptr.getRefData().setCount(0);
- }
+ MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
};
diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp
index 603515ff4a..45e5b0f187 100644
--- a/apps/openmw/mwscript/statsextensions.cpp
+++ b/apps/openmw/mwscript/statsextensions.cpp
@@ -124,10 +124,9 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
- MWWorld::Class::get(ptr)
- .getCreatureStats(ptr)
- .getAttribute(mIndex)
- .setModified (value, 0);
+ MWMechanics::Stat<int> attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex);
+ attribute.setModified (value, 0);
+ ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute);
}
};
@@ -147,16 +146,16 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
+ MWMechanics::Stat<int> attribute = MWWorld::Class::get(ptr)
+ .getCreatureStats(ptr)
+ .getAttribute(mIndex);
+
value +=
- MWWorld::Class::get(ptr)
- .getCreatureStats(ptr)
- .getAttribute(mIndex)
- .getModified();
+ attribute.getModified();
- MWWorld::Class::get(ptr)
- .getCreatureStats(ptr)
- .getAttribute(mIndex)
+ attribute
.setModified (value, 0, 100);
+ ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute);
}
};
@@ -209,6 +208,7 @@ namespace MWScript
.getDynamic (mIndex));
stat.setModified (value, 0);
+ stat.setCurrent(value);
MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat);
}
@@ -355,8 +355,6 @@ namespace MWScript
if (newLevel<0)
newLevel = 0;
- else if (newLevel>100)
- newLevel = 100;
progress = (progress / stats.getSkillGain (mIndex, class_, -1, level))
* stats.getSkillGain (mIndex, class_, -1, newLevel);
diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp
index 6246daee22..e441809778 100644
--- a/apps/openmw/mwscript/transformationextensions.cpp
+++ b/apps/openmw/mwscript/transformationextensions.cpp
@@ -208,6 +208,14 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
+ if (!ptr.isInCell())
+ return;
+
+ if (ptr.getRefData().getHandle() == "player")
+ {
+ MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true);
+ }
+
std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Float pos = runtime[0].mFloat;
@@ -272,6 +280,14 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
+ if (!ptr.isInCell())
+ return;
+
+ if (ptr.getRefData().getHandle() == "player")
+ {
+ MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true);
+ }
+
Interpreter::Type_Float x = runtime[0].mFloat;
runtime.pop();
Interpreter::Type_Float y = runtime[0].mFloat;
@@ -329,6 +345,14 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
+ if (!ptr.isInCell())
+ return;
+
+ if (ptr.getRefData().getHandle() == "player")
+ {
+ MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true);
+ }
+
Interpreter::Type_Float x = runtime[0].mFloat;
runtime.pop();
Interpreter::Type_Float y = runtime[0].mFloat;
@@ -450,62 +474,16 @@ namespace MWScript
}
};
- template<class R>
- class OpPlaceAtPc : public Interpreter::Opcode0
- {
- public:
-
- virtual void execute (Interpreter::Runtime& runtime)
- {
- std::string itemID = runtime.getStringLiteral (runtime[0].mInteger);
- runtime.pop();
-
- Interpreter::Type_Integer count = runtime[0].mInteger;
- runtime.pop();
- Interpreter::Type_Float distance = runtime[0].mFloat;
- runtime.pop();
- Interpreter::Type_Integer direction = runtime[0].mInteger;
- runtime.pop();
-
- if (count<0)
- throw std::runtime_error ("count must be non-negative");
-
- // no-op
- if (count == 0)
- return;
-
- ESM::Position ipos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition();
- Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]);
- Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
- if(direction == 0) pos = pos + distance*rot.yAxis();
- else if(direction == 1) pos = pos - distance*rot.yAxis();
- else if(direction == 2) pos = pos - distance*rot.xAxis();
- else if(direction == 3) pos = pos + distance*rot.xAxis();
- else throw std::runtime_error ("direction must be 0,1,2 or 3");
-
- ipos.pos[0] = pos.x;
- ipos.pos[1] = pos.y;
- ipos.pos[2] = pos.z;
- ipos.rot[0] = 0;
- ipos.rot[1] = 0;
- ipos.rot[2] = 0;
-
- MWWorld::CellStore* store = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
- MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
- ref.getPtr().getCellRef().mPos = ipos;
- ref.getPtr().getRefData().setCount(count);
- MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos);
- }
- };
-
- template<class R>
- class OpPlaceAtMe : public Interpreter::Opcode0
+ template<class R, bool pc>
+ class OpPlaceAt : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
- MWWorld::Ptr me = R()(runtime);
+ MWWorld::Ptr actor = pc
+ ? MWBase::Environment::get().getWorld()->getPlayer().getPlayer()
+ : R()(runtime);
std::string itemID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
@@ -524,7 +502,7 @@ namespace MWScript
if (count == 0)
return;
- ESM::Position ipos = me.getRefData().getPosition();
+ ESM::Position ipos = actor.getRefData().getPosition();
Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]);
Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
if(direction == 0) pos = pos + distance*rot.yAxis();
@@ -536,16 +514,26 @@ namespace MWScript
ipos.pos[0] = pos.x;
ipos.pos[1] = pos.y;
ipos.pos[2] = pos.z;
- ipos.rot[0] = 0;
- ipos.rot[1] = 0;
- ipos.rot[2] = 0;
- MWWorld::CellStore* store = me.getCell();
- MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
+ if (actor.getClass().isActor())
+ {
+ // TODO: should this depend on the 'direction' parameter?
+ ipos.rot[0] = 0;
+ ipos.rot[1] = 0;
+ ipos.rot[2] = 0;
+ }
+ else
+ {
+ ipos.rot[0] = actor.getRefData().getPosition().rot[0];
+ ipos.rot[1] = actor.getRefData().getPosition().rot[1];
+ ipos.rot[2] = actor.getRefData().getPosition().rot[2];
+ }
+ // create item
+ MWWorld::CellStore* store = actor.getCell();
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count);
ref.getPtr().getCellRef().mPos = ipos;
- ref.getPtr().getRefData().setCount(count);
- MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos);
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos);
}
};
@@ -629,6 +617,10 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr ptr = R()(runtime);
+
+ if (!ptr.isInCell())
+ return;
+
ptr.getRefData().getLocalRotation().rot[0] = 0;
ptr.getRefData().getLocalRotation().rot[1] = 0;
ptr.getRefData().getLocalRotation().rot[2] = 0;
@@ -648,6 +640,9 @@ namespace MWScript
{
const MWWorld::Ptr& ptr = R()(runtime);
+ if (!ptr.isInCell())
+ return;
+
std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
@@ -683,6 +678,9 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
+ if (!ptr.isInCell())
+ return;
+
std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
@@ -730,9 +728,9 @@ namespace MWScript
interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell<ExplicitRef>);
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell<ImplicitRef>);
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem<ImplicitRef>);
- interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAtPc<ImplicitRef>);
- interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAtMe<ImplicitRef>);
- interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAtMe<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt<ImplicitRef, true>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt<ImplicitRef, false>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt<ExplicitRef, false>);
interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale<ImplicitRef>);
interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale<ExplicitRef>);
interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate<ImplicitRef>);
diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp
index d5c382a419..c836974425 100644
--- a/apps/openmw/mwsound/ffmpeg_decoder.cpp
+++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp
@@ -158,6 +158,16 @@ void FFmpeg_Decoder::open(const std::string &fname)
mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek);
if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0)
{
+ if (mFormatCtx->pb != NULL)
+ {
+ if (mFormatCtx->pb->buffer != NULL)
+ {
+ av_free(mFormatCtx->pb->buffer);
+ mFormatCtx->pb->buffer = NULL;
+ }
+ av_free(mFormatCtx->pb);
+ mFormatCtx->pb = NULL;
+ }
avformat_free_context(mFormatCtx);
mFormatCtx = NULL;
fail("Failed to allocate input stream");
@@ -195,6 +205,14 @@ void FFmpeg_Decoder::open(const std::string &fname)
}
catch(std::exception &e)
{
+ if (mFormatCtx->pb->buffer != NULL)
+ {
+ av_free(mFormatCtx->pb->buffer);
+ mFormatCtx->pb->buffer = NULL;
+ }
+ av_free(mFormatCtx->pb);
+ mFormatCtx->pb = NULL;
+
avformat_close_input(&mFormatCtx);
throw;
}
@@ -211,9 +229,22 @@ void FFmpeg_Decoder::close()
if(mFormatCtx)
{
- AVIOContext* context = mFormatCtx->pb;
+ if (mFormatCtx->pb != NULL)
+ {
+ // mFormatCtx->pb->buffer must be freed by hand,
+ // if not, valgrind will show memleak, see:
+ //
+ // https://trac.ffmpeg.org/ticket/1357
+ //
+ if (mFormatCtx->pb->buffer != NULL)
+ {
+ av_free(mFormatCtx->pb->buffer);
+ mFormatCtx->pb->buffer = NULL;
+ }
+ av_free(mFormatCtx->pb);
+ mFormatCtx->pb = NULL;
+ }
avformat_close_input(&mFormatCtx);
- av_free(context);
}
mDataStream.setNull();
diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp
index 372be83938..2e52739ac5 100644
--- a/apps/openmw/mwsound/soundmanagerimp.cpp
+++ b/apps/openmw/mwsound/soundmanagerimp.cpp
@@ -252,7 +252,7 @@ namespace MWSound
float basevol = volumeFromType(Play_TypeVoice);
std::string filePath = "Sound/"+filename;
const ESM::Position &pos = ptr.getRefData().getPosition();
- const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]);
+ const Ogre::Vector3 objpos(pos.pos);
MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f,
20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0);
@@ -354,7 +354,7 @@ namespace MWSound
float min, max;
std::string file = lookup(soundId, volume, min, max);
const ESM::Position &pos = ptr.getRefData().getPosition();
- const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]);
+ const Ogre::Vector3 objpos(pos.pos);
sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset);
if((mode&Play_NoTrack))
@@ -584,7 +584,7 @@ namespace MWSound
if(!ptr.isEmpty())
{
const ESM::Position &pos = ptr.getRefData().getPosition();
- const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]);
+ const Ogre::Vector3 objpos(pos.pos);
snditer->first->setPosition(objpos);
}
//update fade out
diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp
index 3353ae0eed..669a190675 100644
--- a/apps/openmw/mwworld/actionapply.hpp
+++ b/apps/openmw/mwworld/actionapply.hpp
@@ -1,4 +1,3 @@
-
#ifndef GAME_MWWORLD_ACTIONAPPLY_H
#define GAME_MWWORLD_ACTIONAPPLY_H
diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp
index 63efff738e..f5d7e26361 100644
--- a/apps/openmw/mwworld/actioneat.cpp
+++ b/apps/openmw/mwworld/actioneat.cpp
@@ -3,45 +3,25 @@
#include <cstdlib>
-#include <components/esm/loadskil.hpp>
-
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwmechanics/creaturestats.hpp"
-#include "../mwmechanics/npcstats.hpp"
+#include "../mwworld/containerstore.hpp"
-#include "esmstore.hpp"
#include "class.hpp"
namespace MWWorld
{
void ActionEat::executeImp (const Ptr& actor)
{
- // remove used item
- getTarget().getRefData().setCount (getTarget().getRefData().getCount()-1);
-
- // check for success
- const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
- MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
-
- float x =
- (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
- 0.2 * creatureStats.getAttribute (1).getModified()
- + 0.1 * creatureStats.getAttribute (7).getModified())
- * creatureStats.getFatigueTerm();
-
- if (x>=100*static_cast<float> (std::rand()) / RAND_MAX)
- {
- // apply to actor
- std::string id = Class::get (getTarget()).getId (getTarget());
+ // remove used item (assume the item is present in inventory)
+ getTarget().getContainerStore()->remove(getTarget(), 1, actor);
+
+ // apply to actor
+ std::string id = Class::get (getTarget()).getId (getTarget());
- Class::get (actor).apply (actor, id, actor);
- // we ignore the result here. Skill increases no matter if the ingredient did something or not.
-
- // increase skill
+ if (Class::get (actor).apply (actor, id, actor))
Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1);
- }
}
ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {}
diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp
index 0f1a85ddaa..0d091e7425 100644
--- a/apps/openmw/mwworld/actionequip.cpp
+++ b/apps/openmw/mwworld/actionequip.cpp
@@ -32,15 +32,15 @@ namespace MWWorld
case 0:
return;
case 2:
- invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end());
+ invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor);
break;
case 3:
- invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end());
+ invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor);
break;
}
// slots that this item can be equipped in
- std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget());
+ std::pair<std::vector<int>, bool> slots_ = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget());
// retrieve ContainerStoreIterator to the item
MWWorld::ContainerStoreIterator it = invStore.begin();
@@ -57,14 +57,17 @@ namespace MWWorld
bool equipped = false;
// equip the item in the first free slot
- for (std::vector<int>::const_iterator slot=slots.first.begin();
- slot!=slots.first.end(); ++slot)
+ for (std::vector<int>::const_iterator slot=slots_.first.begin();
+ slot!=slots_.first.end(); ++slot)
{
+ // if the item is equipped already, nothing to do
+ if (invStore.getSlot(*slot) == it)
+ return;
// if all slots are occupied, replace the last slot
- if (slot == --slots.first.end())
+ if (slot == --slots_.first.end())
{
- invStore.equip(*slot, it);
+ invStore.equip(*slot, it, actor);
equipped = true;
break;
}
@@ -72,7 +75,7 @@ namespace MWWorld
if (invStore.getSlot(*slot) == invStore.end())
{
// slot is not occupied
- invStore.equip(*slot, it);
+ invStore.equip(*slot, it, actor);
equipped = true;
break;
}
diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp
index ae5ffc3b90..773fde81e3 100644
--- a/apps/openmw/mwworld/actionteleport.cpp
+++ b/apps/openmw/mwworld/actionteleport.cpp
@@ -3,6 +3,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
+#include "player.hpp"
namespace MWWorld
{
@@ -14,9 +15,12 @@ namespace MWWorld
void ActionTeleport::executeImp (const Ptr& actor)
{
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ world->getPlayer().setTeleported(true);
+
if (mCellName.empty())
- MWBase::Environment::get().getWorld()->changeToExteriorCell (mPosition);
+ world->changeToExteriorCell (mPosition);
else
- MWBase::Environment::get().getWorld()->changeToInteriorCell (mCellName, mPosition);
+ world->changeToInteriorCell (mCellName, mPosition);
}
}
diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp
new file mode 100644
index 0000000000..d723b98239
--- /dev/null
+++ b/apps/openmw/mwworld/actiontrap.cpp
@@ -0,0 +1,16 @@
+#include "actiontrap.hpp"
+
+#include "../mwmechanics/spellcasting.hpp"
+
+namespace MWWorld
+{
+
+ void ActionTrap::executeImp(const Ptr &actor)
+ {
+ MWMechanics::CastSpell cast(mTrapSource, actor);
+ cast.cast(mSpellId);
+
+ mTrapSource.getCellRef().mTrap = "";
+ }
+
+}
diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp
new file mode 100644
index 0000000000..4c2f4139f7
--- /dev/null
+++ b/apps/openmw/mwworld/actiontrap.hpp
@@ -0,0 +1,29 @@
+#ifndef GAME_MWWORLD_ACTIONTRAP_H
+#define GAME_MWWORLD_ACTIONTRAP_H
+
+#include <string>
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionTrap : public Action
+ {
+ std::string mSpellId;
+ MWWorld::Ptr mTrapSource;
+
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ /// @param spellId
+ /// @param actor Actor that activated the trap
+ /// @param trapSource
+ ActionTrap (const Ptr& actor, const std::string& spellId, const Ptr& trapSource)
+ : Action(false, actor), mSpellId(spellId), mTrapSource(trapSource) {}
+ };
+}
+
+
+#endif
diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellfunctors.hpp
index 4b1f70096a..5115fa02db 100644
--- a/apps/openmw/mwworld/cellfunctors.hpp
+++ b/apps/openmw/mwworld/cellfunctors.hpp
@@ -4,7 +4,7 @@
#include <vector>
#include <string>
-#include "refdata.hpp"
+#include "ptr.hpp"
namespace ESM
{
@@ -18,13 +18,13 @@ namespace MWWorld
{
std::vector<Ogre::SceneNode*> mHandles;
- bool operator() (ESM::CellRef& ref, RefData& data)
+ bool operator() (MWWorld::Ptr ptr)
{
- Ogre::SceneNode* handle = data.getBaseNode();
+ Ogre::SceneNode* handle = ptr.getRefData().getBaseNode();
if (handle)
mHandles.push_back (handle);
- data.setBaseNode(0);
+ ptr.getRefData().setBaseNode(0);
return true;
}
};
diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp
index 37c4b6a3f4..865c0d01fc 100644
--- a/apps/openmw/mwworld/cells.cpp
+++ b/apps/openmw/mwworld/cells.cpp
@@ -129,9 +129,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& ce
if (cell.mState==Ptr::CellStore::State_Preloaded)
{
- std::string lowerCase = Misc::StringUtils::lowerCase(name);
-
- if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), lowerCase))
+ if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), name))
{
cell.load (mStore, mReader);
}
@@ -261,3 +259,15 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
// giving up
return Ptr();
}
+
+void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector<MWWorld::Ptr> &out)
+{
+ for (std::map<std::pair<int, int>, Ptr::CellStore>::iterator iter = mExteriors.begin();
+ iter!=mExteriors.end(); ++iter)
+ {
+ Ptr ptr = getPtrAndCache (name, iter->second);
+ if (!ptr.isEmpty())
+ out.push_back(ptr);
+ }
+
+}
diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp
index 0c51cf4520..31de2f60e8 100644
--- a/apps/openmw/mwworld/cells.hpp
+++ b/apps/openmw/mwworld/cells.hpp
@@ -47,8 +47,15 @@ namespace MWWorld
Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false);
///< \param searchInContainers Only affect loaded cells.
+ /// @note name must be lower case
+ /// @note name must be lower case
Ptr getPtr (const std::string& name);
+
+ /// Get all Ptrs referencing \a name in exterior cells
+ /// @note Due to the current implementation of getPtr this only supports one Ptr per cell.
+ /// @note name must be lower case
+ void getExteriorPtrs (const std::string& name, std::vector<MWWorld::Ptr>& out);
};
}
diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp
index bcbc5e415a..8731c42dc6 100644
--- a/apps/openmw/mwworld/cellstore.hpp
+++ b/apps/openmw/mwworld/cellstore.hpp
@@ -140,9 +140,12 @@ namespace MWWorld
{
for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end();
++iter)
- if (!functor (iter->mRef, iter->mData))
+ {
+ if (!iter->mData.getCount())
+ continue;
+ if (!functor (MWWorld::Ptr(&*iter, this)))
return false;
-
+ }
return true;
}
diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp
index d3d1aff49b..ffe81a4ac8 100644
--- a/apps/openmw/mwworld/class.cpp
+++ b/apps/openmw/mwworld/class.cpp
@@ -139,7 +139,7 @@ namespace MWWorld
float Class::getRemainingUsageTime (const Ptr& ptr) const
{
- throw std::runtime_error ("class does not support time-based uses");
+ return -1;
}
std::string Class::getScript (const Ptr& ptr) const
diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp
index 2db293e688..d737c18a22 100644
--- a/apps/openmw/mwworld/class.hpp
+++ b/apps/openmw/mwworld/class.hpp
@@ -162,7 +162,7 @@ namespace MWWorld
virtual float getRemainingUsageTime (const Ptr& ptr) const;
///< Returns the remaining duration of the object, such as an equippable light
- /// source. (default implementation: throw an exception)
+ /// source. (default implementation: -1, i.e. infinite)
virtual std::string getScript (const Ptr& ptr) const;
///< Return name of the script attached to ptr (default implementation: return an empty
@@ -290,6 +290,8 @@ namespace MWWorld
virtual bool isPersistent (const MWWorld::Ptr& ptr) const;
+ virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; }
+
virtual Ptr
copyToCell(const Ptr &ptr, CellStore &cell) const;
diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp
index c6768f5fdd..686e790a38 100644
--- a/apps/openmw/mwworld/containerstore.cpp
+++ b/apps/openmw/mwworld/containerstore.cpp
@@ -63,7 +63,7 @@ namespace
}
}
-MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {}
+MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {}
MWWorld::ContainerStore::~ContainerStore() {}
@@ -77,22 +77,53 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
return ContainerStoreIterator (this);
}
+void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container)
+{
+ if (ptr.getRefData().getCount() <= 1)
+ return;
+ addNewStack(ptr)->getRefData().setCount(ptr.getRefData().getCount()-1);
+ remove(ptr, ptr.getRefData().getCount()-1, container);
+}
+
bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
{
- /// \todo add current enchantment charge here when it is implemented
- if ( Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID)
- && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks
- && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier)
+ const MWWorld::Class& cls1 = MWWorld::Class::get(ptr1);
+ const MWWorld::Class& cls2 = MWWorld::Class::get(ptr2);
+
+ if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID))
+ return false;
+
+ // If it has an enchantment, don't stack when some of the charge is already used
+ if (!ptr1.getClass().getEnchantment(ptr1).empty())
+ {
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
+ ptr1.getClass().getEnchantment(ptr1));
+ float maxCharge = enchantment->mData.mCharge;
+ float enchantCharge1 = ptr1.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr1.getCellRef().mEnchantmentCharge;
+ float enchantCharge2 = ptr2.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr2.getCellRef().mEnchantmentCharge;
+ if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge)
+ return false;
+ }
+
+ return ptr1 != ptr2 // an item never stacks onto itself
&& ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner
&& ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul
- // item that is already partly used up never stacks
- && (!MWWorld::Class::get(ptr1).hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1
- || MWWorld::Class::get(ptr1).getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge)
- && (!MWWorld::Class::get(ptr2).hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1
- || MWWorld::Class::get(ptr2).getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge))
- return true;
- return false;
+ && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2)
+
+ && cls1.getScript(ptr1) == cls2.getScript(ptr2)
+
+ // item that is already partly used up never stacks
+ && (!cls1.hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1
+ || cls1.getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge)
+ && (!cls2.hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1
+ || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge);
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr)
+{
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count);
+ return add(ref.getPtr(), actorPtr);
}
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr)
@@ -100,6 +131,15 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
MWWorld::ContainerStoreIterator it = addImp(itemPtr);
MWWorld::Ptr item = *it;
+ // we may have copied an item from the world, so reset a few things first
+ item.getRefData().setBaseNode(NULL);
+ item.getCellRef().mPos.rot[0] = 0;
+ item.getCellRef().mPos.rot[1] = 0;
+ item.getCellRef().mPos.rot[2] = 0;
+ item.getCellRef().mPos.pos[0] = 0;
+ item.getCellRef().mPos.pos[1] = 0;
+ item.getCellRef().mPos.pos[2] = 0;
+
std::string script = MWWorld::Class::get(item).getScript(item);
if(script != "")
{
@@ -140,22 +180,20 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
|| Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100"))
{
- MWWorld::ManualRef ref(esmStore, "Gold_001");
-
int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount();
- ref.getPtr().getRefData().setCount(count);
for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter)
{
if (Misc::StringUtils::ciEqual((*iter).get<ESM::Miscellaneous>()->mRef.mRefID, "gold_001"))
{
- (*iter).getRefData().setCount( (*iter).getRefData().getCount() + count);
+ iter->getRefData().setCount(iter->getRefData().getCount() + count);
flagAsModified();
return iter;
}
}
- return addImpl(ref.getPtr());
+ MWWorld::ManualRef ref(esmStore, "Gold_001", count);
+ return addNewStack(ref.getPtr());
}
// determine whether to stack or not
@@ -171,10 +209,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
}
}
// if we got here, this means no stacking
- return addImpl(ptr);
+ return addNewStack(ptr);
}
-MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr)
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr)
{
ContainerStoreIterator it = begin();
@@ -198,6 +236,44 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr
return it;
}
+int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor)
+{
+ int toRemove = count;
+
+ for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter)
+ if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, itemId))
+ toRemove -= remove(*iter, toRemove, actor);
+
+ flagAsModified();
+
+ // number of removed items
+ return count - toRemove;
+}
+
+int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor)
+{
+ assert(this == item.getContainerStore());
+
+ int toRemove = count;
+ RefData& itemRef = item.getRefData();
+
+ if (itemRef.getCount() <= toRemove)
+ {
+ toRemove -= itemRef.getCount();
+ itemRef.setCount(0);
+ }
+ else
+ {
+ itemRef.setCount(itemRef.getCount() - toRemove);
+ toRemove = 0;
+ }
+
+ flagAsModified();
+
+ // number of removed items
+ return count - toRemove;
+}
+
void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store)
{
for (std::vector<ESM::ContItem>::const_iterator iter (items.mList.begin()); iter!=items.mList.end();
@@ -216,7 +292,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
try
{
- ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id);
+ ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name())
{
@@ -266,7 +342,6 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
}
else
{
- ref.getPtr().getRefData().setCount (count);
ref.getPtr().getCellRef().mOwner = owner;
addImp (ref.getPtr());
}
@@ -289,15 +364,9 @@ void MWWorld::ContainerStore::clear()
void MWWorld::ContainerStore::flagAsModified()
{
- ++mStateId;
mWeightUpToDate = false;
}
-int MWWorld::ContainerStore::getStateId() const
-{
- return mStateId;
-}
-
float MWWorld::ContainerStore::getWeight() const
{
if (!mWeightUpToDate)
diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp
index 9a11f1603b..b34c710063 100644
--- a/apps/openmw/mwworld/containerstore.hpp
+++ b/apps/openmw/mwworld/containerstore.hpp
@@ -49,7 +49,6 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Probe> probes;
MWWorld::CellRefList<ESM::Repair> repairs;
MWWorld::CellRefList<ESM::Weapon> weapons;
- int mStateId;
mutable float mCachedWeight;
mutable bool mWeightUpToDate;
ContainerStoreIterator addImp (const Ptr& ptr);
@@ -75,9 +74,27 @@ namespace MWWorld
///
/// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item.
+ ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr);
+ ///< Utility to construct a ManualRef and call add(ptr, actorPtr)
+
+ int remove(const std::string& itemId, int count, const Ptr& actor);
+ ///< Remove \a count item(s) designated by \a itemId from this container.
+ ///
+ /// @return the number of items actually removed
+
+ virtual int remove(const Ptr& item, int count, const Ptr& actor);
+ ///< Remove \a count item(s) designated by \a item from this inventory.
+ ///
+ /// @return the number of items actually removed
+
+ void unstack (const Ptr& ptr, const Ptr& container);
+ ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1).
+
protected:
- ContainerStoreIterator addImpl (const Ptr& ptr);
- ///< Add the item to this container (no stacking)
+ ContainerStoreIterator addNewStack (const Ptr& ptr);
+ ///< Add the item to this container (do not try to stack it onto existing items)
+
+ virtual void flagAsModified();
public:
@@ -90,15 +107,6 @@ namespace MWWorld
void clear();
///< Empty container.
- virtual void flagAsModified();
- ///< \attention This function is internal to the world model and should not be called from
- /// outside.
-
- int getStateId() const;
- ///< This ID is changed every time the container is modified or items in the container
- /// are accessed in a way that may be used to modify the item.
- /// \note This method of change-tracking will ocasionally yield false positives.
-
float getWeight() const;
///< Return total weight of the items contained in *this.
diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp
index ab88787175..f1bff11a28 100644
--- a/apps/openmw/mwworld/esmstore.cpp
+++ b/apps/openmw/mwworld/esmstore.cpp
@@ -70,8 +70,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
if (it == mStores.end()) {
if (n.val == ESM::REC_INFO) {
+ std::string id = esm.getHNOString("INAM");
if (dialogue) {
dialogue->mInfo.push_back(ESM::DialInfo());
+ dialogue->mInfo.back().mId = id;
dialogue->mInfo.back().load(esm);
} else {
std::cerr << "error: info record without dialog" << std::endl;
diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp
index 98cb6d347b..2c7c05317d 100644
--- a/apps/openmw/mwworld/inventorystore.cpp
+++ b/apps/openmw/mwworld/inventorystore.cpp
@@ -8,8 +8,12 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/spellcasting.hpp"
+
#include "esmstore.hpp"
#include "class.hpp"
@@ -32,14 +36,17 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store)
}
}
-void MWWorld::InventoryStore::initSlots (TSlots& slots)
+void MWWorld::InventoryStore::initSlots (TSlots& slots_)
{
for (int i=0; i<Slots; ++i)
- slots.push_back (end());
+ slots_.push_back (end());
}
-MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false)
- , mSelectedEnchantItem(end())
+MWWorld::InventoryStore::InventoryStore()
+ : mSelectedEnchantItem(end())
+ , mUpdatesEnabled (true)
+ , mFirstAutoEquip(true)
+ , mListener(NULL)
{
initSlots (mSlots);
}
@@ -47,17 +54,21 @@ MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false)
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
: ContainerStore (store)
, mSelectedEnchantItem(end())
+ , mListener(NULL)
+ , mUpdatesEnabled(true)
{
mMagicEffects = store.mMagicEffects;
- mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
+ mFirstAutoEquip = store.mFirstAutoEquip;
mSelectedEnchantItem = store.mSelectedEnchantItem;
+ mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
copySlots (store);
}
MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
{
mMagicEffects = store.mMagicEffects;
- mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
+ mFirstAutoEquip = store.mFirstAutoEquip;
+ mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
ContainerStore::operator= (store);
mSlots.clear();
copySlots (store);
@@ -70,77 +81,66 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr,
// Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
if ((actorPtr.getRefData().getHandle() != "player")
- && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf()))
+ && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())
+ && !actorPtr.getClass().getCreatureStats(actorPtr).isDead())
{
std::string type = itemPtr.getTypeName();
if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
autoEquip(actorPtr);
}
+ updateRechargingItems();
+
return retVal;
}
-void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator)
+void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor)
{
+ if (iterator == end())
+ throw std::runtime_error ("can't equip end() iterator, use unequip function instead");
+
if (slot<0 || slot>=static_cast<int> (mSlots.size()))
throw std::runtime_error ("slot number out of range");
if (iterator.getContainerStore()!=this)
throw std::runtime_error ("attempt to equip an item that is not in the inventory");
- std::pair<std::vector<int>, bool> slots;
- if (iterator!=end())
- {
- slots = Class::get (*iterator).getEquipmentSlots (*iterator);
+ std::pair<std::vector<int>, bool> slots_;
- if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end())
- throw std::runtime_error ("invalid slot");
- }
+ slots_ = Class::get (*iterator).getEquipmentSlots (*iterator);
+
+ if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end())
+ throw std::runtime_error ("invalid slot");
- // restack item previously in this slot (if required)
if (mSlots[slot] != end())
- {
- for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
- {
- if (stacks(*iter, *mSlots[slot]))
- {
- iter->getRefData().setCount( iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount() );
- mSlots[slot]->getRefData().setCount(0);
- break;
- }
- }
- }
+ unequipSlot(slot, actor);
// unstack item pointed to by iterator if required
- if (iterator!=end() && !slots.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped
+ if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped
{
// add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1
int count = iterator->getRefData().getCount();
iterator->getRefData().setCount(count-1);
- addImpl(*iterator);
+ addNewStack(*iterator);
iterator->getRefData().setCount(1);
}
mSlots[slot] = iterator;
flagAsModified();
+
+ fireEquipmentChangedEvent();
+ updateMagicEffects(actor);
+
+ // Update HUD icon for player weapon
+ if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
+ MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*getSlot(slot));
}
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
{
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
- {
- MWWorld::ContainerStoreIterator it = getSlot(slot);
- if (it != end())
- {
- equip(slot, end());
- std::string script = MWWorld::Class::get(*it).getScript(*it);
-
- // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
- if((actor.getRefData().getHandle() == "player") && (script != ""))
- (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
- }
- }
+ unequipSlot(slot, actor);
}
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
@@ -153,40 +153,73 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
if (mSlots[slot]->getRefData().getCount()<1)
{
- // object has been deleted
- mSlots[slot] = end();
- return end();
+ // Object has been deleted
+ // This should no longer happen, since the new remove function will unequip first
+ throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
}
return mSlots[slot];
}
-void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
+void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
{
- const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc);
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
+ const MWMechanics::NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
- TSlots slots;
- initSlots (slots);
+ TSlots slots_;
+ initSlots (slots_);
+
+ // Disable model update during auto-equip
+ mUpdatesEnabled = false;
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
{
Ptr test = *iter;
+
+ // Don't autoEquip lights
+ if (test.getTypeName() == typeid(ESM::Light).name())
+ {
+ continue;
+ }
+
int testSkill = MWWorld::Class::get (test).getEquipmentSkill (test);
std::pair<std::vector<int>, bool> itemsSlots =
MWWorld::Class::get (*iter).getEquipmentSlots (*iter);
+ // Skip items that have *only* harmful permanent effects
+ if (!test.getClass().getEnchantment(test).empty())
+ {
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Enchantment* enchantment = store.get<ESM::Enchantment>().find(test.getClass().getEnchantment(test));
+ bool harmfulEffect = false;
+ bool usefulEffect = false;
+ if (enchantment->mData.mType == ESM::Enchantment::ConstantEffect)
+ {
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = enchantment->mEffects.mList.begin();
+ it != enchantment->mEffects.mList.end(); ++it)
+ {
+ const ESM::MagicEffect* effect = store.get<ESM::MagicEffect>().find(it->mEffectID);
+ if (effect->mData.mFlags & ESM::MagicEffect::Harmful)
+ harmfulEffect = true;
+ else
+ usefulEffect = true;
+ }
+ }
+ if (harmfulEffect && !usefulEffect)
+ continue;
+ }
+
for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
iter2!=itemsSlots.first.end(); ++iter2)
{
bool use = false;
- if (slots.at (*iter2)==end())
+ if (slots_.at (*iter2)==end())
use = true; // slot was empty before -> skip all further checks
else
{
- Ptr old = *slots.at (*iter2);
+ Ptr old = *slots_.at (*iter2);
if (!use)
{
@@ -219,15 +252,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
}
}
- switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first)
+ switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first)
{
case 0:
continue;
case 2:
- invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end());
+ invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor);
break;
case 3:
- invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end());
+ invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor);
break;
}
@@ -239,62 +272,159 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
// add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1
int count = iter->getRefData().getCount();
iter->getRefData().setCount(count-1);
- addImpl(*iter);
+ addNewStack(*iter);
iter->getRefData().setCount(1);
}
}
- slots[*iter2] = iter;
+ slots_[*iter2] = iter;
break;
}
}
bool changed = false;
- for (std::size_t i=0; i<slots.size(); ++i)
- if (slots[i]!=mSlots[i])
+ for (std::size_t i=0; i<slots_.size(); ++i)
+ if (slots_[i]!=mSlots[i])
{
changed = true;
}
+ mUpdatesEnabled = true;
+
if (changed)
{
- mSlots.swap (slots);
+ mSlots.swap (slots_);
+ fireEquipmentChangedEvent();
+ updateMagicEffects(actor);
flagAsModified();
}
}
-const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects()
+const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
+{
+ return mMagicEffects;
+}
+
+void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
{
- if (!mMagicEffectsUpToDate)
+ // To avoid excessive updates during auto-equip
+ if (!mUpdatesEnabled)
+ return;
+
+ // Delay update until the listener is set up
+ if (!mListener)
+ return;
+
+ mMagicEffects = MWMechanics::MagicEffects();
+
+ for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
- mMagicEffects = MWMechanics::MagicEffects();
+ if (*iter==end())
+ continue;
- for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
- if (*iter!=end())
- {
- std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter);
+ std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter);
- if (!enchantmentId.empty())
+ if (!enchantmentId.empty())
+ {
+ const ESM::Enchantment& enchantment =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
+
+ if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
+ continue;
+
+ std::vector<EffectParams> params;
+
+ bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end());
+ if (!existed)
+ {
+ // Roll some dice, one for each effect
+ params.resize(enchantment.mEffects.mList.size());
+ for (unsigned int i=0; i<params.size();++i)
+ params[i].mRandom = static_cast<float> (std::rand()) / RAND_MAX;
+
+ // Try resisting each effect
+ int i=0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
+ effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
{
- const ESM::Enchantment& enchantment =
- *MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
+ params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor);
+ ++i;
+ }
- if (enchantment.mData.mType==ESM::Enchantment::ConstantEffect)
- mMagicEffects.add (enchantment.mEffects);
+ // Note that using the RefID as a key here is not entirely correct.
+ // Consider equipping the same item twice (e.g. a ring)
+ // However, permanent enchantments with a random magnitude are kind of an exploit anyway,
+ // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
+ mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params;
+ }
+ else
+ params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID];
+
+ int i=0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
+ effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i)
+ {
+ const ESM::MagicEffect *magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
+ effectIt->mEffectID);
+
+ // Fully resisted?
+ if (params[i].mMultiplier == 0)
+ continue;
+
+ float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
+ magnitude *= params[i].mMultiplier;
+
+ if (!existed)
+ {
+ // During first auto equip, we don't play any sounds.
+ // Basically we don't want sounds when the actor is first loaded,
+ // the items should appear as if they'd always been equipped.
+ mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip,
+ !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin());
+
+ // Apply instant effects
+ MWMechanics::CastSpell cast(actor, actor);
+ if (magnitude)
+ cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude);
}
+
+ if (magnitude)
+ mMagicEffects.add (*effectIt, magnitude);
}
+ }
+ }
- mMagicEffectsUpToDate = true;
+ // Now drop expired effects
+ for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin();
+ it != mPermanentMagicEffectMagnitudes.end();)
+ {
+ bool found = false;
+ for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
+ {
+ if (*iter == end())
+ continue;
+ if ((**iter).getCellRef().mRefID == it->first)
+ {
+ found = true;
+ }
+ }
+ if (!found)
+ mPermanentMagicEffectMagnitudes.erase(it++);
+ else
+ ++it;
}
- return mMagicEffects;
+ // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping
+ MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
+
+ mFirstAutoEquip = false;
}
void MWWorld::InventoryStore::flagAsModified()
{
ContainerStore::flagAsModified();
- mMagicEffectsUpToDate = false;
}
bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
@@ -303,14 +433,16 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
if (!canStack)
return false;
- // don't stack if the item being checked against is currently equipped.
+ // don't stack if either item is currently equipped
for (TSlots::const_iterator iter (mSlots.begin());
iter!=mSlots.end(); ++iter)
{
- if (*iter != end() && ptr1 == **iter)
- return false;
- if (*iter != end() && ptr2 == **iter)
- return false;
+ if (*iter != end() && (ptr1 == **iter || ptr2 == **iter))
+ {
+ bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second;
+ if (!stackWhenEquipped)
+ return false;
+ }
}
return true;
@@ -325,3 +457,196 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem(
{
return mSelectedEnchantItem;
}
+
+int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor)
+{
+ for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
+ {
+ if (mSlots[slot] == end())
+ continue;
+
+ if (*mSlots[slot] == item)
+ {
+ // restacking is disabled cause it may break removal
+ unequipSlot(slot, actor, false);
+ break;
+ }
+ }
+
+ int retCount = ContainerStore::remove(item, count, actor);
+
+ // If an armor/clothing item is removed, try to find a replacement,
+ // but not for the player nor werewolves.
+ if ((actor.getRefData().getHandle() != "player")
+ && !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf()))
+ {
+ std::string type = item.getTypeName();
+ if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
+ && !actor.getClass().getCreatureStats(actor).isDead())
+ autoEquip(actor);
+ }
+
+ if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end()
+ && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player")
+ {
+ mSelectedEnchantItem = end();
+ MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
+ }
+
+ updateRechargingItems();
+
+ return retCount;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack)
+{
+ ContainerStoreIterator it = getSlot(slot);
+
+ if (it != end())
+ {
+ ContainerStoreIterator retval = it;
+
+ // empty this slot
+ mSlots[slot] = end();
+
+ if (restack) {
+ // restack item previously in this slot
+ for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
+ {
+ if (stacks(*iter, *it))
+ {
+ iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount());
+ it->getRefData().setCount(0);
+ retval = iter;
+ break;
+ }
+ }
+ }
+
+ if (actor.getRefData().getHandle() == "player")
+ {
+ // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
+ const std::string& script = Class::get(*it).getScript(*it);
+ if (script != "")
+ (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
+
+ // Update HUD icon when removing player weapon or selected enchanted item.
+ // We have to check for both as the weapon could also be the enchanted item.
+ if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
+ {
+ // weapon
+ MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon();
+ }
+ if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it))
+ {
+ // enchanted item
+ mSelectedEnchantItem = end();
+ MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
+ }
+ }
+
+ fireEquipmentChangedEvent();
+ updateMagicEffects(actor);
+
+ return retval;
+ }
+
+ return it;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor)
+{
+ for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
+ {
+ MWWorld::ContainerStoreIterator equipped = getSlot(slot);
+ if (equipped != end() && *equipped == item)
+ return unequipSlot(slot, actor);
+ }
+
+ throw std::runtime_error ("attempt to unequip an item that is not currently equipped");
+}
+
+void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener, const Ptr& actor)
+{
+ mListener = listener;
+ updateMagicEffects(actor);
+}
+
+void MWWorld::InventoryStore::fireEquipmentChangedEvent()
+{
+ if (!mUpdatesEnabled)
+ return;
+ if (mListener)
+ mListener->equipmentChanged();
+}
+
+void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor)
+{
+ for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
+ {
+ if (*iter==end())
+ continue;
+
+ std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter);
+ if (enchantmentId.empty())
+ continue;
+
+ const ESM::Enchantment& enchantment =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
+
+ if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
+ continue;
+
+ if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end())
+ continue;
+
+ int i=0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
+ effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
+ {
+ const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i];
+ float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
+ magnitude *= params.mMultiplier;
+ visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude);
+
+ ++i;
+ }
+ }
+}
+
+void MWWorld::InventoryStore::updateRechargingItems()
+{
+ mRechargingItems.clear();
+ for (ContainerStoreIterator it = begin(); it != end(); ++it)
+ {
+ if (it->getClass().getEnchantment(*it) != "")
+ {
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
+ it->getClass().getEnchantment(*it));
+ if (enchantment->mData.mType == ESM::Enchantment::WhenUsed
+ || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
+ mRechargingItems.push_back(std::make_pair(it, enchantment->mData.mCharge));
+ }
+ }
+}
+
+void MWWorld::InventoryStore::rechargeItems(float duration)
+{
+ for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it)
+ {
+ if (it->first->getCellRef().mEnchantmentCharge == -1
+ || it->first->getCellRef().mEnchantmentCharge == it->second)
+ continue;
+
+ static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ "fMagicItemRechargePerSecond")->getFloat();
+
+ it->first->getCellRef().mEnchantmentCharge = std::min (it->first->getCellRef().mEnchantmentCharge + fMagicItemRechargePerSecond * duration,
+ it->second);
+ }
+}
+
+void MWWorld::InventoryStore::purgeEffect(short effectId)
+{
+ mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude);
+}
diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp
index f0cba0f9fe..e764f64fb9 100644
--- a/apps/openmw/mwworld/inventorystore.hpp
+++ b/apps/openmw/mwworld/inventorystore.hpp
@@ -12,6 +12,25 @@ namespace MWMechanics
namespace MWWorld
{
+ class InventoryStoreListener
+ {
+ public:
+ /**
+ * Fired when items are equipped or unequipped
+ */
+ virtual void equipmentChanged () {}
+
+ /**
+ * @param effect
+ * @param isNew Is this effect new (e.g. the item for it was just now manually equipped)
+ * or was it loaded from a savegame / initial game state? \n
+ * If it isn't new, non-looping VFX should not be played.
+ * @param playSound Play effect sound?
+ */
+ virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) {}
+
+ };
+
///< \brief Variant of the ContainerStore for NPCs
class InventoryStore : public ContainerStore
{
@@ -43,19 +62,48 @@ namespace MWWorld
private:
- mutable MWMechanics::MagicEffects mMagicEffects;
- mutable bool mMagicEffectsUpToDate;
+ MWMechanics::MagicEffects mMagicEffects;
+
+ InventoryStoreListener* mListener;
+
+ // Enables updates of magic effects and actor model whenever items are equipped or unequipped.
+ // This is disabled during autoequip to avoid excessive updates
+ bool mUpdatesEnabled;
+
+ bool mFirstAutoEquip;
+
+ // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here.
+ // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update.
+ struct EffectParams
+ {
+ // Modifier to scale between min and max magnitude
+ float mRandom;
+ // Multiplier for when an effect was fully or partially resisted
+ float mMultiplier;
+ };
+
+ typedef std::map<std::string, std::vector<EffectParams> > TEffectMagnitudes;
+ TEffectMagnitudes mPermanentMagicEffectMagnitudes;
typedef std::vector<ContainerStoreIterator> TSlots;
- mutable TSlots mSlots;
+ TSlots mSlots;
// selected magic item (for using enchantments of type "Cast once" or "Cast when used")
ContainerStoreIterator mSelectedEnchantItem;
+ // (item, max charge)
+ typedef std::vector<std::pair<ContainerStoreIterator, float> > TRechargingItems;
+ TRechargingItems mRechargingItems;
+
void copySlots (const InventoryStore& store);
- void initSlots (TSlots& slots);
+ void initSlots (TSlots& slots_);
+
+ void updateMagicEffects(const Ptr& actor);
+ void updateRechargingItems();
+
+ void fireEquipmentChangedEvent();
public:
@@ -76,7 +124,7 @@ namespace MWWorld
///
/// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item.
- void equip (int slot, const ContainerStoreIterator& iterator);
+ void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor);
///< \note \a iterator can be an end-iterator
void setSelectedEnchantItem(const ContainerStoreIterator& iterator);
@@ -92,13 +140,11 @@ namespace MWWorld
void unequipAll(const MWWorld::Ptr& actor);
///< Unequip all currently equipped items.
- void autoEquip (const MWWorld::Ptr& npc);
+ void autoEquip (const MWWorld::Ptr& actor);
///< Auto equip items according to stats and item value.
- const MWMechanics::MagicEffects& getMagicEffects();
+ const MWMechanics::MagicEffects& getMagicEffects() const;
///< Return magic effects from worn items.
- ///
- /// \todo make this const again, after the constness of Ptrs and iterators has been addressed.
virtual void flagAsModified();
///< \attention This function is internal to the world model and should not be called from
@@ -106,8 +152,37 @@ namespace MWWorld
virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2);
///< @return true if the two specified objects can stack with each other
- /// @note ptr1 is the item that is already in this container
+ virtual int remove(const Ptr& item, int count, const Ptr& actor);
+ ///< Remove \a count item(s) designated by \a item from this inventory.
+ ///
+ /// @return the number of items actually removed
+
+ ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true);
+ ///< Unequip \a slot.
+ ///
+ /// @return an iterator to the item that was previously in the slot
+ /// (if \a restack is true, the item can be re-stacked so its count
+ /// may differ from when it was equipped).
+
+ ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor);
+ ///< Unequip an item identified by its Ptr. An exception is thrown
+ /// if the item is not currently equipped.
+ ///
+ /// @return an iterator to the item that was previously in the slot
+ /// (it can be re-stacked so its count may be different than when it
+ /// was equipped).
+
+ void setListener (InventoryStoreListener* listener, const Ptr& actor);
+ ///< Set a listener for various events, see \a InventoryStoreListener
+
+ void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor);
+
+ void rechargeItems (float duration);
+ ///< Restore charge on enchanted items. Note this should only be done for the player.
+
+ void purgeEffect (short effectId);
+ ///< Remove a magic effect
};
}
diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp
index 6616165fae..1cdcd8484e 100644
--- a/apps/openmw/mwworld/manualref.hpp
+++ b/apps/openmw/mwworld/manualref.hpp
@@ -37,7 +37,7 @@ namespace MWWorld
public:
- ManualRef (const MWWorld::ESMStore& store, const std::string& name)
+ ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1)
{
// create
if (!create (store.get<ESM::Activator>(), name) &&
@@ -74,6 +74,7 @@ namespace MWWorld
cellRef.mTeleport = false;
cellRef.mLockLevel = 0;
cellRef.mReferenceBlocked = 0;
+ mPtr.getRefData().setCount(count);
}
const Ptr& getPtr() const
diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp
index 433fe08925..2a7f5948e4 100644
--- a/apps/openmw/mwworld/physicssystem.cpp
+++ b/apps/openmw/mwworld/physicssystem.cpp
@@ -18,6 +18,8 @@
#include "../mwbase/world.hpp" // FIXME
#include "../mwbase/environment.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
#include <components/esm/loadgmst.hpp>
#include "../mwworld/esmstore.hpp"
@@ -573,9 +575,35 @@ namespace MWWorld
if(cell->hasWater())
waterlevel = cell->mWater;
+ float oldHeight = iter->first.getRefData().getPosition().pos[2];
+
+ bool waterCollision = false;
+ if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects()
+ .get(ESM::MagicEffect::WaterWalking).mMagnitude
+ && cell->hasWater()
+ && !world->isUnderwater(iter->first.getCell(),
+ Ogre::Vector3(iter->first.getRefData().getPosition().pos)))
+ waterCollision = true;
+
+ btStaticPlaneShape planeShape(btVector3(0,0,1), waterlevel);
+ btCollisionObject object;
+ object.setCollisionShape(&planeShape);
+
+ if (waterCollision)
+ mEngine->dynamicsWorld->addCollisionObject(&object);
+
Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum,
world->isFlying(iter->first),
waterlevel, mEngine);
+
+ if (waterCollision)
+ mEngine->dynamicsWorld->removeCollisionObject(&object);
+
+ float heightDiff = newpos.z - oldHeight;
+
+ if (heightDiff < 0)
+ iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
+
mMovementResults.push_back(std::make_pair(iter->first, newpos));
}
diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp
index e26c2e2a52..c594454028 100644
--- a/apps/openmw/mwworld/player.cpp
+++ b/apps/openmw/mwworld/player.cpp
@@ -18,8 +18,11 @@ namespace MWWorld
{
Player::Player (const ESM::NPC *player, const MWBase::World& world)
: mCellStore(0),
+ mLastKnownExteriorPosition(0,0,0),
mAutoMove(false),
- mForwardBackward (0)
+ mForwardBackward (0),
+ mTeleported(false),
+ mMarkedCell(NULL)
{
mPlayer.mBase = player;
mPlayer.mRef.mRefID = "player";
@@ -145,4 +148,27 @@ namespace MWWorld
MWWorld::Ptr ptr = getPlayer();
return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState();
}
+
+ bool Player::wasTeleported() const
+ {
+ return mTeleported;
+ }
+
+ void Player::setTeleported(bool teleported)
+ {
+ mTeleported = teleported;
+ }
+
+ void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition)
+ {
+ mMarkedCell = markedCell;
+ mMarkedPosition = markedPosition;
+ }
+
+ void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position &markedPosition) const
+ {
+ markedCell = mMarkedCell;
+ if (mMarkedCell)
+ markedPosition = mMarkedPosition;
+ }
}
diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp
index d78b1901c4..1df848111b 100644
--- a/apps/openmw/mwworld/player.hpp
+++ b/apps/openmw/mwworld/player.hpp
@@ -6,6 +6,8 @@
#include "../mwmechanics/drawstate.hpp"
+#include <OgreVector3.h>
+
namespace ESM
{
struct NPC;
@@ -28,13 +30,30 @@ namespace MWWorld
MWWorld::CellStore *mCellStore;
std::string mSign;
+ Ogre::Vector3 mLastKnownExteriorPosition;
+
+ ESM::Position mMarkedPosition;
+ // If no position was marked, this is NULL
+ CellStore* mMarkedCell;
+
bool mAutoMove;
int mForwardBackward;
-
+ bool mTeleported;
public:
Player(const ESM::NPC *player, const MWBase::World& world);
+ // For mark/recall magic effects
+ void markPosition (CellStore* markedCell, ESM::Position markedPosition);
+ void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const;
+
+ /// Interiors can not always be mapped to a world position. However
+ /// world position is still required for divine / almsivi magic effects
+ /// and the player arrow on the global map.
+ /// TODO: This should be stored in the savegame, too.
+ void setLastKnownExteriorPosition (const Ogre::Vector3& position) { mLastKnownExteriorPosition = position; }
+ Ogre::Vector3 getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; }
+
void set (const ESM::NPC *player);
void setCell (MWWorld::CellStore *cellStore);
@@ -64,6 +83,9 @@ namespace MWWorld
void yaw(float yaw);
void pitch(float pitch);
void roll(float roll);
+
+ bool wasTeleported() const;
+ void setTeleported(bool teleported);
};
}
#endif
diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp
index 127ab1364c..384bd71b11 100644
--- a/apps/openmw/mwworld/ptr.cpp
+++ b/apps/openmw/mwworld/ptr.cpp
@@ -25,9 +25,6 @@ ESM::CellRef& MWWorld::Ptr::getCellRef() const
{
assert(mRef);
- if (mContainerStore)
- mContainerStore->flagAsModified();
-
return mRef->mRef;
}
@@ -35,9 +32,6 @@ MWWorld::RefData& MWWorld::Ptr::getRefData() const
{
assert(mRef);
- if (mContainerStore)
- mContainerStore->flagAsModified();
-
return mRef->mData;
}
diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp
index 642f5412c8..d5701efc51 100644
--- a/apps/openmw/mwworld/refdata.hpp
+++ b/apps/openmw/mwworld/refdata.hpp
@@ -73,6 +73,11 @@ namespace MWWorld
void setLocals (const ESM::Script& script);
void setCount (int count);
+ /// Set object count (an object pile is a simple object with a count >1).
+ ///
+ /// \warning Do not call setCount() to add or remove objects from a
+ /// container or an actor's inventory. Call ContainerStore::add() or
+ /// ContainerStore::remove() instead.
MWScript::Locals& getLocals();
diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp
index 0c98ca504f..dab272f7c0 100644
--- a/apps/openmw/mwworld/scene.cpp
+++ b/apps/openmw/mwworld/scene.cpp
@@ -111,15 +111,15 @@ namespace MWWorld
mRendering.removeCell(*iter);
MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter);
+
MWBase::Environment::get().getMechanicsManager()->drop (*iter);
+
MWBase::Environment::get().getSoundManager()->stopSound (*iter);
mActiveCells.erase(*iter);
}
void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener)
{
- // register local scripts
- MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
if(result.second)
@@ -157,11 +157,16 @@ namespace MWWorld
mRendering.requestMap(cell);
mRendering.configureAmbient(*cell);
}
+
+ // register local scripts
+ // ??? Should this go into the above if block ???
+ MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
}
void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr old = world->getPlayer().getPlayer();
world->getPlayer().setCell(cell);
MWWorld::Ptr player = world->getPlayer().getPlayer();
@@ -181,7 +186,7 @@ namespace MWWorld
MWBase::MechanicsManager *mechMgr =
MWBase::Environment::get().getMechanicsManager();
- mechMgr->add(player);
+ mechMgr->updateCell(old, player);
mechMgr->watchActor(player);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
@@ -198,14 +203,12 @@ namespace MWWorld
void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos)
{
+ mRendering.enableTerrain(true);
Nif::NIFFile::CacheLock cachelock;
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
- // remove active
- MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
-
std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText);
@@ -434,8 +437,6 @@ namespace MWWorld
MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y);
- mRendering.enableTerrain(true);
-
changeCell (x, y, position, true);
}
diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp
index e3edad352a..73c3c4b126 100644
--- a/apps/openmw/mwworld/scene.hpp
+++ b/apps/openmw/mwworld/scene.hpp
@@ -71,8 +71,6 @@ namespace MWWorld
void loadCell (CellStore *cell, Loading::Listener* loadingListener);
void changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos);
- ///< Move from exterior to interior or from interior cell to a different
- /// interior cell.
CellStore* getCurrentCell ();
diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp
index 90452f6614..c25197319e 100644
--- a/apps/openmw/mwworld/store.hpp
+++ b/apps/openmw/mwworld/store.hpp
@@ -258,7 +258,7 @@ namespace MWWorld
typename std::vector<T *>::iterator sharedIter = mShared.begin();
typename std::vector<T *>::iterator end = sharedIter + mStatic.size();
- while (sharedIter != mShared.end() && sharedIter != end) {
+ while (sharedIter != mShared.end() && sharedIter != end) {
if((*sharedIter)->mId == item.mId) {
mShared.erase(sharedIter);
break;
diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp
index 6f5dbe23fe..513dcf6c72 100644
--- a/apps/openmw/mwworld/weather.cpp
+++ b/apps/openmw/mwworld/weather.cpp
@@ -195,7 +195,7 @@ void WeatherManager::setWeather(const String& weather, bool instant)
}
mNextWeather = weather;
- mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600;
+ mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f;
}
mFirstUpdate = false;
}
@@ -324,7 +324,8 @@ void WeatherManager::update(float duration)
mWeatherUpdateTime -= timePassed;
- const bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior());
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior());
if (!exterior)
{
mRendering->sunDisable(false);
@@ -334,32 +335,7 @@ void WeatherManager::update(float duration)
return;
}
- // Exterior
- std::string regionstr = Misc::StringUtils::lowerCase(MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion);
-
- if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion)
- {
- mCurrentRegion = regionstr;
- mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
-
- std::string weatherType = "clear";
-
- if (mRegionOverrides.find(regionstr) != mRegionOverrides.end())
- weatherType = mRegionOverrides[regionstr];
- else
- {
- // get weather probabilities for the current region
- const ESM::Region *region =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().search (regionstr);
-
- if (region != 0)
- {
- weatherType = nextWeather(region);
- }
- }
-
- setWeather(weatherType, false);
- }
+ switchToNextWeather(false);
if (mNextWeather != "")
{
@@ -473,7 +449,7 @@ void WeatherManager::update(float duration)
{
// pick a random sound
int sound = rand() % 4;
- std::string* soundName;
+ std::string* soundName = NULL;
if (sound == 0) soundName = &mThunderSoundID0;
else if (sound == 1) soundName = &mThunderSoundID1;
else if (sound == 2) soundName = &mThunderSoundID2;
@@ -707,3 +683,44 @@ float WeatherManager::getWindSpeed() const
{
return mWindSpeed;
}
+
+bool WeatherManager::isDark() const
+{
+ bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior()
+ || MWBase::Environment::get().getWorld()->isCellQuasiExterior());
+ return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1);
+}
+
+void WeatherManager::switchToNextWeather(bool instantly)
+{
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ if (world->isCellExterior() || world->isCellQuasiExterior())
+ {
+ std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayer().getPlayer().getCell()->mCell->mRegion);
+
+ if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion)
+ {
+ mCurrentRegion = regionstr;
+ mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
+
+ std::string weatherType = "clear";
+
+ if (mRegionOverrides.find(regionstr) != mRegionOverrides.end())
+ {
+ weatherType = mRegionOverrides[regionstr];
+ }
+ else
+ {
+ // get weather probabilities for the current region
+ const ESM::Region *region = world->getStore().get<ESM::Region>().search (regionstr);
+
+ if (region != 0)
+ {
+ weatherType = nextWeather(region);
+ }
+ }
+
+ setWeather(weatherType, instantly);
+ }
+ }
+}
diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp
index 80cbe0418e..fa2d9bd8e1 100644
--- a/apps/openmw/mwworld/weather.hpp
+++ b/apps/openmw/mwworld/weather.hpp
@@ -128,6 +128,7 @@ namespace MWWorld
* @param ID of the weather setting to shift to
*/
void changeWeather(const std::string& region, const unsigned int id);
+ void switchToNextWeather(bool instantly = true);
/**
* Per-frame update
@@ -152,6 +153,9 @@ namespace MWWorld
void modRegion(const std::string &regionid, const std::vector<char> &chances);
+ /// @see World::isDark
+ bool isDark() const;
+
private:
float mHour;
int mDay, mMonth;
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
index 6a4a380d65..148c8f3016 100644
--- a/apps/openmw/mwworld/worldimp.cpp
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -26,6 +26,8 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/spellcasting.hpp"
+
#include "../mwrender/sky.hpp"
#include "../mwrender/animation.hpp"
@@ -37,6 +39,7 @@
#include "cellfunctors.hpp"
#include "containerstore.hpp"
#include "inventorystore.hpp"
+#include "actionteleport.hpp"
#include "contentloader.hpp"
#include "esmloader.hpp"
@@ -105,7 +108,7 @@ namespace MWWorld
void load(const boost::filesystem::path& filepath, int& index)
{
- LoadersContainer::iterator it(mLoaders.find(filepath.extension().string()));
+ LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string())));
if (it != mLoaders.end())
{
it->second->load(filepath, index);
@@ -212,7 +215,7 @@ namespace MWWorld
: mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
mSky (true), mCells (mStore, mEsm),
mActivationDistanceOverride (mActivationDistanceOverride),
- mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true),
+ mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(false),
mFacedDistance(FLT_MAX), mGodMode(false)
{
mPhysics = new PhysicsSystem(renderer);
@@ -265,6 +268,7 @@ namespace MWWorld
// Rebuild player
setupPlayer();
+ mPlayer->setCell(NULL);
MWWorld::Ptr player = mPlayer->getPlayer();
// removes NpcStats, ContainerStore etc
@@ -495,12 +499,14 @@ namespace MWWorld
if (!ptr.isEmpty())
return ptr;
+ std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
+
// active cells
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
iter!=mWorldScene->getActiveCells().end(); ++iter)
{
Ptr::CellStore* cellstore = *iter;
- Ptr ptr = mCells.getPtr (name, *cellstore, true);
+ Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true);
if (!ptr.isEmpty())
return ptr;
@@ -508,7 +514,7 @@ namespace MWWorld
if (!activeOnly)
{
- Ptr ptr = mCells.getPtr (name);
+ Ptr ptr = mCells.getPtr (lowerCaseName);
if (!ptr.isEmpty())
return ptr;
@@ -605,6 +611,8 @@ namespace MWWorld
void World::advanceTime (double hours)
{
+ MWBase::Environment::get().getMechanicsManager()->advanceTime(hours*3600);
+
mWeatherManager->advanceTime (hours);
hours += mGlobalVariables->getFloat ("gamehour");
@@ -745,12 +753,16 @@ namespace MWWorld
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
{
- return mWorldScene->changeToInteriorCell(cellName, position);
+ removeContainerScripts(getPlayer().getPlayer());
+ mWorldScene->changeToInteriorCell(cellName, position);
+ addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell());
}
void World::changeToExteriorCell (const ESM::Position& position)
{
- return mWorldScene->changeToExteriorCell(position);
+ removeContainerScripts(getPlayer().getPlayer());
+ mWorldScene->changeToExteriorCell(position);
+ addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell());
}
void World::markCellAsUnchanged()
@@ -784,30 +796,9 @@ namespace MWWorld
MWWorld::Ptr World::getFacedObject()
{
- std::pair<float, std::string> result;
-
- if (!mRendering->occlusionQuerySupported())
- result = mPhysics->getFacedHandle (getMaxActivationDistance ());
- else
- result = std::make_pair (mFacedDistance, mFacedHandle);
-
- if (result.second.empty())
- return MWWorld::Ptr ();
-
- MWWorld::Ptr object = searchPtrViaHandle (result.second);
- float ActivationDistance;
-
- if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
- ActivationDistance = getObjectActivationDistance ()*50;
- else if (object.getTypeName ().find("NPC") != std::string::npos)
- ActivationDistance = getNpcActivationDistance ();
- else
- ActivationDistance = getObjectActivationDistance ();
-
- if (result.first > ActivationDistance)
- return MWWorld::Ptr ();
-
- return object;
+ if (mFacedHandle.empty())
+ return MWWorld::Ptr();
+ return searchPtrViaHandle(mFacedHandle);
}
std::pair<MWWorld::Ptr,Ogre::Vector3> World::getHitContact(const MWWorld::Ptr &ptr, float distance)
@@ -835,12 +826,13 @@ namespace MWWorld
void World::deleteObject (const Ptr& ptr)
{
- if (ptr.getRefData().getCount()>0)
+ if (ptr.getRefData().getCount() > 0)
{
- ptr.getRefData().setCount (0);
+ ptr.getRefData().setCount(0);
- if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end() &&
- ptr.getRefData().isEnabled())
+ if (ptr.isInCell()
+ && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end()
+ && ptr.getRefData().isEnabled())
{
mWorldScene->removeObjectFromScene (ptr);
mLocalScripts.remove (ptr);
@@ -877,6 +869,7 @@ namespace MWWorld
int cellY = newCell.mCell->getGridY();
mWorldScene->changeCell(cellX, cellY, pos, false);
}
+ addContainerScripts (getPlayer().getPlayer(), &newCell);
}
else
{
@@ -884,15 +877,16 @@ namespace MWWorld
copyObjectToCell(ptr, newCell, pos);
else if (!mWorldScene->isCellActive(newCell))
{
- MWWorld::Class::get(ptr)
- .copyToCell(ptr, newCell)
- .getRefData()
- .setBaseNode(0);
-
mWorldScene->removeObjectFromScene(ptr);
mLocalScripts.remove(ptr);
removeContainerScripts (ptr);
haveToMove = false;
+
+ MWWorld::Ptr newPtr = MWWorld::Class::get(ptr)
+ .copyToCell(ptr, newCell);
+ newPtr.getRefData().setBaseNode(0);
+
+ objectLeftActiveCell(ptr, newPtr);
}
else
{
@@ -917,7 +911,7 @@ namespace MWWorld
ptr.getRefData().setCount(0);
}
}
- if (haveToMove)
+ if (haveToMove && ptr.getRefData().getBaseNode())
{
mRendering->moveObject(ptr, vec);
mPhysics->moveObject (ptr);
@@ -1046,7 +1040,7 @@ namespace MWWorld
void World::adjustPosition(const Ptr &ptr)
{
- Ogre::Vector3 pos (ptr.getRefData().getPosition().pos[0], ptr.getRefData().getPosition().pos[1], ptr.getRefData().getPosition().pos[2]);
+ Ogre::Vector3 pos (ptr.getRefData().getPosition().pos);
if(!ptr.getRefData().getBaseNode())
{
@@ -1079,9 +1073,9 @@ namespace MWWorld
adjust);
}
- void World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos)
+ MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos)
{
- copyObjectToCell(ptr,Cell,pos);
+ return copyObjectToCell(ptr,Cell,pos,false);
}
void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const
@@ -1113,12 +1107,10 @@ namespace MWWorld
void World::doPhysics(float duration)
{
- /* No duration? Shouldn't be any movement, then. */
- if(duration <= 0.0f)
- return;
-
processDoors(duration);
+ moveProjectiles(duration);
+
const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
PtrVelocityList::const_iterator player(results.end());
for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++)
@@ -1275,15 +1267,22 @@ namespace MWWorld
mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true);
}
- mWeatherManager->update (duration);
+ updateWeather(duration);
mWorldScene->update (duration, paused);
- doPhysics (duration);
+ if (!paused)
+ doPhysics (duration);
performUpdateSceneQueries ();
updateWindowManager ();
+
+ if (mPlayer->getPlayer().getCell()->isExterior())
+ {
+ ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition();
+ mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos));
+ }
}
void World::updateWindowManager ()
@@ -1324,6 +1323,14 @@ namespace MWWorld
void World::updateFacedHandle ()
{
+ float telekinesisRangeBonus =
+ mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects()
+ .get(ESM::MagicEffect::Telekinesis).mMagnitude;
+ telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus);
+
+ float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus;
+ activationDistance += mRendering->getCameraDistance();
+
// send new query
// figure out which object we want to test against
std::vector < std::pair < float, std::string > > results;
@@ -1331,13 +1338,13 @@ namespace MWWorld
{
float x, y;
MWBase::Environment::get().getWindowManager()->getMousePosition(x, y);
- results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ());
+ results = mPhysics->getFacedHandles(x, y, activationDistance);
if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50);
}
else
{
- results = mPhysics->getFacedHandles(getMaxActivationDistance ());
+ results = mPhysics->getFacedHandles(activationDistance);
}
// ignore the player and other things we're not interested in
@@ -1420,10 +1427,8 @@ namespace MWWorld
return d;
}
- std::vector<World::DoorMarker> World::getDoorMarkers (CellStore* cell)
+ void World::getDoorMarkers (CellStore* cell, std::vector<World::DoorMarker>& out)
{
- std::vector<World::DoorMarker> result;
-
MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors;
CellRefList<ESM::Door>::List& refList = doors.mList;
for (CellRefList<ESM::Door>::List::iterator it = refList.begin(); it != refList.end(); ++it)
@@ -1439,11 +1444,9 @@ namespace MWWorld
newMarker.x = pos.pos[0];
newMarker.y = pos.pos[1];
- result.push_back(newMarker);
+ out.push_back(newMarker);
}
}
-
- return result;
}
void World::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y)
@@ -1475,7 +1478,7 @@ namespace MWWorld
item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1);
}
- bool World::placeObject (const Ptr& object, float cursorX, float cursorY)
+ bool World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount)
{
std::pair<bool, Ogre::Vector3> result = mPhysics->castRay(cursorX, cursorY);
@@ -1500,9 +1503,14 @@ namespace MWWorld
pos.rot[0] = 0;
pos.rot[1] = 0;
- Ptr dropped = copyObjectToCell(object, *cell, pos);
+ // copy the object and set its count
+ int origCount = object.getRefData().getCount();
+ object.getRefData().setCount(amount);
+ Ptr dropped = copyObjectToCell(object, *cell, pos, true);
+ object.getRefData().setCount(origCount);
+
+ // only the player place items in the world, so no need to check actor
PCDropped(dropped);
- object.getRefData().setCount(0);
return true;
}
@@ -1519,18 +1527,21 @@ namespace MWWorld
}
- Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos)
+ Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos, bool adjustPos)
{
/// \todo add searching correct cell for position specified
MWWorld::Ptr dropped =
MWWorld::Class::get(object).copyToCell(object, cell, pos);
- Ogre::Vector3 min, max;
- if (mPhysics->getObjectAABB(object, min, max)) {
- float *pos = dropped.getRefData().getPosition().pos;
- pos[0] -= (min.x + max.x) / 2;
- pos[1] -= (min.y + max.y) / 2;
- pos[2] -= min.z;
+ if (object.getClass().isActor() || adjustPos)
+ {
+ Ogre::Vector3 min, max;
+ if (mPhysics->getObjectAABB(object, min, max)) {
+ float *pos = dropped.getRefData().getPosition().pos;
+ pos[0] -= (min.x + max.x) / 2;
+ pos[1] -= (min.y + max.y) / 2;
+ pos[2] -= min.z;
+ }
}
if (mWorldScene->isCellActive(cell)) {
@@ -1547,7 +1558,7 @@ namespace MWWorld
return dropped;
}
- void World::dropObjectOnGround (const Ptr& actor, const Ptr& object)
+ void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount)
{
MWWorld::Ptr::CellStore* cell = actor.getCell();
@@ -1558,7 +1569,7 @@ namespace MWWorld
pos.rot[1] = 0;
Ogre::Vector3 orig =
- Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]);
+ Ogre::Vector3(pos.pos);
Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1);
float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2];
@@ -1568,10 +1579,14 @@ namespace MWWorld
mPhysics->castRay(orig, dir, len);
pos.pos[2] = hit.second.z;
+ // copy the object and set its count
+ int origCount = object.getRefData().getCount();
+ object.getRefData().setCount(amount);
Ptr dropped = copyObjectToCell(object, *cell, pos);
+ object.getRefData().setCount(origCount);
+
if(actor == mPlayer->getPlayer()) // Only call if dropped by player
PCDropped(dropped);
- object.getRefData().setCount(0);
}
void World::processChangedSettings(const Settings::CategorySettingVector& settings)
@@ -1591,7 +1606,8 @@ namespace MWWorld
return false;
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
- if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0)
+ if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0
+ && isLevitationEnabled())
return true;
// TODO: Check if flying creature
@@ -1795,9 +1811,9 @@ namespace MWWorld
{
std::vector<std::string> mHandles;
- bool operator() (ESM::CellRef& ref, RefData& data)
+ bool operator() (Ptr ptr)
{
- Ogre::SceneNode* handle = data.getBaseNode();
+ Ogre::SceneNode* handle = ptr.getRefData().getBaseNode();
if (handle)
mHandles.push_back(handle->getName());
return true;
@@ -1818,6 +1834,28 @@ namespace MWWorld
}
}
+ bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
+ {
+ // This is a placeholder! Needs to go into an NPC awareness check function (see
+ // https://wiki.openmw.org/index.php?title=Research:NPC_AI_Behaviour#NPC_Awareness_Check )
+ if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude)
+ return false;
+ if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude > 100)
+ return false;
+
+ Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents();
+ float* pos1 = npc.getRefData().getPosition().pos;
+ Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents();
+ float* pos2 = targetNpc.getRefData().getPosition().pos;
+
+ btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z);
+ btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z);
+
+ std::pair<std::string, float> result = mPhysEngine->rayTest(from, to,false);
+ if(result.first == "") return true;
+ return false;
+ }
+
void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
{
OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle());
@@ -1884,17 +1922,8 @@ namespace MWWorld
int y = ext->getGridY();
indexToPosition(x, y, pos.pos[0], pos.pos[1], true);
- ESM::Land* land = getStore().get<ESM::Land>().search(x, y);
- if (land) {
- if (!land->isDataLoaded(ESM::Land::DATA_VHGT)) {
- land->loadData(ESM::Land::DATA_VHGT);
- }
- pos.pos[2] = land->mLandData->mHeights[ESM::Land::LAND_NUM_VERTS / 2 + 1];
- }
- else {
- std::cerr << "Land data for cell at (" << x << ", " << y << ") not found\n";
- pos.pos[2] = 0;
- }
+ // Note: Z pos will be adjusted by adjustPosition later
+ pos.pos[2] = 0;
return true;
}
@@ -1936,31 +1965,13 @@ namespace MWWorld
if(werewolf)
{
- ManualRef ref(getStore(), "WerewolfRobe");
- ref.getPtr().getRefData().setCount(1);
-
- // Configure item's script variables
- std::string script = Class::get(ref.getPtr()).getScript(ref.getPtr());
- if(script != "")
- {
- const ESM::Script *esmscript = getStore().get<ESM::Script>().find(script);
- ref.getPtr().getRefData().setLocals(*esmscript);
- }
+ InventoryStore &inv = actor.getClass().getInventoryStore(actor);
- // Not sure this is right
- InventoryStore &inv = Class::get(actor).getInventoryStore(actor);
- inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor));
+ inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("WerewolfRobe", 1, actor), actor);
}
else
{
- ContainerStore &store = Class::get(actor).getContainerStore(actor);
-
- const std::string item = "WerewolfRobe";
- for(ContainerStoreIterator iter(store.begin());iter != store.end();++iter)
- {
- if(Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
- iter->getRefData().setCount(0);
- }
+ actor.getClass().getContainerStore(actor).remove("WerewolfRobe", 1, actor);
}
if(actor.getRefData().getHandle() == "player")
@@ -2019,4 +2030,391 @@ namespace MWWorld
}
}
}
+
+ bool World::startSpellCast(const Ptr &actor)
+ {
+ MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+
+ std::string message;
+ bool fail = false;
+ bool isPlayer = (actor == getPlayer().getPlayer());
+
+ std::string selectedSpell = stats.getSpells().getSelectedSpell();
+
+ if (!selectedSpell.empty())
+ {
+ const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
+
+ // Check mana
+ MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
+ if (magicka.getCurrent() < spell->mData.mCost)
+ {
+ message = "#{sMagicInsufficientSP}";
+ fail = true;
+ }
+
+ // If this is a power, check if it was already used in the last 24h
+ if (!fail && spell->mData.mType == ESM::Spell::ST_Power)
+ {
+ if (stats.canUsePower(spell->mId))
+ stats.usePower(spell->mId);
+ else
+ {
+ message = "#{sPowerAlreadyUsed}";
+ fail = true;
+ }
+ }
+
+ // Reduce mana
+ magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
+ stats.setMagicka(magicka);
+ }
+
+ if (isPlayer && fail)
+ MWBase::Environment::get().getWindowManager()->messageBox(message);
+
+ return !fail;
+ }
+
+ void World::castSpell(const Ptr &actor)
+ {
+ MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+ InventoryStore& inv = actor.getClass().getInventoryStore(actor);
+
+ MWWorld::Ptr target = getFacedObject();
+
+ std::string selectedSpell = stats.getSpells().getSelectedSpell();
+
+ MWMechanics::CastSpell cast(actor, target);
+
+ if (!selectedSpell.empty())
+ {
+ const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
+
+ cast.cast(spell);
+ }
+ else if (inv.getSelectedEnchantItem() != inv.end())
+ {
+ cast.cast(*inv.getSelectedEnchantItem());
+ }
+ }
+
+ void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
+ const MWWorld::Ptr& actor, const std::string& sourceName)
+ {
+ std::string projectileModel;
+ std::string sound;
+ float speed = 0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
+ iter!=effects.mList.end(); ++iter)
+ {
+ if (iter->mRange != ESM::RT_Target)
+ continue;
+
+ const ESM::MagicEffect *magicEffect = getStore().get<ESM::MagicEffect>().find (
+ iter->mEffectID);
+
+ projectileModel = magicEffect->mBolt;
+ if (projectileModel.empty())
+ projectileModel = "VFX_DefaultBolt";
+
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+
+ if (!magicEffect->mBoltSound.empty())
+ sound = magicEffect->mBoltSound;
+ else
+ sound = schools[magicEffect->mData.mSchool] + " bolt";
+
+ speed = magicEffect->mData.mSpeed;
+ break;
+ }
+ if (projectileModel.empty())
+ return;
+
+ // Spawn at 0.75 * ActorHeight
+ float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75;
+
+ MWWorld::ManualRef ref(getStore(), projectileModel);
+ ESM::Position pos;
+ pos.pos[0] = actor.getRefData().getPosition().pos[0];
+ pos.pos[1] = actor.getRefData().getPosition().pos[1];
+ pos.pos[2] = actor.getRefData().getPosition().pos[2] + height;
+ pos.rot[0] = actor.getRefData().getPosition().rot[0];
+ pos.rot[1] = actor.getRefData().getPosition().rot[1];
+ pos.rot[2] = actor.getRefData().getPosition().rot[2];
+ ref.getPtr().getCellRef().mPos = pos;
+ MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos);
+
+ ProjectileState state;
+ state.mSourceName = sourceName;
+ state.mId = id;
+ state.mActorHandle = actor.getRefData().getHandle();
+ state.mSpeed = speed;
+ state.mEffects = effects;
+ state.mStack = stack;
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f);
+
+ mProjectiles[ptr] = state;
+ }
+
+ void World::moveProjectiles(float duration)
+ {
+ std::map<std::string, ProjectileState> moved;
+ for (std::map<MWWorld::Ptr, ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
+ {
+ if (!mWorldScene->isCellActive(*it->first.getCell()))
+ {
+ mProjectiles.erase(it++);
+ continue;
+ }
+
+ MWWorld::Ptr ptr = it->first;
+
+ Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
+
+ // TODO: Why -rot.z, but not -rot.x?
+ Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z);
+ orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X);
+
+ // This is just a guess, probably wrong
+ static float fProjectileMinSpeed = getStore().get<ESM::GameSetting>().find("fProjectileMinSpeed")->getFloat();
+ static float fProjectileMaxSpeed = getStore().get<ESM::GameSetting>().find("fProjectileMaxSpeed")->getFloat();
+ float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * it->second.mSpeed;
+
+ Ogre::Vector3 direction = orient.yAxis();
+ direction.normalise();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ Ogre::Vector3 newPos = pos + direction * duration * speed;
+
+ // Check for impact
+ btVector3 from(pos.x, pos.y, pos.z);
+ btVector3 to(newPos.x, newPos.y, newPos.z);
+ std::vector<std::pair<float, std::string> > collisions = mPhysEngine->rayTest2(from, to);
+ bool explode = false;
+ for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt)
+ {
+ MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second);
+ if (obstacle == ptr)
+ continue;
+
+ explode = true;
+
+ MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
+ if (caster.isEmpty())
+ caster = obstacle;
+ if (obstacle.isEmpty())
+ {
+ // Terrain
+ }
+ else
+ {
+ MWMechanics::CastSpell cast(caster, obstacle);
+ cast.mStack = it->second.mStack;
+ cast.mId = it->second.mId;
+ cast.mSourceName = it->second.mSourceName;
+ cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false);
+ }
+
+ deleteObject(ptr);
+ mProjectiles.erase(it++);
+ }
+
+ if (explode)
+ {
+ // TODO: Explode
+ continue;
+ }
+
+ std::string handle = ptr.getRefData().getHandle();
+
+ moveObject(ptr, newPos.x, newPos.y, newPos.z);
+
+ // HACK: Re-fetch Ptrs if necessary, since the cell might have changed
+ if (!ptr.getRefData().getCount())
+ {
+ moved[handle] = it->second;
+ mProjectiles.erase(it++);
+ }
+ else
+ ++it;
+ }
+
+ // HACK: Re-fetch Ptrs if necessary, since the cell might have changed
+ for (std::map<std::string, ProjectileState>::iterator it = moved.begin(); it != moved.end(); ++it)
+ {
+ MWWorld::Ptr newPtr = searchPtrViaHandle(it->first);
+ if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted
+ continue;
+ mProjectiles[getPtrViaHandle(it->first)] = it->second;
+ }
+ }
+
+ void World::objectLeftActiveCell(Ptr object, Ptr movedPtr)
+ {
+ // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information
+ if (mProjectiles.find(object) != mProjectiles.end())
+ {
+ deleteObject(movedPtr);
+ }
+ }
+
+ void World::breakInvisibility(const Ptr &actor)
+ {
+ actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
+ actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
+ }
+
+ bool World::isDark() const
+ {
+ return mWeatherManager->isDark();
+ }
+
+ bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result)
+ {
+ MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors;
+ CellRefList<ESM::Door>::List& refList = doors.mList;
+
+ // Check if any door in the cell leads to an exterior directly
+ for (CellRefList<ESM::Door>::List::iterator it = refList.begin(); it != refList.end(); ++it)
+ {
+ MWWorld::LiveCellRef<ESM::Door>& ref = *it;
+ if (ref.mRef.mTeleport && ref.mRef.mDestCell.empty())
+ {
+ ESM::Position pos = ref.mRef.mDoorDest;
+ result = Ogre::Vector3(pos.pos);
+ return true;
+ }
+ }
+
+ // No luck :(
+ return false;
+ }
+
+ void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
+ const std::string& id, Ogre::Vector3 worldPos)
+ {
+ MWWorld::Ptr closestMarker;
+ float closestDistance = FLT_MAX;
+
+ std::vector<MWWorld::Ptr> markers;
+ mCells.getExteriorPtrs(id, markers);
+
+ for (std::vector<MWWorld::Ptr>::iterator it = markers.begin(); it != markers.end(); ++it)
+ {
+ ESM::Position pos = it->getRefData().getPosition();
+ Ogre::Vector3 markerPos = Ogre::Vector3(pos.pos);
+ float distance = worldPos.squaredDistance(markerPos);
+ if (distance < closestDistance)
+ {
+ closestDistance = distance;
+ closestMarker = *it;
+ }
+
+ }
+
+ MWWorld::ActionTeleport action("", closestMarker.getRefData().getPosition());
+ action.execute(ptr);
+ }
+
+ void World::updateWeather(float duration)
+ {
+ if (mPlayer->wasTeleported())
+ {
+ mPlayer->setTeleported(false);
+ mWeatherManager->switchToNextWeather(true);
+ }
+
+ mWeatherManager->update(duration);
+ }
+
+ struct AddDetectedReference
+ {
+ AddDetectedReference(std::vector<Ptr>& out, Ptr detector, World::DetectionType type, float squaredDist)
+ : mOut(out), mDetector(detector), mType(type), mSquaredDist(squaredDist)
+ {
+ }
+
+ std::vector<Ptr>& mOut;
+ Ptr mDetector;
+ float mSquaredDist;
+ World::DetectionType mType;
+ bool operator() (MWWorld::Ptr ptr)
+ {
+ if (Ogre::Vector3(ptr.getRefData().getPosition().pos).squaredDistance(
+ Ogre::Vector3(mDetector.getRefData().getPosition().pos)) >= mSquaredDist)
+ return true;
+
+ if (!ptr.getRefData().isEnabled())
+ return true;
+
+ // Consider references inside containers as well
+ if (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name())
+ {
+ MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
+ {
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ if (needToAdd(*it))
+ {
+ mOut.push_back(ptr);
+ return true;
+ }
+ }
+ }
+ }
+
+ if (needToAdd(ptr))
+ mOut.push_back(ptr);
+
+ return true;
+ }
+
+ bool needToAdd (MWWorld::Ptr ptr)
+ {
+ if (mType == World::Detect_Creature && ptr.getClass().getTypeName() != typeid(ESM::Creature).name())
+ return false;
+ if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr))
+ return false;
+ if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty())
+ return false;
+ return true;
+ }
+ };
+
+ void World::listDetectedReferences(const Ptr &ptr, std::vector<Ptr> &out, DetectionType type)
+ {
+ const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects();
+ float dist=0;
+ if (type == World::Detect_Creature)
+ dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectAnimal)).mMagnitude;
+ else if (type == World::Detect_Key)
+ dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectKey)).mMagnitude;
+ else if (type == World::Detect_Enchantment)
+ dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectEnchantment)).mMagnitude;
+
+ if (!dist)
+ return;
+
+ dist = feetToGameUnits(dist);
+
+ AddDetectedReference functor (out, ptr, type, dist*dist);
+
+ const Scene::CellStoreCollection& active = mWorldScene->getActiveCells();
+ for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it)
+ {
+ MWWorld::CellStore* cellStore = *it;
+ cellStore->forEach(functor);
+ }
+ }
+
+ float World::feetToGameUnits(float feet)
+ {
+ // Looks like there is no GMST for this. This factor was determined in experiments
+ // with the Telekinesis effect.
+ return feet * 22;
+ }
}
diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp
index aadf7ce981..1aecb6fb64 100644
--- a/apps/openmw/mwworld/worldimp.hpp
+++ b/apps/openmw/mwworld/worldimp.hpp
@@ -87,6 +87,26 @@ namespace MWWorld
std::map<MWWorld::Ptr, int> mDoorStates;
///< only holds doors that are currently moving. 0 means closing, 1 opening
+ struct ProjectileState
+ {
+ // Id of spell or enchantment to apply when it hits
+ std::string mId;
+
+ // Actor who casted this projectile
+ std::string mActorHandle;
+
+ // Name of item to display as effect source in magic menu (in case we casted an enchantment)
+ std::string mSourceName;
+
+ ESM::EffectList mEffects;
+
+ float mSpeed;
+
+ bool mStack;
+ };
+
+ std::map<MWWorld::Ptr, ProjectileState> mProjectiles;
+ void updateWeather(float duration);
int getDaysPerMonth (int month) const;
void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust);
@@ -94,8 +114,7 @@ namespace MWWorld
bool moveObjectImp (const Ptr& ptr, float x, float y, float z);
///< @return true if the active cell (cell player is in) changed
-
- Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos);
+ Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos, bool adjustPos=true);
void updateWindowManager ();
void performUpdateSceneQueries ();
@@ -112,6 +131,8 @@ namespace MWWorld
void processDoors(float duration);
///< Run physics simulation and modify \a world accordingly.
+ void moveProjectiles(float duration);
+
void doPhysics(float duration);
///< Run physics simulation and modify \a world accordingly.
@@ -131,6 +152,11 @@ namespace MWWorld
bool mTeleportEnabled;
bool mLevitationEnabled;
+ /// Called when \a object is moved to an inactive cell
+ void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr);
+
+ float feetToGameUnits(float feet);
+
public:
World (OEngine::Render::OgreRenderer& renderer,
@@ -178,7 +204,7 @@ namespace MWWorld
virtual Ogre::Vector2 getNorthVector (CellStore* cell);
///< get north vector (OGRE coordinates) for given interior cell
- virtual std::vector<DoorMarker> getDoorMarkers (MWWorld::CellStore* cell);
+ virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector<DoorMarker>& out);
///< get a list of teleport door markers for a given cell, to be displayed on the local map
virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y);
@@ -286,7 +312,7 @@ namespace MWWorld
virtual void localRotateObject (const Ptr& ptr, float x, float y, float z);
- virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos);
+ virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos);
///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr.
virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false)
@@ -354,14 +380,19 @@ namespace MWWorld
virtual void update (float duration, bool paused);
- virtual bool placeObject (const Ptr& object, float cursorX, float cursorY);
- ///< place an object into the gameworld at the specified cursor position
+ virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount);
+ ///< copy and place an object into the gameworld at the specified cursor position
/// @param object
/// @param cursor X (relative 0-1)
/// @param cursor Y (relative 0-1)
+ /// @param number of objects to place
/// @return true if the object was placed, or false if it was rejected because the position is too far away
- virtual void dropObjectOnGround (const Ptr& actor, const Ptr& object);
+ virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount);
+ ///< copy and place an object into the gameworld at the given actor's position
+ /// @param actor giving the dropped object position
+ /// @param object
+ /// @param number of objects to place
virtual bool canPlaceObject(float cursorX, float cursorY);
///< @return true if it is possible to place on object at specified cursor location
@@ -420,6 +451,9 @@ namespace MWWorld
virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out);
///< get all items in active cells owned by this Npc
+ virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc);
+ ///< get Line of Sight (morrowind stupid implementation)
+
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable);
virtual void setupExternalRendering (MWRender::ExternalRendering& rendering);
@@ -466,6 +500,40 @@ namespace MWWorld
virtual bool getGodModeState();
virtual bool toggleGodMode();
+
+ /**
+ * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met.
+ * @param actor
+ * @return true if the spell can be casted (i.e. the animation should start)
+ */
+ virtual bool startSpellCast (const MWWorld::Ptr& actor);
+
+ /**
+ * @brief Cast the actual spell, should be called mid-animation
+ * @param actor
+ */
+ virtual void castSpell (const MWWorld::Ptr& actor);
+
+ virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
+ const MWWorld::Ptr& actor, const std::string& sourceName);
+
+ virtual void breakInvisibility (const MWWorld::Ptr& actor);
+ // Are we in an exterior or pseudo-exterior cell and it's night?
+ virtual bool isDark() const;
+
+ virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result);
+
+ /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
+ /// closest to \a worldPos.
+ /// @note id must be lower case
+ virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr,
+ const std::string& id, Ogre::Vector3 worldPos);
+
+ /// List all references (filtered by \a type) detected by \a ptr. The range
+ /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type.
+ /// @note This also works for references in containers.
+ virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector<MWWorld::Ptr>& out,
+ DetectionType type);
};
}
diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake
index 97feddffeb..b75f3105a6 100644
--- a/cmake/FindBullet.cmake
+++ b/cmake/FindBullet.cmake
@@ -60,8 +60,6 @@ _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision)
_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d)
_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath)
_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d)
-_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody)
-_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d)
# handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if
@@ -69,12 +67,11 @@ _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletS
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet DEFAULT_MSG
BULLET_DYNAMICS_LIBRARY BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY
- BULLET_SOFTBODY_LIBRARY BULLET_INCLUDE_DIR)
+ BULLET_INCLUDE_DIR)
set(BULLET_INCLUDE_DIRS ${BULLET_INCLUDE_DIR})
if(BULLET_FOUND)
_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY)
_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY)
_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY)
- _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_SOFTBODY_LIBRARY)
endif()
diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake
index fb4a090c6e..96f93cf34a 100644
--- a/cmake/FindOGRE.cmake
+++ b/cmake/FindOGRE.cmake
@@ -551,4 +551,3 @@ set(OGRE_MEDIA_SEARCH_SUFFIX
clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR)
find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH}
PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX})
-
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index acb70b04db..a037fd5fa4 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -19,7 +19,7 @@ add_component_dir (nif
)
add_component_dir (nifogre
- ogrenifloader skeleton material mesh
+ ogrenifloader skeleton material mesh particles controller
)
add_component_dir (nifbullet
@@ -47,8 +47,8 @@ add_component_dir (misc
)
add_component_dir (files
- linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager
- filelibrary ogreplugin constrainedfiledatastream lowlevelfile
+ linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager
+ constrainedfiledatastream lowlevelfile
)
add_component_dir (compiler
@@ -65,17 +65,22 @@ add_component_dir (interpreter
add_component_dir (translation
translation
)
-
+
add_component_dir (terrain
quadtreenode chunk world storage material
)
-
+
add_component_dir (loadinglistener
loadinglistener
)
+add_component_dir (ogreinit
+ ogreinit ogreplugin
+ )
+
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)
+
find_package(Qt4 COMPONENTS QtCore QtGui)
if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY)
diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp
index 65f6e112a3..e95f6f6985 100644
--- a/components/compiler/extensions0.cpp
+++ b/components/compiler/extensions0.cpp
@@ -59,10 +59,14 @@ namespace Compiler
extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit);
extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit);
extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit);
+ extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI);
+ extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI);
extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit);
extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit);
extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit);
extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit);
+ extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit);
+ extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit);
}
}
diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp
index aca24e0d30..b7d44a851e 100644
--- a/components/compiler/opcodes.hpp
+++ b/components/compiler/opcodes.hpp
@@ -25,7 +25,7 @@ namespace Compiler
const int opcodeAiFollowExplicit = 0x20023;
const int opcodeAiFollowCell = 0x20024;
const int opcodeAiFollowCellExplicit = 0x20025;
- const int opcodeSetHello = 0x200015e;
+ const int opcodeSetHello = 0x200015c;
const int opcodeSetHelloExplicit = 0x200015d;
const int opcodeSetFight = 0x200015e;
const int opcodeSetFightExplicit = 0x200015f;
@@ -49,6 +49,10 @@ namespace Compiler
const int opcodeGetFleeExplicit = 0x20001c4;
const int opcodeGetAlarm = 0x20001c5;
const int opcodeGetAlarmExplicit = 0x20001c6;
+ const int opcodeGetLineOfSight = 0x2000222;
+ const int opcodeGetLineOfSightExplicit = 0x2000223;
+ const int opcodeToggleAI = 0x2000224;
+ const int opcodeToggleAIExplicit = 0x2000225;
}
namespace Animation
@@ -65,7 +69,7 @@ namespace Compiler
{
const int opcodeCellChanged = 0x2000000;
const int opcodeCOC = 0x2000026;
- const int opcodeCOE = 0x200008e;
+ const int opcodeCOE = 0x2000226;
const int opcodeGetInterior = 0x2000131;
const int opcodeGetPCCell = 0x2000136;
const int opcodeGetWaterLevel = 0x2000141;
diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp
index 5f3575eb40..0d274474c6 100644
--- a/components/contentselector/model/contentmodel.cpp
+++ b/components/contentselector/model/contentmodel.cpp
@@ -1,6 +1,8 @@
#include "contentmodel.hpp"
#include "esmfile.hpp"
+#include <stdexcept>
+
#include <QDir>
#include <QTextCodec>
#include <QDebug>
@@ -205,8 +207,11 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int
case Qt::CheckStateRole:
{
- if (!file->isGameFile())
- return isChecked(file->filePath());
+ if (file->isGameFile())
+ return QVariant();
+
+ return mCheckStates[file->filePath()];
+
break;
}
diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp
index 47cb0b99ed..5f1066cf8d 100644
--- a/components/esm/cellref.hpp
+++ b/components/esm/cellref.hpp
@@ -20,7 +20,7 @@ namespace ESM
public:
int mRefnum; // Reference number
- std::string mRefID; // ID of object being referenced
+ std::string mRefID; // ID of object being referenced (must be lowercase)
float mScale; // Scale applied to mesh
@@ -89,4 +89,4 @@ namespace ESM
};
}
-#endif \ No newline at end of file
+#endif
diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp
index 4f248cc65f..f86ad3b51b 100644
--- a/components/esm/loadinfo.cpp
+++ b/components/esm/loadinfo.cpp
@@ -10,7 +10,6 @@ namespace ESM
void DialInfo::load(ESMReader &esm)
{
- mId = esm.getHNString("INAM");
mPrev = esm.getHNString("PNAM");
mNext = esm.getHNString("NNAM");
@@ -50,8 +49,8 @@ void DialInfo::load(ESMReader &esm)
mFactionLess = false;
if (subName.val == REC_FNAM)
{
- mNpcFaction = esm.getHString();
- if (mNpcFaction == "FFFF")
+ mFaction = esm.getHString();
+ if (mFaction == "FFFF")
mFactionLess = true;
if (esm.isEmptyOrGetName())
return;
@@ -124,14 +123,13 @@ void DialInfo::load(ESMReader &esm)
void DialInfo::save(ESMWriter &esm) const
{
- esm.writeHNCString("INAM", mId);
esm.writeHNCString("PNAM", mPrev);
esm.writeHNCString("NNAM", mNext);
esm.writeHNT("DATA", mData, 12);
esm.writeHNOCString("ONAM", mActor);
esm.writeHNOCString("RNAM", mRace);
esm.writeHNOCString("CNAM", mClass);
- esm.writeHNOCString("FNAM", mNpcFaction);
+ esm.writeHNOCString("FNAM", mFaction);
esm.writeHNOCString("ANAM", mCell);
esm.writeHNOCString("DNAM", mPcFaction);
esm.writeHNOCString("SNAM", mSound);
@@ -155,4 +153,28 @@ void DialInfo::save(ESMWriter &esm) const
}
}
+ void DialInfo::blank()
+ {
+ mData.mUnknown1 = 0;
+ mData.mDisposition = 0;
+ mData.mRank = 0;
+ mData.mGender = 0;
+ mData.mPCrank = 0;
+ mData.mUnknown2 = 0;
+
+ mSelects.clear();
+ mPrev.clear();
+ mNext.clear();
+ mActor.clear();
+ mRace.clear();
+ mClass.clear();
+ mFaction.clear();
+ mPcFaction.clear();
+ mCell.clear();
+ mSound.clear();
+ mResponse.clear();
+ mResultScript.clear();
+ mFactionLess = false;
+ mQuestStatus = QS_None;
+ }
}
diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp
index 2589ea7b87..737494f6c2 100644
--- a/components/esm/loadinfo.hpp
+++ b/components/esm/loadinfo.hpp
@@ -50,10 +50,10 @@ struct DialInfo
// Journal quest indices (introduced with the quest system in Tribunal)
enum QuestStatus
{
- QS_None,
- QS_Name,
- QS_Finished,
- QS_Restart,
+ QS_None = 0,
+ QS_Name = 1,
+ QS_Finished = 2,
+ QS_Restart = 3,
QS_Deleted
};
@@ -65,7 +65,7 @@ struct DialInfo
std::string mId, mPrev, mNext;
// Various references used in determining when to select this item.
- std::string mActor, mRace, mClass, mNpcFaction, mPcFaction, mCell;
+ std::string mActor, mRace, mClass, mFaction, mPcFaction, mCell;
// Sound and text associated with this item
std::string mSound, mResponse;
@@ -102,6 +102,9 @@ struct DialInfo
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
};
}
diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp
index ede200d79d..9c97eaa4de 100644
--- a/components/esm/loadland.cpp
+++ b/components/esm/loadland.cpp
@@ -160,10 +160,10 @@ void Land::loadData(int flags)
}
mEsm->restoreContext(mContext);
- memset(mLandData->mNormals, 0, LAND_NUM_VERTS * 3);
+ memset(mLandData->mNormals, 0, sizeof(mLandData->mNormals));
if (mEsm->isNextSub("VNML")) {
- condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(VNML));
+ condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals));
}
if (mEsm->isNextSub("VHGT")) {
diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp
index 5649f99801..32abb77993 100644
--- a/components/esm/loadland.hpp
+++ b/components/esm/loadland.hpp
@@ -72,13 +72,13 @@ struct Land
};
#pragma pack(pop)
- typedef signed char VNML[LAND_NUM_VERTS * 3];
+ typedef signed char VNML;
struct LandData
{
float mHeightOffset;
float mHeights[LAND_NUM_VERTS];
- VNML mNormals;
+ VNML mNormals[LAND_NUM_VERTS * 3];
uint16_t mTextures[LAND_NUM_TEXTURES];
bool mUsingColours;
diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp
index 1a90f5b09c..f601915395 100644
--- a/components/esm/loadmgef.cpp
+++ b/components/esm/loadmgef.cpp
@@ -81,6 +81,98 @@ void MagicEffect::save(ESMWriter &esm) const
esm.writeHNOString("DESC", mDescription);
}
+short MagicEffect::getResistanceEffect(short effect)
+{
+ // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute
+
+ // <Effect, Effect providing resistance against first effect>
+ std::map<short, short> effects;
+ effects[DisintegrateArmor] = Sanctuary;
+ effects[DisintegrateWeapon] = Sanctuary;
+
+ for (int i=0; i<5; ++i)
+ effects[DrainAttribute+i] = ResistMagicka;
+ for (int i=0; i<5; ++i)
+ effects[DamageAttribute+i] = ResistMagicka;
+ for (int i=0; i<5; ++i)
+ effects[AbsorbAttribute+i] = ResistMagicka;
+ for (int i=0; i<10; ++i)
+ effects[WeaknessToFire+i] = ResistMagicka;
+
+ effects[Burden] = ResistMagicka;
+ effects[Charm] = ResistMagicka;
+ effects[Silence] = ResistMagicka;
+ effects[Blind] = ResistMagicka;
+ effects[Sound] = ResistMagicka;
+
+ for (int i=0; i<2; ++i)
+ {
+ effects[CalmHumanoid] = ResistMagicka;
+ effects[FrenzyHumanoid] = ResistMagicka;
+ effects[DemoralizeHumanoid] = ResistMagicka;
+ effects[RallyHumanoid] = ResistMagicka;
+ }
+
+ effects[TurnUndead] = ResistMagicka;
+
+ effects[FireDamage] = ResistFire;
+ effects[FrostDamage] = ResistFrost;
+ effects[ShockDamage] = ResistShock;
+ effects[Vampirism] = ResistCommonDisease;
+ effects[Corprus] = ResistCorprusDisease;
+ effects[Poison] = ResistPoison;
+ effects[Paralyze] = ResistParalysis;
+
+ if (effects.find(effect) != effects.end())
+ return effects[effect];
+ else
+ return -1;
+}
+
+short MagicEffect::getWeaknessEffect(short effect)
+{
+ std::map<short, short> effects;
+
+ for (int i=0; i<5; ++i)
+ effects[DrainAttribute+i] = WeaknessToMagicka;
+ for (int i=0; i<5; ++i)
+ effects[DamageAttribute+i] = WeaknessToMagicka;
+ for (int i=0; i<5; ++i)
+ effects[AbsorbAttribute+i] = WeaknessToMagicka;
+ for (int i=0; i<10; ++i)
+ effects[WeaknessToFire+i] = WeaknessToMagicka;
+
+ effects[Burden] = WeaknessToMagicka;
+ effects[Charm] = WeaknessToMagicka;
+ effects[Silence] = WeaknessToMagicka;
+ effects[Blind] = WeaknessToMagicka;
+ effects[Sound] = WeaknessToMagicka;
+
+ for (int i=0; i<2; ++i)
+ {
+ effects[CalmHumanoid] = WeaknessToMagicka;
+ effects[FrenzyHumanoid] = WeaknessToMagicka;
+ effects[DemoralizeHumanoid] = WeaknessToMagicka;
+ effects[RallyHumanoid] = WeaknessToMagicka;
+ }
+
+ effects[TurnUndead] = WeaknessToMagicka;
+
+ effects[FireDamage] = WeaknessToFire;
+ effects[FrostDamage] = WeaknessToFrost;
+ effects[ShockDamage] = WeaknessToShock;
+ effects[Vampirism] = WeaknessToCommonDisease;
+ effects[Corprus] = WeaknessToCorprusDisease;
+ effects[Poison] = WeaknessToPoison;
+
+ // Weakness to magicka or -1 ?
+ effects[Paralyze] = WeaknessToMagicka;
+
+ if (effects.find(effect) != effects.end())
+ return effects[effect];
+ else
+ return -1;
+}
static std::map<short,std::string> genNameMap()
{
diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp
index 9d7397a341..77056b9ec6 100644
--- a/components/esm/loadmgef.hpp
+++ b/components/esm/loadmgef.hpp
@@ -49,8 +49,9 @@ struct MagicEffect
int mSchool; // SpellSchool, see defs.hpp
float mBaseCost;
int mFlags;
- // Properties of the fired magic 'ball' I think
- int mRed, mBlue, mGreen;
+ // Glow color for enchanted items with this effect
+ int mRed, mGreen, mBlue;
+ // Properties of the fired magic 'ball'
float mSpeed, mSize, mSizeCap;
}; // 36 bytes
@@ -58,14 +59,20 @@ struct MagicEffect
static const std::string &effectIdToString(short effectID);
static short effectStringToId(const std::string &effect);
+
+ /// Returns the effect that provides resistance against \a effect (or -1 if there's none)
+ static short getResistanceEffect(short effect);
+ /// Returns the effect that induces weakness against \a effect (or -1 if there's none)
+ static short getWeaknessEffect(short effect);
+
MagnitudeDisplayType getMagnitudeDisplayType() const;
MEDTstruct mData;
std::string mIcon, mParticle; // Textures
- std::string mCasting, mHit, mArea; // Statics
- std::string mBolt; // Weapon
+ std::string mCasting, mHit, mArea; // ESM::Static
+ std::string mBolt; // ESM::Weapon
std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds
std::string mDescription;
@@ -232,7 +239,9 @@ struct MagicEffect
SummonBear = 139,
SummonBonewolf = 140,
SummonCreature04 = 141,
- SummonCreature05 = 142
+ SummonCreature05 = 142,
+
+ Length
};
};
}
diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp
index 9fff2d885d..e5b851bf0c 100644
--- a/components/esm/loadnpc.cpp
+++ b/components/esm/loadnpc.cpp
@@ -10,7 +10,7 @@ namespace ESM
void NPC::load(ESMReader &esm)
{
- mNpdt52.mGold = -10;
+ //mNpdt52.mGold = -10;
mPersistent = esm.getRecordFlags() & 0x0400;
@@ -29,12 +29,12 @@ void NPC::load(ESMReader &esm)
esm.getSubHeader();
if (esm.getSubSize() == 52)
{
- mNpdtType = 52;
+ mNpdtType = NPC_DEFAULT;
esm.getExact(&mNpdt52, 52);
}
else if (esm.getSubSize() == 12)
{
- mNpdtType = 12;
+ mNpdtType = NPC_WITH_AUTOCALCULATED_STATS;
esm.getExact(&mNpdt12, 12);
}
else
@@ -76,9 +76,9 @@ void NPC::save(ESMWriter &esm) const
esm.writeHNCString("KNAM", mHair);
esm.writeHNOCString("SCRI", mScript);
- if (mNpdtType == 52)
+ if (mNpdtType == NPC_DEFAULT)
esm.writeHNT("NPDT", mNpdt52, 52);
- else if (mNpdtType == 12)
+ else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS)
esm.writeHNT("NPDT", mNpdt12, 12);
esm.writeHNT("FLAG", mFlags);
@@ -114,7 +114,7 @@ void NPC::save(ESMWriter &esm) const
mNpdt52.mLevel = 0;
mNpdt52.mStrength = mNpdt52.mIntelligence = mNpdt52.mWillpower = mNpdt52.mAgility =
mNpdt52.mSpeed = mNpdt52.mEndurance = mNpdt52.mPersonality = mNpdt52.mLuck = 0;
- for (int i=0; i<27; ++i) mNpdt52.mSkills[i] = 0;
+ for (int i=0; i< Skill::Length; ++i) mNpdt52.mSkills[i] = 0;
mNpdt52.mReputation = 0;
mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0;
mNpdt52.mDisposition = 0;
diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp
index d9e691669c..1eac8d64fe 100644
--- a/components/esm/loadnpc.hpp
+++ b/components/esm/loadnpc.hpp
@@ -8,6 +8,7 @@
#include "loadcont.hpp"
#include "aipackage.hpp"
#include "spelllist.hpp"
+#include "loadskil.hpp"
namespace ESM {
@@ -58,6 +59,12 @@ struct NPC
Metal = 0x0800 // Metal blood effect (golden?)
};
+ enum NpcType
+ {
+ NPC_WITH_AUTOCALCULATED_STATS = 12,
+ NPC_DEFAULT = 52
+ };
+
#pragma pack(push)
#pragma pack(1)
@@ -73,7 +80,7 @@ struct NPC
mPersonality,
mLuck;
- char mSkills[27];
+ char mSkills[Skill::Length];
char mReputation;
short mHealth, mMana, mFatigue;
char mDisposition, mFactionID, mRank;
diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp
index 87a8d1d57e..262d4f6faa 100644
--- a/components/esm/loadtes3.cpp
+++ b/components/esm/loadtes3.cpp
@@ -19,7 +19,15 @@ void ESM::Header::blank()
void ESM::Header::load (ESMReader &esm)
{
- esm.getHNT (mData, "HEDR", 300);
+ if (esm.isNextSub("HEDR"))
+ {
+ esm.getSubHeader();
+ esm.getT(mData.version);
+ esm.getT(mData.type);
+ mData.author.assign(esm.getString(sizeof(mData.author.name)));
+ mData.desc.assign(esm.getString(sizeof(mData.desc.name)));
+ esm.getT(mData.records);
+ }
if (esm.isNextSub ("FORM"))
{
@@ -52,4 +60,4 @@ void ESM::Header::save (ESMWriter &esm)
esm.writeHNCString ("MAST", iter->name);
esm.writeHNT ("DATA", iter->size);
}
-} \ No newline at end of file
+}
diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp
index 75c877dc5d..761b7ca5ad 100644
--- a/components/files/configurationmanager.cpp
+++ b/components/files/configurationmanager.cpp
@@ -26,9 +26,10 @@ ConfigurationManager::ConfigurationManager()
{
setupTokensMapping();
- boost::filesystem::create_directories(mFixedPath.getUserPath());
+ boost::filesystem::create_directories(mFixedPath.getUserConfigPath());
+ boost::filesystem::create_directories(mFixedPath.getUserDataPath());
- mLogPath = mFixedPath.getUserPath();
+ mLogPath = mFixedPath.getUserConfigPath();
}
ConfigurationManager::~ConfigurationManager()
@@ -39,19 +40,19 @@ void ConfigurationManager::setupTokensMapping()
{
mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath));
mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath));
- mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserPath));
+ mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserConfigPath));
mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath));
}
void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables,
boost::program_options::options_description& description)
{
- loadConfig(mFixedPath.getUserPath(), variables, description);
+ loadConfig(mFixedPath.getUserConfigPath(), variables, description);
boost::program_options::notify(variables);
loadConfig(mFixedPath.getLocalPath(), variables, description);
boost::program_options::notify(variables);
- loadConfig(mFixedPath.getGlobalPath(), variables, description);
+ loadConfig(mFixedPath.getGlobalConfigPath(), variables, description);
boost::program_options::notify(variables);
}
@@ -141,12 +142,17 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path,
const boost::filesystem::path& ConfigurationManager::getGlobalPath() const
{
- return mFixedPath.getGlobalPath();
+ return mFixedPath.getGlobalConfigPath();
}
-const boost::filesystem::path& ConfigurationManager::getUserPath() const
+const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const
{
- return mFixedPath.getUserPath();
+ return mFixedPath.getUserConfigPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getUserDataPath() const
+{
+ return mFixedPath.getUserDataPath();
}
const boost::filesystem::path& ConfigurationManager::getLocalPath() const
diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp
index 4df8716647..35144fe04f 100644
--- a/components/files/configurationmanager.hpp
+++ b/components/files/configurationmanager.hpp
@@ -36,7 +36,7 @@ struct ConfigurationManager
/**< Fixed paths */
const boost::filesystem::path& getGlobalPath() const;
- const boost::filesystem::path& getUserPath() const;
+ const boost::filesystem::path& getUserConfigPath() const;
const boost::filesystem::path& getLocalPath() const;
const boost::filesystem::path& getGlobalDataPath() const;
diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp
deleted file mode 100644
index ce2d95f57c..0000000000
--- a/components/files/filelibrary.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-#include "filelibrary.hpp"
-
-#include <iostream>
-
-#include <boost/filesystem.hpp>
-#include <../components/misc/stringops.hpp>
-
-namespace Files
-{
- // Looks for a string in a vector of strings
- bool containsVectorString(const StringVector& list, const std::string& str)
- {
- for (StringVector::const_iterator iter = list.begin();
- iter != list.end(); ++iter)
- {
- if (*iter == str)
- return true;
- }
- return false;
- }
-
- // Searches a path and adds the results to the library
- void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict,
- const StringVector &acceptableExtensions)
- {
- if (!boost::filesystem::exists(root))
- {
- std::cout << "Warning " << root.string() << " does not exist.\n";
- return;
- }
-
- std::string fileExtension;
- std::string type;
-
- // remember the last location of the priority list when listing new items
- int length = mPriorityList.size();
-
- // First makes a list of all candidate files
- FileLister(root, mPriorityList, recursive);
-
- // Then sort these files into sections according to the folder they belong to
- for (PathContainer::iterator listIter = mPriorityList.begin() + length;
- listIter != mPriorityList.end(); ++listIter)
- {
- if( !acceptableExtensions.empty() )
- {
- fileExtension = boost::filesystem::path (listIter->extension()).string();
- Misc::StringUtils::toLower(fileExtension);
- if(!containsVectorString(acceptableExtensions, fileExtension))
- continue;
- }
-
- type = boost::filesystem::path (listIter->parent_path().leaf()).string();
- if (!strict)
- Misc::StringUtils::toLower(type);
-
- mMap[type].push_back(*listIter);
- // std::cout << "Added path: " << listIter->string() << " in section "<< type <<std::endl;
- }
- }
-
- // Returns true if the named section exists
- bool FileLibrary::containsSection(std::string sectionName, bool strict)
- {
- if (!strict)
- Misc::StringUtils::toLower(sectionName);
- StringPathContMap::const_iterator mapIter = mMap.find(sectionName);
- if (mapIter == mMap.end())
- return false;
- else
- return true;
- }
-
- // Returns a pointer to const for a section of the library
- const PathContainer* FileLibrary::section(std::string sectionName, bool strict)
- {
- if (!strict)
- Misc::StringUtils::toLower(sectionName);
- StringPathContMap::const_iterator mapIter = mMap.find(sectionName);
- if (mapIter == mMap.end())
- {
- //std::cout << "Empty\n";
- return &mEmptyPath;
- }
- else
- {
- return &(mapIter->second);
- }
- }
-
- // Searches the library for an item and returns a boost path to it
- boost::filesystem::path FileLibrary::locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName)
- {
- boost::filesystem::path result("");
- if (sectionName == "")
- {
- return FileListLocator(mPriorityList, boost::filesystem::path(item), strict, ignoreExtensions);
- }
- else
- {
- if (!containsSection(sectionName, strict))
- {
- std::cout << "Warning: There is no section named " << sectionName << "\n";
- return result;
- }
- result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict, ignoreExtensions);
- }
- return result;
- }
-
- // Prints all the available sections, used for debugging
- void FileLibrary::printSections()
- {
- for(StringPathContMap::const_iterator mapIter = mMap.begin();
- mapIter != mMap.end(); ++mapIter)
- {
- std::cout << mapIter->first <<std::endl;
- }
- }
-}
diff --git a/components/files/filelibrary.hpp b/components/files/filelibrary.hpp
deleted file mode 100644
index 6abe972690..0000000000
--- a/components/files/filelibrary.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef COMPONENTS_FILES_FILELIBRARY_HPP
-#define COMPONENTS_FILES_FILELIBRARY_HPP
-
-#include <components/files/fileops.hpp>
-
-namespace Files
-{
- typedef std::map<std::string, PathContainer> StringPathContMap;
- typedef std::vector<std::string> StringVector;
-
- /// Looks for a string in a vector of strings
- bool containsVectorString(const StringVector& list, const std::string& str);
-
- /// \brief Searches directories and makes lists of files according to folder name
- class FileLibrary
- {
- private:
- StringPathContMap mMap;
- PathContainer mEmptyPath;
- PathContainer mPriorityList;
-
- public:
- /// Searches a path and adds the results to the library
- /// Recursive search and fs strict options are available
- /// Takes a vector of acceptable files extensions, if none is given it lists everything.
- void add(const boost::filesystem::path &root, bool recursive, bool strict,
- const StringVector &acceptableExtensions);
-
- /// Returns true if the named section exists
- /// You can run this check before running section()
- bool containsSection(std::string sectionName, bool strict);
-
- /// Returns a pointer to const for a section of the library
- /// which is essentially a PathContainer.
- /// If the section does not exists it returns a pointer to an empty path.
- const PathContainer* section(std::string sectionName, bool strict);
-
- /// Searches the library for an item and returns a boost path to it
- /// Optionally you can provide a specific section
- /// The result is the first that comes up according to alphabetical
- /// section naming
- boost::filesystem::path locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName="");
-
- /// Prints all the available sections, used for debugging
- void printSections();
- };
-}
-
-#endif
diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp
deleted file mode 100644
index fbc2eef056..0000000000
--- a/components/files/fileops.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-#include "fileops.hpp"
-
-#include <iostream>
-
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string.hpp>
-#include <../components/misc/stringops.hpp>
-
-namespace Files
-{
-
-bool isFile(const char *name)
-{
- return boost::filesystem::exists(boost::filesystem::path(name));
-}
-
- // Returns true if the last part of the superset matches the subset
- bool endingMatches(const std::string& superset, const std::string& subset)
- {
- if (subset.length() > superset.length())
- return false;
- return superset.substr(superset.length() - subset.length()) == subset;
- }
-
- // Makes a list of files from a directory
- void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive)
- {
- if (!boost::filesystem::exists(currentPath))
- {
- std::cout << "WARNING: " << currentPath.string() << " does not exist.\n";
- return ;
- }
- if (recursive)
- {
- for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string());
- itr != end; ++itr )
- {
- if ( boost::filesystem::is_regular_file(*itr))
- list.push_back(itr->path());
- }
- }
- else
- {
- for ( boost::filesystem::directory_iterator end, itr(currentPath.string());
- itr != end; ++itr )
- {
- if ( boost::filesystem::is_regular_file(*itr))
- list.push_back(itr->path());
- }
- }
- }
-
- // Locates path in path container
- boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind,
- bool strict, bool ignoreExtensions)
- {
- boost::filesystem::path result("");
- if (list.empty())
- return result;
-
- std::string toFindStr;
- if (ignoreExtensions)
- toFindStr = boost::filesystem::basename(toFind);
- else
- toFindStr = toFind.string();
-
- std::string fullPath;
-
- // The filesystems slash sets the default slash
- std::string slash;
- std::string wrongslash;
- if(list[0].string().find("\\") != std::string::npos)
- {
- slash = "\\";
- wrongslash = "/";
- }
- else
- {
- slash = "/";
- wrongslash = "\\";
- }
-
- // The file being looked for is converted to the new slash
- if(toFindStr.find(wrongslash) != std::string::npos )
- {
- boost::replace_all(toFindStr, wrongslash, slash);
- }
-
- if (!strict)
- {
- Misc::StringUtils::toLower(toFindStr);
- }
-
- for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it)
- {
- fullPath = it->string();
- if (ignoreExtensions)
- fullPath.erase(fullPath.length() -
- boost::filesystem::path (it->extension()).string().length());
-
- if (!strict)
- {
- Misc::StringUtils::toLower(fullPath);
- }
- if(endingMatches(fullPath, toFindStr))
- {
- result = *it;
- break;
- }
- }
- return result;
- }
-
- // Overloaded form of the locator that takes a string and returns a string
- std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions)
- {
- return FileListLocator(list, boost::filesystem::path(toFind), strict, ignoreExtensions).string();
- }
-
-}
diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp
deleted file mode 100644
index bf1c51485f..0000000000
--- a/components/files/fileops.hpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#ifndef COMPONENTS_FILES_FILEOPS_HPP
-#define COMPONENTS_FILES_FILEOPS_HPP
-
-#include <map>
-#include <vector>
-#include <string>
-
-#include <boost/filesystem/path.hpp>
-
-namespace Files
-{
-
-///\brief Check if a given path is an existing file (not a directory)
-///\param [in] name - filename
-bool isFile(const char *name);
-
- /// A vector of Boost Paths, very handy
- typedef std::vector<boost::filesystem::path> PathContainer;
-
- /// Makes a list of files from a directory by taking a boost
- /// path and a Path Container and adds to the Path container
- /// all files in the path. It has a recursive option.
- void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive);
-
- /// Locates boost path in path container
- /// returns the path from the container
- /// that contains the searched path.
- /// If it's not found it returns and empty path
- /// Takes care of slashes, backslashes and it has a strict option.
- boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind,
- bool strict, bool ignoreExtensions);
-
- /// Overloaded form of the locator that takes a string and returns a string
- std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions);
-
-}
-
-#endif /* COMPONENTS_FILES_FILEOPS_HPP */
diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp
index a309dc9fb6..cfd3458ce1 100644
--- a/components/files/fixedpath.hpp
+++ b/components/files/fixedpath.hpp
@@ -48,8 +48,9 @@ struct FixedPath
*/
FixedPath(const std::string& application_name)
: mPath(application_name + "/")
- , mUserPath(mPath.getUserPath())
- , mGlobalPath(mPath.getGlobalPath())
+ , mUserConfigPath(mPath.getUserConfigPath())
+ , mUserDataPath(mPath.getUserDataPath())
+ , mGlobalConfigPath(mPath.getGlobalConfigPath())
, mLocalPath(mPath.getLocalPath())
, mGlobalDataPath(mPath.getGlobalDataPath())
, mInstallPath(mPath.getInstallPath())
@@ -59,28 +60,27 @@ struct FixedPath
/**
* \brief Return path pointing to the user local configuration directory.
- *
- * \return boost::filesystem::path
*/
- const boost::filesystem::path& getUserPath() const
+ const boost::filesystem::path& getUserConfigPath() const
+ {
+ return mUserConfigPath;
+ }
+
+ const boost::filesystem::path& getUserDataPath() const
{
- return mUserPath;
+ return mUserDataPath;
}
/**
* \brief Return path pointing to the global (system) configuration directory.
- *
- * \return boost::filesystem::path
*/
- const boost::filesystem::path& getGlobalPath() const
+ const boost::filesystem::path& getGlobalConfigPath() const
{
- return mGlobalPath;
+ return mGlobalConfigPath;
}
/**
* \brief Return path pointing to the directory where application was started.
- *
- * \return boost::filesystem::path
*/
const boost::filesystem::path& getLocalPath() const
{
@@ -105,8 +105,9 @@ struct FixedPath
private:
PathType mPath;
- boost::filesystem::path mUserPath; /**< User path */
- boost::filesystem::path mGlobalPath; /**< Global path */
+ boost::filesystem::path mUserConfigPath; /**< User path */
+ boost::filesystem::path mUserDataPath;
+ boost::filesystem::path mGlobalConfigPath; /**< Global path */
boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */
boost::filesystem::path mGlobalDataPath; /**< Global application data path */
diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp
index c974a91d35..d285f4229c 100644
--- a/components/files/linuxpath.cpp
+++ b/components/files/linuxpath.cpp
@@ -8,6 +8,39 @@
#include <unistd.h>
#include <boost/filesystem/fstream.hpp>
+
+namespace
+{
+ boost::filesystem::path getUserHome()
+ {
+ const char* dir = getenv("HOME");
+ if (dir == NULL)
+ {
+ struct passwd* pwd = getpwuid(getuid());
+ if (pwd != NULL)
+ {
+ dir = pwd->pw_dir;
+ }
+ }
+ if (dir == NULL)
+ return boost::filesystem::path();
+ else
+ return boost::filesystem::path(dir);
+ }
+
+ boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback)
+ {
+ const char* result = getenv(envVariable.c_str());
+ if (!result)
+ return fallback;
+ boost::filesystem::path dir(result);
+ if (dir.empty())
+ return fallback;
+ else
+ return dir;
+ }
+}
+
/**
* \namespace Files
*/
@@ -19,51 +52,22 @@ LinuxPath::LinuxPath(const std::string& application_name)
{
}
-boost::filesystem::path LinuxPath::getUserPath() const
+boost::filesystem::path LinuxPath::getUserConfigPath() const
{
- boost::filesystem::path userPath(".");
-
- const char* theDir = getenv("HOME");
- if (theDir == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- theDir = pwd->pw_dir;
- }
- }
-
- if (theDir != NULL)
- {
- userPath = boost::filesystem::path(theDir);
- }
+ return getEnv("XDG_CONFIG_HOME", getUserHome() / ".config") / mName;
+}
- return userPath / ".config" / mName;
+boost::filesystem::path LinuxPath::getUserDataPath() const
+{
+ return getEnv("XDG_DATA_HOME", getUserHome() / ".local/share") / mName;
}
boost::filesystem::path LinuxPath::getCachePath() const
{
- boost::filesystem::path userPath(".");
-
- const char* theDir = getenv("HOME");
- if (theDir == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- theDir = pwd->pw_dir;
- }
- }
-
- if (theDir != NULL)
- {
- userPath = boost::filesystem::path(theDir);
- }
-
- return userPath / ".cache" / mName;
+ return getEnv("XDG_CACHE_HOME", getUserHome() / ".cache") / mName;
}
-boost::filesystem::path LinuxPath::getGlobalPath() const
+boost::filesystem::path LinuxPath::getGlobalConfigPath() const
{
boost::filesystem::path globalPath("/etc/");
return globalPath / mName;
@@ -84,17 +88,9 @@ boost::filesystem::path LinuxPath::getInstallPath() const
{
boost::filesystem::path installPath;
- char *homePath = getenv("HOME");
- if (homePath == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- homePath = pwd->pw_dir;
- }
- }
+ boost::filesystem::path homePath = getUserHome();
- if (homePath != NULL)
+ if (!homePath.empty())
{
boost::filesystem::path wineDefaultRegistry(homePath);
wineDefaultRegistry /= ".wine/system.reg";
diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp
index 6acf2a2d5f..b710165b44 100644
--- a/components/files/linuxpath.hpp
+++ b/components/files/linuxpath.hpp
@@ -20,44 +20,34 @@ struct LinuxPath
/**
* \brief Return path to the user directory.
- *
- * \return boost::filesystem::path
*/
- boost::filesystem::path getUserPath() const;
+ boost::filesystem::path getUserConfigPath() const;
+
+ boost::filesystem::path getUserDataPath() const;
/**
- * \brief Return path to the global (system) directory where game files could be placed.
- *
- * \return boost::filesystem::path
+ * \brief Return path to the global (system) directory where config files can be placed.
*/
- boost::filesystem::path getGlobalPath() const;
+ boost::filesystem::path getGlobalConfigPath() const;
/**
* \brief Return path to the runtime configuration directory which is the
* place where an application was started.
- *
- * \return boost::filesystem::path
*/
boost::filesystem::path getLocalPath() const;
/**
- * \brief
- *
- * \return boost::filesystem::path
+ * \brief Return path to the global (system) directory where game files can be placed.
*/
boost::filesystem::path getGlobalDataPath() const;
/**
* \brief
- *
- * \return boost::filesystem::path
*/
boost::filesystem::path getCachePath() const;
/**
* \brief Gets the path of the installed Morrowind version if there is one.
- *
- * \return boost::filesystem::path
*/
boost::filesystem::path getInstallPath() const;
diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp
index 9edcd6ef2a..3e53f53061 100644
--- a/components/files/macospath.cpp
+++ b/components/files/macospath.cpp
@@ -11,9 +11,26 @@
* FIXME: Someone with MacOS system should check this and correct if necessary
*/
-/**
- * \namespace Files
- */
+namespace
+{
+ boost::filesystem::path getUserHome()
+ {
+ const char* dir = getenv("HOME");
+ if (dir == NULL)
+ {
+ struct passwd* pwd = getpwuid(getuid());
+ if (pwd != NULL)
+ {
+ dir = pwd->pw_dir;
+ }
+ }
+ if (dir == NULL)
+ return boost::filesystem::path();
+ else
+ return boost::filesystem::path(dir);
+ }
+}
+
namespace Files
{
@@ -22,28 +39,24 @@ MacOsPath::MacOsPath(const std::string& application_name)
{
}
-boost::filesystem::path MacOsPath::getUserPath() const
+boost::filesystem::path MacOsPath::getUserConfigPath() const
{
- boost::filesystem::path userPath(".");
+ boost::filesystem::path userPath (getUserHome());
+ userPath /= "Library/Preferences/";
- const char* theDir = getenv("HOME");
- if (theDir == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- theDir = pwd->pw_dir;
- }
- }
- if (theDir != NULL)
- {
- userPath = boost::filesystem::path(theDir) / "Library/Preferences/";
- }
+ return userPath / mName;
+}
+
+boost::filesystem::path MacOsPath::getUserDataPath() const
+{
+ // TODO: probably wrong?
+ boost::filesystem::path userPath (getUserHome());
+ userPath /= "Library/Preferences/";
return userPath / mName;
}
-boost::filesystem::path MacOsPath::getGlobalPath() const
+boost::filesystem::path MacOsPath::getGlobalConfigPath() const
{
boost::filesystem::path globalPath("/Library/Preferences/");
return globalPath / mName;
@@ -51,23 +64,9 @@ boost::filesystem::path MacOsPath::getGlobalPath() const
boost::filesystem::path MacOsPath::getCachePath() const
{
- boost::filesystem::path userPath(".");
-
- const char* theDir = getenv("HOME");
- if (theDir == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- theDir = pwd->pw_dir;
- }
- }
- if (theDir != NULL)
- {
- userPath = boost::filesystem::path(theDir) / "Library/Caches" / mName;
- }
-
- return userPath;
+ boost::filesystem::path userPath (getUserHome());
+ userPath /= "Library/Caches";
+ return userPath / mName;
}
boost::filesystem::path MacOsPath::getLocalPath() const
@@ -85,17 +84,9 @@ boost::filesystem::path MacOsPath::getInstallPath() const
{
boost::filesystem::path installPath;
- char *homePath = getenv("HOME");
- if (homePath == NULL)
- {
- struct passwd* pwd = getpwuid(getuid());
- if (pwd != NULL)
- {
- homePath = pwd->pw_dir;
- }
- }
+ boost::filesystem::path homePath = getUserHome();
- if (homePath != NULL)
+ if (!homePath.empty())
{
boost::filesystem::path wineDefaultRegistry(homePath);
wineDefaultRegistry /= ".wine/system.reg";
diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp
index 576ec16812..7a7dc55778 100644
--- a/components/files/macospath.hpp
+++ b/components/files/macospath.hpp
@@ -23,14 +23,16 @@ struct MacOsPath
*
* \return boost::filesystem::path
*/
- boost::filesystem::path getUserPath() const;
+ boost::filesystem::path getUserConfigPath() const;
+
+ boost::filesystem::path getUserDataPath() const;
/**
* \brief Return path to the global (system) directory.
*
* \return boost::filesystem::path
*/
- boost::filesystem::path getGlobalPath() const;
+ boost::filesystem::path getGlobalConfigPath() const;
/**
* \brief Return path to the runtime directory which is the
diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp
index e8f1a2b08f..ea1ca56d3c 100644
--- a/components/files/windowspath.cpp
+++ b/components/files/windowspath.cpp
@@ -25,7 +25,7 @@ WindowsPath::WindowsPath(const std::string& application_name)
{
}
-boost::filesystem::path WindowsPath::getUserPath() const
+boost::filesystem::path WindowsPath::getUserConfigPath() const
{
boost::filesystem::path userPath(".");
@@ -41,7 +41,13 @@ boost::filesystem::path WindowsPath::getUserPath() const
return userPath / mName;
}
-boost::filesystem::path WindowsPath::getGlobalPath() const
+boost::filesystem::path WindowsPath::getUserDataPath() const
+{
+ // Have some chaos, windows people!
+ return getUserConfigPath();
+}
+
+boost::filesystem::path WindowsPath::getGlobalConfigPath() const
{
boost::filesystem::path globalPath(".");
@@ -63,12 +69,12 @@ boost::filesystem::path WindowsPath::getLocalPath() const
boost::filesystem::path WindowsPath::getGlobalDataPath() const
{
- return getGlobalPath();
+ return getGlobalConfigPath();
}
boost::filesystem::path WindowsPath::getCachePath() const
{
- return getUserPath() / "cache";
+ return getUserConfigPath() / "cache";
}
boost::filesystem::path WindowsPath::getInstallPath() const
diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp
index 6044b67c21..31d0e0e7c1 100644
--- a/components/files/windowspath.hpp
+++ b/components/files/windowspath.hpp
@@ -29,14 +29,16 @@ struct WindowsPath
*
* \return boost::filesystem::path
*/
- boost::filesystem::path getUserPath() const;
+ boost::filesystem::path getUserConfigPath() const;
+
+ boost::filesystem::path getUserDataPath() const;
/**
* \brief Returns "X:\Program Files\"
*
* \return boost::filesystem::path
*/
- boost::filesystem::path getGlobalPath() const;
+ boost::filesystem::path getGlobalConfigPath() const;
/**
* \brief Return local path which is a location where
diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp
index 10937e6bca..ea1e9fc912 100644
--- a/components/interpreter/interpreter.cpp
+++ b/components/interpreter/interpreter.cpp
@@ -166,31 +166,37 @@ namespace Interpreter
void Interpreter::installSegment0 (int code, Opcode1 *opcode)
{
+ assert(mSegment0.find(code) == mSegment0.end());
mSegment0.insert (std::make_pair (code, opcode));
}
void Interpreter::installSegment1 (int code, Opcode2 *opcode)
{
+ assert(mSegment1.find(code) == mSegment1.end());
mSegment1.insert (std::make_pair (code, opcode));
}
void Interpreter::installSegment2 (int code, Opcode1 *opcode)
{
+ assert(mSegment2.find(code) == mSegment2.end());
mSegment2.insert (std::make_pair (code, opcode));
}
void Interpreter::installSegment3 (int code, Opcode1 *opcode)
{
+ assert(mSegment3.find(code) == mSegment3.end());
mSegment3.insert (std::make_pair (code, opcode));
}
void Interpreter::installSegment4 (int code, Opcode2 *opcode)
{
+ assert(mSegment4.find(code) == mSegment4.end());
mSegment4.insert (std::make_pair (code, opcode));
}
void Interpreter::installSegment5 (int code, Opcode0 *opcode)
{
+ assert(mSegment5.find(code) == mSegment5.end());
mSegment5.insert (std::make_pair (code, opcode));
}
diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp
index 19a0688b26..5e9dde2514 100644
--- a/components/misc/utf8stream.hpp
+++ b/components/misc/utf8stream.hpp
@@ -14,12 +14,12 @@ public:
static UnicodeChar sBadChar () { return UnicodeChar (0xFFFFFFFF); }
Utf8Stream (Point begin, Point end) :
- cur (begin), nxt (begin), end (end)
+ cur (begin), nxt (begin), end (end), val(Utf8Stream::sBadChar())
{
}
Utf8Stream (std::pair <Point, Point> range) :
- cur (range.first), nxt (range.first), end (range.second)
+ cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar())
{
}
diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp
index 011e0e4452..e44f4a6f30 100644
--- a/components/nif/controller.hpp
+++ b/components/nif/controller.hpp
@@ -306,7 +306,7 @@ public:
class NiFlipController : public Controller
{
public:
- int mTexSlot;
+ int mTexSlot; // NiTexturingProperty::TextureType
float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources
NiSourceTextureList mSources;
diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp
index 402eadefb4..0f7e658fb8 100644
--- a/components/nif/niffile.cpp
+++ b/components/nif/niffile.cpp
@@ -210,7 +210,7 @@ static const RecordFactoryEntry recordFactories [] = {
{ "AvoidNode", &construct <NiNode >, RC_AvoidNode },
{ "NiBSParticleNode", &construct <NiNode >, RC_NiBSParticleNode },
{ "NiBSAnimationNode", &construct <NiNode >, RC_NiBSAnimationNode },
- { "NiBillboardNode", &construct <NiNode >, RC_NiNode },
+ { "NiBillboardNode", &construct <NiNode >, RC_NiBillboardNode },
{ "NiTriShape", &construct <NiTriShape >, RC_NiTriShape },
{ "NiRotatingParticles", &construct <NiRotatingParticles >, RC_NiRotatingParticles },
{ "NiAutoNormalParticles", &construct <NiAutoNormalParticles >, RC_NiAutoNormalParticles },
diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp
index 6e629772e6..91ae93b40b 100644
--- a/components/nif/niffile.hpp
+++ b/components/nif/niffile.hpp
@@ -200,7 +200,7 @@ struct KeyListT {
}
}
else
- nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType));
+ nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType));
}
};
typedef KeyListT<float,&NIFStream::getFloat> FloatKeyList;
diff --git a/components/nif/node.hpp b/components/nif/node.hpp
index 917bc8add3..6816a79a2e 100644
--- a/components/nif/node.hpp
+++ b/components/nif/node.hpp
@@ -137,7 +137,8 @@ struct NiNode : Node
AnimFlag_AutoPlay = 0x0020
};
enum BSParticleFlags {
- ParticleFlag_AutoPlay = 0x0020
+ ParticleFlag_AutoPlay = 0x0020,
+ ParticleFlag_LocalSpace = 0x0080
};
void read(NIFStream *nif)
diff --git a/components/nif/record.hpp b/components/nif/record.hpp
index 87e342dca5..079b335f05 100644
--- a/components/nif/record.hpp
+++ b/components/nif/record.hpp
@@ -36,6 +36,7 @@ enum RecordType
{
RC_MISSING = 0,
RC_NiNode,
+ RC_NiBillboardNode,
RC_AvoidNode,
RC_NiTriShape,
RC_NiRotatingParticles,
diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp
new file mode 100644
index 0000000000..6d7f6ab3fb
--- /dev/null
+++ b/components/nifogre/controller.hpp
@@ -0,0 +1,95 @@
+#ifndef COMPONENTS_NIFOGRE_CONTROLLER_H
+#define COMPONENTS_NIFOGRE_CONTROLLER_H
+
+#include <components/nif/niffile.hpp>
+#include <OgreController.h>
+
+namespace NifOgre
+{
+
+ class ValueInterpolator
+ {
+ protected:
+ float interpKey(const Nif::FloatKeyList::VecType &keys, float time, float def=0.f) const
+ {
+ if (keys.size() == 0)
+ return def;
+
+ if(time <= keys.front().mTime)
+ return keys.front().mValue;
+
+ Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1);
+ for(;iter != keys.end();iter++)
+ {
+ if(iter->mTime < time)
+ continue;
+
+ Nif::FloatKeyList::VecType::const_iterator last(iter-1);
+ float a = (time-last->mTime) / (iter->mTime-last->mTime);
+ return last->mValue + ((iter->mValue - last->mValue)*a);
+ }
+ return keys.back().mValue;
+ }
+
+ Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) const
+ {
+ if(time <= keys.front().mTime)
+ return keys.front().mValue;
+
+ Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1);
+ for(;iter != keys.end();iter++)
+ {
+ if(iter->mTime < time)
+ continue;
+
+ Nif::Vector3KeyList::VecType::const_iterator last(iter-1);
+ float a = (time-last->mTime) / (iter->mTime-last->mTime);
+ return last->mValue + ((iter->mValue - last->mValue)*a);
+ }
+ return keys.back().mValue;
+ }
+ };
+
+ // FIXME: Should not be here.
+ class DefaultFunction : public Ogre::ControllerFunction<Ogre::Real>
+ {
+ private:
+ float mFrequency;
+ float mPhase;
+ float mStartTime;
+ public:
+ float mStopTime;
+
+ public:
+ DefaultFunction(const Nif::Controller *ctrl, bool deltaInput)
+ : Ogre::ControllerFunction<Ogre::Real>(deltaInput)
+ , mFrequency(ctrl->frequency)
+ , mPhase(ctrl->phase)
+ , mStartTime(ctrl->timeStart)
+ , mStopTime(ctrl->timeStop)
+ {
+ if(mDeltaInput)
+ mDeltaCount = mPhase;
+ }
+
+ virtual Ogre::Real calculate(Ogre::Real value)
+ {
+ if(mDeltaInput)
+ {
+ mDeltaCount += value*mFrequency;
+ if(mDeltaCount < mStartTime)
+ mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount,
+ mStopTime - mStartTime);
+ mDeltaCount = std::fmod(mDeltaCount - mStartTime,
+ mStopTime - mStartTime) + mStartTime;
+ return mDeltaCount;
+ }
+
+ value = std::min(mStopTime, std::max(mStartTime, value+mPhase));
+ return value;
+ }
+ };
+
+}
+
+#endif
diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp
index 55f064c555..8ae86b64a8 100644
--- a/components/nifogre/material.cpp
+++ b/components/nifogre/material.cpp
@@ -152,7 +152,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
Nif::ControllerPtr ctrls = texprop->controller;
while(!ctrls.empty())
{
- warn("Unhandled texture controller "+ctrls->recName+" in "+name);
+ if (ctrls->recType != Nif::RC_NiFlipController) // Handled in ogrenifloader
+ warn("Unhandled texture controller "+ctrls->recName+" in "+name);
ctrls = ctrls->next;
}
}
@@ -237,7 +238,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
Nif::ControllerPtr ctrls = matprop->controller;
while(!ctrls.empty())
{
- warn("Unhandled material controller "+ctrls->recName+" in "+name);
+ if (ctrls->recType != Nif::RC_NiAlphaController && ctrls->recType != Nif::RC_NiMaterialColorController)
+ warn("Unhandled material controller "+ctrls->recName+" in "+name);
ctrls = ctrls->next;
}
}
@@ -323,6 +325,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture]));
instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture]));
instance->setProperty("emissiveMap", sh::makeProperty(texName[Nif::NiTexturingProperty::GlowTexture]));
+ instance->setProperty("darkMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DarkTexture]));
+ if (!texName[Nif::NiTexturingProperty::BaseTexture].empty())
+ {
+ instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true)));
+ instance->setProperty("diffuseMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::BaseTexture].uvSet)));
+ }
if (!texName[Nif::NiTexturingProperty::GlowTexture].empty())
{
instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true)));
@@ -333,30 +341,44 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true)));
instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet)));
}
+ if (!texName[Nif::NiTexturingProperty::DarkTexture].empty())
+ {
+ instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true)));
+ instance->setProperty("darkMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DarkTexture].uvSet)));
+ }
+
+ bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty()
+ && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos;
+ instance->setProperty("use_parallax", sh::makeProperty(new sh::BooleanValue(useParallax)));
for(int i = 0;i < 7;i++)
{
if(i == Nif::NiTexturingProperty::BaseTexture ||
i == Nif::NiTexturingProperty::DetailTexture ||
+ i == Nif::NiTexturingProperty::DarkTexture ||
i == Nif::NiTexturingProperty::BumpTexture ||
i == Nif::NiTexturingProperty::GlowTexture)
continue;
if(!texName[i].empty())
- warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i));
+ warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i) + " in " + name);
}
if (vertexColour)
instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true)));
- // Add transparency if NiAlphaProperty was present
- NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]);
- if (result.first)
+ // Override alpha flags based on our override list (transparency-overrides.cfg)
+ if (!texName[0].empty())
{
- alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */
- alphaTest = result.second;
- depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on
+ NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]);
+ if (result.first)
+ {
+ alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */
+ alphaTest = result.second;
+ depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on
+ }
}
+ // Add transparency if NiAlphaProperty was present
if((alphaFlags&1))
{
std::string blend_mode;
@@ -385,7 +407,6 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off")));
// depth_func???
- sh::Factory::getInstance()._ensureMaterial(name, "Default");
return name;
}
diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp
index 8843ac6c6c..b02c7c2366 100644
--- a/components/nifogre/material.hpp
+++ b/components/nifogre/material.hpp
@@ -37,9 +37,9 @@ class NIFMaterialLoader {
static std::map<size_t,std::string> sMaterialMap;
+public:
static std::string findTextureName(const std::string &filename);
-public:
static Ogre::String getMaterial(const Nif::ShapeData *shapedata,
const Ogre::String &name, const Ogre::String &group,
const Nif::NiTexturingProperty *texprop,
diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp
index ca92f62d49..80e377a49f 100644
--- a/components/nifogre/mesh.cpp
+++ b/components/nifogre/mesh.cpp
@@ -116,19 +116,6 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape
Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC;
bool vertShadowBuffer = false;
- if(!shape->controller.empty())
- {
- Nif::ControllerPtr ctrl = shape->controller;
- do {
- if(ctrl->recType == Nif::RC_NiGeomMorpherController)
- {
- vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY;
- vertShadowBuffer = true;
- break;
- }
- } while(!(ctrl=ctrl->next).empty());
- }
-
if(skin != NULL)
{
vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY;
@@ -347,6 +334,40 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape
if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest))
mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest);
}
+
+
+ if(!shape->controller.empty())
+ {
+ Nif::ControllerPtr ctrl = shape->controller;
+ do {
+ // Load GeomMorpherController into an Ogre::Pose and Animation
+ if(ctrl->recType == Nif::RC_NiGeomMorpherController)
+ {
+ const Nif::NiGeomMorpherController *geom =
+ static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
+
+ const std::vector<Nif::NiMorphData::MorphData>& morphs = geom->data.getPtr()->mMorphs;
+ // Note we are not interested in morph 0, which just contains the original vertices
+ for (unsigned int i = 1; i < morphs.size(); ++i)
+ {
+ Ogre::Pose* pose = mesh->createPose(i);
+ const Nif::NiMorphData::MorphData& data = morphs[i];
+ for (unsigned int v = 0; v < data.mVertices.size(); ++v)
+ pose->addVertex(v, data.mVertices[v]);
+
+ Ogre::String animationID = Ogre::StringConverter::toString(ctrl->recIndex)
+ + "_" + Ogre::StringConverter::toString(i);
+ Ogre::VertexAnimationTrack* track =
+ mesh->createAnimation(animationID, 0)
+ ->createVertexTrack(1, Ogre::VAT_POSE);
+ Ogre::VertexPoseKeyFrame* keyframe = track->createVertexPoseKeyFrame(0);
+ keyframe->addPoseReference(i-1, 1);
+ }
+
+ break;
+ }
+ } while(!(ctrl=ctrl->next).empty());
+ }
}
diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp
index 3bb9ea2309..63e9057664 100644
--- a/components/nifogre/ogrenifloader.cpp
+++ b/components/nifogre/ogrenifloader.cpp
@@ -37,53 +37,253 @@
#include <OgreSkeletonManager.h>
#include <OgreControllerManager.h>
+#include <extern/shiny/Main/Factory.hpp>
+
#include <components/nif/node.hpp>
#include <components/misc/stringops.hpp>
#include "skeleton.hpp"
#include "material.hpp"
#include "mesh.hpp"
+#include "controller.hpp"
namespace NifOgre
{
-// FIXME: Should not be here.
-class DefaultFunction : public Ogre::ControllerFunction<Ogre::Real>
+Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable)
{
-private:
- float mFrequency;
- float mPhase;
- float mStartTime;
- float mStopTime;
+ if (mClonedMaterials.find(movable) != mClonedMaterials.end())
+ return mClonedMaterials[movable];
-public:
- DefaultFunction(const Nif::Controller *ctrl, bool deltaInput)
- : Ogre::ControllerFunction<Ogre::Real>(deltaInput)
- , mFrequency(ctrl->frequency)
- , mPhase(ctrl->phase)
- , mStartTime(ctrl->timeStart)
- , mStopTime(ctrl->timeStop)
+ else
+ {
+ Ogre::MaterialPtr mat;
+ if (Ogre::Entity* ent = dynamic_cast<Ogre::Entity*>(movable))
+ mat = ent->getSubEntity(0)->getMaterial();
+ else if (Ogre::ParticleSystem* partSys = dynamic_cast<Ogre::ParticleSystem*>(movable))
+ mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName());
+
+ static int count=0;
+ Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++);
+ sh::Factory::getInstance().createMaterialInstance(newName, mat->getName());
+ // Make sure techniques are created
+ sh::Factory::getInstance()._ensureMaterial(newName, "Default");
+ mat = Ogre::MaterialManager::getSingleton().getByName(newName);
+
+ mClonedMaterials[movable] = mat;
+
+ if (Ogre::Entity* ent = dynamic_cast<Ogre::Entity*>(movable))
+ ent->getSubEntity(0)->setMaterial(mat);
+ else if (Ogre::ParticleSystem* partSys = dynamic_cast<Ogre::ParticleSystem*>(movable))
+ partSys->setMaterialName(mat->getName());
+
+ return mat;
+ }
+}
+
+MaterialControllerManager::~MaterialControllerManager()
+{
+ for (std::map<Ogre::MovableObject*, Ogre::MaterialPtr>::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it)
+ {
+ sh::Factory::getInstance().destroyMaterialInstance(it->second->getName());
+ }
+}
+
+ObjectScene::~ObjectScene()
+{
+ for(size_t i = 0;i < mLights.size();i++)
+ {
+ Ogre::Light *light = mLights[i];
+ // If parent is a scene node, it was created specifically for this light. Destroy it now.
+ if(light->isAttached() && !light->isParentTagPoint())
+ mSceneMgr->destroySceneNode(light->getParentSceneNode());
+ mSceneMgr->destroyLight(light);
+ }
+ for(size_t i = 0;i < mParticles.size();i++)
+ mSceneMgr->destroyParticleSystem(mParticles[i]);
+ for(size_t i = 0;i < mEntities.size();i++)
+ mSceneMgr->destroyEntity(mEntities[i]);
+ mControllers.clear();
+ mLights.clear();
+ mParticles.clear();
+ mEntities.clear();
+ mSkelBase = NULL;
+}
+
+void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera)
+{
+ for (std::vector<Ogre::Node*>::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it)
{
- if(mDeltaInput)
- mDeltaCount = mPhase;
+ assert(mSkelBase);
+ Ogre::Node* node = *it;
+ node->_setDerivedOrientation(mSkelBase->getParentNode()->_getDerivedOrientation().Inverse() *
+ camera->getRealOrientation());
}
+}
- virtual Ogre::Real calculate(Ogre::Real value)
+// Animates a texture
+class FlipController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>
{
- if(mDeltaInput)
+ private:
+ Ogre::MovableObject* mMovable;
+ int mTexSlot;
+ float mDelta;
+ std::vector<std::string> mTextures;
+ MaterialControllerManager* mMaterialControllerMgr;
+
+ public:
+ Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr)
+ : mMovable(movable)
+ , mMaterialControllerMgr(materialControllerMgr)
{
- mDeltaCount += value*mFrequency;
- if(mDeltaCount < mStartTime)
- mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount,
- mStopTime - mStartTime);
- mDeltaCount = std::fmod(mDeltaCount - mStartTime,
- mStopTime - mStartTime) + mStartTime;
- return mDeltaCount;
+ mTexSlot = ctrl->mTexSlot;
+ mDelta = ctrl->mDelta;
+ for (unsigned int i=0; i<ctrl->mSources.length(); ++i)
+ {
+ const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr();
+ if (!tex->external)
+ std::cerr << "Warning: Found internal texture, ignoring." << std::endl;
+ mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename));
+ }
}
- value = std::min(mStopTime, std::max(mStartTime, value+mPhase));
- return value;
- }
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real time)
+ {
+ if (mDelta == 0)
+ return;
+ int curTexture = int(time / mDelta) % mTextures.size();
+
+ Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
+ Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
+ while(techs.hasMoreElements())
+ {
+ Ogre::Technique *tech = techs.getNext();
+ Ogre::Technique::PassIterator passes = tech->getPassIterator();
+ while(passes.hasMoreElements())
+ {
+ Ogre::Pass *pass = passes.getNext();
+ Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator();
+ while (textures.hasMoreElements())
+ {
+ Ogre::TextureUnitState *texture = textures.getNext();
+ if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture)
+ || (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture)
+ || (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture)
+ || (texture->getName() == "darkMap" && mTexSlot == Nif::NiTexturingProperty::DarkTexture)
+ || (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture))
+ texture->setTextureName(mTextures[curTexture]);
+ }
+ }
+ }
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class AlphaController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
+ {
+ private:
+ Ogre::MovableObject* mMovable;
+ Nif::FloatKeyList mData;
+ MaterialControllerManager* mMaterialControllerMgr;
+
+ public:
+ Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr)
+ : mMovable(movable)
+ , mData(data->mKeyList)
+ , mMaterialControllerMgr(materialControllerMgr)
+ {
+ }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real time)
+ {
+ float value = interpKey(mData.mKeys, time);
+ Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
+ Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
+ while(techs.hasMoreElements())
+ {
+ Ogre::Technique *tech = techs.getNext();
+ Ogre::Technique::PassIterator passes = tech->getPassIterator();
+ while(passes.hasMoreElements())
+ {
+ Ogre::Pass *pass = passes.getNext();
+ Ogre::ColourValue diffuse = pass->getDiffuse();
+ diffuse.a = value;
+ pass->setDiffuse(diffuse);
+ }
+ }
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class MaterialColorController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
+ {
+ private:
+ Ogre::MovableObject* mMovable;
+ Nif::Vector3KeyList mData;
+ MaterialControllerManager* mMaterialControllerMgr;
+
+ public:
+ Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr)
+ : mMovable(movable)
+ , mData(data->mKeyList)
+ , mMaterialControllerMgr(materialControllerMgr)
+ {
+ }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real time)
+ {
+ Ogre::Vector3 value = interpKey(mData.mKeys, time);
+ Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
+ Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
+ while(techs.hasMoreElements())
+ {
+ Ogre::Technique *tech = techs.getNext();
+ Ogre::Technique::PassIterator passes = tech->getPassIterator();
+ while(passes.hasMoreElements())
+ {
+ Ogre::Pass *pass = passes.getNext();
+ Ogre::ColourValue diffuse = pass->getDiffuse();
+ diffuse.r = value.x;
+ diffuse.g = value.y;
+ diffuse.b = value.z;
+ pass->setDiffuse(diffuse);
+ }
+ }
+ }
+ };
+
+ typedef DefaultFunction Function;
};
class VisController
@@ -107,9 +307,6 @@ public:
return mData.back().isSet;
}
- // FIXME: We are not getting all objects here. Skinned meshes get
- // attached to the object's root node, and won't be connected via a
- // TagPoint.
static void setVisible(Ogre::Node *node, int vis)
{
Ogre::Node::ChildNodeIterator iter = node->getChildIterator();
@@ -118,6 +315,12 @@ public:
node = iter.getNext();
setVisible(node, vis);
+ // Skinned meshes and particle systems are attached to the scene node, not the bone.
+ // We use the Node's user data to connect it with the mesh / particle system.
+ Ogre::Any customData = node->getUserObjectBindings().getUserAny();
+ if (!customData.isEmpty())
+ Ogre::any_cast<Ogre::MovableObject*>(customData)->setVisible(vis);
+
Ogre::TagPoint *tag = dynamic_cast<Ogre::TagPoint*>(node);
if(tag != NULL)
{
@@ -162,48 +365,14 @@ public:
class KeyframeController
{
public:
- class Value : public NodeTargetValue<Ogre::Real>
+ class Value : public NodeTargetValue<Ogre::Real>, public ValueInterpolator
{
private:
Nif::QuaternionKeyList mRotations;
Nif::Vector3KeyList mTranslations;
Nif::FloatKeyList mScales;
- static float interpKey(const Nif::FloatKeyList::VecType &keys, float time)
- {
- if(time <= keys.front().mTime)
- return keys.front().mValue;
-
- Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1);
- for(;iter != keys.end();iter++)
- {
- if(iter->mTime < time)
- continue;
-
- Nif::FloatKeyList::VecType::const_iterator last(iter-1);
- float a = (time-last->mTime) / (iter->mTime-last->mTime);
- return last->mValue + ((iter->mValue - last->mValue)*a);
- }
- return keys.back().mValue;
- }
-
- static Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time)
- {
- if(time <= keys.front().mTime)
- return keys.front().mValue;
-
- Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1);
- for(;iter != keys.end();iter++)
- {
- if(iter->mTime < time)
- continue;
-
- Nif::Vector3KeyList::VecType::const_iterator last(iter-1);
- float a = (time-last->mTime) / (iter->mTime-last->mTime);
- return last->mValue + ((iter->mValue - last->mValue)*a);
- }
- return keys.back().mValue;
- }
+ using ValueInterpolator::interpKey;
static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time)
{
@@ -275,43 +444,24 @@ public:
class UVController
{
public:
- class Value : public Ogre::ControllerValue<Ogre::Real>
+ class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
{
private:
- Ogre::MaterialPtr mMaterial;
+ Ogre::MovableObject* mMovable;
Nif::FloatKeyList mUTrans;
Nif::FloatKeyList mVTrans;
Nif::FloatKeyList mUScale;
Nif::FloatKeyList mVScale;
-
- static float lookupValue(const Nif::FloatKeyList &keys, float time, float def)
- {
- if(keys.mKeys.size() == 0)
- return def;
-
- if(time <= keys.mKeys.front().mTime)
- return keys.mKeys.front().mValue;
-
- Nif::FloatKeyList::VecType::const_iterator iter(keys.mKeys.begin()+1);
- for(;iter != keys.mKeys.end();iter++)
- {
- if(iter->mTime < time)
- continue;
-
- Nif::FloatKeyList::VecType::const_iterator last(iter-1);
- float a = (time-last->mTime) / (iter->mTime-last->mTime);
- return last->mValue + ((iter->mValue - last->mValue)*a);
- }
- return keys.mKeys.back().mValue;
- }
+ MaterialControllerManager* mMaterialControllerMgr;
public:
- Value(const Ogre::MaterialPtr &material, const Nif::NiUVData *data)
- : mMaterial(material)
+ Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr)
+ : mMovable(movable)
, mUTrans(data->mKeyList[0])
, mVTrans(data->mKeyList[1])
, mUScale(data->mKeyList[2])
, mVScale(data->mKeyList[3])
+ , mMaterialControllerMgr(materialControllerMgr)
{ }
virtual Ogre::Real getValue() const
@@ -322,12 +472,14 @@ public:
virtual void setValue(Ogre::Real value)
{
- float uTrans = lookupValue(mUTrans, value, 0.0f);
- float vTrans = lookupValue(mVTrans, value, 0.0f);
- float uScale = lookupValue(mUScale, value, 1.0f);
- float vScale = lookupValue(mVScale, value, 1.0f);
+ float uTrans = interpKey(mUTrans.mKeys, value, 0.0f);
+ float vTrans = interpKey(mVTrans.mKeys, value, 0.0f);
+ float uScale = interpKey(mUScale.mKeys, value, 1.0f);
+ float vScale = interpKey(mVScale.mKeys, value, 1.0f);
- Ogre::Material::TechniqueIterator techs = mMaterial->getTechniqueIterator();
+ Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable);
+
+ Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator();
while(techs.hasMoreElements())
{
Ogre::Technique *tech = techs.getNext();
@@ -379,17 +531,22 @@ public:
class GeomMorpherController
{
public:
- class Value : public Ogre::ControllerValue<Ogre::Real>
+ class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
{
private:
- Ogre::SubEntity *mSubEntity;
+ Ogre::Entity *mEntity;
std::vector<Nif::NiMorphData::MorphData> mMorphs;
+ size_t mControllerIndex;
+
+ std::vector<Ogre::Vector3> mVertices;
public:
- Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data)
- : mSubEntity(subent)
+ Value(Ogre::Entity *ent, const Nif::NiMorphData *data, size_t controllerIndex)
+ : mEntity(ent)
, mMorphs(data->mMorphs)
- { }
+ , mControllerIndex(controllerIndex)
+ {
+ }
virtual Ogre::Real getValue() const
{
@@ -397,9 +554,25 @@ public:
return 0.0f;
}
- virtual void setValue(Ogre::Real value)
+ virtual void setValue(Ogre::Real time)
{
- // TODO: Implement
+ if (mMorphs.size() <= 1)
+ return;
+ int i = 1;
+ for (std::vector<Nif::NiMorphData::MorphData>::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i)
+ {
+ float val = 0;
+ if (!it->mData.mKeys.empty())
+ val = interpKey(it->mData.mKeys, time);
+ val = std::max(0.f, std::min(1.f, val));
+
+ Ogre::String animationID = Ogre::StringConverter::toString(mControllerIndex)
+ + "_" + Ogre::StringConverter::toString(i);
+
+ Ogre::AnimationState* state = mEntity->getAnimationState(animationID);
+ state->setEnabled(val > 0);
+ state->setWeight(val);
+ }
}
};
@@ -418,15 +591,8 @@ class NIFObjectLoader
std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl;
}
- static void fail(const std::string &msg)
- {
- std::cerr << "NIFObjectLoader: Fail: "<< msg << std::endl;
- abort();
- }
-
-
static void createEntity(const std::string &name, const std::string &group,
- Ogre::SceneManager *sceneMgr, ObjectList &objectlist,
+ Ogre::SceneManager *sceneMgr, ObjectScenePtr scene,
const Nif::Node *node, int flags, int animflags)
{
const Nif::NiTriShape *shape = static_cast<const Nif::NiTriShape*>(node);
@@ -441,18 +607,27 @@ class NIFObjectLoader
NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex);
Ogre::Entity *entity = sceneMgr->createEntity(fullname);
+
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ // Enable skeleton-based bounding boxes. With the static bounding box,
+ // the animation may cause parts to go outside the box and cause culling problems.
+ if (entity->hasSkeleton())
+ entity->setUpdateBoundingBoxFromSkeleton(true);
+#endif
+
entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden));
- objectlist.mEntities.push_back(entity);
- if(objectlist.mSkelBase)
+ scene->mEntities.push_back(entity);
+ if(scene->mSkelBase)
{
if(entity->hasSkeleton())
- entity->shareSkeletonInstanceWith(objectlist.mSkelBase);
+ entity->shareSkeletonInstanceWith(scene->mSkelBase);
else
{
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex);
- Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
- objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), entity);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast<Ogre::MovableObject*>(entity)));
+ scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity);
}
}
@@ -463,34 +638,107 @@ class NIFObjectLoader
{
const Nif::NiUVController *uv = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
- const Ogre::MaterialPtr &material = entity->getSubEntity(0)->getMaterial();
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr()));
- Ogre::ControllerFunctionRealPtr func(OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)));
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr));
- objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
else if(ctrl->recType == Nif::RC_NiGeomMorpherController)
{
const Nif::NiGeomMorpherController *geom = static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
- Ogre::SubEntity *subent = entity->getSubEntity(0);
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(subent, geom->data.getPtr()));
- Ogre::ControllerFunctionRealPtr func(OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)));
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(
+ entity, geom->data.getPtr(), geom->recIndex));
+
+ GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
- objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
ctrl = ctrl->next;
}
+
+ createMaterialControllers(shape, entity, animflags, scene);
}
+ static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene)
+ {
+ const Nif::NiTexturingProperty *texprop = NULL;
+ const Nif::NiMaterialProperty *matprop = NULL;
+ const Nif::NiAlphaProperty *alphaprop = NULL;
+ const Nif::NiVertexColorProperty *vertprop = NULL;
+ const Nif::NiZBufferProperty *zprop = NULL;
+ const Nif::NiSpecularProperty *specprop = NULL;
+ const Nif::NiWireframeProperty *wireprop = NULL;
+ node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
+
+ Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
+ Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
+ Ogre::ControllerValueRealPtr());
+
+ if(matprop)
+ {
+ Nif::ControllerPtr ctrls = matprop->controller;
+ while(!ctrls.empty())
+ {
+ if (ctrls->recType == Nif::RC_NiAlphaController)
+ {
+ const Nif::NiAlphaController *alphaCtrl = dynamic_cast<const Nif::NiAlphaController*>(ctrls.getPtr());
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr));
+ AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ else if (ctrls->recType == Nif::RC_NiMaterialColorController)
+ {
+ const Nif::NiMaterialColorController *matCtrl = dynamic_cast<const Nif::NiMaterialColorController*>(ctrls.getPtr());
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr));
+ MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+
+ ctrls = ctrls->next;
+ }
+ }
+ if (texprop)
+ {
+ Nif::ControllerPtr ctrls = texprop->controller;
+ while(!ctrls.empty())
+ {
+ if (ctrls->recType == Nif::RC_NiFlipController)
+ {
+ const Nif::NiFlipController *flipCtrl = dynamic_cast<const Nif::NiFlipController*>(ctrls.getPtr());
+
+
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value(
+ movable, flipCtrl, &scene->mMaterialControllerMgr));
+ FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+
+ ctrls = ctrls->next;
+ }
+ }
+ }
- static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl)
+ static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys,
+ const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone,
+ const std::string& skelBaseName)
{
Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif");
emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f,
@@ -526,6 +774,8 @@ class NIFObjectLoader
affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point");
affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection));
affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition));
+ affector->setParameter("skelbase", skelBaseName);
+ affector->setParameter("bone", bone->getName());
}
else if(e->recType == Nif::RC_NiParticleColorModifier)
{
@@ -558,8 +808,8 @@ class NIFObjectLoader
}
static void createParticleSystem(const std::string &name, const std::string &group,
- Ogre::SceneManager *sceneMgr, ObjectList &objectlist,
- const Nif::Node *partnode, int flags, int partflags)
+ Ogre::SceneNode *sceneNode, ObjectScenePtr scene,
+ const Nif::Node *partnode, int flags, int partflags, int animflags)
{
const Nif::NiAutoNormalParticlesData *particledata = NULL;
if(partnode->recType == Nif::RC_NiAutoNormalParticles)
@@ -572,7 +822,7 @@ class NIFObjectLoader
fullname += "@type="+partnode->name;
Misc::StringUtils::toLower(fullname);
- Ogre::ParticleSystem *partsys = sceneMgr->createParticleSystem();
+ Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem();
const Nif::NiTexturingProperty *texprop = NULL;
const Nif::NiMaterialProperty *matprop = NULL;
@@ -589,13 +839,13 @@ class NIFObjectLoader
vertprop, zprop, specprop,
wireprop, needTangents));
- partsys->setDefaultDimensions(particledata->particleRadius*2.0f,
- particledata->particleRadius*2.0f);
partsys->setCullIndividually(false);
partsys->setParticleQuota(particledata->numParticles);
- // TODO: There is probably a field or flag to specify this, as some
- // particle effects have it and some don't.
- partsys->setKeepParticlesInLocalSpace(true);
+ partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace));
+
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ scene->mSkelBase->attachObjectToBone(trgtbone->getName(), partsys);
Nif::ControllerPtr ctrl = partnode->controller;
while(!ctrl.empty())
@@ -604,38 +854,41 @@ class NIFObjectLoader
{
const Nif::NiParticleSystemController *partctrl = static_cast<const Nif::NiParticleSystemController*>(ctrl.getPtr());
- createParticleEmitterAffectors(partsys, partctrl);
- if(!partctrl->emitter.empty() && !partsys->isAttached())
+ partsys->setDefaultDimensions(partctrl->size*2, partctrl->size*2);
+
+ if(!partctrl->emitter.empty())
{
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex);
- Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
- objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ // Set the emitter bone as user data on the particle system
+ // so the emitters/affectors can access it easily.
+ partsys->getUserObjectBindings().setUserAny(Ogre::Any(trgtbone));
+ createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName());
}
Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
Ogre::ControllerValueRealPtr dstval(OGRE_NEW ParticleSystemController::Value(partsys, partctrl));
- Ogre::ControllerFunctionRealPtr func(OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay)));
- objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ ParticleSystemController::Function* function =
+ OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
ctrl = ctrl->next;
}
- if(!partsys->isAttached())
- {
- int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex);
- Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
- objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys);
- }
-
partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden));
- objectlist.mParticles.push_back(partsys);
+ scene->mParticles.push_back(partsys);
+
+ createMaterialControllers(partnode, partsys, animflags, scene);
}
- static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectList &objectlist, int animflags)
+ static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags)
{
do {
if(ctrl->recType == Nif::RC_NiVisController)
@@ -643,14 +896,17 @@ class NIFObjectLoader
const Nif::NiVisController *vis = static_cast<const Nif::NiVisController*>(ctrl.getPtr());
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
- Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr()));
- Ogre::ControllerFunctionRealPtr func(OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)));
- objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
else if(ctrl->recType == Nif::RC_NiKeyframeController)
{
@@ -658,14 +914,16 @@ class NIFObjectLoader
if(!key->data.empty())
{
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
- Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr()));
- Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)));
+ KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
- objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
}
ctrl = ctrl->next;
@@ -713,8 +971,8 @@ class NIFObjectLoader
static void createObjects(const std::string &name, const std::string &group,
- Ogre::SceneManager *sceneMgr, const Nif::Node *node,
- ObjectList &objectlist, int flags, int animflags, int partflags)
+ Ogre::SceneNode *sceneNode, const Nif::Node *node,
+ ObjectScenePtr scene, int flags, int animflags, int partflags)
{
// Do not create objects for the collision shape (includes all children)
if(node->recType == Nif::RC_RootCollisionNode)
@@ -732,6 +990,17 @@ class NIFObjectLoader
else
flags |= node->flags;
+ if (node->recType == Nif::RC_NiBillboardNode)
+ {
+ // TODO: figure out what the flags mean.
+ // NifSkope has names for them, but doesn't implement them.
+ // Change mBillboardNodes to map <Bone, billboard type>
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex);
+ Ogre::Bone* bone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ bone->setManuallyControlled(true);
+ scene->mBillboardNodes.push_back(bone);
+ }
+
Nif::ExtraPtr e = node->extra;
while(!e.empty())
{
@@ -739,8 +1008,11 @@ class NIFObjectLoader
{
const Nif::NiTextKeyExtraData *tk = static_cast<const Nif::NiTextKeyExtraData*>(e.getPtr());
- int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex);
- extractTextKeys(tk, objectlist.mTextKeys[trgtid]);
+ if (scene->mSkelBase)
+ {
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex);
+ extractTextKeys(tk, scene->mTextKeys[trgtid]);
+ }
}
else if(e->recType == Nif::RC_NiStringExtraData)
{
@@ -759,7 +1031,7 @@ class NIFObjectLoader
}
if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode))
- createNodeControllers(name, node->controller, objectlist, animflags);
+ createNodeControllers(name, node->controller, scene, animflags);
if(node->recType == Nif::RC_NiCamera)
{
@@ -768,13 +1040,13 @@ class NIFObjectLoader
if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000))
{
- createEntity(name, group, sceneMgr, objectlist, node, flags, animflags);
+ createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags);
}
if((node->recType == Nif::RC_NiAutoNormalParticles ||
node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000))
{
- createParticleSystem(name, group, sceneMgr, objectlist, node, flags, partflags);
+ createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags);
}
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
@@ -784,14 +1056,14 @@ class NIFObjectLoader
for(size_t i = 0;i < children.length();i++)
{
if(!children[i].empty())
- createObjects(name, group, sceneMgr, children[i].getPtr(), objectlist, flags, animflags, partflags);
+ createObjects(name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags);
}
}
}
static void createSkelBase(const std::string &name, const std::string &group,
Ogre::SceneManager *sceneMgr, const Nif::Node *node,
- ObjectList &objectlist)
+ ObjectScenePtr scene)
{
/* This creates an empty mesh to which a skeleton gets attached. This
* is to ensure we have an entity with a skeleton instance, even if all
@@ -800,12 +1072,12 @@ class NIFObjectLoader
if(meshMgr.getByName(name).isNull())
NIFMeshLoader::createMesh(name, name, group, ~(size_t)0);
- objectlist.mSkelBase = sceneMgr->createEntity(name);
- objectlist.mEntities.push_back(objectlist.mSkelBase);
+ scene->mSkelBase = sceneMgr->createEntity(name);
+ scene->mEntities.push_back(scene->mSkelBase);
}
public:
- static void load(Ogre::SceneManager *sceneMgr, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0)
+ static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0)
{
Nif::NIFFile::ptr nif = Nif::NIFFile::create(name);
if(nif->numRoots() < 1)
@@ -829,9 +1101,9 @@ public:
!NIFSkeletonLoader::createSkeleton(name, group, node).isNull())
{
// Create a base skeleton entity if this NIF needs one
- createSkelBase(name, group, sceneMgr, node, objectlist);
+ createSkelBase(name, group, sceneNode->getCreator(), node, scene);
}
- createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0);
+ createObjects(name, group, sceneNode, node, scene, flags, 0, 0);
}
static void loadKf(Ogre::Skeleton *skel, const std::string &name,
@@ -894,37 +1166,37 @@ public:
};
-ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
+ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
{
- ObjectList objectlist;
+ ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));;
Misc::StringUtils::toLower(name);
- NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group);
+ NIFObjectLoader::load(parentNode, scene, name, group);
- for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ for(size_t i = 0;i < scene->mEntities.size();i++)
{
- Ogre::Entity *entity = objectlist.mEntities[i];
+ Ogre::Entity *entity = scene->mEntities[i];
if(!entity->isAttached())
parentNode->attachObject(entity);
}
- return objectlist;
+ return scene;
}
-ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonename,
+ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename,
Ogre::SceneNode *parentNode,
std::string name, const std::string &group)
{
- ObjectList objectlist;
+ ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));
Misc::StringUtils::toLower(name);
- NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group);
+ NIFObjectLoader::load(parentNode, scene, name, group);
bool isskinned = false;
- for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ for(size_t i = 0;i < scene->mEntities.size();i++)
{
- Ogre::Entity *ent = objectlist.mEntities[i];
- if(objectlist.mSkelBase != ent && ent->hasSkeleton())
+ Ogre::Entity *ent = scene->mEntities[i];
+ if(scene->mSkelBase != ent && ent->hasSkeleton())
{
isskinned = true;
break;
@@ -939,12 +1211,12 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena
{
std::string filter = "@shape=tri "+bonename;
Misc::StringUtils::toLower(filter);
- for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ for(size_t i = 0;i < scene->mEntities.size();i++)
{
- Ogre::Entity *entity = objectlist.mEntities[i];
+ Ogre::Entity *entity = scene->mEntities[i];
if(entity->hasSkeleton())
{
- if(entity == objectlist.mSkelBase ||
+ if(entity == scene->mSkelBase ||
entity->getMesh()->getName().find(filter) != std::string::npos)
parentNode->attachObject(entity);
}
@@ -957,9 +1229,9 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena
}
else
{
- for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ for(size_t i = 0;i < scene->mEntities.size();i++)
{
- Ogre::Entity *entity = objectlist.mEntities[i];
+ Ogre::Entity *entity = scene->mEntities[i];
if(!entity->isAttached())
{
Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity);
@@ -968,21 +1240,21 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena
}
}
- return objectlist;
+ return scene;
}
-ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
+ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
{
- ObjectList objectlist;
+ ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));
Misc::StringUtils::toLower(name);
- NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group, 0xC0000000);
+ NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000);
- if(objectlist.mSkelBase)
- parentNode->attachObject(objectlist.mSkelBase);
+ if(scene->mSkelBase)
+ parentNode->attachObject(scene->mSkelBase);
- return objectlist;
+ return scene;
}
diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp
index edad13a9a3..badb6ccd3b 100644
--- a/components/nifogre/ogrenifloader.hpp
+++ b/components/nifogre/ogrenifloader.hpp
@@ -25,6 +25,7 @@
#define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP
#include <OgreResource.h>
+#include <OgreMaterial.h>
#include <OgreController.h>
#include <vector>
@@ -37,36 +38,68 @@
namespace NifOgre
{
+/**
+ * @brief Clones materials as necessary to not make controllers affect other objects (that share the original material).
+ */
+class MaterialControllerManager
+{
+public:
+ ~MaterialControllerManager();
+
+ /// @attention if \a movable is an Entity, it needs to have *one* SubEntity
+ Ogre::MaterialPtr getWritableMaterial (Ogre::MovableObject* movable);
+
+private:
+ std::map<Ogre::MovableObject*, Ogre::MaterialPtr> mClonedMaterials;
+};
+
typedef std::multimap<float,std::string> TextKeyMap;
static const char sTextKeyExtraDataID[] = "TextKeyExtraData";
-struct ObjectList {
+struct ObjectScene {
Ogre::Entity *mSkelBase;
std::vector<Ogre::Entity*> mEntities;
std::vector<Ogre::ParticleSystem*> mParticles;
std::vector<Ogre::Light*> mLights;
+ // Nodes that should always face the camera when rendering
+ std::vector<Ogre::Node*> mBillboardNodes;
+
+ Ogre::SceneManager* mSceneMgr;
+
+ // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length.
+ float mMaxControllerLength;
+
std::map<int,TextKeyMap> mTextKeys;
+ MaterialControllerManager mMaterialControllerMgr;
+
std::vector<Ogre::Controller<Ogre::Real> > mControllers;
- ObjectList() : mSkelBase(0)
+ ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mMaxControllerLength(0), mSceneMgr(sceneMgr)
{ }
+
+ ~ObjectScene();
+
+ // Rotate nodes in mBillboardNodes so they face the given camera
+ void rotateBillboardNodes(Ogre::Camera* camera);
};
+typedef Ogre::SharedPtr<ObjectScene> ObjectScenePtr;
+
class Loader
{
public:
- static ObjectList createObjects(Ogre::Entity *parent, const std::string &bonename,
+ static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename,
Ogre::SceneNode *parentNode,
std::string name,
const std::string &group="General");
- static ObjectList createObjects(Ogre::SceneNode *parentNode,
+ static ObjectScenePtr createObjects(Ogre::SceneNode *parentNode,
std::string name,
const std::string &group="General");
- static ObjectList createObjectBase(Ogre::SceneNode *parentNode,
+ static ObjectScenePtr createObjectBase(Ogre::SceneNode *parentNode,
std::string name,
const std::string &group="General");
diff --git a/libs/openengine/ogre/particles.cpp b/components/nifogre/particles.cpp
index 707bd75e08..a1433a6690 100644
--- a/libs/openengine/ogre/particles.cpp
+++ b/components/nifogre/particles.cpp
@@ -5,11 +5,22 @@
#include <OgreParticleEmitter.h>
#include <OgreParticleAffector.h>
#include <OgreParticle.h>
+#include <OgreBone.h>
+#include <OgreTagPoint.h>
+#include <OgreEntity.h>
+#include <OgreSkeletonInstance.h>
+#include <OgreSceneNode.h>
+#include <OgreSceneManager.h>
/* FIXME: "Nif" isn't really an appropriate emitter name. */
class NifEmitter : public Ogre::ParticleEmitter
{
public:
+ Ogre::Bone* mEmitterBone;
+ Ogre::Bone* mParticleBone;
+
+ Ogre::ParticleSystem* getPartSys() { return mParent; }
+
/** Command object for the emitter width (see Ogre::ParamCommand).*/
class CmdWidth : public Ogre::ParamCommand
{
@@ -120,6 +131,9 @@ public:
NifEmitter(Ogre::ParticleSystem *psys)
: Ogre::ParticleEmitter(psys)
{
+ mEmitterBone = Ogre::any_cast<Ogre::Bone*>(psys->getUserObjectBindings().getUserAny());
+ Ogre::TagPoint* tag = static_cast<Ogre::TagPoint*>(mParent->getParentNode());
+ mParticleBone = static_cast<Ogre::Bone*>(tag->getParent());
initDefaults("Nif");
}
@@ -142,22 +156,40 @@ public:
yOff = Ogre::Math::SymmetricRandom() * mYRange;
zOff = Ogre::Math::SymmetricRandom() * mZRange;
- particle->position = mPosition + xOff + yOff + zOff;
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ Ogre::Vector3& position = particle->mPosition;
+ Ogre::Vector3& direction = particle->mDirection;
+ Ogre::ColourValue& colour = particle->mColour;
+ Ogre::Real& totalTimeToLive = particle->mTotalTimeToLive;
+ Ogre::Real& timeToLive = particle->mTimeToLive;
+#else
+ Ogre::Vector3& position = particle->position;
+ Ogre::Vector3& direction = particle->direction;
+ Ogre::ColourValue& colour = particle->colour;
+ Ogre::Real& totalTimeToLive = particle->totalTimeToLive;
+ Ogre::Real& timeToLive = particle->timeToLive;
+#endif
+
+ position = xOff + yOff + zOff +
+ mParticleBone->_getDerivedOrientation().Inverse() * (mEmitterBone->_getDerivedPosition()
+ - mParticleBone->_getDerivedPosition());
// Generate complex data by reference
- genEmissionColour(particle->colour);
+ genEmissionColour(colour);
// NOTE: We do not use mDirection/mAngle for the initial direction.
Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom();
Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom();
- particle->direction = (Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) *
+ direction = (mParticleBone->_getDerivedOrientation().Inverse()
+ * mEmitterBone->_getDerivedOrientation() *
+ Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) *
Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) *
Ogre::Vector3::UNIT_Z;
- genEmissionVelocity(particle->direction);
+ genEmissionVelocity(direction);
// Generate simpler data
- particle->timeToLive = particle->totalTimeToLive = genEmissionTTL();
+ timeToLive = totalTimeToLive = genEmissionTTL();
}
/** Overloaded to update the trans. matrix */
@@ -337,9 +369,9 @@ NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd;
Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys)
{
- Ogre::ParticleEmitter *emit = OGRE_NEW NifEmitter(psys);
- mEmitters.push_back(emit);
- return emit;
+ Ogre::ParticleEmitter *emitter = OGRE_NEW NifEmitter(psys);
+ mEmitters.push_back(emitter);
+ return emitter;
}
@@ -404,9 +436,13 @@ public:
/** See Ogre::ParticleAffector. */
void _initParticle(Ogre::Particle *particle)
{
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ const Ogre::Real life_time = particle->mTotalTimeToLive;
+ Ogre::Real particle_time = particle->mTimeToLive;
+#else
const Ogre::Real life_time = particle->totalTimeToLive;
Ogre::Real particle_time = particle->timeToLive;
-
+#endif
Ogre::Real width = mParent->getDefaultWidth();
Ogre::Real height = mParent->getDefaultHeight();
if(life_time-particle_time < mGrowTime)
@@ -431,9 +467,13 @@ public:
while (!pi.end())
{
Ogre::Particle *p = pi.getNext();
- const Ogre::Real life_time = p->totalTimeToLive;
- Ogre::Real particle_time = p->timeToLive;
-
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ const Ogre::Real life_time = p->mTotalTimeToLive;
+ Ogre::Real particle_time = p->mTimeToLive;
+#else
+ const Ogre::Real life_time = p->totalTimeToLive;
+ Ogre::Real particle_time = p->timeToLive;
+#endif
Ogre::Real width = mParent->getDefaultWidth();
Ogre::Real height = mParent->getDefaultHeight();
if(life_time-particle_time < mGrowTime)
@@ -492,6 +532,11 @@ class GravityAffector : public Ogre::ParticleAffector
};
public:
+ Ogre::Bone* mEmitterBone;
+ Ogre::Bone* mParticleBone;
+
+ Ogre::ParticleSystem* getPartSys() { return mParent; }
+
/** Command object for force (see Ogre::ParamCommand).*/
class CmdForce : public Ogre::ParamCommand
{
@@ -586,6 +631,10 @@ public:
, mPosition(0.0f)
, mDirection(0.0f)
{
+ mEmitterBone = Ogre::any_cast<Ogre::Bone*>(psys->getUserObjectBindings().getUserAny());
+ Ogre::TagPoint* tag = static_cast<Ogre::TagPoint*>(mParent->getParentNode());
+ mParticleBone = static_cast<Ogre::Bone*>(tag->getParent());
+
mType = "Gravity";
// Init parameters
@@ -656,7 +705,11 @@ protected:
while (!pi.end())
{
Ogre::Particle *p = pi.getNext();
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ p->mDirection += vec;
+#else
p->direction += vec;
+#endif
}
}
@@ -667,8 +720,18 @@ protected:
while (!pi.end())
{
Ogre::Particle *p = pi.getNext();
- const Ogre::Vector3 vec = (p->position - mPosition).normalisedCopy() * force;
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ Ogre::Vector3 position = p->mPosition;
+#else
+ Ogre::Vector3 position = p->position;
+#endif
+
+ Ogre::Vector3 vec = (mPosition - position).normalisedCopy() * force;
+#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
+ p->mDirection += vec;
+#else
p->direction += vec;
+#endif
}
}
diff --git a/libs/openengine/ogre/particles.hpp b/components/nifogre/particles.hpp
index e1f3fd282c..e1f3fd282c 100644
--- a/libs/openengine/ogre/particles.hpp
+++ b/components/nifogre/particles.hpp
diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp
index 04bffdeab4..e01ae22efd 100644
--- a/components/nifogre/skeleton.cpp
+++ b/components/nifogre/skeleton.cpp
@@ -30,6 +30,7 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node,
node->recType == Nif::RC_RootCollisionNode || /* handled in nifbullet (hopefully) */
node->recType == Nif::RC_NiTriShape || /* Handled in the mesh loader */
node->recType == Nif::RC_NiBSAnimationNode || /* Handled in the object loader */
+ node->recType == Nif::RC_NiBillboardNode || /* Handled in the object loader */
node->recType == Nif::RC_NiBSParticleNode ||
node->recType == Nif::RC_NiCamera ||
node->recType == Nif::RC_NiAutoNormalParticles ||
@@ -85,14 +86,23 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node)
{
/* We need to be a little aggressive here, since some NIFs have a crap-ton
* of nodes and Ogre only supports 256 bones. We will skip a skeleton if:
- * There are no bones used for skinning, there are no controllers, there
+ * There are no bones used for skinning, there are no keyframe controllers, there
* are no nodes named "AttachLight", and the tree consists of NiNode,
* NiTriShape, and RootCollisionNode types only.
*/
if(node->boneTrafo)
return true;
- if(!node->controller.empty() || node->name == "AttachLight")
+ if(!node->controller.empty())
+ {
+ Nif::ControllerPtr ctrl = node->controller;
+ do {
+ if(ctrl->recType == Nif::RC_NiKeyframeController)
+ return true;
+ } while(!(ctrl=ctrl->next).empty());
+ }
+
+ if (node->name == "AttachLight")
return true;
if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode)
diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp
new file mode 100644
index 0000000000..46424a29af
--- /dev/null
+++ b/components/ogreinit/ogreinit.cpp
@@ -0,0 +1,172 @@
+#include "ogreinit.hpp"
+
+#include <string>
+
+#include <OgreRoot.h>
+#include <OgreParticleEmitterFactory.h>
+#include <OgreParticleSystemManager.h>
+
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+#include <OSX/macUtils.h>
+#endif
+
+#include <components/nifogre/particles.hpp>
+
+#include "ogreplugin.hpp"
+
+namespace OgreInit
+{
+
+ OgreInit::OgreInit()
+ : mRoot(NULL)
+ #ifdef ENABLE_PLUGIN_CgProgramManager
+ , mCgPlugin(NULL)
+ #endif
+ #ifdef ENABLE_PLUGIN_OctreeSceneManager
+ , mOctreePlugin(NULL)
+ #endif
+ #ifdef ENABLE_PLUGIN_ParticleFX
+ , mParticleFXPlugin(NULL)
+ #endif
+ #ifdef ENABLE_PLUGIN_GL
+ , mGLPlugin(NULL)
+ #endif
+ #ifdef ENABLE_PLUGIN_Direct3D9
+ , mD3D9Plugin(NULL)
+ #endif
+ {}
+
+ Ogre::Root* OgreInit::init(const std::string &logPath)
+ {
+ // Set up logging first
+ new Ogre::LogManager;
+ Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath);
+
+ // Disable logging to cout/cerr
+ log->setDebugOutputEnabled(false);
+
+ mRoot = new Ogre::Root("", "", "");
+
+ #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX)
+ loadStaticPlugins();
+ #else
+ loadPlugins();
+ #endif
+
+ loadParticleFactories();
+
+ return mRoot;
+ }
+
+ OgreInit::~OgreInit()
+ {
+ delete mRoot;
+
+ std::vector<Ogre::ParticleEmitterFactory*>::iterator ei;
+ for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei)
+ OGRE_DELETE (*ei);
+ mEmitterFactories.clear();
+
+ std::vector<Ogre::ParticleAffectorFactory*>::iterator ai;
+ for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai)
+ OGRE_DELETE (*ai);
+ mAffectorFactories.clear();
+
+ #ifdef ENABLE_PLUGIN_GL
+ delete mGLPlugin;
+ mGLPlugin = NULL;
+ #endif
+ #ifdef ENABLE_PLUGIN_Direct3D9
+ delete mD3D9Plugin;
+ mD3D9Plugin = NULL;
+ #endif
+ #ifdef ENABLE_PLUGIN_CgProgramManager
+ delete mCgPlugin;
+ mCgPlugin = NULL;
+ #endif
+ #ifdef ENABLE_PLUGIN_OctreeSceneManager
+ delete mOctreePlugin;
+ mOctreePlugin = NULL;
+ #endif
+ #ifdef ENABLE_PLUGIN_ParticleFX
+ delete mParticleFXPlugin;
+ mParticleFXPlugin = NULL;
+ #endif
+ }
+
+ void OgreInit::loadStaticPlugins()
+ {
+ #ifdef ENABLE_PLUGIN_GL
+ mGLPlugin = new Ogre::GLPlugin();
+ mRoot->installPlugin(mGLPlugin);
+ #endif
+ #ifdef ENABLE_PLUGIN_Direct3D9
+ mD3D9Plugin = new Ogre::D3D9Plugin();
+ mRoot->installPlugin(mD3D9Plugin);
+ #endif
+ #ifdef ENABLE_PLUGIN_CgProgramManager
+ mCgPlugin = new Ogre::CgPlugin();
+ mRoot->installPlugin(mCgPlugin);
+ #endif
+ #ifdef ENABLE_PLUGIN_OctreeSceneManager
+ mOctreePlugin = new Ogre::OctreePlugin();
+ mRoot->installPlugin(mOctreePlugin);
+ #endif
+ #ifdef ENABLE_PLUGIN_ParticleFX
+ mParticleFXPlugin = new Ogre::ParticleFXPlugin();
+ mRoot->installPlugin(mParticleFXPlugin);
+ #endif
+ }
+
+ void OgreInit::loadPlugins()
+ {
+ std::string pluginDir;
+ const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR");
+ if (pluginEnv)
+ pluginDir = pluginEnv;
+ else
+ {
+ #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ pluginDir = ".\\";
+ #endif
+ #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ pluginDir = OGRE_PLUGIN_DIR;
+ // if path is not specified try to find plugins inside the app bundle
+ if (pluginDir.empty())
+ pluginDir = Ogre::macPluginPath();
+ #endif
+ #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+ pluginDir = OGRE_PLUGIN_DIR_REL;
+ #endif
+ }
+
+ boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir));
+
+ pluginDir = absPluginPath.string();
+
+ Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot);
+ Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot);
+ Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot);
+ Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot);
+ Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot);
+ Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot);
+ }
+
+ void OgreInit::loadParticleFactories()
+ {
+ Ogre::ParticleEmitterFactory *emitter;
+ emitter = OGRE_NEW NifEmitterFactory();
+ Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter);
+ mEmitterFactories.push_back(emitter);
+
+ Ogre::ParticleAffectorFactory *affector;
+ affector = OGRE_NEW GrowFadeAffectorFactory();
+ Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector);
+ mAffectorFactories.push_back(affector);
+
+ affector = OGRE_NEW GravityAffectorFactory();
+ Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector);
+ mAffectorFactories.push_back(affector);
+ }
+
+}
diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp
new file mode 100644
index 0000000000..b6fe4631a8
--- /dev/null
+++ b/components/ogreinit/ogreinit.hpp
@@ -0,0 +1,75 @@
+#ifndef OPENMW_COMPONENTS_OGREINIT_H
+#define OPENMW_COMPONENTS_OGREINIT_H
+
+#include <vector>
+#include <string>
+
+// Static plugin headers
+#ifdef ENABLE_PLUGIN_CgProgramManager
+# include "OgreCgPlugin.h"
+#endif
+#ifdef ENABLE_PLUGIN_OctreeSceneManager
+# include "OgreOctreePlugin.h"
+#endif
+#ifdef ENABLE_PLUGIN_ParticleFX
+# include "OgreParticleFXPlugin.h"
+#endif
+#ifdef ENABLE_PLUGIN_GL
+# include "OgreGLPlugin.h"
+#endif
+#ifdef ENABLE_PLUGIN_Direct3D9
+# include "OgreD3D9Plugin.h"
+#endif
+
+namespace Ogre
+{
+ class ParticleEmitterFactory;
+ class ParticleAffectorFactory;
+ class Root;
+}
+
+namespace OgreInit
+{
+ /**
+ * @brief Starts Ogre::Root and loads required plugins and NIF particle factories
+ */
+ class OgreInit
+ {
+ public:
+ OgreInit();
+
+ Ogre::Root* init(const std::string &logPath // Path to directory where to store log files
+ );
+
+ ~OgreInit();
+
+ private:
+ std::vector<Ogre::ParticleEmitterFactory*> mEmitterFactories;
+ std::vector<Ogre::ParticleAffectorFactory*> mAffectorFactories;
+ Ogre::Root* mRoot;
+
+ void loadStaticPlugins();
+ void loadPlugins();
+ void loadParticleFactories();
+
+
+ #ifdef ENABLE_PLUGIN_CgProgramManager
+ Ogre::CgPlugin* mCgPlugin;
+ #endif
+ #ifdef ENABLE_PLUGIN_OctreeSceneManager
+ Ogre::OctreePlugin* mOctreePlugin;
+ #endif
+ #ifdef ENABLE_PLUGIN_ParticleFX
+ Ogre::ParticleFXPlugin* mParticleFXPlugin;
+ #endif
+ #ifdef ENABLE_PLUGIN_GL
+ Ogre::GLPlugin* mGLPlugin;
+ #endif
+ #ifdef ENABLE_PLUGIN_Direct3D9
+ Ogre::D3D9Plugin* mD3D9Plugin;
+ #endif
+
+ };
+}
+
+#endif
diff --git a/components/files/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp
index c319f77589..c319f77589 100644
--- a/components/files/ogreplugin.cpp
+++ b/components/ogreinit/ogreplugin.cpp
diff --git a/components/files/ogreplugin.hpp b/components/ogreinit/ogreplugin.hpp
index 6fcf613768..6fcf613768 100644
--- a/components/files/ogreplugin.hpp
+++ b/components/ogreinit/ogreplugin.hpp
diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp
index ebf6046ff6..5dcdb7fed8 100644
--- a/components/terrain/material.cpp
+++ b/components/terrain/material.cpp
@@ -4,6 +4,8 @@
#include <OgreTechnique.h>
#include <OgrePass.h>
+#include <boost/functional/hash.hpp>
+
#include <extern/shiny/Main/Factory.hpp>
namespace
@@ -36,6 +38,8 @@ namespace Terrain
: mShaders(shaders)
, mShadows(false)
, mSplitShadows(false)
+ , mNormalMapping(true)
+ , mParallaxMapping(true)
{
}
@@ -85,7 +89,7 @@ namespace Terrain
{
assert(mLayerList.size() == mBlendmapList.size()+1);
std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
- for (std::vector<std::string>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
+ for (std::vector<LayerInfo>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
{
Ogre::Pass* pass = technique->createPass();
pass->setLightingEnabled(false);
@@ -117,7 +121,7 @@ namespace Terrain
}
// Add the actual layer texture on top of the alpha map.
- tus = pass->createTextureUnitState("textures\\" + *layer);
+ tus = pass->createTextureUnitState(layer->mDiffuseMap);
if (!first)
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
@@ -156,6 +160,10 @@ namespace Terrain
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true)));
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0")));
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0")));
+ p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false)));
+ p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false)));
+ p->mShaderProperties.setProperty ("normal_maps",
+ sh::makeProperty (new sh::IntValue(0)));
sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
@@ -210,6 +218,10 @@ namespace Terrain
}
}
++neededTextureUnits; // layer texture
+
+ // Check if this layer has a normal map
+ if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap)
+ ++neededTextureUnits; // normal map
if (neededTextureUnits <= remainingTextureUnits)
{
// We can fit another!
@@ -238,6 +250,8 @@ namespace Terrain
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
+ p->mShaderProperties.setProperty ("normal_map_enabled",
+ sh::makeProperty (new sh::BooleanValue(false)));
// blend maps
// the index of the first blend map used in this pass
@@ -254,17 +268,39 @@ namespace Terrain
}
// layer maps
+ bool anyNormalMaps = false;
+ bool anyParallax = false;
+ size_t normalMaps = 0;
for (int i = 0; i < numLayersInThisPass; ++i)
{
+ const LayerInfo& layer = mLayerList[layerOffset+i];
+ // diffuse map
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
- diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i])));
+ diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap)));
+
+ // normal map (optional)
+ bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap;
+ bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax;
+ if (useNormalMap)
+ {
+ anyNormalMaps = true;
+ anyParallax = anyParallax || useParallax;
+ sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i));
+ normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap)));
+ }
+ p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i),
+ sh::makeProperty (new sh::BooleanValue(useNormalMap)));
+ p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i),
+ sh::makeProperty (new sh::BooleanValue(useParallax)));
+ boost::hash_combine(normalMaps, useNormalMap);
+ boost::hash_combine(normalMaps, useNormalMap && layer.mParallax);
if (i+layerOffset > 0)
{
int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i);
std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i);
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
- sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
+ sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
}
else
{
@@ -274,6 +310,14 @@ namespace Terrain
sh::makeProperty (new sh::StringValue("")));
}
}
+ p->mShaderProperties.setProperty ("normal_map_enabled",
+ sh::makeProperty (new sh::BooleanValue(anyNormalMaps)));
+ p->mShaderProperties.setProperty ("parallax_enabled",
+ sh::makeProperty (new sh::BooleanValue(anyParallax)));
+ // Since the permutation handler can't handle dynamic property names,
+ // combine normal map settings for all layers into one value
+ p->mShaderProperties.setProperty ("normal_maps",
+ sh::makeProperty (new sh::IntValue(normalMaps)));
// shadow
if (shadows)
@@ -290,6 +334,8 @@ namespace Terrain
// Make sure the pass index is fed to the permutation handler, because blendmap components may be different
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset)));
+ assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits);
+
layerOffset += numLayersInThisPass;
}
}
diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp
index 330ed3d147..e7e0678991 100644
--- a/components/terrain/material.hpp
+++ b/components/terrain/material.hpp
@@ -3,6 +3,8 @@
#include <OgreMaterial.h>
+#include "storage.hpp"
+
namespace Terrain
{
@@ -15,13 +17,15 @@ namespace Terrain
/// so if this parameter is true, then the supplied blend maps are expected to be packed.
MaterialGenerator (bool shaders);
- void setLayerList (const std::vector<std::string>& layerList) { mLayerList = layerList; }
+ void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; }
bool hasLayers() { return mLayerList.size(); }
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
void enableShadows(bool shadows) { mShadows = shadows; }
+ void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; }
+ void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; }
void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
@@ -43,12 +47,14 @@ namespace Terrain
private:
Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap);
- std::vector<std::string> mLayerList;
+ std::vector<LayerInfo> mLayerList;
std::vector<Ogre::TexturePtr> mBlendmapList;
std::string mCompositeMap;
bool mShaders;
bool mShadows;
bool mSplitShadows;
+ bool mNormalMapping;
+ bool mParallaxMapping;
};
}
diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp
index ef2c610138..02225cb02d 100644
--- a/components/terrain/quadtreenode.cpp
+++ b/components/terrain/quadtreenode.cpp
@@ -140,17 +140,19 @@ namespace
}
QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
- : mSize(size)
- , mCenter(center)
- , mParent(parent)
- , mDirection(dir)
+ : mMaterialGenerator(NULL)
+ , mIsActive(false)
, mIsDummy(false)
+ , mSize(size)
+ , mLodLevel(Log2(mSize))
+ , mBounds(Ogre::AxisAlignedBox::BOX_NULL)
+ , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
+ , mDirection(dir)
+ , mCenter(center)
, mSceneNode(NULL)
+ , mParent(parent)
, mTerrain(terrain)
, mChunk(NULL)
- , mMaterialGenerator(NULL)
- , mBounds(Ogre::AxisAlignedBox::BOX_NULL)
- , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
{
mBounds.setNull();
for (int i=0; i<4; ++i)
@@ -168,8 +170,6 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
pos = mCenter - pos;
mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0));
- mLodLevel = Log2(mSize);
-
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
}
@@ -371,7 +371,7 @@ void QuadTreeNode::destroyChunks(bool children)
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
- mMaterialGenerator->setLayerList(std::vector<std::string>());
+ mMaterialGenerator->setLayerList(std::vector<LayerInfo>());
mMaterialGenerator->setCompositeMap("");
}
@@ -414,7 +414,7 @@ void QuadTreeNode::ensureLayerInfo()
return;
std::vector<Ogre::TexturePtr> blendmaps;
- std::vector<std::string> layerList;
+ std::vector<LayerInfo> layerList;
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
mMaterialGenerator->setLayerList(layerList);
@@ -427,11 +427,13 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
if (mIsDummy)
{
- // TODO - why is this completely black?
// TODO - store this default material somewhere instead of creating one for each empty cell
MaterialGenerator matGen(mTerrain->getShadersEnabled());
- std::vector<std::string> layer;
- layer.push_back("_land_default.dds");
+ std::vector<LayerInfo> layer;
+ LayerInfo info;
+ info.mDiffuseMap = "textures\\_land_default.dds";
+ info.mParallax = false;
+ layer.push_back(info);
matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
return;
diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp
index f00677e97f..9d6b44de8d 100644
--- a/components/terrain/storage.cpp
+++ b/components/terrain/storage.cpp
@@ -4,9 +4,10 @@
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
+#include <OgreResourceGroupManager.h>
#include <OgreRoot.h>
-#include <boost/multi_array.hpp>
+#include <boost/algorithm/string.hpp>
namespace Terrain
{
@@ -302,7 +303,7 @@ namespace Terrain
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
- bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<std::string> &layerList)
+ bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<LayerInfo> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
@@ -336,7 +337,7 @@ namespace Terrain
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
- layerList.push_back(getTextureName(*it));
+ layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
@@ -466,5 +467,34 @@ namespace Terrain
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
+ LayerInfo Storage::getLayerInfo(const std::string& texture)
+ {
+ // Already have this cached?
+ if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
+ return mLayerInfoMap[texture];
+
+ LayerInfo info;
+ info.mParallax = false;
+ info.mDiffuseMap = "textures\\" + texture;
+ std::string texture_ = texture;
+ boost::replace_last(texture_, ".", "_nh.");
+ if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
+ {
+ info.mNormalMap = "textures\\" + texture_;
+ info.mParallax = true;
+ }
+ else
+ {
+ texture_ = texture;
+ boost::replace_last(texture_, ".", "_n.");
+ if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
+ info.mNormalMap = "textures\\" + texture_;
+ }
+
+ mLayerInfoMap[texture] = info;
+
+ return info;
+ }
+
}
diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp
index b82f6bbb62..68fae01afa 100644
--- a/components/terrain/storage.hpp
+++ b/components/terrain/storage.hpp
@@ -11,6 +11,13 @@
namespace Terrain
{
+ struct LayerInfo
+ {
+ std::string mDiffuseMap;
+ std::string mNormalMap;
+ bool mParallax; // Height info in normal map alpha channel?
+ };
+
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
class Storage
{
@@ -58,7 +65,7 @@ namespace Terrain
/// @param layerList names of the layer textures used will be written here
void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps,
- std::vector<std::string>& layerList);
+ std::vector<LayerInfo>& layerList);
float getHeightAt (const Ogre::Vector3& worldPos);
@@ -77,6 +84,10 @@ namespace Terrain
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
+
+ std::map<std::string, LayerInfo> mLayerInfoMap;
+
+ LayerInfo getLayerInfo(const std::string& texture);
};
}
diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h
index e69913b59c..50ad417fe5 100644
--- a/extern/oics/tinyxml.h
+++ b/extern/oics/tinyxml.h
@@ -349,7 +349,7 @@ protected:
{
//strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe),
// and the null terminator isn't needed
- for( int i=0; p[i] && i<*length; ++i ) {
+ for( int i=0; i<*length && p[i]; ++i ) {
_value[i] = p[i];
}
return p + (*length);
diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp
index f45c5cdc24..35ec92a706 100644
--- a/extern/sdl4ogre/cursormanager.hpp
+++ b/extern/sdl4ogre/cursormanager.hpp
@@ -22,9 +22,6 @@ public:
/// \brief Follow up a cursorChanged() call with enough info to create an cursor.
virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) = 0;
- /// \brief Tell the manager when the cursor visibility changed
- virtual void cursorVisibilityChange(bool visible) = 0;
-
/// \brief sets whether to actively manage cursors or not
virtual void setEnabled(bool enabled) = 0;
};
diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h
index e6e8434cb9..48adb45456 100644
--- a/extern/sdl4ogre/events.h
+++ b/extern/sdl4ogre/events.h
@@ -70,6 +70,8 @@ public:
/** @remarks The window got / lost input focus */
virtual void windowFocusChange( bool have_focus ) {}
+ virtual void windowClosed () {}
+
virtual void windowResized (int x, int y) {}
};
diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp
index d14a9ffa09..65fb7f98bc 100644
--- a/extern/sdl4ogre/sdlcursormanager.cpp
+++ b/extern/sdl4ogre/sdlcursormanager.cpp
@@ -10,7 +10,6 @@ namespace SFO
SDLCursorManager::SDLCursorManager() :
mEnabled(false),
- mCursorVisible(false),
mInitialized(false)
{
}
@@ -70,27 +69,7 @@ namespace SFO
void SDLCursorManager::_setGUICursor(const std::string &name)
{
- if(mEnabled && mCursorVisible)
- {
- SDL_SetCursor(mCursorMap.find(name)->second);
- _setCursorVisible(mCursorVisible);
- }
- }
-
- void SDLCursorManager::_setCursorVisible(bool visible)
- {
- if(!mEnabled)
- return;
-
- SDL_ShowCursor(visible ? SDL_TRUE : SDL_FALSE);
- }
-
- void SDLCursorManager::cursorVisibilityChange(bool visible)
- {
- mCursorVisible = visible;
-
- _setGUICursor(mCurrentCursor);
- _setCursorVisible(visible);
+ SDL_SetCursor(mCursorMap.find(name)->second);
}
void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y)
diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp
index 8940220d41..7ba69f013e 100644
--- a/extern/sdl4ogre/sdlcursormanager.hpp
+++ b/extern/sdl4ogre/sdlcursormanager.hpp
@@ -19,14 +19,12 @@ namespace SFO
virtual bool cursorChanged(const std::string &name);
virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y);
- virtual void cursorVisibilityChange(bool visible);
private:
void _createCursorFromResource(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y);
void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel);
void _setGUICursor(const std::string& name);
- void _setCursorVisible(bool visible);
typedef std::map<std::string, SDL_Cursor*> CursorMap;
CursorMap mCursorMap;
@@ -34,7 +32,6 @@ namespace SFO
std::string mCurrentCursor;
bool mEnabled;
bool mInitialized;
- bool mCursorVisible;
};
}
diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp
index df74bba3b6..d48e43c010 100644
--- a/extern/sdl4ogre/sdlinputwrapper.cpp
+++ b/extern/sdl4ogre/sdlinputwrapper.cpp
@@ -9,7 +9,7 @@ namespace SFO
{
/// \brief General purpose wrapper for OGRE applications around SDL's event
/// queue, mostly used for handling input-related events.
- InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow) :
+ InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow, bool grab) :
mSDLWindow(window),
mOgreWindow(ogreWindow),
mWarpCompensate(false),
@@ -23,7 +23,12 @@ namespace SFO
mJoyListener(NULL),
mKeyboardListener(NULL),
mMouseListener(NULL),
- mWindowListener(NULL)
+ mWindowListener(NULL),
+ mWindowHasFocus(true),
+ mWantGrab(false),
+ mWantRelative(false),
+ mWantMouseVisible(false),
+ mAllowGrab(grab)
{
_setupOISKeys();
}
@@ -51,13 +56,16 @@ namespace SFO
switch(evt.type)
{
case SDL_MOUSEMOTION:
- //ignore this if it happened due to a warp
+ // Ignore this if it happened due to a warp
if(!_handleWarpMotion(evt.motion))
{
- mMouseListener->mouseMoved(_packageMouseMotion(evt));
+ // If in relative mode, don't trigger events unless window has focus
+ if (!mWantRelative || mWindowHasFocus)
+ mMouseListener->mouseMoved(_packageMouseMotion(evt));
- //try to keep the mouse inside the window
- _wrapMousePointer(evt.motion);
+ // Try to keep the mouse inside the window
+ if (mWindowHasFocus)
+ _wrapMousePointer(evt.motion);
}
break;
case SDL_MOUSEWHEEL:
@@ -103,7 +111,8 @@ namespace SFO
handleWindowEvent(evt);
break;
case SDL_QUIT:
- Ogre::Root::getSingleton().queueEndRendering();
+ if (mWindowListener)
+ mWindowListener->windowClosed();
break;
default:
std::cerr << "Unhandled SDL event of type " << evt.type << std::endl;
@@ -117,16 +126,16 @@ namespace SFO
switch (evt.window.event) {
case SDL_WINDOWEVENT_ENTER:
mMouseInWindow = true;
+ updateMouseSettings();
break;
case SDL_WINDOWEVENT_LEAVE:
mMouseInWindow = false;
- SDL_SetWindowGrab(mSDLWindow, SDL_FALSE);
- SDL_SetRelativeMouseMode(SDL_FALSE);
+ updateMouseSettings();
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
int w,h;
SDL_GetWindowSize(mSDLWindow, &w, &h);
- // TODO: Fix Ogre to handle this more consistently
+ // TODO: Fix Ogre to handle this more consistently (fixed in 1.9)
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
mOgreWindow->resize(w, h);
#else
@@ -137,7 +146,7 @@ namespace SFO
break;
case SDL_WINDOWEVENT_RESIZED:
- // TODO: Fix Ogre to handle this more consistently
+ // TODO: Fix Ogre to handle this more consistently (fixed in 1.9)
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
mOgreWindow->resize(evt.window.data1, evt.window.data2);
#else
@@ -148,10 +157,15 @@ namespace SFO
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
+ mWindowHasFocus = true;
+ updateMouseSettings();
if (mWindowListener)
mWindowListener->windowFocusChange(true);
+
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
+ mWindowHasFocus = false;
+ updateMouseSettings();
if (mWindowListener)
mWindowListener->windowFocusChange(false);
break;
@@ -192,25 +206,43 @@ namespace SFO
/// \brief Locks the pointer to the window
void InputWrapper::setGrabPointer(bool grab)
{
- mGrabPointer = grab && mMouseInWindow;
- SDL_SetWindowGrab(mSDLWindow, grab && mMouseInWindow ? SDL_TRUE : SDL_FALSE);
+ mWantGrab = grab;
+ updateMouseSettings();
}
/// \brief Set the mouse to relative positioning. Doesn't move the cursor
/// and disables mouse acceleration.
void InputWrapper::setMouseRelative(bool relative)
{
- if(mMouseRelative == relative && mMouseInWindow)
+ mWantRelative = relative;
+ updateMouseSettings();
+ }
+
+ void InputWrapper::setMouseVisible(bool visible)
+ {
+ mWantMouseVisible = visible;
+ updateMouseSettings();
+ }
+
+ void InputWrapper::updateMouseSettings()
+ {
+ mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus;
+ SDL_SetWindowGrab(mSDLWindow, mGrabPointer && mAllowGrab ? SDL_TRUE : SDL_FALSE);
+
+ SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus);
+
+ bool relative = mWantRelative && mMouseInWindow && mWindowHasFocus;
+ if(mMouseRelative == relative)
return;
- mMouseRelative = relative && mMouseInWindow;
+ mMouseRelative = relative;
mWrapPointer = false;
//eep, wrap the pointer manually if the input driver doesn't support
//relative positioning natively
- int success = SDL_SetRelativeMouseMode(relative && mMouseInWindow ? SDL_TRUE : SDL_FALSE);
- if(relative && mMouseInWindow && success != 0)
+ int success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE);
+ if(relative && success != 0)
mWrapPointer = true;
//now remove all mouse events using the old setting from the queue
diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp
index 1bd8947a0d..a2b698f860 100644
--- a/extern/sdl4ogre/sdlinputwrapper.hpp
+++ b/extern/sdl4ogre/sdlinputwrapper.hpp
@@ -16,7 +16,7 @@ namespace SFO
class InputWrapper
{
public:
- InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow);
+ InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow, bool grab);
~InputWrapper();
void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; }
@@ -28,6 +28,7 @@ namespace SFO
bool isModifierHeld(SDL_Keymod mod);
bool isKeyDown(SDL_Scancode key);
+ void setMouseVisible (bool visible);
void setMouseRelative(bool relative);
bool getMouseRelative() { return mMouseRelative; }
void setGrabPointer(bool grab);
@@ -36,6 +37,8 @@ namespace SFO
void warpMouse(int x, int y);
+ void updateMouseSettings();
+
private:
void handleWindowEvent(const SDL_Event& evt);
@@ -57,14 +60,20 @@ namespace SFO
Uint16 mWarpX;
Uint16 mWarpY;
bool mWarpCompensate;
- bool mMouseRelative;
bool mWrapPointer;
+
+ bool mAllowGrab;
+ bool mWantMouseVisible;
+ bool mWantGrab;
+ bool mWantRelative;
bool mGrabPointer;
+ bool mMouseRelative;
Sint32 mMouseZ;
Sint32 mMouseX;
Sint32 mMouseY;
+ bool mWindowHasFocus;
bool mMouseInWindow;
SDL_Window* mSDLWindow;
diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp
index 97984609ea..15c8599583 100644
--- a/extern/shiny/Main/Factory.hpp
+++ b/extern/shiny/Main/Factory.hpp
@@ -259,8 +259,8 @@ namespace sh
Platform* mPlatform;
MaterialInstance* findInstance (const std::string& name);
+ private:
MaterialInstance* searchInstance (const std::string& name);
-
/// @return was anything removed?
bool removeCache (const std::string& pattern);
diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp
index 3725d5f354..9f309fbcda 100644
--- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp
+++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp
@@ -64,8 +64,11 @@ namespace sh
bool OgrePlatform::supportsShaderSerialization ()
{
- // Not very reliable in OpenGL mode (requires extension), and somehow doesn't work on linux even if the extension is present
+ #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0)
+ return true;
+ #else
return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos;
+ #endif
}
bool OgrePlatform::supportsMaterialQueuedListener ()
@@ -110,10 +113,15 @@ namespace sh
void OgrePlatform::serializeShaders (const std::string& file)
{
- std::fstream output;
- output.open(file.c_str(), std::ios::out | std::ios::binary);
- Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false));
- Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache);
+ #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0)
+ if (Ogre::GpuProgramManager::getSingleton().isCacheDirty())
+ #endif
+ {
+ std::fstream output;
+ output.open(file.c_str(), std::ios::out | std::ios::binary);
+ Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false));
+ Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache);
+ }
}
void OgrePlatform::deserializeShaders (const std::string& file)
@@ -143,7 +151,7 @@ namespace sh
else if (typeid(*value) == typeid(IntValue))
type = Ogre::GCT_INT1;
else
- assert(0);
+ throw std::runtime_error("unexpected type");
params->addConstantDefinition(name, type);
mSharedParameters[name] = params;
}
diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp
index 54dda3523f..f215f4ab7d 100644
--- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp
+++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp
@@ -1,5 +1,8 @@
#include "OgreTextureUnitState.hpp"
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
#include "OgrePass.hpp"
#include "OgrePlatform.hpp"
#include "OgreMaterialSerializer.hpp"
@@ -28,6 +31,32 @@ namespace sh
setTextureName (retrieveValue<StringValue>(value, context).get());
return true;
}
+ else if (name == "anim_texture2")
+ {
+ std::string val = retrieveValue<StringValue>(value, context).get();
+ std::vector<std::string> tokens;
+ boost::split(tokens, val, boost::is_any_of(" "));
+ assert(tokens.size() == 3);
+ std::string texture = tokens[0];
+ int frames = boost::lexical_cast<int>(tokens[1]);
+ float duration = boost::lexical_cast<float>(tokens[2]);
+
+ std::vector<Ogre::String> frameTextures;
+ for (int i=0; i<frames; ++i)
+ {
+ std::stringstream stream;
+ stream << std::setw(2);
+ stream << std::setfill('0');
+ stream << i;
+ stream << '.';
+ std::string tex = texture;
+ boost::replace_last(tex, ".", stream.str());
+ frameTextures.push_back(tex);
+ }
+
+ mTextureUnitState->setAnimatedTextureName(&frameTextures[0], frames, duration);
+ return true;
+ }
else if (name == "create_in_ffp")
return true; // handled elsewhere
diff --git a/files/materials/core.h b/files/materials/core.h
index 6f8179c08d..a912e23562 100644
--- a/files/materials/core.h
+++ b/files/materials/core.h
@@ -91,7 +91,7 @@ precision mediump float;
#define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name)
- #define shMatrixMult(m, v) (m * v)
+ #define shMatrixMult(m, v) ((m) * (v))
#define shOutputPosition gl_Position
diff --git a/files/materials/objects.mat b/files/materials/objects.mat
index 8f8734d629..751b512431 100644
--- a/files/materials/objects.mat
+++ b/files/materials/objects.mat
@@ -1,17 +1,23 @@
material openmw_objects_base
{
diffuse 1.0 1.0 1.0 1.0
- specular 0 0 0 0
+ specular 0 0 0 0 1
ambient 1.0 1.0 1.0
emissive 0.0 0.0 0.0
vertmode 0
diffuseMap black.png
normalMap
emissiveMap
+ darkMap
use_emissive_map false
use_detail_map false
+ use_diffuse_map false
+ use_dark_map false
emissiveMapUVSet 0
detailMapUVSet 0
+ diffuseMapUVSet 0
+ darkMapUVSet 0
+ use_parallax false
scene_blend default
depth_write default
@@ -19,6 +25,8 @@ material openmw_objects_base
alpha_rejection default
transparent_sorting default
polygon_mode default
+ env_map false
+ env_map_color 1 1 1
pass
{
@@ -31,8 +39,15 @@ material openmw_objects_base
normalMap $normalMap
emissiveMapUVSet $emissiveMapUVSet
detailMapUVSet $detailMapUVSet
+ diffuseMapUVSet $diffuseMapUVSet
+ darkMapUVSet $darkMapUVSet
emissiveMap $emissiveMap
detailMap $detailMap
+ diffuseMap $diffuseMap
+ darkMap $darkMap
+ env_map $env_map
+ env_map_color $env_map_color
+ use_parallax $use_parallax
}
diffuse $diffuse
@@ -49,8 +64,8 @@ material openmw_objects_base
texture_unit diffuseMap
{
direct_texture $diffuseMap
- create_in_ffp true
- tex_coord_set $emissiveMapUVSet
+ create_in_ffp $use_diffuse_map
+ tex_coord_set $diffuseMapUVSet
}
texture_unit normalMap
@@ -60,12 +75,13 @@ material openmw_objects_base
num_mipmaps 4
}
- texture_unit emissiveMap
+ texture_unit darkMap
{
- create_in_ffp $use_emissive_map
- colour_op add
- direct_texture $emissiveMap
- tex_coord_set $emissiveMapUVSet
+ create_in_ffp $use_dark_map
+ colour_op_ex modulate src_current src_texture
+ alpha_op_ex modulate src_current src_texture
+ direct_texture $darkMap
+ tex_coord_set $darkMapUVSet
}
texture_unit detailMap
@@ -75,6 +91,22 @@ material openmw_objects_base
direct_texture $detailMap
tex_coord_set $detailMapUVSet
}
+
+ texture_unit emissiveMap
+ {
+ create_in_ffp $use_emissive_map
+ colour_op add
+ direct_texture $emissiveMap
+ tex_coord_set $emissiveMapUVSet
+ }
+
+ texture_unit envMap
+ {
+ create_in_ffp $env_map
+ env_map spherical
+ anim_texture2 textures\magicitem\caust.dds 32 2
+ colour_op add
+ }
texture_unit shadowMap0
{
diff --git a/files/materials/objects.shader b/files/materials/objects.shader
index 36f92bfd91..93368f1f68 100644
--- a/files/materials/objects.shader
+++ b/files/materials/objects.shader
@@ -17,9 +17,15 @@
#define NORMAL_MAP @shPropertyHasValue(normalMap)
#define EMISSIVE_MAP @shPropertyHasValue(emissiveMap)
#define DETAIL_MAP @shPropertyHasValue(detailMap)
+#define DIFFUSE_MAP @shPropertyHasValue(diffuseMap)
+#define DARK_MAP @shPropertyHasValue(darkMap)
+
+#define PARALLAX @shPropertyBool(use_parallax)
+#define PARALLAX_SCALE 0.04
+#define PARALLAX_BIAS -0.02
// right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more
-#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet))
+#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet) || @shPropertyString(diffuseMapUVSet) || @shPropertyString(darkMapUVSet))
// if normal mapping is enabled, we force pixel lighting
#define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap))
@@ -30,6 +36,12 @@
#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
+#define ENV_MAP @shPropertyBool(env_map)
+
+#define SPECULAR 1
+
+#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR
+
#ifdef SH_VERTEX_SHADER
// ------------------------------------- VERTEX ---------------------------------------
@@ -61,15 +73,12 @@
shOutput(float3, tangentPassthrough)
#endif
-#if !VERTEX_LIGHTING
+#if NEED_NORMAL
shOutput(float3, normalPassthrough)
#endif
-#ifdef NEED_DEPTH
- shOutput(float, depthPassthrough)
-#endif
-
- shOutput(float3, objSpacePositionPassthrough)
+ // Depth in w
+ shOutput(float4, objSpacePositionPassthrough)
#if VERTEXCOLOR_MODE != 0
shColourInput(float4)
@@ -79,18 +88,19 @@
shOutput(float4, colourPassthrough)
#endif
+#if ENV_MAP || VERTEX_LIGHTING
+ shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix)
+#endif
+
#if VERTEX_LIGHTING
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
- shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix)
#if VERTEXCOLOR_MODE != 2
shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour)
#endif
-#if VERTEXCOLOR_MODE != 2
shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour)
-#endif
#if VERTEXCOLOR_MODE != 1
shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour)
#endif
@@ -125,10 +135,23 @@
UV.zw = uv1;
#endif
+#if ENV_MAP || VERTEX_LIGHTING
+ float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
+#endif
+
+#if ENV_MAP
+ float3 viewVec = normalize( shMatrixMult(worldView, shInputPosition).xyz);
+
+ float3 r = reflect( viewVec, viewNormal );
+ float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) );
+ UV.z = r.x/m + 0.5;
+ UV.w = r.y/m + 0.5;
+#endif
+
#if NORMAL_MAP
tangentPassthrough = tangent.xyz;
#endif
-#if !VERTEX_LIGHTING
+#if NEED_NORMAL
normalPassthrough = normal.xyz;
#endif
#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
@@ -151,14 +174,14 @@
float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix);
- depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z;
+ objSpacePositionPassthrough.w = shMatrixMult(fixedWVP, shInputPosition).z;
#else
- depthPassthrough = shOutputPosition.z;
+ objSpacePositionPassthrough.w = shOutputPosition.z;
#endif
#endif
- objSpacePositionPassthrough = shInputPosition.xyz;
+ objSpacePositionPassthrough.xyz = shInputPosition.xyz;
#if SHADOWS
lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
@@ -173,7 +196,6 @@
#if VERTEX_LIGHTING
float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz;
- float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
float3 lightDir;
float d;
@@ -212,9 +234,7 @@
lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz;
#endif
-#if VERTEXCOLOR_MODE != 2
lightResult.a *= materialDiffuse.a;
-#endif
#endif
}
@@ -228,34 +248,51 @@
#endif
SH_BEGIN_PROGRAM
+#if DIFFUSE_MAP
shSampler2D(diffuseMap)
+#endif
#if NORMAL_MAP
shSampler2D(normalMap)
#endif
-#if EMISSIVE_MAP
- shSampler2D(emissiveMap)
+#if DARK_MAP
+ shSampler2D(darkMap)
#endif
#if DETAIL_MAP
shSampler2D(detailMap)
#endif
+#if EMISSIVE_MAP
+ shSampler2D(emissiveMap)
+#endif
+
+#if ENV_MAP
+ shSampler2D(envMap)
+ shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color)
+#endif
+
+#if ENV_MAP || SPECULAR || PARALLAX
+ shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space)
+#endif
+#if SPECULAR
+ shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0)
+ shUniform(float3, lightPosObjSpace0) @shAutoConstant(lightPosObjSpace0, light_position_object_space, 0)
+ shUniform(float, matShininess) @shAutoConstant(matShininess, surface_shininess)
+ shUniform(float3, matSpec) @shAutoConstant(matSpec, surface_specular_colour)
+#endif
+
shInput(float4, UV)
#if NORMAL_MAP
shInput(float3, tangentPassthrough)
#endif
-#if !VERTEX_LIGHTING
+#if NEED_NORMAL
shInput(float3, normalPassthrough)
#endif
-#ifdef NEED_DEPTH
- shInput(float, depthPassthrough)
-#endif
-
- shInput(float3, objSpacePositionPassthrough)
+ shInput(float4, objSpacePositionPassthrough)
#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
shInput(float4, colourPassthrough)
@@ -298,7 +335,6 @@
shInput(float4, lightResult)
shInput(float3, directionalResult)
#else
- shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
@@ -307,9 +343,7 @@
#if VERTEXCOLOR_MODE != 2
shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour)
#endif
- #if VERTEXCOLOR_MODE != 2
shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour)
- #endif
#if VERTEXCOLOR_MODE != 1
shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour)
#endif
@@ -317,18 +351,17 @@
SH_START_PROGRAM
{
- shOutputColour(0) = shSample(diffuseMap, UV.xy);
+ float4 newUV = UV;
-#if DETAIL_MAP
-#if @shPropertyString(detailMapUVSet)
- shOutputColour(0) *= shSample(detailMap, UV.zw)*2;
-#else
- shOutputColour(0) *= shSample(detailMap, UV.xy)*2;
+#ifdef NEED_DEPTH
+ float depthPassthrough = objSpacePositionPassthrough.w;
#endif
+
+#if NEED_NORMAL
+ float3 normal = normalPassthrough;
#endif
#if NORMAL_MAP
- float3 normal = normalPassthrough;
float3 binormal = cross(tangentPassthrough.xyz, normal.xyz);
float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz);
@@ -336,13 +369,51 @@
tbn = transpose(tbn);
#endif
- float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1;
+ float4 normalTex = shSample(normalMap, UV.xy);
+
+ normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2 - 1 ));
+#endif
+
+#if ENV_MAP || SPECULAR || PARALLAX
+ float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz);
+#endif
+
+#if PARALLAX
+ float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir));
- normal = normalize (shMatrixMult( transpose(tbn), TSnormal ));
+ newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy;
+#endif
+
+#if DIFFUSE_MAP
+ #if @shPropertyString(diffuseMapUVSet)
+ float4 diffuse = shSample(diffuseMap, newUV.zw);
+ #else
+ float4 diffuse = shSample(diffuseMap, newUV.xy);
+ #endif
+#else
+ float4 diffuse = float4(1,1,1,1);
+#endif
+
+#if DETAIL_MAP
+#if @shPropertyString(detailMapUVSet)
+ diffuse *= shSample(detailMap, newUV.zw)*2;
+#else
+ diffuse *= shSample(detailMap, newUV.xy)*2;
+#endif
+#endif
+
+#if DARK_MAP
+#if @shPropertyString(darkMapUVSet)
+ diffuse *= shSample(darkMap, newUV.zw);
+#else
+ diffuse *= shSample(darkMap, newUV.xy);
+#endif
#endif
+ shOutputColour(0) = diffuse;
+
#if !VERTEX_LIGHTING
- float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz;
+ float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz;
float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
float3 lightDir;
@@ -381,10 +452,8 @@
lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz;
#endif
-#if VERTEXCOLOR_MODE != 2
lightResult.a *= materialDiffuse.a;
#endif
-#endif
// shadows only for the first (directional) light
#if SHADOWS
@@ -407,7 +476,7 @@
#if (UNDERWATER) || (FOG)
- float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz;
+ float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough.xyz,1)).xyz;
#endif
#if UNDERWATER
@@ -420,6 +489,32 @@
shOutputColour(0) *= lightResult;
#endif
+#if EMISSIVE_MAP
+ #if @shPropertyString(emissiveMapUVSet)
+ shOutputColour(0).xyz += shSample(emissiveMap, newUV.zw).xyz;
+ #else
+ shOutputColour(0).xyz += shSample(emissiveMap, newUV.xy).xyz;
+ #endif
+#endif
+
+#if ENV_MAP
+ // Everything looks better with fresnel
+ float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0);
+ float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1));
+
+ shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color;
+#endif
+
+#if SPECULAR
+ float3 light0Dir = normalize(lightPosObjSpace0.xyz);
+
+ float NdotL = max(dot(normal, light0Dir), 0);
+ float3 halfVec = normalize (light0Dir + eyeDir);
+
+ float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec;
+ shOutputColour(0).xyz += specular * shadow * diffuse.a;
+#endif
+
#if FOG
float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w);
@@ -432,14 +527,6 @@
#endif
-#if EMISSIVE_MAP
- #if @shPropertyString(emissiveMapUVSet)
- shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz;
- #else
- shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz;
- #endif
-#endif
-
// prevent negative colour output (for example with negative lights)
shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0));
}
diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader
index 861841a84c..86eef36ffa 100644
--- a/files/materials/terrain.shader
+++ b/files/materials/terrain.shader
@@ -27,6 +27,16 @@
#define COMPOSITE_MAP @shPropertyBool(display_composite_map)
+#define NORMAL_MAP @shPropertyBool(normal_map_enabled)
+#define PARALLAX @shPropertyBool(parallax_enabled)
+
+#define VERTEX_LIGHTING (!NORMAL_MAP)
+
+#define PARALLAX_SCALE 0.04
+#define PARALLAX_BIAS -0.02
+
+// This is just for the permutation handler
+#define NORMAL_MAPS @shPropertyString(normal_maps)
#if NEED_DEPTH
@shAllocatePassthrough(1, depth)
@@ -37,8 +47,13 @@
@shAllocatePassthrough(3, worldPos)
#if LIGHTING
+@shAllocatePassthrough(3, normalPassthrough)
+#if VERTEX_LIGHTING
@shAllocatePassthrough(3, lightResult)
@shAllocatePassthrough(3, directionalResult)
+#else
+@shAllocatePassthrough(3, colourPassthrough)
+#endif
#if SHADOWS
@shAllocatePassthrough(4, lightSpacePos0)
@@ -63,18 +78,18 @@
#endif
shVertexInput(float2, uv0)
- shVertexInput(float2, uv1) // lodDelta, lodThreshold
#if LIGHTING
shNormalInput(float4)
shColourInput(float4)
- shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
+#if VERTEX_LIGHTING
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
-
+#endif
+
#if SHADOWS
shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
#endif
@@ -123,6 +138,13 @@
@shPassthroughAssign(worldPos, worldPos.xyz);
#if LIGHTING
+ @shPassthroughAssign(normalPassthrough, normal.xyz);
+#endif
+#if LIGHTING && !VERTEX_LIGHTING
+ @shPassthroughAssign(colourPassthrough, colour.xyz);
+#endif
+
+#if LIGHTING
#if SHADOWS
float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
@@ -139,6 +161,7 @@
#endif
+#if VERTEX_LIGHTING
// Lighting
float3 lightDir;
float d;
@@ -164,6 +187,7 @@
@shPassthroughAssign(lightResult, lightResult);
@shPassthroughAssign(directionalResult, directionalResult);
+#endif
#endif
}
@@ -189,6 +213,9 @@
@shForeach(@shPropertyString(num_layers))
shSampler2D(diffuseMap@shIterator)
+#if @shPropertyBool(use_normal_map_@shIterator)
+ shSampler2D(normalMap@shIterator)
+#endif
@shEndForeach
#endif
@@ -201,6 +228,15 @@
@shPassthroughFragmentInputs
#if LIGHTING
+
+#if !VERTEX_LIGHTING
+shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_array, @shGlobalSettingString(num_lights))
+shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
+shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
+shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
+shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix)
+#endif
+
#if SHADOWS
shSampler2D(shadowMap0)
shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset))
@@ -220,13 +256,21 @@
#if (UNDERWATER) || (FOG)
shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
- shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position)
#endif
#if UNDERWATER
shUniform(float, waterLevel) @shSharedParameter(waterLevel)
#endif
+
+// For specular
+#if LIGHTING
+ shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0)
+ shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0)
+#endif
+
+shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position)
+
SH_START_PROGRAM
{
@@ -237,12 +281,32 @@
float2 UV = @shPassthroughReceive(UV);
float3 worldPos = @shPassthroughReceive(worldPos);
-
-
+
+#if LIGHTING
+ float3 normal = @shPassthroughReceive(normalPassthrough);
+#endif
+
+#if LIGHTING && !VERTEX_LIGHTING
+
+#if NORMAL_MAP
+ // derive the tangent space basis
+ float3 tangent = float3(1,0, 0);
+
+ float3 binormal = normalize(cross(tangent, normal));
+ tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal
+
+ // derive final matrix
+ float3x3 tbn = float3x3(tangent, binormal, normal);
+ #if SH_GLSL
+ tbn = transpose(tbn);
+ #endif
+#endif
+
+#endif
+
#if UNDERWATER
float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel);
#endif
-
#if !IS_FIRST_PASS
// Opacity the previous passes should have, i.e. 1 - (opacity of this pass)
@@ -252,6 +316,8 @@ float previousAlpha = 1.f;
shOutputColour(0) = float4(1,1,1,1);
+float3 TSnormal = float3(0,0,1);
+
#if COMPOSITE_MAP
shOutputColour(0).xyz = shSample(compositeMap, UV).xyz;
#else
@@ -266,39 +332,88 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
@shEndForeach
- float3 albedo = float3(0,0,0);
+ float4 albedo = float4(0,0,0,1);
+
+ float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents
+ float2 thisLayerUV;
+ float4 normalTex;
- float2 layerUV = UV * 16;
+ float3 eyeDir = normalize(cameraPos.xyz - worldPos);
+#if PARALLAX
+ float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir));
+#endif
@shForeach(@shPropertyString(num_layers))
+ thisLayerUV = layerUV;
+#if @shPropertyBool(use_normal_map_@shIterator)
+ normalTex = shSample(normalMap@shIterator, thisLayerUV);
+#if @shIterator == 0 && IS_FIRST_PASS
+ TSnormal = normalize(normalTex.xyz * 2 - 1);
+#else
+ TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator));
+#endif
+#endif
+#if @shPropertyBool(use_parallax_@shIterator)
+ thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS );
+#endif
#if IS_FIRST_PASS
#if @shIterator == 0
// first layer of first pass is the base layer and doesn't need a blend map
- albedo = shSample(diffuseMap0, layerUV).rgb;
+ albedo = shSample(diffuseMap0, layerUV);
#else
- albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator));
+ albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator));
#endif
#else
#if @shIterator == 0
- albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator);
+ albedo = shSample(diffuseMap@shIterator, layerUV);
#else
- albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator));
+ albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator));
#endif
previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator);
#endif
+
+
@shEndForeach
- shOutputColour(0).rgb *= albedo;
+ shOutputColour(0).rgb *= albedo.xyz;
#endif
#if LIGHTING
+
+#if VERTEX_LIGHTING
// Lighting
float3 lightResult = @shPassthroughReceive(lightResult);
float3 directionalResult = @shPassthroughReceive(directionalResult);
-
+#else
+
+#if NORMAL_MAP
+ normal = normalize (shMatrixMult( transpose(tbn), TSnormal ));
+#endif
+
+ float3 colour = @shPassthroughReceive(colourPassthrough);
+ float3 lightDir;
+ float d;
+ float3 lightResult = float3(0,0,0);
+ @shForeach(@shGlobalSettingString(num_lights))
+ lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w);
+ d = length(lightDir);
+ lightDir = normalize(lightDir);
+
+ lightResult.xyz += lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(normal.xyz, lightDir), 0);
+#if @shIterator == 0
+ float3 directionalResult = lightResult.xyz;
+#endif
+ @shEndForeach
+ lightResult.xyz += lightAmbient.xyz;
+ lightResult.xyz *= colour.xyz;
+ directionalResult.xyz *= colour.xyz;
+#endif
+
// shadows only for the first (directional) light
#if SHADOWS
float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0);
@@ -325,6 +440,17 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow));
#endif
+#if LIGHTING && !COMPOSITE_MAP
+ // Specular
+ float3 light0Dir = normalize(lightPos0.xyz);
+
+ float NdotL = max(dot(normal, light0Dir), 0);
+ float3 halfVec = normalize (light0Dir + eyeDir);
+
+ float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0;
+ shOutputColour(0).xyz += specular * (1.f-albedo.a) * shadow;
+#endif
+
#if FOG
float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);
diff --git a/files/materials/water.mat b/files/materials/water.mat
index 1e5f8c8e04..ade55f326f 100644
--- a/files/materials/water.mat
+++ b/files/materials/water.mat
@@ -37,7 +37,7 @@ material Water
texture_unit normalMap
{
- texture water_nm.png 5
+ texture water_nm.png
}
texture_unit rippleNormalMap
diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt
index 21153a4740..70fdf42489 100644
--- a/files/mygui/CMakeLists.txt
+++ b/files/mygui/CMakeLists.txt
@@ -80,6 +80,8 @@ set(MYGUI_FILES
openmw_merchantrepair.layout
openmw_repair.layout
openmw_companion_window.layout
+ openmw_savegame_dialog.layout
+ openmw_recharge_dialog.layout
smallbars.png
DejaVuLGCSansMono.ttf
markers.png
diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout
index b368a6407c..d13a5a02de 100644
--- a/files/mygui/openmw_chargen_birth.layout
+++ b/files/mygui/openmw_chargen_birth.layout
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 491 378" name="_Main">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 527 378" name="_Main">
<!-- Birthsign list -->
- <Widget type="ListBox" skin="MW_List" position="8 8 196 137" name="BirthsignList"/>
+ <Widget type="ListBox" skin="MW_List" position="8 8 232 137" name="BirthsignList"/>
<!-- Birthsign image -->
- <Widget type="Widget" skin="MW_Box" position="212 8 263 137" align="Left Top">
+ <Widget type="Widget" skin="MW_Box" position="248 8 263 137" align="Left Top">
<Widget type="ImageBox" skin="ImageBox" position="2 2 259 133" name="BirthsignImage" align="Left Top"/>
</Widget>
<!-- Spell list -->
- <Widget type="Widget" skin="" position="8 160 465 178" align="Left Top" name="SpellArea"/>
+ <Widget type="Widget" skin="" position="8 160 519 178" align="Left Top" name="SpellArea"/>
<!-- Dialog buttons -->
- <Widget type="HBox" position="0 338 475 24">
+ <Widget type="HBox" position="0 338 511 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout
index 3c0348b663..aae3c70354 100644
--- a/files/mygui/openmw_chargen_class.layout
+++ b/files/mygui/openmw_chargen_class.layout
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 478 316" name="_Main">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 491 316" name="_Main">
<!-- Class list -->
- <Widget type="ListBox" skin="MW_List" position="8 8 181 138" name="ClassList"/>
+ <Widget type="ListBox" skin="MW_List" position="8 8 194 138" name="ClassList"/>
<!-- Class image -->
- <Widget type="Widget" skin="MW_Box" position="197 8 265 138" align="Left Top">
+ <Widget type="Widget" skin="MW_Box" position="210 8 265 138" align="Left Top">
<Widget type="ImageBox" skin="ImageBox" position="2 2 261 134" name="ClassImage" align="Left Top"/>
</Widget>
<!-- Specialization -->
- <Widget type="Widget" skin="" position="15 156 484 178" align="Left Top">
+ <Widget type="Widget" skin="" position="8 156 484 178" align="Left Top">
- <Widget type="TextBox" skin="HeaderText" position="0 0 162 18" name="SpecializationT" align="Left Top">
+ <Widget type="TextBox" skin="HeaderText" position="0 0 166 18" name="SpecializationT" align="Left Top">
<Property key="Caption" value="#{sChooseClassMenu1}"/>
<Property key="TextAlign" value="Left Top"/>
<UserString key="ToolTipType" value="Layout"/>
@@ -22,12 +22,12 @@
<UserString key="Caption_Text" value="#{sCreateClassMenuHelp1}"/>
</Widget>
- <Widget type="TextBox" skin="SandText" position="0 18 162 18" name="SpecializationName" align="Left Top">
+ <Widget type="TextBox" skin="SandText" position="0 18 166 18" name="SpecializationName" align="Left Top">
<Property key="TextAlign" value="Left Top"/>
</Widget>
<!-- Favorite Attributes -->
- <Widget type="TextBox" skin="HeaderText" position="0 41 162 18" name="FavoriteAttributesT" align="Left Top">
+ <Widget type="TextBox" skin="HeaderText" position="0 41 166 18" name="FavoriteAttributesT" align="Left Top">
<Property key="Caption" value="#{sChooseClassMenu2}"/>
<Property key="TextAlign" value="Left Top"/>
<UserString key="ToolTipType" value="Layout"/>
@@ -35,37 +35,37 @@
<UserString key="Caption_Text" value="#{sCreateClassMenuHelp2}"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatName" position="0 59 162 18" name="FavoriteAttribute0" align="Left Top"/>
- <Widget type="MWAttribute" skin="MW_StatName" position="0 77 162 18" name="FavoriteAttribute1" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatName" position="0 59 166 18" name="FavoriteAttribute0" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatName" position="0 77 166 18" name="FavoriteAttribute1" align="Left Top"/>
<!-- Major Skills -->
- <Widget type="TextBox" skin="HeaderText" position="162 0 162 18" name="MajorSkillT" align="Left Top">
+ <Widget type="TextBox" skin="HeaderText" position="166 0 162 18" name="MajorSkillT" align="Left Top">
<Property key="TextAlign" value="Left Top"/>
<Property key="Caption" value="#{sChooseClassMenu3}"/>
</Widget>
- <Widget type="MWSkill" skin="MW_StatName" position="162 18 162 18" name="MajorSkill0" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="162 36 162 18" name="MajorSkill1" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="162 54 162 18" name="MajorSkill2" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="162 72 162 18" name="MajorSkill3" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="162 90 162 18" name="MajorSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="166 18 166 18" name="MajorSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="166 36 166 18" name="MajorSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="166 54 166 18" name="MajorSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="166 72 166 18" name="MajorSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="166 90 166 18" name="MajorSkill4" align="Left Top"/>
<!-- Minor Skills -->
- <Widget type="TextBox" skin="HeaderText" position="325 0 162 18" name="MinorSkillT" align="Left Top">
+ <Widget type="TextBox" skin="HeaderText" position="332 0 166 18" name="MinorSkillT" align="Left Top">
<Property key="Caption" value="#{sChooseClassMenu4}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
- <Widget type="MWSkill" skin="MW_StatName" position="325 18 162 18" name="MinorSkill0" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="325 36 162 18" name="MinorSkill1" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="325 54 162 18" name="MinorSkill2" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="325 72 162 18" name="MinorSkill3" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatName" position="325 90 162 18" name="MinorSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="332 18 166 18" name="MinorSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="332 36 166 18" name="MinorSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="332 54 166 18" name="MinorSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="332 72 166 18" name="MinorSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatName" position="332 90 166 18" name="MinorSkill4" align="Left Top"/>
</Widget>
<!-- Dialog buttons -->
- <Widget type="HBox" position="0 276 462 24">
+ <Widget type="HBox" position="0 276 475 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout
index 92382640b9..890e2aac17 100644
--- a/files/mygui/openmw_chargen_create_class.layout
+++ b/files/mygui/openmw_chargen_create_class.layout
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 450 198" name="_Main">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 498 198" name="_Main">
<!-- Class name -->
<Widget type="TextBox" skin="ProgressText" position="8 8 48 23" name="LabelT" align="Left Top">
- <Property key="Caption" value="#{sName}"/>
+ <Property key="Caption" value="#{sName}:"/>
<Property key="TextAlign" value="Left VCenter"/>
+ <Property key="TextColour" value="0.82 0.74 0.58"/>
</Widget>
- <Widget type="EditBox" skin="MW_TextEdit" position="72 8 362 23" name="EditName" align="HStretch Top">
+ <Widget type="EditBox" skin="MW_TextEdit" position="72 8 410 23" name="EditName" align="HStretch Top">
<Property key="Caption" value="#{sCustomClassName}"/>
</Widget>
@@ -22,12 +23,12 @@
<UserString key="Caption_Text" value="#{sCreateClassMenuHelp1}"/>
</Widget>
- <Widget type="Button" skin="SandTextButton" position="0 18 156 18" name="SpecializationName" align="Left Top">
+ <Widget type="Button" skin="SandTextButton" position="0 18 166 18" name="SpecializationName" align="Left Top">
<Property key="TextAlign" value="Left Top"/>
</Widget>
<!-- Favorite Attributes -->
- <Widget type="TextBox" skin="HeaderText" position="0 41 156 18" name="FavoriteAttributesT" align="Left Top">
+ <Widget type="TextBox" skin="HeaderText" position="0 41 166 18" name="FavoriteAttributesT" align="Left Top">
<Property key="Caption" value="#{sChooseClassMenu2}"/>
<Property key="TextAlign" value="Left Top"/>
<UserString key="ToolTipType" value="Layout"/>
@@ -35,37 +36,37 @@
<UserString key="Caption_Text" value="#{sCreateClassMenuHelp2}"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameButton" position="0 59 156 18" name="FavoriteAttribute0" align="Left Top"/>
- <Widget type="MWAttribute" skin="MW_StatNameButton" position="0 77 156 18" name="FavoriteAttribute1" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButton" position="0 59 166 18" name="FavoriteAttribute0" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButton" position="0 77 166 18" name="FavoriteAttribute1" align="Left Top"/>
<!-- Major Skills -->
- <Widget type="TextBox" skin="HeaderText" position="156 0 158 18" name="MajorSkillT" align="Left Top">
- <Property key="Caption" value="Major Skills:"/>
+ <Widget type="TextBox" skin="HeaderText" position="166 0 166 18" name="MajorSkillT" align="Left Top">
+ <Property key="Caption" value="#{sChooseClassMenu3}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="156 18 158 18" name="MajorSkill0" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="156 36 158 18" name="MajorSkill1" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="156 54 158 18" name="MajorSkill2" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="156 72 158 18" name="MajorSkill3" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="156 90 158 18" name="MajorSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="166 18 166 18" name="MajorSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="166 36 166 18" name="MajorSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="166 54 166 18" name="MajorSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="166 72 166 18" name="MajorSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="166 90 166 18" name="MajorSkill4" align="Left Top"/>
<!-- Minor Skills -->
- <Widget type="TextBox" skin="HeaderText" position="314 0 140 18" name="MinorSkillT" align="Left Top">
- <Property key="Caption" value="Minor Skills:"/>
+ <Widget type="TextBox" skin="HeaderText" position="332 0 166 18" name="MinorSkillT" align="Left Top">
+ <Property key="Caption" value="#{sChooseClassMenu4}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="314 18 140 18" name="MinorSkill0" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="314 36 140 18" name="MinorSkill1" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="314 54 140 18" name="MinorSkill2" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="314 72 140 18" name="MinorSkill3" align="Left Top"/>
- <Widget type="MWSkill" skin="MW_StatNameButton" position="314 90 140 18" name="MinorSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="332 18 166 18" name="MinorSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="332 36 166 18" name="MinorSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="332 54 166 18" name="MinorSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="332 72 166 18" name="MinorSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="332 90 166 18" name="MinorSkill4" align="Left Top"/>
</Widget>
<!-- Dialog buttons -->
- <Widget type="HBox" position="0 158 434 24">
+ <Widget type="HBox" position="0 158 482 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout
index 5d18f4bff8..db55e87546 100644
--- a/files/mygui/openmw_chargen_review.layout
+++ b/files/mygui/openmw_chargen_review.layout
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 520 428" name="_Main">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 541 428" name="_Main">
<!-- Player Name, Race, Class and Birthsign -->
- <Widget type="Widget" skin="MW_Box" position="8 8 244 126">
+ <Widget type="Widget" skin="MW_Box" position="8 8 265 126">
<Widget type="Button" skin="MW_Button" position="8 8 64 23" name="NameButton">
<Property key="Caption" value="#{sName}"/>
</Widget>
@@ -17,27 +17,27 @@
<Widget type="Button" skin="MW_Button" position="8 95 54 23" name="SignButton">
<Property key="Caption" value="#{sBirthSign}"/>
</Widget>
- <Widget type="TextBox" skin="SandTextRight" position="97 10 140 18" name="NameText"/>
- <Widget type="TextBox" skin="SandTextRight" position="97 39 140 18" name="RaceText"/>
- <Widget type="TextBox" skin="SandTextRight" position="97 68 140 18" name="ClassText"/>
- <Widget type="TextBox" skin="SandTextRight" position="97 97 140 18" name="SignText"/>
+ <Widget type="TextBox" skin="SandTextRight" position="97 10 161 18" name="NameText"/>
+ <Widget type="TextBox" skin="SandTextRight" position="97 39 161 18" name="RaceText"/>
+ <Widget type="TextBox" skin="SandTextRight" position="97 68 161 18" name="ClassText"/>
+ <Widget type="TextBox" skin="SandTextRight" position="97 97 161 18" name="SignText"/>
</Widget>
<!-- Player Health, Magicka and Fatigue -->
- <Widget type="Widget" skin="MW_Box" position="8 144 244 72">
- <Widget type="MWDynamicStat" skin="MW_DynamicStat_Red" position="8 8 228 18" name="Health">
+ <Widget type="Widget" skin="MW_Box" position="8 144 265 72">
+ <Widget type="MWDynamicStat" skin="MW_DynamicStat_Red" position="8 8 249 18" name="Health">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="HealthToolTip"/>
<UserString key="ImageTexture_HealthImage" value="icons\k\health.dds"/>
<Property key="Caption" value="#{sHealth}"/>
</Widget>
- <Widget type="MWDynamicStat" skin="MW_DynamicStat_Blue" position="8 27 228 18" name="Magicka">
+ <Widget type="MWDynamicStat" skin="MW_DynamicStat_Blue" position="8 27 249 18" name="Magicka">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="HealthToolTip"/>
<UserString key="ImageTexture_HealthImage" value="icons\k\magicka.dds"/>
<Property key="Caption" value="#{sMagic}"/>
</Widget>
- <Widget type="MWDynamicStat" skin="MW_DynamicStat_Green" position="8 46 228 18" name="Fatigue">
+ <Widget type="MWDynamicStat" skin="MW_DynamicStat_Green" position="8 46 249 18" name="Fatigue">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="HealthToolTip"/>
<UserString key="ImageTexture_HealthImage" value="icons\k\fatigue.dds"/>
@@ -46,57 +46,57 @@
</Widget>
<!-- Player attributes -->
- <Widget type="Widget" skin="MW_Box" position="8 224 244 156">
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 4 229 18" name="Attribute0">
+ <Widget type="Widget" skin="MW_Box" position="8 224 265 156">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 4 250 18" name="Attribute0">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
<UserString key="Caption_AttributeDescription" value="#{sStrDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_strength.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 22 229 18" name="Attribute1">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 22 250 18" name="Attribute1">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
<UserString key="Caption_AttributeDescription" value="#{sIntDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_int.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 40 229 18" name="Attribute2">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 40 250 18" name="Attribute2">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
<UserString key="Caption_AttributeDescription" value="#{sWilDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_wilpower.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 58 229 18" name="Attribute3">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 58 250 18" name="Attribute3">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
<UserString key="Caption_AttributeDescription" value="#{sAgiDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_agility.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 76 229 18" name="Attribute4">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 76 250 18" name="Attribute4">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
<UserString key="Caption_AttributeDescription" value="#{sSpdDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_speed.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 94 229 18" name="Attribute5">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 94 250 18" name="Attribute5">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
<UserString key="Caption_AttributeDescription" value="#{sEndDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_endurance.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 112 229 18" name="Attribute6">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 112 250 18" name="Attribute6">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
<UserString key="Caption_AttributeDescription" value="#{sPerDesc}"/>
<UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_personality.dds"/>
</Widget>
- <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 130 229 18" name="Attribute7">
+ <Widget type="MWAttribute" skin="MW_StatNameValue" position="8 130 250 18" name="Attribute7">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
@@ -106,12 +106,12 @@
</Widget>
<!-- Player Skills -->
- <Widget type="Widget" skin="MW_Box" position="260 7 244 372" align="Left VStretch" name="Skills">
+ <Widget type="Widget" skin="MW_Box" position="281 7 244 372" align="Left VStretch" name="Skills">
<Widget type="ScrollView" skin="MW_ScrollView" position="8 6 232 362" align="Stretch" name="SkillView"/>
</Widget>
<!-- Dialogue Buttons -->
- <Widget type="HBox" position="0 388 504 24">
+ <Widget type="HBox" position="0 388 525 24">
<Widget type="Widget">
<UserString key="HStretch" value="true"/>
</Widget>
diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout
index 003eb1c6ac..5fc2862981 100644
--- a/files/mygui/openmw_itemselection_dialog.layout
+++ b/files/mygui/openmw_itemselection_dialog.layout
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <Widget type="Window" skin="MW_Dialog" position="0 0 380 155" layer="Windows" name="_Main">
+ <Widget type="Window" skin="MW_Dialog" position="0 0 380 285" layer="Windows" name="_Main">
<Widget type="TextBox" skin="SandText" position="8 8 300 18" name="Label"/>
- <Widget type="ItemView" skin="MW_ItemView" position="8 34 355 70" name="ItemView" align="Left Top Stretch">
+ <Widget type="ItemView" skin="MW_ItemView" position="8 34 355 200" name="ItemView" align="Left Top Stretch">
</Widget>
- <Widget type="AutoSizedButton" skin="MW_Button" position="340 110 24 24" name="CancelButton">
+ <Widget type="AutoSizedButton" skin="MW_Button" position="340 240 24 24" name="CancelButton">
<Property key="ExpandDirection" value="Left"/>
<Property key="Caption" value="#{sCancel}"/>
</Widget>
diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml
index 02c11c3549..7972527acc 100644
--- a/files/mygui/openmw_list.skin.xml
+++ b/files/mygui/openmw_list.skin.xml
@@ -126,6 +126,19 @@
</Skin>
+ <Skin name="MW_PopupList" size="516 516" align="Left Top">
+ <Property key="NeedKey" value="true"/>
+ <Property key="SkinLine" value="MW_ListLine"/>
+ <Property key="HeightLine" value="20"/>
+
+ <Child type="Widget" skin="BlackBG" offset="0 0 516 516" align="Stretch"/>
+ <Child type="Widget" skin="MW_Box" offset="0 0 516 516" align="Stretch"/>
+
+ <Child type="MWScrollBar" skin="MW_VScroll" offset="498 3 14 509" align="Right VStretch" name="VScroll"/>
+
+ <Child type="Widget" skin="" offset="3 3 493 509" align="Stretch" name="Client"/>
+ </Skin>
+
<Skin name="MW_ItemView" size="516 516" align="Left Top">
<Child type="Widget" skin="MW_Box" offset="0 0 516 516" align="Stretch"/>
diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout
index dfdb57648d..b2d29271bc 100644
--- a/files/mygui/openmw_messagebox.layout
+++ b/files/mygui/openmw_messagebox.layout
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
- <!--Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 0 0" name="_Main">
- <Widget type="TextBox" skin="TextBox" position="4 4 4 4" name="message"/>
- </Widget-->
- <Widget type="Window" skin="MW_Dialog" layer="Notification" position="0 0 0 0" name="_Main">
- <Widget type="EditBox" skin="MW_TextEditClient" position="5 -5 0 0" name="message" align="Left Top Stretch">
+ <Widget type="VBox" skin="MW_Dialog" layer="Notification" position="0 0 0 0" name="_Main">
+ <Property key="Padding" value="10"/>
+ <Property key="AutoResize" value="true"/>
+
+ <Widget type="AutoSizedEditBox" skin="MW_TextEditClient" position="0 0 300 0" name="message" align="Left Top">
<Property key="FontName" value="Default"/>
<Property key="TextAlign" value="Center"/>
<Property key="TextColour" value="0.75 0.6 0.35"/>
diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout
new file mode 100644
index 0000000000..49e7357645
--- /dev/null
+++ b/files/mygui/openmw_recharge_dialog.layout
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 329 253" name="_Main">
+
+ <Widget type="Widget" skin="" position="4 4 321 42" name="GemBox">
+ <Widget type="ImageBox" skin="ImageBox" position="5 6 32 32" name="GemIcon"/>
+
+ <Widget type="TextBox" skin="SandText" position="55 13 250 18" name="ChargeLabel">
+ <Property key="Caption" value="#{sQuality}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="6 46 309 160" align="Left Stretch" name="Box">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 301 152" align="Left Top Stretch" name="View">
+ <Property key="CanvasAlign" value="Left"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="239 214 75 24" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml
index 2c3908a1b9..3901ab8252 100644
--- a/files/mygui/openmw_resources.xml
+++ b/files/mygui/openmw_resources.xml
@@ -301,4 +301,22 @@
</Widget>
</Resource>
+ <Resource type="ResourceLayout" name="MW_ComboBox" version="3.2.0">
+ <Widget type="Widget" skin="" position="65 10 100 26" name="Root">
+ <Property key="MaxListLength" value="200"/>
+ <Property key="SmoothShow" value="true"/>
+ <Property key="ModeDrop" value="true"/>
+
+ <Widget type="Widget" skin="MW_Box" position="0 0 100 26" align="Stretch">
+ <Widget type="TextBox" skin="SandText" position="2 2 75 20" align="Stretch" name="Client">
+ <Property key="TextAlign" value="Left VCenter"/>
+ </Widget>
+ <Widget type="ListBox" skin="MW_PopupList" position="65 38 100 200" style="Popup" layer="Popup" name="List">
+ <Property key="Visible" value="false"/>
+ </Widget>
+ <Widget type="Button" skin="MW_ArrowDown" position="79 7 14 11" align="Right VCenter" name="Button"/>
+ </Widget>
+ </Widget>
+ </Resource>
+
</MyGUI>
diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout
new file mode 100644
index 0000000000..18de6a2399
--- /dev/null
+++ b/files/mygui/openmw_savegame_dialog.layout
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="VBox" skin="MW_Dialog" layer="Windows" position="0 0 600 400" name="_Main">
+ <Property key="Padding" value="8"/>
+ <Property key="Spacing" value="6"/>
+
+ <Widget type="HBox" skin="">
+ <UserString key="HStretch" value="true"/>
+ <UserString key="VStretch" value="true"/>
+
+
+ <Widget type="VBox" skin="">
+
+ <UserString key="HStretch" value="true"/>
+ <UserString key="VStretch" value="true"/>
+
+ <Widget type="ComboBox" skin="MW_ComboBox" position="0 0 200 24" name="SelectCharacter">
+ <Property key="Caption" value="Select Character"/>
+ <Property key="AddItem" value="Gandalf (Level 654)"/>
+ <Property key="AddItem" value="Frodo (Level 3)"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+
+ <Widget type="ListBox" skin="MW_List" position="0 0 200 200" name="SaveList">
+ <UserString key="HStretch" value="true"/>
+ <UserString key="VStretch" value="true"/>
+ <Property key="AddItem" value="Quicksave"/>
+ <Property key="AddItem" value="Autosave"/>
+ <Property key="AddItem" value="Save 3"/>
+ <Property key="AddItem" value="Save 2"/>
+ <Property key="AddItem" value="Save 1"/>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="VBox" skin="">
+ <UserString key="HStretch" value="false"/>
+ <UserString key="VStretch" value="true"/>
+ <Property key="Spacing" value="4"/>
+
+ <Widget type="AutoSizedButton" skin="" name="Spacer">
+ <Property key="Visible" value="false"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="0 0 263 137">
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 259 133" name="Screenshot"/>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="0 0 263 0" name="InfoText">
+ <Property key="Static" value="true"/>
+ <Property key="MultiLine" value="true"/>
+
+ <Property key="Caption" value="4:21 AM\nTuesday, November 5, 2013\n\nLevel 23\nBalmora, Guild of Mages\n16 Last Seed (Day 12) 9 a.m. "/>
+ </Widget>
+
+
+ </Widget>
+
+</Widget>
+
+ <Widget type="HBox" skin="">
+ <UserString key="HStretch" value="true"/>
+ <Widget type="EditBox" skin="MW_TextEdit" name="SaveNameEdit">
+ <UserString key="HStretch" value="true"/>
+ <UserString key="VStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OkButton">
+ <Property key="Caption" value="#{sOk}"/>
+ </Widget>
+
+ </Widget>
+
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout
index ebfaf678a7..61103963db 100644
--- a/files/mygui/openmw_settings_window.layout
+++ b/files/mygui/openmw_settings_window.layout
@@ -65,6 +65,12 @@
</Widget>
</Widget>
+ <Widget type="HBox" skin="" position="4 260 260 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="GrabCursorButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Grab cursor"/>
+ </Widget>
+ </Widget>
</Widget>
<Widget type="TabItem" skin="" position="4 28 360 312">
<Property key="Caption" value=" #{sAudio} "/>
@@ -216,7 +222,11 @@
<Widget type="TextBox" skin="NormalText" position="4 4 300 24" align="Left Top">
<Property key="Caption" value="Texture filtering"/>
</Widget>
- <Widget type="AutoSizedButton" skin="MW_Button" position="18 28 140 24" align="Left Top" name="TextureFilteringButton"/>
+ <Widget type="ComboBox" skin="MW_ComboBox" position="14 28 110 24" align="Left Top" name="TextureFilteringButton">
+ <Property key="AddItem" value="Bilinear"/>
+ <Property key="AddItem" value="Trilinear"/>
+ <Property key="AddItem" value="Anisotropic"/>
+ </Widget>
<Widget type="Widget" skin="" position="184 4 300 50" align="Left Top" name="AnisotropyBox">
<Widget type="TextBox" skin="SandText" position="0 0 300 24" align="Left Top" name="AnisotropyLabel">
@@ -308,9 +318,9 @@
</Widget>
<Widget type="HBox" position="4 28 350 24">
- <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsTextureSize"/>
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="TerrainShadows"/>
<Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
- <Property key="Caption" value="Texture size"/>
+ <Property key="Caption" value="Terrain shadows"/>
</Widget>
</Widget>
@@ -336,9 +346,14 @@
</Widget>
<Widget type="HBox" position="4 140 350 24">
- <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="TerrainShadows"/>
+ <Widget type="ComboBox" skin="MW_ComboBox" align="Left Top" name="ShadowsTextureSize" position="0 0 60 24">
+ <Property key="AddItem" value="512"/>
+ <Property key="AddItem" value="1024"/>
+ <Property key="AddItem" value="2048"/>
+ <Property key="AddItem" value="4096"/>
+ </Widget>
<Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
- <Property key="Caption" value="Terrain shadows"/>
+ <Property key="Caption" value="Texture size"/>
</Widget>
</Widget>
diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout
index 5ae3f96caf..36c28c450e 100644
--- a/files/mygui/openmw_stats_window.layout
+++ b/files/mygui/openmw_stats_window.layout
@@ -2,7 +2,7 @@
<MyGUI type="Layout">
<Widget type="ExposedWindow" skin="MW_Window_Pinnable" layer="Windows" position="0 0 500 342" name="_Main">
- <Property key="MinSize" value="500 342"/>
+ <Property key="MinSize" value="300 200"/>
<Widget type="Widget" skin="" name="LeftPane" position="0 0 220 342">
diff --git a/files/opencs.desktop b/files/opencs.desktop
index f6aad5b097..80afa26bce 100644
--- a/files/opencs.desktop
+++ b/files/opencs.desktop
@@ -3,6 +3,7 @@ Type=Application
Name=OpenMW Content Editor
GenericName=Content Editor
Comment=A replacement for the Morrowind Construction Set.
+Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit
TryExec=opencs
Exec=opencs
Icon=opencs
diff --git a/files/openmw.desktop b/files/openmw.desktop
index 118cd3bbe6..3e26018d0c 100644
--- a/files/openmw.desktop
+++ b/files/openmw.desktop
@@ -3,6 +3,7 @@ Type=Application
Name=OpenMW Launcher
GenericName=Role Playing Game
Comment=An engine replacement for The Elder Scrolls III: Morrowind
+Keywords=Morrowind;Reimplementation Mods;esm;bsa
TryExec=omwlauncher
Exec=omwlauncher
Icon=openmw
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
index f191430df1..22391fe936 100644
--- a/files/settings-default.cfg
+++ b/files/settings-default.cfg
@@ -53,8 +53,7 @@ texture filtering = anisotropic
anisotropy = 4
# Number of texture mipmaps to generate
-# This setting is currently ignored due to mipmap generation problems on Intel/AMD
-#num mipmaps = 5
+num mipmaps = 8
shader mode =
@@ -156,6 +155,8 @@ voice volume = 1.0
[Input]
+grab cursor = true
+
invert y axis = false
camera sensitivity = 1.0
@@ -169,3 +170,69 @@ ui y multiplier = 1.0
[Game]
# Always use the most powerful attack when striking with a weapon (chop, slash or thrust)
best attack = false
+
+[Windows]
+inventory x = 0
+inventory y = 0.4275
+inventory w = 0.6225
+inventory h = 0.5725
+
+inventory container x = 0
+inventory container y = 0.4275
+inventory container w = 0.6225
+inventory container h = 0.5725
+
+inventory barter x = 0
+inventory barter y = 0.4275
+inventory barter w = 0.6225
+inventory barter h = 0.5725
+
+inventory companion x = 0
+inventory companion y = 0.4275
+inventory companion w = 0.6225
+inventory companion h = 0.5725
+
+container x = 0.25
+container y = 0
+container w = 0.75
+container h = 0.375
+
+companion x = 0.25
+companion y = 0
+companion w = 0.75
+companion h = 0.375
+
+map x = 0.625
+map y = 0
+map w = 0.375
+map h = 0.5725
+
+barter x = 0.25
+barter y = 0
+barter w = 0.75
+barter h = 0.375
+
+alchemy x = 0.25
+alchemy y = 0.25
+alchemy w = 0.5
+alchemy h = 0.5
+
+stats x = 0
+stats y = 0
+stats w = 0.375
+stats h = 0.4275
+
+spells x = 0.3775
+spells y = 0.4275
+spells w = 0.375
+spells h = 0.5725
+
+console x = 0
+console y = 0
+console w = 1
+console h = 0.5
+
+dialogue h = 0.810
+dialogue w = 0.810
+dialogue x = 0.095
+dialogue y = 0.095
diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp
index b0fa07fd60..de9ea6f57c 100644
--- a/libs/openengine/bullet/BtOgre.cpp
+++ b/libs/openengine/bullet/BtOgre.cpp
@@ -819,6 +819,8 @@ namespace BtOgre {
*/
DynamicRenderable::DynamicRenderable()
+ : mVertexBufferCapacity(0)
+ , mIndexBufferCapacity(0)
{
}
diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp
deleted file mode 100644
index c20415884a..0000000000
--- a/libs/openengine/bullet/CMotionState.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#include "CMotionState.h"
-#include "physic.hpp"
-
-#include <btBulletDynamicsCommon.h>
-#include <btBulletCollisionCommon.h>
-#include <components/nifbullet/bulletnifloader.hpp>
-
-namespace OEngine {
-namespace Physic
-{
-
- CMotionState::CMotionState(PhysicEngine* eng,std::string name)
- : isPC(false)
- , isNPC(true)
- {
- pEng = eng;
- tr.setIdentity();
- pName = name;
- }
-
- void CMotionState::getWorldTransform(btTransform &worldTrans) const
- {
- worldTrans = tr;
- }
-
- void CMotionState::setWorldTransform(const btTransform &worldTrans)
- {
- tr = worldTrans;
-
- PhysicEvent evt;
- evt.isNPC = isNPC;
- evt.isPC = isPC;
- evt.newTransform = tr;
- evt.RigidBodyName = pName;
-
- if(isPC)
- {
- pEng->PEventList.push_back(evt);
- }
- else
- {
- pEng->NPEventList.push_back(evt);
- }
- }
-
-}}
diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h
deleted file mode 100644
index 3508ab4ef1..0000000000
--- a/libs/openengine/bullet/CMotionState.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#ifndef OENGINE_CMOTIONSTATE_H
-#define OENGINE_CMOTIONSTATE_H
-
-#include <BulletDynamics/Dynamics/btRigidBody.h>
-#include <string>
-
-namespace OEngine {
-namespace Physic
-{
- class PhysicEngine;
-
- /**
- * A CMotionState is associated with a single RigidBody.
- * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform.
- * for more info, see the bullet Wiki at btMotionState.
- */
- class CMotionState:public btMotionState
- {
- public:
-
- CMotionState(PhysicEngine* eng,std::string name);
-
- /**
- * Return the position of the RigidBody.
- */
- virtual void getWorldTransform(btTransform &worldTrans) const;
-
- /**
- * Function called by bullet when the RigidBody is moved.
- * It add an event to the EventList of the PhysicEngine class.
- */
- virtual void setWorldTransform(const btTransform &worldTrans);
-
- protected:
- PhysicEngine* pEng;
- btTransform tr;
- bool isNPC;
- bool isPC;
-
- std::string pName;
- };
-
- struct PhysicEvent
- {
- bool isNPC;
- bool isPC;
- btTransform newTransform;
- std::string RigidBodyName;
- };
-
-}}
-#endif
diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp
index e33edda183..4e80088bf7 100644
--- a/libs/openengine/bullet/physic.cpp
+++ b/libs/openengine/bullet/physic.cpp
@@ -3,7 +3,6 @@
#include <btBulletCollisionCommon.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <components/nifbullet/bulletnifloader.hpp>
-#include "CMotionState.h"
#include "OgreRoot.h"
#include "btKinematicCharacterController.h"
#include "BtOgrePG.h"
@@ -318,9 +317,7 @@ namespace Physic
btVector3 scl(triSize, triSize, 1);
hfShape->setLocalScaling(scl);
- CMotionState* newMotionState = new CMotionState(this,name);
-
- btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,hfShape);
+ btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,0,hfShape);
RigidBody* body = new RigidBody(CI,name);
body->getWorldTransform().setOrigin(btVector3( (x+0.5)*triSize*(sqrtVerts-1), (y+0.5)*triSize*(sqrtVerts-1), (maxh+minh)/2.f));
@@ -401,12 +398,9 @@ namespace Physic
else
shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale));
- //create the motionState
- CMotionState* newMotionState = new CMotionState(this,name);
-
//create the real body
btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo
- (0,newMotionState, raycasting ? shape->mRaycastingShape : shape->mCollisionShape);
+ (0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape);
RigidBody* body = new RigidBody(CI,name);
body->mPlaceable = placeable;
diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp
index f28f95ccb8..6cd7244b85 100644
--- a/libs/openengine/bullet/physic.hpp
+++ b/libs/openengine/bullet/physic.hpp
@@ -38,7 +38,6 @@ namespace MWWorld
namespace OEngine {
namespace Physic
{
- class CMotionState;
struct PhysicEvent;
class PhysicEngine;
class RigidBody;
@@ -157,17 +156,7 @@ namespace Physic
private:
void disableCollisionBody();
void enableCollisionBody();
-public:
-//HACK: in Visual Studio 2010 and presumably above, this structures alignment
-// must be 16, but the built in operator new & delete don't properly
-// perform this alignment.
-#if _MSC_VER >= 1600
- void * operator new (size_t Size) { return _aligned_malloc (Size, 16); }
- void operator delete (void * Data) { _aligned_free (Data); }
-#endif
-
- private:
OEngine::Physic::RigidBody* mBody;
OEngine::Physic::RigidBody* mRaycastingBody;
@@ -329,12 +318,6 @@ public:
const btVector3 &origin,
btCollisionObject *object);
- //event list of non player object
- std::list<PhysicEvent> NPEventList;
-
- //event list affecting the player
- std::list<PhysicEvent> PEventList;
-
//Bullet Stuff
btOverlappingPairCache* pairCache;
btBroadphaseInterface* broadphase;
diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp
index 9040dfb90e..26a3fdab84 100644
--- a/libs/openengine/gui/layout.hpp
+++ b/libs/openengine/gui/layout.hpp
@@ -150,10 +150,10 @@ namespace GUI
MyGUI::IntSize size = button->getTextSize();
button->setSize(size.width + 24, button->getSize().height);
}
+ MyGUI::Widget* mMainWidget;
protected:
- MyGUI::Widget* mMainWidget;
std::string mPrefix;
std::string mLayoutName;
MyGUI::VectorWidgetPtr mListWindowRoot;
diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp
index 9127812408..9e5ec5414d 100644
--- a/libs/openengine/ogre/renderer.cpp
+++ b/libs/openengine/ogre/renderer.cpp
@@ -1,138 +1,42 @@
#include "renderer.hpp"
#include "fader.hpp"
-#include "particles.hpp"
#include <SDL.h>
-#include "OgreRoot.h"
-#include "OgreRenderWindow.h"
-#include "OgreLogManager.h"
-#include "OgreLog.h"
-#include "OgreTextureManager.h"
-#include "OgreTexture.h"
-#include "OgreHardwarePixelBuffer.h"
-#include <OgreParticleSystemManager.h>
-#include "OgreParticleAffectorFactory.h"
-
-#include <boost/filesystem.hpp>
-
-#include <components/files/ogreplugin.hpp>
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+#include <OgreTextureManager.h>
+#include <OgreTexture.h>
+#include <OgreHardwarePixelBuffer.h>
#include <extern/sdl4ogre/sdlwindowhelper.hpp>
+#include <components/ogreinit/ogreinit.hpp>
+
#include <cassert>
-#include <cstdlib>
#include <stdexcept>
using namespace Ogre;
using namespace OEngine::Render;
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
-
-CustomRoot::CustomRoot(const Ogre::String& pluginFileName,
- const Ogre::String& configFileName,
- const Ogre::String& logFileName)
-: Ogre::Root(pluginFileName, configFileName, logFileName)
-{}
-
-bool CustomRoot::isQueuedEnd() const
-{
- return mQueuedEnd;
-}
-
-#endif
-
void OgreRenderer::cleanup()
{
delete mFader;
mFader = NULL;
- delete mRoot;
- mRoot = NULL;
+ if (mWindow)
+ Ogre::Root::getSingleton().destroyRenderTarget(mWindow);
+ mWindow = NULL;
+
+ delete mOgreInit;
+ mOgreInit = NULL;
// If we don't do this, the desktop resolution is not restored on exit
SDL_SetWindowFullscreen(mSDLWindow, 0);
SDL_DestroyWindow(mSDLWindow);
mSDLWindow = NULL;
-
- unloadPlugins();
-}
-
-void OgreRenderer::start()
-{
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- // we need this custom main loop because otherwise Ogre's Carbon message pump will
- // steal input events even from our Cocoa window
- // There's no way to disable Ogre's message pump other that comment pump code in Ogre's source
- do {
- if (!mRoot->renderOneFrame()) {
- break;
- }
-
- } while (!mRoot->isQueuedEnd());
-#else
- mRoot->startRendering();
-#endif
-}
-
-void OgreRenderer::loadPlugins()
-{
- #ifdef ENABLE_PLUGIN_GL
- mGLPlugin = new Ogre::GLPlugin();
- mRoot->installPlugin(mGLPlugin);
- #endif
- #ifdef ENABLE_PLUGIN_Direct3D9
- mD3D9Plugin = new Ogre::D3D9Plugin();
- mRoot->installPlugin(mD3D9Plugin);
- #endif
- #ifdef ENABLE_PLUGIN_CgProgramManager
- mCgPlugin = new Ogre::CgPlugin();
- mRoot->installPlugin(mCgPlugin);
- #endif
- #ifdef ENABLE_PLUGIN_OctreeSceneManager
- mOctreePlugin = new Ogre::OctreePlugin();
- mRoot->installPlugin(mOctreePlugin);
- #endif
- #ifdef ENABLE_PLUGIN_ParticleFX
- mParticleFXPlugin = new Ogre::ParticleFXPlugin();
- mRoot->installPlugin(mParticleFXPlugin);
- #endif
-}
-
-void OgreRenderer::unloadPlugins()
-{
- std::vector<Ogre::ParticleEmitterFactory*>::iterator ei;
- for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei)
- OGRE_DELETE (*ei);
- mEmitterFactories.clear();
-
- std::vector<Ogre::ParticleAffectorFactory*>::iterator ai;
- for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai)
- OGRE_DELETE (*ai);
- mAffectorFactories.clear();
-
- #ifdef ENABLE_PLUGIN_GL
- delete mGLPlugin;
- mGLPlugin = NULL;
- #endif
- #ifdef ENABLE_PLUGIN_Direct3D9
- delete mD3D9Plugin;
- mD3D9Plugin = NULL;
- #endif
- #ifdef ENABLE_PLUGIN_CgProgramManager
- delete mCgPlugin;
- mCgPlugin = NULL;
- #endif
- #ifdef ENABLE_PLUGIN_OctreeSceneManager
- delete mOctreePlugin;
- mOctreePlugin = NULL;
- #endif
- #ifdef ENABLE_PLUGIN_ParticleFX
- delete mParticleFXPlugin;
- mParticleFXPlugin = NULL;
- #endif
}
void OgreRenderer::update(float dt)
@@ -152,75 +56,11 @@ float OgreRenderer::getFPS()
void OgreRenderer::configure(const std::string &logPath,
const std::string& renderSystem,
- const std::string& rttMode,
- bool _logging)
+ const std::string& rttMode
+ )
{
- // Set up logging first
- new LogManager;
- Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log"));
- logging = _logging;
-
- if(logging)
- // Full log detail
- log->setLogDetail(LL_BOREME);
- else
- // Disable logging
- log->setDebugOutputEnabled(false);
-
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- mRoot = new CustomRoot("", "", "");
-#else
- mRoot = new Root("", "", "");
-#endif
-
- #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX)
- loadPlugins();
- #endif
-
- std::string pluginDir;
- const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR");
- if (pluginEnv)
- pluginDir = pluginEnv;
- else
- {
-#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- pluginDir = ".\\";
-#endif
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- pluginDir = OGRE_PLUGIN_DIR;
-#endif
-#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
- pluginDir = OGRE_PLUGIN_DIR_REL;
-#endif
- }
-
- boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir));
-
- pluginDir = absPluginPath.string();
-
- Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot);
- Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot);
- Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot);
- Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot);
- Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot);
- Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot);
-
-
- Ogre::ParticleEmitterFactory *emitter;
- emitter = OGRE_NEW NifEmitterFactory();
- Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter);
- mEmitterFactories.push_back(emitter);
-
-
- Ogre::ParticleAffectorFactory *affector;
- affector = OGRE_NEW GrowFadeAffectorFactory();
- Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector);
- mAffectorFactories.push_back(affector);
-
- affector = OGRE_NEW GravityAffectorFactory();
- Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector);
- mAffectorFactories.push_back(affector);
-
+ mOgreInit = new OgreInit::OgreInit();
+ mRoot = mOgreInit->init(logPath + "/ogre.log");
RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem);
if (rs == 0)
diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp
index 89edc567dd..767e7cf99e 100644
--- a/libs/openengine/ogre/renderer.hpp
+++ b/libs/openengine/ogre/renderer.hpp
@@ -7,38 +7,15 @@
#include <string>
-// Static plugin headers
-#ifdef ENABLE_PLUGIN_CgProgramManager
-# include "OgreCgPlugin.h"
-#endif
-#ifdef ENABLE_PLUGIN_OctreeSceneManager
-# include "OgreOctreePlugin.h"
-#endif
-#ifdef ENABLE_PLUGIN_ParticleFX
-# include "OgreParticleFXPlugin.h"
-#endif
-#ifdef ENABLE_PLUGIN_GL
-# include "OgreGLPlugin.h"
-#endif
-#ifdef ENABLE_PLUGIN_Direct3D9
-# include "OgreD3D9Plugin.h"
-#endif
-
-#include "OgreTexture.h"
-#include <OgreWindowEventUtilities.h>
+#include <OgreTexture.h>
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
-#include <OgreRoot.h>
-#endif
struct SDL_Window;
struct SDL_Surface;
namespace Ogre
{
-#if OGRE_PLATFORM != OGRE_PLATFORM_APPLE
class Root;
-#endif
class RenderWindow;
class SceneManager;
class Camera;
@@ -47,6 +24,11 @@ namespace Ogre
class ParticleAffectorFactory;
}
+namespace OgreInit
+{
+ class OgreInit;
+}
+
namespace OEngine
{
namespace Render
@@ -61,17 +43,6 @@ namespace OEngine
std::string icon;
};
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- class CustomRoot : public Ogre::Root {
- public:
- bool isQueuedEnd() const;
-
- CustomRoot(const Ogre::String& pluginFileName = "plugins.cfg",
- const Ogre::String& configFileName = "ogre.cfg",
- const Ogre::String& logFileName = "Ogre.log");
- };
-#endif
-
class Fader;
class WindowSizeListener
@@ -82,35 +53,16 @@ namespace OEngine
class OgreRenderer
{
-#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
- CustomRoot *mRoot;
-#else
Ogre::Root *mRoot;
-#endif
Ogre::RenderWindow *mWindow;
SDL_Window *mSDLWindow;
Ogre::SceneManager *mScene;
Ogre::Camera *mCamera;
Ogre::Viewport *mView;
- #ifdef ENABLE_PLUGIN_CgProgramManager
- Ogre::CgPlugin* mCgPlugin;
- #endif
- #ifdef ENABLE_PLUGIN_OctreeSceneManager
- Ogre::OctreePlugin* mOctreePlugin;
- #endif
- #ifdef ENABLE_PLUGIN_ParticleFX
- Ogre::ParticleFXPlugin* mParticleFXPlugin;
- #endif
- #ifdef ENABLE_PLUGIN_GL
- Ogre::GLPlugin* mGLPlugin;
- #endif
- #ifdef ENABLE_PLUGIN_Direct3D9
- Ogre::D3D9Plugin* mD3D9Plugin;
- #endif
+
+ OgreInit::OgreInit* mOgreInit;
+
Fader* mFader;
- std::vector<Ogre::ParticleEmitterFactory*> mEmitterFactories;
- std::vector<Ogre::ParticleAffectorFactory*> mAffectorFactories;
- bool logging;
WindowSizeListener* mWindowListener;
@@ -122,24 +74,9 @@ namespace OEngine
, mScene(NULL)
, mCamera(NULL)
, mView(NULL)
- , mWindowListener(NULL)
- #ifdef ENABLE_PLUGIN_CgProgramManager
- , mCgPlugin(NULL)
- #endif
- #ifdef ENABLE_PLUGIN_OctreeSceneManager
- , mOctreePlugin(NULL)
- #endif
- #ifdef ENABLE_PLUGIN_ParticleFX
- , mParticleFXPlugin(NULL)
- #endif
- #ifdef ENABLE_PLUGIN_GL
- , mGLPlugin(NULL)
- #endif
- #ifdef ENABLE_PLUGIN_Direct3D9
- , mD3D9Plugin(NULL)
- #endif
+ , mOgreInit(NULL)
, mFader(NULL)
- , logging(false)
+ , mWindowListener(NULL)
{
}
@@ -150,8 +87,7 @@ namespace OEngine
void configure(
const std::string &logPath, // Path to directory where to store log files
const std::string &renderSystem,
- const std::string &rttMode,
- bool _logging); // Enable or disable logging
+ const std::string &rttMode); // Enable or disable logging
/// Create a window with the given title
void createWindow(const std::string &title, const WindowSettings& settings);
@@ -167,13 +103,6 @@ namespace OEngine
/// Kill the renderer.
void cleanup();
- /// Start the main rendering loop
- void start();
-
- void loadPlugins();
-
- void unloadPlugins();
-
void update(float dt);
/// Write a screenshot to file
diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore
new file mode 100644
index 0000000000..ce5852a376
--- /dev/null
+++ b/manual/opencs/.gitignore
@@ -0,0 +1,6 @@
+*.backup
+*.aux
+*.log
+*.toc
+*.pdf
+*.out
diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex
new file mode 100644
index 0000000000..2c1377ec1e
--- /dev/null
+++ b/manual/opencs/creating_file.tex
@@ -0,0 +1,25 @@
+\section{OpenCS starting dialog}
+\subsection{Introduction}
+The great day has come. Today, you shall open \OCS{} application. And when you do this, you shall see our starting dialog window that holds three buttons
+that can bring both pain and happiness. So just do this, please.
+
+\subsection{Basics}
+Back to the manual? Great! As you can see, the starting window holds just three buttons. Since you are already familiar with our files system, they come
+to you with no surprise.\\
+
+First, there is a \textbf{Create A New Game} button. Clearly, you should press it when you want to create a game file. Than, what \textbf{Create A New Addon} button do?
+Yes! You are right! This button will create any addon content file (and new project file associated with it)! Wonderful! And what the last remaining button do? \textbf{Edit A Content File}? Well, it comes with no surprise that this should be used when you need to alter existing content file, either a game or addon.\\
+
+\paragraph{Selecting Files For New Addon}
+As We wrote earlier, both \OMW{} and \OCS{} are operating with dependency idea in mind. As You remember you should only depend on files you are actually using. But how?\\
+It is simple. When you click either \textbf{Create new Addon} you will be asked to choose those with a new dialog window. The window is using vertical layout, first you should consider the the top element, the one that allows you to select a game file with drop down menu. Since we are operating on the assumption that there is only one game file loaded at the time, you can depend only on one game file. Next, choose addons that you want to use in your addon with checkboxes.\\
+
+The last thing to do is to name your your addon and click create.
+
+\paragraph{Selecting File for Editing}
+Clicking \textbf{Edit A Content File} will show somewhat similar window. Here you should select your Game file with drop down menu. If you want to edit this game file, simply click \textbf{OK} button. If you want to alter addon depending on that file, mark it with checkbox and than click \textbf{Ok} button.
+
+\subsection{Advanced}
+If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Those are general OpenCS settings. We will cover this is separate section.\\
+
+And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file
diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex
new file mode 100644
index 0000000000..b483dac5a2
--- /dev/null
+++ b/manual/opencs/files_and_directories.tex
@@ -0,0 +1,120 @@
+\section{Files and Directories}
+\subsection{Introduction}
+This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts,
+and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on \OCS.
+
+\subsection{Used terms} %TODO
+
+\subsection{Basics}
+
+\paragraph{Directories}
+OpenMW and \OCS{} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration
+files and few different folders. The location of the user directory is hard coded for each supported operating system.
+
+%TODO list paths.
+In addition to this single hard coded directory, both \OMW{} and \OCS{} need a~place to seek for actual data files of the game:
+textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support
+multiple such paths (we call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory
+where original \MW{} is either installed or unpacked. You are free to specify as many data paths as you would like,
+however, there is one special data path that, as described later, is used to store newly created content files.
+
+\paragraph{Content files}
+\BS{} \MW{} engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those
+is not clear, and often confusing. You would expect the ESM (master) file is used to specify one master, that is modified by the ESPs plugins,
+and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be
+described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid
+in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. \OMW{} achieves
+this with our own content file types.
+
+We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed
+with our engine in mind: game files and addon files together called ``content files``.
+
+\subparagraph{OpenMW content files}
+Game and Addon files are concept somewhat similar to the old ESM/ESP, only in the way it should be from the very beginning. Nothing easier
+to describe. If you want to make new game using \OMW{} as engine (so called ``total conversion'') you should create a game file.
+If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should
+consider is if your project is about changing other game, or creating a new one. Simple as that.
+
+Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files.
+
+%TODO describe what content files contains. and what not.
+\subparagraph{\MW{} content files}
+Using our content files is recommended solution for projects that are intended to used with \OMW{} engine. However some players
+wish to use original \MW{} engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very
+successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content.
+Because of this \OCS{} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead
+using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very
+last section of this manual. %not finished TODO add the said section. Most likely when more features are present.
+
+The actual creation of new files is described in the next chapter. Here we are gonna focus only on details that you need to know
+in order to create your first \OCS{} file while full understanding your needs. For now let's jut remember that content files
+are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier).
+
+\subparagraph{Dependencies}
+Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can not work otherwise.
+Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That is right:
+we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island
+for a game) or other addon files (house on the said island). It is a good idea to be dependent only on files that are really changed
+in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that
+this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just
+keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file.
+
+Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original
+and dirty {ESP/ESM} system) at the time and therefore no game file can depend on other game file, and since game file makes the base
+for addon files -- it can not depend on addon files.
+
+%\subparagraph{Loading order} %TODO
+\paragraph{Project files}
+Project files act as containers for data not used by the \OMW{} game engine itself, but still useful for OpenCS. The shining example
+of this data category are without doubt record filters (described in the later section of the manual you are reading currently).
+As a mod author you probably do not need and/or want to distribute project files at all, they are meant to be used only by you.
+
+As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work
+on new content file and project file was not found, it will be created.
+Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file
+with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file.
+
+%TODO where are they stored.
+Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly
+created project files, and a place where \OCS{} looks for already existing files.
+
+\paragraph{Resources files}
+%textures, sounds, whatever
+Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files:
+models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new \OMW{}
+file type do not contain any of those, it is clear that they have to be deliver with a different file. It is also clear that this,
+let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than
+a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}.
+Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going
+to do just that. Later, you will learn how to make use of those files in your content.
+
+\subparagraph{Audio}
+OpenMW is using {FFmpeg} for audio playback, and so we support every audio type that is supported by this library. This makes a huge list.
+Below is only small portion of supported file types.
+
+\begin{description}
+ \item mp3 ({MPEG}-1 {Part 3 Layer 3}) popular audio file format and \textit{de facto} standard for storing audio. Used by the \MW{} game.
+ \item ogg open source, multimedia container file using high quality vorbis audio codec. Recommended.
+\end{description}
+
+\subparagraph{Video}
+As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover
+only the most significant.
+
+\begin{description}
+ \item bik videos used by original \MW{} game.
+ \item mp4 multimedia container which use more advanced codecs ({MPEG-4 Parts 2,3,10}) with a better audio and video compression rate,
+ but also requiring more {CPU} intensive decoding -- this makes it probably less suited for storing sounds in computer games, but good for videos.
+ \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded,
+ but since game logic is not running during cut scenes we can recommend it for use with \OMW.
+ \item ogv alternative, open source container using theora codec for video and vorbis for audio.
+\end{description}
+
+\subparagraph{Textures and images}
+Original \MW{} game uses {DDS} and {TGA} files for all kind of two dimensional images and textures alike. In addition, engine supported BMP
+files for some reason ({BMP} is a terrible format for a video game). We also support extended set of image files -- including {JPEG} and {PNG}.
+JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks.
+However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with {DirectX} rendering backend. You probabbly still want
+to use {DDS} files for textures.
+
+%\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file
diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex
new file mode 100644
index 0000000000..36d97e0f5e
--- /dev/null
+++ b/manual/opencs/filters.tex
@@ -0,0 +1,205 @@
+\section{Record filters}
+\subsection{Introduction}
+Filters are the key element of \OCS{} use cases by allowing rapid and easy access to the searched records presented in all tables.
+Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in
+the this section of the manual are perfectly clear to you.
+
+Do not be afraid though, filters are fairly intuitive and easy to use.
+
+\subsubsection{Used Terms}
+
+\begin{description}
+ \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according
+ to the some criteria. In case of \OCS: records are being filtered according to the criteria of user choice. Criteria are written
+ down in language with simple syntax.
+ \item[Criteria] describes condition under with any any record is being select by the filter.
+ \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is:
+ written with correct syntax.
+ \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates
+ either to the true or false for every column record at the time.
+ \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions
+ together in order to create filter that will check for criteria placed in two (again: or more) columns (logical \textit{or}, \textit{and}).
+ \item[unary] is any expression that expects one other expression. The example is \textit{not} expression. In fact \textit{not} is the only useful
+ unary expression in \OCS{} record filters.
+ \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later.
+\end{description}
+
+\subsubsection{Basics}
+In fact you do not need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity
+with \OCS{} is inside basics section.
+
+\subsubsection{Interface}
+Above each table there is a field that is used to enter filter: either predefined by the \OMW{} developers or made by you, the user.
+You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application
+menu view, and click filters. You should see set of default filters, made by the \OMW{} team in the table with the following columns: filter,
+description and modified.
+
+\begin{description}
+ \item[ID] contains the name of the filter.
+ \item[Modified] just like in all other tables you have seen so far modified indicates if a filter was added, modified or removed.
+ \item[Filter] column containing expression of the filter.
+ \item[Description] contains the short description of the filter function. Do not expect any surprises there.
+\end{description}
+
+So let's learn how to actually use those to speed up your work.
+\subsubsection{Using predefined filters}
+Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables
+table and type in the filters field the following: \mono{project::weapons}. As soon as you complete the text, table will magicly alter
+and will show only the weapons. As you could noticed \mono{project::weapons} is nothing else than a~ID of one of the predefined filters. That is it:
+in order to use the filter inside the table you simply type it is name inside the filter field.
+
+To make life easier filter IDs follow simple convention.
+
+\begin{itemize}
+ \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance \mono{project::weapons} filter
+ contains the word weapons (did you noticed?). Plural form is always used.
+ \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance \mono{project::weaponssilver} will
+ filter only silver weapons (new mechanic introduced by the \BM{}, silver weapons deal double damage against werewolfs) and
+ \mono{project::weaponsmagical} will filter only magical weapons (able to hurt ghosts and other supernatural creatures).
+ \item There are few exceptions from the above rule. For instance there is a \mono{project::added}, \mono{project::removed},
+ \mono{project::modyfied}, \mono{project::base}.
+ You would probably except something more like \mono{project::statusadded} but in this case typing this few extra characters would only
+ help to break your keyboard faster.
+\end{itemize}
+
+We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple.
+
+\subsection{Advanced}
+Back to the manual? Great.
+
+If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions.
+Finally, you will have to write this with correct syntax. As a result table will show only desired rows.
+
+Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong
+\subsubsection{Namespaces}
+Did you noticed that every default filter has \mono{project::} prefix? It is a \textit{namespace}, a~term borrowed from the \CPP{} language.
+In case of \OCS{} namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}.
+But what does it mean in case of filters? Well, short explanation is actually simple.
+\begin{description}
+ \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart \OCS{} and filter
+ is still there.
+ \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit \OCS{} (close session)
+ the filter will be gone. Forever! Until then it can be found inside the filters table.
+\end{description}
+In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session)
+anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that
+you do not need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with
+exclamation mark: ``!''.
+
+Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start
+with nullary expressions that will allow you to create a basic filter.
+
+\subsubsection{Nullary expressions}
+All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: \mono{string}) and secondly:
+condition that will be checked inside brackets (for instance \mono{string(something, something)}). If conditions of your expression will be meet
+by a record (technical speaking: expression will evaluate to true) the record will show up in the table.
+
+It is clear that you need to know what are you checking, that is: what column of the table contains information that you are interested
+in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column
+you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma.
+
+\paragraph{String -- string(``column'', ``value'')}
+String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.}
+just a word for anything composed of characters. In case of \OCS{} this is in fact true for every value inside the column that is not composed
+of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression\footnote{There is no
+Boolean (``true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true,
+when record contains in the specified column exactly the same value as specified.
+
+Since majority of the columns contain string values, string is among the most often used expressions. Examples:
+\begin{itemize}
+ \item \mono{string(``Record Type'', ``Weapon'')} -- will evaluate to true for all records containing \mono{Weapon} in the \mono{Record Type} column cell.
+ This group contains every weapon (including arrows and bolts) found in the game.
+ \item \mono{string(``Portable'', ``true'')} -- will evaluate to true for all records containing word true inside \mono{Portable} column cell.
+ This group contains every portable light sources (lanterns, torches etc.).
+\end{itemize}
+This is probably enough to create around 90 string filters you will eventually need. However, this expression is even more powerful
+-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched
+by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression:
+\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet.
+There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it?
+
+Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression
+that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned
+\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers.
+
+Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple:
+when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular
+expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first,
+you will clearly need way to determinate what letters you want to match (word is composed by letters).
+
+Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string.
+You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{}
+to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern
+\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with
+\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because \mono{.*} is used.
+
+You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when
+\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that
+starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, but it will also cover
+strings starting with \mono{l}.
+
+What if you want to match more than just one latter? Just use \mono{(|)}. it is pretty similar to the above one letter as you see, but it is
+used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be
+true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}.
+
+Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on
+Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively to be sure.
+
+\paragraph{Value -- value(``value'', (``open'', ``close''))}
+While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like
+``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria
+inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.
+As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this:
+\begin{itemize}
+ \item To include value use [] brackets. For value equal 5, expression \mono{value(something, [5, 10])} will evaluate to true.
+ \item To exclude value use () brackets. For value equal 5, expression \mono{value(something, (5, 10))} will evaluate to false.
+ \item Mixing brackets is completely legal. For value equal 10, expression \mono{value(something, [5, 10)} will evaluate to true.
+ The same expression will evaluate to false for value equal 10.
+\end{itemize}
+
+\paragraph{``true'' and ``false''}
+Nullary \textit{true} and \textit{false} do not accept any arguments, and always evaluates to true (in case of \textit{true})
+and false (in case of \textit{false}) no matter what. The main usage of this expressions is the give users ability to quickly
+disable some part of the filter that makes heavy use of the logical expressions.
+
+\subsubsection{Logical expressions}
+This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the \OCS{} is logical
+\textit{not}, while the remaining binary expressions are: \textit{or}, \textit{and}. This clearly makes them (from the user point of view)
+belonging to the same group of logical expressions.
+
+\paragraph{not -- not expression()}
+Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before
+expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets
+are not needed: \textit{not} will revert only the first expression following it.
+
+To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true
+for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}.
+This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with
+\textit{or}, \textit{and} filters.
+
+\paragraph{or -- or(expression1(), expression2())}
+\textit{Or} is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma.
+
+\textit{Or} expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following
+\mono{or(string(``record type'', npc), string(``record type'', creature))} and will show both npcs and creatures.
+
+\paragraph{and -- and(expression1(), expression2())}
+\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ``or'' you can use two or more arguments,
+separated by a comma.
+As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types,
+excluding gauntlets you can write the following: \mono{and (not string(``armor type'', ``.* gauntlet''), string(``Record Type'', ``Armor''))}.
+
+\subsubsection{Creating and saving filter}
+In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu.
+A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of
+the filter (described earlier) and desired ID of the filter. After pressing OK button new entry will show up in the filters table. This filter
+does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry
+and write it down there.
+Done! You are free to use your filter.
+
+\subsubsection{Replacing the default filters set}
+OpenCS allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project,
+add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (do not forget to remove .omwaddon.project extension)
+and place it inside your configuration directory.
+
+The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file.
diff --git a/manual/opencs/img/water.png b/manual/opencs/img/water.png
new file mode 100644
index 0000000000..885c2d9a73
--- /dev/null
+++ b/manual/opencs/img/water.png
Binary files differ
diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex
new file mode 100644
index 0000000000..1d8aebe408
--- /dev/null
+++ b/manual/opencs/main.tex
@@ -0,0 +1,34 @@
+\documentclass[american]{article}
+\usepackage[T1]{fontenc}
+\usepackage{babel}
+\usepackage{txfonts} % Public Times New Roman text & math font
+\usepackage[pdftex]{graphicx}
+\usepackage[final,colorlinks,pdftex,pdfpagelabels=true]{hyperref}
+\author{OpenMW Team}
+
+\def\pdfBorderAttrs{/Border [0 0 0] } % No border arround Links
+
+\def\CPP{{C\kern-.05em\raise.23ex\hbox{+\kern-.05em+}}}
+\hypersetup{%
+ pdfauthor={Copyright \textcopyright{} OpenMW Team },
+ pdftitle={OpenCS user manual}
+}
+\def\mono{\texttt}
+\def\MW{\textit{Morrowind\texttrademark{}}}
+\def\TB{\textit{Tribunal}}
+\def\BM{\textit{Bloodmon}}
+\def\BS{Bethesda Softworks}
+\def\OMW{\hbox{OpenMW}}
+\def\OCS{\hbox{OpenCS}}
+
+\begin{document}
+
+\title{OpenCS User Manual}
+\maketitle
+\tableofcontents{}
+\input{files_and_directories}
+\input{creating_file}
+\input{windows}
+\input{tables}
+\input{filters}
+\end{document}
diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex
new file mode 100644
index 0000000000..e7cc06735a
--- /dev/null
+++ b/manual/opencs/tables.tex
@@ -0,0 +1,103 @@
+\section{Tables}
+
+\subsection{Introduction}
+If you have launched \OCS{} already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables.
+You'd be spot on: \OCS{} is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though.
+Due to the vast amounts of information involved with \MW, tables just made the most sense. You have to be able to spot information quickly
+and be able to change them on the fly.
+Let's browse through the various screens and see what all these tables show.
+
+\subsection{Used Terms}
+
+\subsubsection{Glossary}
+
+\begin{description}
+ \item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else.
+
+ \item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might
+ contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record.
+ In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders
+ to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will
+ only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually.
+\end{description}
+
+\subsubsection{Recurring Terms}
+Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS.
+
+\begin{description}
+\item[ID] Each item, location, sound, etc. gets the same unique identifier in both \OCS{} and \MW. This is usually a very self-explanatory name.
+For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example,
+you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind
+and \OCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name.
+\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist.
+\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with
+optionally the Bloodmoon and Tribunal expansions.
+\item[Added] means that this record was not in the base game and has been added by a~modder.
+\item[Modified] means that the record is part of the base game, but has been changed in some way.
+\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences
+in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it does not mean the bedroll in the basement
+of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced
+by something that still exists otherwise you will get crashes in the worst case scenario.
+\end{description}
+
+\subsection{World Screens}
+The contents of the game world can be changed by choosing one of the options in the appropriate menu at the top of the screen.
+
+\subsubsection{Regions}
+This describes the general areas of Vvardenfell. Each of these areas has different rules about things such as encounters and weather.
+
+\begin{description}
+ \item[Name:] This is how the game will show your location in-game.
+ \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in
+ World > Region Map. If you do not have an application with a colour picker, you can use your favourite search engine to find a colour picker online.
+ \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild.
+\end{description}
+
+\subsubsection{Cells}
+Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora,
+why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would
+be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell,
+the game will load everything that is going on in that cell so you can interact with it.
+
+In the original \MW{} this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen;
+you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in \OCS{} provides you with a list of cells
+in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world).
+
+\begin{description}
+ \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its
+ own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not.
+
+ \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape
+ is added. If part of the landscape goes below height 0, the player will see water. (See illustration.)
+
+ Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than
+ in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them.
+
+ Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in
+ the outside world are exterior cells and should thus \textit{always} be set to false!
+
+ \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB{} expansion took place in a city on
+ the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is
+ in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see
+ the city's very own map, as if he was walking around in an interior cell.
+
+ So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell,
+ but it would need a sky as if it was an exterior cell. That is what this is. This is why the vast majority of the cells you will find in this screen
+ will have this option set to false: It is only meant for these "fake exteriors".
+
+ \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area.
+ It is also possible for a cell not to belong to any region.
+
+\end{description}
+
+\subsubsection{Referenceables}
+This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type
+a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course,
+does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see
+when you hover your reticle over the object.
+
+Let's go through all Record Types and discuss what you can tell \OCS{} about them.
+
+\begin{description}
+ \item[Activator:] This is an item that, when activated, starts a script or even just shows a~tooltip.
+\end{description} \ No newline at end of file
diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex
new file mode 100644
index 0000000000..97bb75e792
--- /dev/null
+++ b/manual/opencs/windows.tex
@@ -0,0 +1,54 @@
+\section{Windows}
+\subsection{Introduction}
+This section describes the multiple windows interface of the \OCS{} editor. This design principle was chosen in order
+to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced
+windows management features, like for instance: multiple desktops found commonly on many open source desktop environments.
+However, it is enough to have a single large screen to see the advantages of this concept.
+
+OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts
+applied commonly in various applications. For instance dialog windows are really hard to find in the \OCS. You are free to try,
+though.
+
+Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly
+focused on practical ways of organizing work with the \OCS.
+
+\subsection{Basics}
+After starting \OCS{} and choosing content files to use a editor window should show up. It probably does not look surprising:
+there is a menubar at the top, and there is a~large empty area. That is it: a brand new \OCS{} window contains only menubar
+and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}.
+You are free to do so, just try to explore the menubar.
+
+You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's
+just focus on the windows itself.
+
+\paragraph{Creating new windows}
+is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect,
+it is also blank, and you are free to add any of the \OCS{} panels.
+
+\paragraph{Closing opened window}
+is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure.
+Closing last \OCS{} window will also terminate application session.
+
+\paragraph{Multi-everything}
+is the main foundation of \OCS{} interface. You are free to create as many windows as you want to, free to populate it with
+any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and
+you are wonder if you are able to have one hundred \OCS{} windows showing panels of the same type, well most likely you are
+able to do so.
+
+The principle behind this design decision is easy to see for \BS{} made editor, but maybe not so clear for users who are
+just about to begin their wonderful journey of modding.
+
+\subsection{Advanced}
+So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often
+have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense
+to have all the space for just that one table. More often, you are required to work with two and switch them from time to time.
+All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change
+active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work
+with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one.
+
+OpenCS is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one
+flexible approach in all cases.
+
+There is no point in digging deeper in the windows of \OCS. Let's explore panels, starting with tables.
+
+%We should write some tips and tricks here. \ No newline at end of file
diff --git a/readme.txt b/readme.txt
index afcfadea3d..242dcb5c0c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind
OpenMW is an attempt at recreating the engine for the popular role-playing game
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
-Version: 0.27.0
+Version: 0.28.0
License: GPL (see GPL3.txt for more information)
Website: http://www.openmw.org
@@ -36,52 +36,148 @@ https://wiki.openmw.org/index.php?title=Development_Environment_Setup
THE DATA PATH
-The data path tells OpenMW where to find your Morrowind files. From 0.12.0 on OpenMW should be able to
+The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to
pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly
(installing Morrowind under WINE is considered a proper install).
-If that does not work for you, please check if you have any leftover openmw.cfg files from versions earlier than 0.12.0. These can interfere with the configuration process, so try to remove then.
-
-If you are running OpenMW without installing it, you still need to manually adjust the data path. Create a text file named openmw.cfg in the location of the binary and enter the following line:
-
-data=path to your data directory
-
-(where you replace "path to your data directory" with the actual location of your data directory)
-
-
COMMAND LINE OPTIONS
Syntax: openmw <options>
Allowed options:
- --help print help message
- --version print version information and quit
- --data arg (=data) set data directories (later directories have higher priority)
- --data-local arg set local data directory (highest priority)
- --resources arg (=resources) set resources directory
- --start arg (=Beshara) set initial cell
- --master arg master file(s)
- --plugin arg plugin file(s)
- --anim-verbose [=arg(=1)] (=0) output animation indices files
- --debug [=arg(=1)] (=0) debug mode
- --nosound [=arg(=1)] (=0) disable all sounds
- --script-verbose [=arg(=1)] (=0) verbose script output
- --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scripts) at startup
- --script-console [=arg(=1)] (=0) enable console-only script functionality
- --script-run arg select a file containing a list of console commands that is executed on startup
- --new-game [=arg(=1)] (=0) activate char gen/new game mechanics
- --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding)
- --encoding arg (=win1252) Character encoding used in OpenMW game messages:
-
- win1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages
-
- win1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages
-
- win1252 - Western European (Latin) alphabet, used by default
-
- --fallback arg fallback values
+ --help print help message
+ --version print version information and quit
+ --data arg (=data) set data directories (later directories
+ have higher priority)
+ --data-local arg set local data directory (highest
+ priority)
+ --fallback-archive arg (=fallback-archive)
+ set fallback BSA archives (later
+ archives have higher priority)
+ --resources arg (=resources) set resources directory
+ --start arg (=Beshara) set initial cell
+ --content arg content file(s): esm/esp, or
+ omwgame/omwaddon
+ --anim-verbose [=arg(=1)] (=0) output animation indices files
+ --no-sound [=arg(=1)] (=0) disable all sounds
+ --script-verbose [=arg(=1)] (=0) verbose script output
+ --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue
+ scripts) at startup
+ --script-console [=arg(=1)] (=0) enable console-only script
+ functionality
+ --script-run arg select a file containing a list of
+ console commands that is executed on
+ startup
+ --new-game [=arg(=1)] (=0) activate char gen/new game mechanics
+ --fs-strict [=arg(=1)] (=0) strict file system handling (no case
+ folding)
+ --encoding arg (=win1252) Character encoding used in OpenMW game
+ messages:
+
+ win1250 - Central and Eastern European
+ such as Polish, Czech, Slovak,
+ Hungarian, Slovene, Bosnian, Croatian,
+ Serbian (Latin script), Romanian and
+ Albanian languages
+
+ win1251 - Cyrillic alphabet such as
+ Russian, Bulgarian, Serbian Cyrillic
+ and other languages
+
+ win1252 - Western European (Latin)
+ alphabet, used by default
+ --fallback arg fallback values
+ --no-grab Don't grab mouse cursor
+ --activate-dist arg (=-1) activation distance override
CHANGELOG
+0.28.0
+
+Bug #399: Inventory changes are not visible immediately
+Bug #417: Apply weather instantly when teleporting
+Bug #566: Global Map position marker not updated for interior cells
+Bug #712: Looting corpse delay
+Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod
+Bug #805: Two TR meshes appear black (v0.24RC)
+Bug #841: Third-person activation distance taken from camera rather than head
+Bug #845: NPCs hold torches during the day
+Bug #855: Vvardenfell Visages Volume I some hairs donĀ“t appear since 0,24
+Bug #856: Maormer race by Mac Kom - The heads are way up
+Bug #864: Walk locks during loading in 3rd person
+Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog
+Bug #882: Hircine's Ring doesn't always work
+Bug #909: [Tamriel Rebuilt] crashes in Akamora
+Bug #922: Launcher writing merged openmw.cfg files
+Bug #943: Random magnitude should be calculated per effect
+Bug #948: Negative fatigue level should be allowed
+Bug #949: Particles in world space
+Bug #950: Hard crash on x64 Linux running --new-game (on startup)
+Bug #951: setMagicka and setFatigue have no effect
+Bug #954: Problem with equipping inventory items when using a keyboard shortcut
+Bug #955: Issues with equipping torches
+Bug #966: Shield is visible when casting spell
+Bug #967: Game crashes when equipping silver candlestick
+Bug #970: Segmentation fault when starting at Bal Isra
+Bug #977: Pressing down key in console doesn't go forward in history
+Bug #979: Tooltip disappears when changing inventory
+Bug #980: Barter: item category is remembered, but not shown
+Bug #981: Mod: replacing model has wrong position/orientation
+Bug #982: Launcher: Addon unchecking is not saved
+Bug #983: Fix controllers to affect objects attached to the base node
+Bug #985: Player can talk to NPCs who are in combat
+Bug #989: OpenMW crashes when trying to include mod with capital .ESP
+Bug #991: Merchants equip items with harmful constant effect enchantments
+Bug #994: Don't cap skills/attributes when set via console
+Bug #998: Setting the max health should also set the current health
+Bug #1005: Torches are visible when casting spells and during hand to hand combat.
+Bug #1006: Many NPCs have 0 skill
+Bug #1007: Console fills up with text
+Bug #1013: Player randomly loses health or dies
+Bug #1014: Persuasion window is not centered in maximized window
+Bug #1015: Player status window scroll state resets on status change
+Bug #1016: Notification window not big enough for all skill level ups
+Bug #1020: Saved window positions are not rescaled appropriately on resolution change
+Bug #1022: Messages stuck permanently on screen when they pile up
+Bug #1023: Journals doesn't open
+Bug #1026: Game loses track of torch usage.
+Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level
+Bug #1029: Quick keys menu: Select compatible replacement when tool used up
+Bug #1042: TES3 header data wrong encoding
+Bug #1045: OS X: deployed OpenCS won't launch
+Bug #1046: All damaged weaponry is worth 1 gold
+Bug #1048: Links in "locked" dialogue are still clickable
+Bug #1052: Using color codes when naming your character actually changes the name's color
+Bug #1054: Spell effects not visible in front of water
+Bug #1055: Power-Spell animation starts even though you already casted it that day
+Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability
+Bug #1063: Crash upon checking out game start ship area in Seyda Neen
+Bug #1064: openmw binaries link to unnecessary libraries
+Bug #1065: Landing from a high place in water still causes fall damage
+Bug #1072: Drawing weapon increases torch brightness
+Bug #1073: Merchants sell stacks of gold
+Feature #43: Visuals for Magic Effects
+Feature #51: Ranged Magic
+Feature #52: Touch Range Magic
+Feature #53: Self Range Magic
+Feature #54: Spell Casting
+Feature #70: Vampirism
+Feature #100: Combat AI
+Feature #171: Implement NIF record NiFlipController
+Feature #410: Window to restore enchanted item charge
+Feature #647: Enchanted item glow
+Feature #723: Invisibility/Chameleon magic effects
+Feature #737: Resist Magicka magic effect
+Feature #758: GetLOS
+Feature #926: Editor: Info-Record tables
+Feature #958: Material controllers
+Feature #959: Terrain bump, specular, & parallax mapping
+Feature #990: Request: unlock mouse when in any menu
+Feature #1018: Do not allow view mode switching while performing an action
+Feature #1027: Vertex morph animation (NiGeomMorpherController)
+Feature #1031: Handle NiBillboardNode
+Feature #1051: Implement NIF texture slot DarkTexture
+Task #873: Unify OGRE initialisation
+
0.27.0
Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp