summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBret Curtis <psi29a@gmail.com>2013-10-17 16:37:22 +0200
committerBret Curtis <psi29a@gmail.com>2013-10-17 16:37:22 +0200
commit9a2b6c69b6064cd7e6fe12f901b11521afbc6dfe (patch)
tree2f4fd739ba9a486ae967e9fe57835729321d967e
Imported Upstream version 0.26.0upstream/0.26.0
-rw-r--r--.travis.yml39
-rw-r--r--CMakeLists.txt722
-rw-r--r--Daedric Font License.txt10
-rw-r--r--DejaVu Font License.txt99
-rw-r--r--Docs/Doxyfile1543
-rw-r--r--Docs/DoxyfilePages1543
-rw-r--r--Docs/mainpage.hpp.cmake5
-rw-r--r--GPL3.txt674
-rw-r--r--OFL.txt93
-rw-r--r--README_Mac.md160
-rw-r--r--apps/bsatool/CMakeLists.txt19
-rw-r--r--apps/bsatool/bsatool.cpp288
-rw-r--r--apps/doc.hpp3
-rw-r--r--apps/esmtool/CMakeLists.txt23
-rw-r--r--apps/esmtool/esmtool.cpp556
-rw-r--r--apps/esmtool/labels.cpp880
-rw-r--r--apps/esmtool/labels.hpp64
-rw-r--r--apps/esmtool/record.cpp1300
-rw-r--r--apps/esmtool/record.hpp136
-rw-r--r--apps/launcher/CMakeLists.txt139
-rw-r--r--apps/launcher/datafilespage.cpp551
-rw-r--r--apps/launcher/datafilespage.hpp88
-rw-r--r--apps/launcher/graphicspage.cpp385
-rw-r--r--apps/launcher/graphicspage.hpp66
-rw-r--r--apps/launcher/main.cpp64
-rw-r--r--apps/launcher/maindialog.cpp854
-rw-r--r--apps/launcher/maindialog.hpp67
-rw-r--r--apps/launcher/playpage.cpp43
-rw-r--r--apps/launcher/playpage.hpp35
-rw-r--r--apps/launcher/settings/gamesettings.cpp175
-rw-r--r--apps/launcher/settings/gamesettings.hpp62
-rw-r--r--apps/launcher/settings/graphicssettings.cpp44
-rw-r--r--apps/launcher/settings/graphicssettings.hpp16
-rw-r--r--apps/launcher/settings/launchersettings.cpp101
-rw-r--r--apps/launcher/settings/launchersettings.hpp19
-rw-r--r--apps/launcher/settings/settingsbase.hpp109
-rw-r--r--apps/launcher/textslotmsgbox.cpp6
-rw-r--r--apps/launcher/textslotmsgbox.hpp13
-rw-r--r--apps/launcher/unshieldthread.cpp487
-rw-r--r--apps/launcher/unshieldthread.hpp57
-rw-r--r--apps/launcher/utils/checkablemessagebox.cpp269
-rw-r--r--apps/launcher/utils/checkablemessagebox.hpp100
-rw-r--r--apps/launcher/utils/textinputdialog.cpp71
-rw-r--r--apps/launcher/utils/textinputdialog.hpp28
-rw-r--r--apps/mwiniimporter/CMakeLists.txt29
-rw-r--r--apps/mwiniimporter/importer.cpp873
-rw-r--r--apps/mwiniimporter/importer.hpp39
-rw-r--r--apps/mwiniimporter/main.cpp101
-rw-r--r--apps/opencs/CMakeLists.txt158
-rw-r--r--apps/opencs/editor.cpp157
-rw-r--r--apps/opencs/editor.hpp66
-rw-r--r--apps/opencs/main.cpp49
-rw-r--r--apps/opencs/model/doc/document.cpp2284
-rw-r--r--apps/opencs/model/doc/document.hpp111
-rw-r--r--apps/opencs/model/doc/documentmanager.cpp38
-rw-r--r--apps/opencs/model/doc/documentmanager.hpp37
-rw-r--r--apps/opencs/model/doc/state.hpp19
-rw-r--r--apps/opencs/model/filter/andnode.cpp20
-rw-r--r--apps/opencs/model/filter/andnode.hpp23
-rw-r--r--apps/opencs/model/filter/booleannode.cpp15
-rw-r--r--apps/opencs/model/filter/booleannode.hpp29
-rw-r--r--apps/opencs/model/filter/filter.hpp25
-rw-r--r--apps/opencs/model/filter/leafnode.cpp8
-rw-r--r--apps/opencs/model/filter/leafnode.hpp20
-rw-r--r--apps/opencs/model/filter/narynode.cpp60
-rw-r--r--apps/opencs/model/filter/narynode.hpp37
-rw-r--r--apps/opencs/model/filter/node.cpp6
-rw-r--r--apps/opencs/model/filter/node.hpp53
-rw-r--r--apps/opencs/model/filter/notnode.cpp10
-rw-r--r--apps/opencs/model/filter/notnode.hpp21
-rw-r--r--apps/opencs/model/filter/ornode.cpp20
-rw-r--r--apps/opencs/model/filter/ornode.hpp23
-rw-r--r--apps/opencs/model/filter/parser.cpp595
-rw-r--r--apps/opencs/model/filter/parser.hpp59
-rw-r--r--apps/opencs/model/filter/textnode.cpp62
-rw-r--r--apps/opencs/model/filter/textnode.hpp33
-rw-r--r--apps/opencs/model/filter/unarynode.cpp26
-rw-r--r--apps/opencs/model/filter/unarynode.hpp34
-rw-r--r--apps/opencs/model/filter/valuenode.cpp71
-rw-r--r--apps/opencs/model/filter/valuenode.hpp37
-rw-r--r--apps/opencs/model/settings/settingcontainer.cpp82
-rw-r--r--apps/opencs/model/settings/settingcontainer.hpp47
-rw-r--r--apps/opencs/model/settings/settingsitem.cpp104
-rw-r--r--apps/opencs/model/settings/settingsitem.hpp63
-rw-r--r--apps/opencs/model/settings/support.cpp1
-rw-r--r--apps/opencs/model/settings/support.hpp39
-rw-r--r--apps/opencs/model/settings/usersettings.cpp355
-rw-r--r--apps/opencs/model/settings/usersettings.hpp94
-rw-r--r--apps/opencs/model/tools/birthsigncheck.cpp39
-rw-r--r--apps/opencs/model/tools/birthsigncheck.hpp29
-rw-r--r--apps/opencs/model/tools/classcheck.cpp72
-rw-r--r--apps/opencs/model/tools/classcheck.hpp29
-rw-r--r--apps/opencs/model/tools/factioncheck.cpp61
-rw-r--r--apps/opencs/model/tools/factioncheck.hpp29
-rw-r--r--apps/opencs/model/tools/mandatoryid.cpp23
-rw-r--r--apps/opencs/model/tools/mandatoryid.hpp38
-rw-r--r--apps/opencs/model/tools/operation.cpp84
-rw-r--r--apps/opencs/model/tools/operation.hpp54
-rw-r--r--apps/opencs/model/tools/racecheck.cpp68
-rw-r--r--apps/opencs/model/tools/racecheck.hpp34
-rw-r--r--apps/opencs/model/tools/regioncheck.cpp33
-rw-r--r--apps/opencs/model/tools/regioncheck.hpp29
-rw-r--r--apps/opencs/model/tools/reportmodel.cpp71
-rw-r--r--apps/opencs/model/tools/reportmodel.hpp37
-rw-r--r--apps/opencs/model/tools/skillcheck.cpp37
-rw-r--r--apps/opencs/model/tools/skillcheck.hpp29
-rw-r--r--apps/opencs/model/tools/soundcheck.cpp29
-rw-r--r--apps/opencs/model/tools/soundcheck.hpp29
-rw-r--r--apps/opencs/model/tools/spellcheck.cpp35
-rw-r--r--apps/opencs/model/tools/spellcheck.hpp29
-rw-r--r--apps/opencs/model/tools/stage.cpp4
-rw-r--r--apps/opencs/model/tools/stage.hpp24
-rw-r--r--apps/opencs/model/tools/tools.cpp147
-rw-r--r--apps/opencs/model/tools/tools.hpp73
-rw-r--r--apps/opencs/model/tools/verifier.cpp7
-rw-r--r--apps/opencs/model/tools/verifier.hpp17
-rw-r--r--apps/opencs/model/world/cell.cpp25
-rw-r--r--apps/opencs/model/world/cell.hpp24
-rw-r--r--apps/opencs/model/world/collection.hpp319
-rw-r--r--apps/opencs/model/world/collectionbase.cpp6
-rw-r--r--apps/opencs/model/world/collectionbase.hpp85
-rw-r--r--apps/opencs/model/world/columnbase.cpp20
-rw-r--r--apps/opencs/model/world/columnbase.hpp82
-rw-r--r--apps/opencs/model/world/columnimp.hpp1221
-rw-r--r--apps/opencs/model/world/columns.cpp199
-rw-r--r--apps/opencs/model/world/columns.hpp186
-rw-r--r--apps/opencs/model/world/commands.cpp125
-rw-r--r--apps/opencs/model/world/commands.hpp104
-rw-r--r--apps/opencs/model/world/data.cpp442
-rw-r--r--apps/opencs/model/world/data.hpp142
-rw-r--r--apps/opencs/model/world/idcollection.hpp86
-rw-r--r--apps/opencs/model/world/idtable.cpp181
-rw-r--r--apps/opencs/model/world/idtable.hpp69
-rw-r--r--apps/opencs/model/world/idtableproxymodel.cpp48
-rw-r--r--apps/opencs/model/world/idtableproxymodel.hpp39
-rw-r--r--apps/opencs/model/world/record.cpp21
-rw-r--r--apps/opencs/model/world/record.hpp127
-rw-r--r--apps/opencs/model/world/ref.cpp13
-rw-r--r--apps/opencs/model/world/ref.hpp26
-rw-r--r--apps/opencs/model/world/refcollection.cpp41
-rw-r--r--apps/opencs/model/world/refcollection.hpp29
-rw-r--r--apps/opencs/model/world/refidadapter.cpp6
-rw-r--r--apps/opencs/model/world/refidadapter.hpp37
-rw-r--r--apps/opencs/model/world/refidadapterimp.cpp575
-rw-r--r--apps/opencs/model/world/refidadapterimp.hpp766
-rw-r--r--apps/opencs/model/world/refidcollection.cpp536
-rw-r--r--apps/opencs/model/world/refidcollection.hpp95
-rw-r--r--apps/opencs/model/world/refiddata.cpp198
-rw-r--r--apps/opencs/model/world/refiddata.hpp188
-rw-r--r--apps/opencs/model/world/regionmap.cpp513
-rw-r--r--apps/opencs/model/world/regionmap.hpp111
-rw-r--r--apps/opencs/model/world/scriptcontext.cpp22
-rw-r--r--apps/opencs/model/world/scriptcontext.hpp26
-rw-r--r--apps/opencs/model/world/universalid.cpp333
-rw-r--r--apps/opencs/model/world/universalid.hpp152
-rw-r--r--apps/opencs/ocspropertywidget.cpp6
-rw-r--r--apps/opencs/ocspropertywidget.hpp18
-rw-r--r--apps/opencs/view/doc/filedialog.cpp272
-rw-r--r--apps/opencs/view/doc/filedialog.hpp66
-rw-r--r--apps/opencs/view/doc/opendialog.cpp62
-rw-r--r--apps/opencs/view/doc/opendialog.hpp17
-rw-r--r--apps/opencs/view/doc/operation.cpp151
-rw-r--r--apps/opencs/view/doc/operation.hpp53
-rw-r--r--apps/opencs/view/doc/operations.cpp69
-rw-r--r--apps/opencs/view/doc/operations.hpp41
-rw-r--r--apps/opencs/view/doc/startup.cpp27
-rw-r--r--apps/opencs/view/doc/startup.hpp24
-rw-r--r--apps/opencs/view/doc/subview.cpp19
-rw-r--r--apps/opencs/view/doc/subview.hpp49
-rw-r--r--apps/opencs/view/doc/subviewfactory.cpp38
-rw-r--r--apps/opencs/view/doc/subviewfactory.hpp55
-rw-r--r--apps/opencs/view/doc/subviewfactoryimp.hpp42
-rw-r--r--apps/opencs/view/doc/view.cpp464
-rw-r--r--apps/opencs/view/doc/view.hpp169
-rw-r--r--apps/opencs/view/doc/viewmanager.cpp362
-rw-r--r--apps/opencs/view/doc/viewmanager.hpp82
-rw-r--r--apps/opencs/view/filter/editwidget.cpp58
-rw-r--r--apps/opencs/view/filter/editwidget.hpp48
-rw-r--r--apps/opencs/view/filter/filterbox.cpp24
-rw-r--r--apps/opencs/view/filter/filterbox.hpp30
-rw-r--r--apps/opencs/view/filter/filtercreator.cpp63
-rw-r--r--apps/opencs/view/filter/filtercreator.hpp41
-rw-r--r--apps/opencs/view/filter/recordfilterbox.cpp27
-rw-r--r--apps/opencs/view/filter/recordfilterbox.hpp34
-rw-r--r--apps/opencs/view/settings/abstractblock.cpp112
-rw-r--r--apps/opencs/view/settings/abstractblock.hpp82
-rw-r--r--apps/opencs/view/settings/abstractpage.cpp44
-rw-r--r--apps/opencs/view/settings/abstractpage.hpp70
-rw-r--r--apps/opencs/view/settings/abstractwidget.cpp78
-rw-r--r--apps/opencs/view/settings/abstractwidget.hpp69
-rw-r--r--apps/opencs/view/settings/blankpage.cpp50
-rw-r--r--apps/opencs/view/settings/blankpage.hpp28
-rw-r--r--apps/opencs/view/settings/customblock.cpp121
-rw-r--r--apps/opencs/view/settings/customblock.hpp47
-rwxr-xr-xapps/opencs/view/settings/datadisplayformatpage.cpp57
-rwxr-xr-xapps/opencs/view/settings/datadisplayformatpage.hpp33
-rw-r--r--apps/opencs/view/settings/editorpage.cpp53
-rw-r--r--apps/opencs/view/settings/editorpage.hpp33
-rw-r--r--apps/opencs/view/settings/groupblock.cpp108
-rw-r--r--apps/opencs/view/settings/groupblock.hpp43
-rw-r--r--apps/opencs/view/settings/groupbox.cpp56
-rw-r--r--apps/opencs/view/settings/groupbox.hpp28
-rw-r--r--apps/opencs/view/settings/itemblock.cpp115
-rw-r--r--apps/opencs/view/settings/itemblock.hpp48
-rw-r--r--apps/opencs/view/settings/proxyblock.cpp152
-rw-r--r--apps/opencs/view/settings/proxyblock.hpp52
-rw-r--r--apps/opencs/view/settings/settingwidget.cpp1
-rw-r--r--apps/opencs/view/settings/settingwidget.hpp214
-rw-r--r--apps/opencs/view/settings/support.cpp1
-rw-r--r--apps/opencs/view/settings/support.hpp206
-rw-r--r--apps/opencs/view/settings/toggleblock.cpp80
-rw-r--r--apps/opencs/view/settings/toggleblock.hpp29
-rw-r--r--apps/opencs/view/settings/usersettingsdialog.cpp113
-rw-r--r--apps/opencs/view/settings/usersettingsdialog.hpp71
-rw-r--r--apps/opencs/view/settings/windowpage.cpp144
-rw-r--r--apps/opencs/view/settings/windowpage.hpp34
-rw-r--r--apps/opencs/view/tools/reportsubview.cpp32
-rw-r--r--apps/opencs/view/tools/reportsubview.hpp42
-rw-r--r--apps/opencs/view/tools/subviews.cpp12
-rw-r--r--apps/opencs/view/tools/subviews.hpp14
-rw-r--r--apps/opencs/view/world/cellcreator.cpp81
-rw-r--r--apps/opencs/view/world/cellcreator.hpp40
-rw-r--r--apps/opencs/view/world/creator.cpp13
-rw-r--r--apps/opencs/view/world/creator.hpp86
-rwxr-xr-xapps/opencs/view/world/datadisplaydelegate.cpp110
-rwxr-xr-xapps/opencs/view/world/datadisplaydelegate.hpp85
-rw-r--r--apps/opencs/view/world/dialoguesubview.cpp98
-rw-r--r--apps/opencs/view/world/dialoguesubview.hpp27
-rw-r--r--apps/opencs/view/world/enumdelegate.cpp121
-rw-r--r--apps/opencs/view/world/enumdelegate.hpp66
-rw-r--r--apps/opencs/view/world/genericcreator.cpp135
-rw-r--r--apps/opencs/view/world/genericcreator.hpp73
-rw-r--r--apps/opencs/view/world/idvalidator.cpp26
-rw-r--r--apps/opencs/view/world/idvalidator.hpp23
-rw-r--r--apps/opencs/view/world/recordstatusdelegate.cpp45
-rw-r--r--apps/opencs/view/world/recordstatusdelegate.hpp40
-rw-r--r--apps/opencs/view/world/referenceablecreator.cpp43
-rw-r--r--apps/opencs/view/world/referenceablecreator.hpp30
-rw-r--r--apps/opencs/view/world/referencecreator.cpp75
-rw-r--r--apps/opencs/view/world/referencecreator.hpp40
-rwxr-xr-xapps/opencs/view/world/refidtypedelegate.cpp60
-rwxr-xr-xapps/opencs/view/world/refidtypedelegate.hpp37
-rw-r--r--apps/opencs/view/world/refrecordtypedelegate.cpp25
-rw-r--r--apps/opencs/view/world/refrecordtypedelegate.hpp58
-rw-r--r--apps/opencs/view/world/regionmapsubview.cpp29
-rw-r--r--apps/opencs/view/world/regionmapsubview.hpp27
-rw-r--r--apps/opencs/view/world/scripthighlighter.cpp118
-rw-r--r--apps/opencs/view/world/scripthighlighter.hpp80
-rw-r--r--apps/opencs/view/world/scriptsubview.cpp99
-rw-r--r--apps/opencs/view/world/scriptsubview.hpp62
-rw-r--r--apps/opencs/view/world/subviews.cpp65
-rw-r--r--apps/opencs/view/world/subviews.hpp14
-rw-r--r--apps/opencs/view/world/table.cpp300
-rw-r--r--apps/opencs/view/world/table.hpp94
-rw-r--r--apps/opencs/view/world/tablebottombox.cpp159
-rw-r--r--apps/opencs/view/world/tablebottombox.hpp82
-rw-r--r--apps/opencs/view/world/tablesubview.cpp78
-rw-r--r--apps/opencs/view/world/tablesubview.hpp43
-rw-r--r--apps/opencs/view/world/util.cpp143
-rw-r--r--apps/opencs/view/world/util.hpp120
-rw-r--r--apps/opencs/view/world/vartypedelegate.cpp103
-rw-r--r--apps/opencs/view/world/vartypedelegate.hpp40
-rw-r--r--apps/openmw/CMakeLists.txt156
-rw-r--r--apps/openmw/config.hpp.cmake9
-rw-r--r--apps/openmw/doc.hpp44
-rw-r--r--apps/openmw/engine.cpp604
-rw-r--r--apps/openmw/engine.hpp191
-rw-r--r--apps/openmw/main.cpp349
-rw-r--r--apps/openmw/mwbase/dialoguemanager.hpp58
-rw-r--r--apps/openmw/mwbase/environment.cpp160
-rw-r--r--apps/openmw/mwbase/environment.hpp92
-rw-r--r--apps/openmw/mwbase/inputmanager.hpp45
-rw-r--r--apps/openmw/mwbase/journal.hpp75
-rw-r--r--apps/openmw/mwbase/mechanicsmanager.hpp120
-rw-r--r--apps/openmw/mwbase/scriptmanager.hpp64
-rw-r--r--apps/openmw/mwbase/soundmanager.hpp153
-rw-r--r--apps/openmw/mwbase/windowmanager.hpp289
-rw-r--r--apps/openmw/mwbase/world.hpp401
-rw-r--r--apps/openmw/mwclass/activator.cpp129
-rw-r--r--apps/openmw/mwclass/activator.hpp43
-rw-r--r--apps/openmw/mwclass/apparatus.cpp164
-rw-r--r--apps/openmw/mwclass/apparatus.hpp64
-rw-r--r--apps/openmw/mwclass/armor.cpp389
-rw-r--r--apps/openmw/mwclass/armor.hpp88
-rw-r--r--apps/openmw/mwclass/book.cpp209
-rw-r--r--apps/openmw/mwclass/book.hpp69
-rw-r--r--apps/openmw/mwclass/classes.cpp50
-rw-r--r--apps/openmw/mwclass/classes.hpp10
-rw-r--r--apps/openmw/mwclass/clothing.cpp308
-rw-r--r--apps/openmw/mwclass/clothing.hpp82
-rw-r--r--apps/openmw/mwclass/container.cpp263
-rw-r--r--apps/openmw/mwclass/container.hpp63
-rw-r--r--apps/openmw/mwclass/creature.cpp454
-rw-r--r--apps/openmw/mwclass/creature.hpp105
-rw-r--r--apps/openmw/mwclass/creaturelevlist.cpp19
-rw-r--r--apps/openmw/mwclass/creaturelevlist.hpp20
-rw-r--r--apps/openmw/mwclass/door.cpp278
-rw-r--r--apps/openmw/mwclass/door.hpp52
-rw-r--r--apps/openmw/mwclass/ingredient.cpp204
-rw-r--r--apps/openmw/mwclass/ingredient.hpp66
-rw-r--r--apps/openmw/mwclass/itemlevlist.cpp19
-rw-r--r--apps/openmw/mwclass/itemlevlist.hpp20
-rw-r--r--apps/openmw/mwclass/light.cpp228
-rw-r--r--apps/openmw/mwclass/light.hpp69
-rw-r--r--apps/openmw/mwclass/lockpick.cpp190
-rw-r--r--apps/openmw/mwclass/lockpick.hpp70
-rw-r--r--apps/openmw/mwclass/misc.cpp255
-rw-r--r--apps/openmw/mwclass/misc.hpp63
-rw-r--r--apps/openmw/mwclass/npc.cpp1077
-rw-r--r--apps/openmw/mwclass/npc.hpp157
-rw-r--r--apps/openmw/mwclass/potion.cpp199
-rw-r--r--apps/openmw/mwclass/potion.hpp62
-rw-r--r--apps/openmw/mwclass/probe.cpp189
-rw-r--r--apps/openmw/mwclass/probe.hpp70
-rw-r--r--apps/openmw/mwclass/repair.cpp181
-rw-r--r--apps/openmw/mwclass/repair.hpp71
-rw-r--r--apps/openmw/mwclass/static.cpp62
-rw-r--r--apps/openmw/mwclass/static.hpp30
-rw-r--r--apps/openmw/mwclass/weapon.cpp448
-rw-r--r--apps/openmw/mwclass/weapon.hpp91
-rw-r--r--apps/openmw/mwdialogue/dialoguemanagerimp.cpp632
-rw-r--r--apps/openmw/mwdialogue/dialoguemanagerimp.hpp98
-rw-r--r--apps/openmw/mwdialogue/filter.cpp664
-rw-r--r--apps/openmw/mwdialogue/filter.hpp68
-rw-r--r--apps/openmw/mwdialogue/journalentry.cpp69
-rw-r--r--apps/openmw/mwdialogue/journalentry.hpp46
-rw-r--r--apps/openmw/mwdialogue/journalimp.cpp121
-rw-r--r--apps/openmw/mwdialogue/journalimp.hpp61
-rw-r--r--apps/openmw/mwdialogue/quest.cpp90
-rw-r--r--apps/openmw/mwdialogue/quest.hpp37
-rw-r--r--apps/openmw/mwdialogue/selectwrapper.cpp305
-rw-r--r--apps/openmw/mwdialogue/selectwrapper.hpp86
-rw-r--r--apps/openmw/mwdialogue/topic.cpp44
-rw-r--r--apps/openmw/mwdialogue/topic.hpp49
-rw-r--r--apps/openmw/mwgui/alchemywindow.cpp266
-rw-r--r--apps/openmw/mwgui/alchemywindow.hpp51
-rw-r--r--apps/openmw/mwgui/birth.cpp247
-rw-r--r--apps/openmw/mwgui/birth.hpp56
-rw-r--r--apps/openmw/mwgui/bookpage.cpp1240
-rw-r--r--apps/openmw/mwgui/bookpage.hpp122
-rw-r--r--apps/openmw/mwgui/bookwindow.cpp208
-rw-r--r--apps/openmw/mwgui/bookwindow.hpp54
-rw-r--r--apps/openmw/mwgui/charactercreation.cpp757
-rw-r--r--apps/openmw/mwgui/charactercreation.hpp114
-rw-r--r--apps/openmw/mwgui/class.cpp886
-rw-r--r--apps/openmw/mwgui/class.hpp302
-rw-r--r--apps/openmw/mwgui/companionitemmodel.cpp34
-rw-r--r--apps/openmw/mwgui/companionitemmodel.hpp22
-rw-r--r--apps/openmw/mwgui/companionwindow.cpp152
-rw-r--r--apps/openmw/mwgui/companionwindow.hpp52
-rw-r--r--apps/openmw/mwgui/confirmationdialog.cpp44
-rw-r--r--apps/openmw/mwgui/confirmationdialog.hpp33
-rw-r--r--apps/openmw/mwgui/console.cpp422
-rw-r--r--apps/openmw/mwgui/console.hpp106
-rw-r--r--apps/openmw/mwgui/container.cpp280
-rw-r--r--apps/openmw/mwgui/container.hpp79
-rw-r--r--apps/openmw/mwgui/containeritemmodel.cpp173
-rw-r--r--apps/openmw/mwgui/containeritemmodel.hpp38
-rw-r--r--apps/openmw/mwgui/controllers.cpp54
-rw-r--r--apps/openmw/mwgui/controllers.hpp46
-rw-r--r--apps/openmw/mwgui/countdialog.cpp104
-rw-r--r--apps/openmw/mwgui/countdialog.hpp39
-rw-r--r--apps/openmw/mwgui/cursor.cpp129
-rw-r--r--apps/openmw/mwgui/cursor.hpp61
-rw-r--r--apps/openmw/mwgui/dialogue.cpp615
-rw-r--r--apps/openmw/mwgui/dialogue.hpp176
-rw-r--r--apps/openmw/mwgui/enchantingdialog.cpp307
-rw-r--r--apps/openmw/mwgui/enchantingdialog.hpp65
-rw-r--r--apps/openmw/mwgui/exposedwindow.cpp24
-rw-r--r--apps/openmw/mwgui/exposedwindow.hpp26
-rw-r--r--apps/openmw/mwgui/fontloader.cpp238
-rw-r--r--apps/openmw/mwgui/fontloader.hpp25
-rw-r--r--apps/openmw/mwgui/formatting.cpp407
-rw-r--r--apps/openmw/mwgui/formatting.hpp67
-rw-r--r--apps/openmw/mwgui/hud.cpp625
-rw-r--r--apps/openmw/mwgui/hud.hpp114
-rw-r--r--apps/openmw/mwgui/imagebutton.cpp63
-rw-r--r--apps/openmw/mwgui/imagebutton.hpp33
-rw-r--r--apps/openmw/mwgui/inventoryitemmodel.cpp103
-rw-r--r--apps/openmw/mwgui/inventoryitemmodel.hpp31
-rw-r--r--apps/openmw/mwgui/inventorywindow.cpp516
-rw-r--r--apps/openmw/mwgui/inventorywindow.hpp112
-rw-r--r--apps/openmw/mwgui/itemmodel.cpp120
-rw-r--r--apps/openmw/mwgui/itemmodel.hpp84
-rw-r--r--apps/openmw/mwgui/itemselection.cpp59
-rw-r--r--apps/openmw/mwgui/itemselection.hpp34
-rw-r--r--apps/openmw/mwgui/itemview.cpp202
-rw-r--r--apps/openmw/mwgui/itemview.hpp52
-rw-r--r--apps/openmw/mwgui/journalbooks.cpp326
-rw-r--r--apps/openmw/mwgui/journalbooks.hpp29
-rw-r--r--apps/openmw/mwgui/journalviewmodel.cpp389
-rw-r--r--apps/openmw/mwgui/journalviewmodel.hpp93
-rw-r--r--apps/openmw/mwgui/journalwindow.cpp474
-rw-r--r--apps/openmw/mwgui/journalwindow.hpp26
-rw-r--r--apps/openmw/mwgui/keywordsearch.cpp0
-rw-r--r--apps/openmw/mwgui/keywordsearch.hpp199
-rw-r--r--apps/openmw/mwgui/levelupdialog.cpp200
-rw-r--r--apps/openmw/mwgui/levelupdialog.hpp42
-rw-r--r--apps/openmw/mwgui/list.cpp169
-rw-r--r--apps/openmw/mwgui/list.hpp83
-rw-r--r--apps/openmw/mwgui/loadingscreen.cpp235
-rw-r--r--apps/openmw/mwgui/loadingscreen.hpp68
-rw-r--r--apps/openmw/mwgui/mainmenu.cpp87
-rw-r--r--apps/openmw/mwgui/mainmenu.hpp23
-rw-r--r--apps/openmw/mwgui/mapwindow.cpp446
-rw-r--r--apps/openmw/mwgui/mapwindow.hpp115
-rw-r--r--apps/openmw/mwgui/merchantrepair.cpp132
-rw-r--r--apps/openmw/mwgui/merchantrepair.hpp37
-rw-r--r--apps/openmw/mwgui/messagebox.cpp429
-rw-r--r--apps/openmw/mwgui/messagebox.hpp105
-rw-r--r--apps/openmw/mwgui/mode.hpp68
-rw-r--r--apps/openmw/mwgui/pickpocketitemmodel.cpp55
-rw-r--r--apps/openmw/mwgui/pickpocketitemmodel.hpp26
-rw-r--r--apps/openmw/mwgui/quickkeysmenu.cpp626
-rw-r--r--apps/openmw/mwgui/quickkeysmenu.hpp106
-rw-r--r--apps/openmw/mwgui/race.cpp400
-rw-r--r--apps/openmw/mwgui/race.hpp107
-rw-r--r--apps/openmw/mwgui/referenceinterface.cpp36
-rw-r--r--apps/openmw/mwgui/referenceinterface.hpp30
-rw-r--r--apps/openmw/mwgui/repair.cpp157
-rw-r--r--apps/openmw/mwgui/repair.hpp45
-rw-r--r--apps/openmw/mwgui/review.cpp366
-rw-r--r--apps/openmw/mwgui/review.hpp95
-rw-r--r--apps/openmw/mwgui/scrollwindow.cpp97
-rw-r--r--apps/openmw/mwgui/scrollwindow.hpp38
-rw-r--r--apps/openmw/mwgui/settingswindow.cpp621
-rw-r--r--apps/openmw/mwgui/settingswindow.hpp99
-rw-r--r--apps/openmw/mwgui/sortfilteritemmodel.cpp171
-rw-r--r--apps/openmw/mwgui/sortfilteritemmodel.hpp53
-rw-r--r--apps/openmw/mwgui/soulgemdialog.cpp33
-rw-r--r--apps/openmw/mwgui/soulgemdialog.hpp28
-rw-r--r--apps/openmw/mwgui/spellbuyingwindow.cpp162
-rw-r--r--apps/openmw/mwgui/spellbuyingwindow.hpp53
-rw-r--r--apps/openmw/mwgui/spellcreationdialog.cpp646
-rw-r--r--apps/openmw/mwgui/spellcreationdialog.hpp156
-rw-r--r--apps/openmw/mwgui/spellicons.cpp298
-rw-r--r--apps/openmw/mwgui/spellicons.hpp51
-rw-r--r--apps/openmw/mwgui/spellwindow.cpp456
-rw-r--r--apps/openmw/mwgui/spellwindow.hpp44
-rw-r--r--apps/openmw/mwgui/statswindow.cpp573
-rw-r--r--apps/openmw/mwgui/statswindow.hpp77
-rw-r--r--apps/openmw/mwgui/textinput.cpp73
-rw-r--r--apps/openmw/mwgui/textinput.hpp33
-rw-r--r--apps/openmw/mwgui/tooltips.cpp764
-rw-r--r--apps/openmw/mwgui/tooltips.hpp112
-rw-r--r--apps/openmw/mwgui/tradeitemmodel.cpp201
-rw-r--r--apps/openmw/mwgui/tradeitemmodel.hpp53
-rw-r--r--apps/openmw/mwgui/tradewindow.cpp466
-rw-r--r--apps/openmw/mwgui/tradewindow.hpp106
-rw-r--r--apps/openmw/mwgui/trainingwindow.cpp168
-rw-r--r--apps/openmw/mwgui/trainingwindow.hpp36
-rw-r--r--apps/openmw/mwgui/travelwindow.cpp188
-rw-r--r--apps/openmw/mwgui/travelwindow.hpp52
-rw-r--r--apps/openmw/mwgui/waitdialog.cpp292
-rw-r--r--apps/openmw/mwgui/waitdialog.hpp69
-rw-r--r--apps/openmw/mwgui/widgets.cpp1007
-rw-r--r--apps/openmw/mwgui/widgets.hpp444
-rw-r--r--apps/openmw/mwgui/windowbase.cpp52
-rw-r--r--apps/openmw/mwgui/windowbase.hpp47
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.cpp1362
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.hpp383
-rw-r--r--apps/openmw/mwgui/windowpinnablebase.cpp32
-rw-r--r--apps/openmw/mwgui/windowpinnablebase.hpp28
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.cpp1069
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.hpp255
-rw-r--r--apps/openmw/mwmechanics/activespells.cpp284
-rw-r--r--apps/openmw/mwmechanics/activespells.hpp79
-rw-r--r--apps/openmw/mwmechanics/actors.cpp354
-rw-r--r--apps/openmw/mwmechanics/actors.hpp88
-rw-r--r--apps/openmw/mwmechanics/aiactivate.cpp21
-rw-r--r--apps/openmw/mwmechanics/aiactivate.hpp23
-rw-r--r--apps/openmw/mwmechanics/aiescort.cpp184
-rw-r--r--apps/openmw/mwmechanics/aiescort.hpp41
-rw-r--r--apps/openmw/mwmechanics/aifollow.cpp27
-rw-r--r--apps/openmw/mwmechanics/aifollow.hpp29
-rw-r--r--apps/openmw/mwmechanics/aipackage.cpp4
-rw-r--r--apps/openmw/mwmechanics/aipackage.hpp29
-rw-r--r--apps/openmw/mwmechanics/aisequence.cpp122
-rw-r--r--apps/openmw/mwmechanics/aisequence.hpp58
-rw-r--r--apps/openmw/mwmechanics/aitravel.cpp111
-rw-r--r--apps/openmw/mwmechanics/aitravel.hpp33
-rw-r--r--apps/openmw/mwmechanics/aiwander.cpp306
-rw-r--r--apps/openmw/mwmechanics/aiwander.hpp64
-rw-r--r--apps/openmw/mwmechanics/alchemy.cpp462
-rw-r--r--apps/openmw/mwmechanics/alchemy.hpp118
-rw-r--r--apps/openmw/mwmechanics/character.cpp1045
-rw-r--r--apps/openmw/mwmechanics/character.hpp193
-rw-r--r--apps/openmw/mwmechanics/creaturestats.cpp345
-rw-r--r--apps/openmw/mwmechanics/creaturestats.hpp172
-rw-r--r--apps/openmw/mwmechanics/drawstate.hpp15
-rw-r--r--apps/openmw/mwmechanics/enchanting.cpp318
-rw-r--r--apps/openmw/mwmechanics/enchanting.hpp49
-rw-r--r--apps/openmw/mwmechanics/magiceffects.cpp163
-rw-r--r--apps/openmw/mwmechanics/magiceffects.hpp83
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.cpp682
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.hpp106
-rw-r--r--apps/openmw/mwmechanics/movement.hpp20
-rw-r--r--apps/openmw/mwmechanics/npcstats.cpp433
-rw-r--r--apps/openmw/mwmechanics/npcstats.hpp157
-rw-r--r--apps/openmw/mwmechanics/objects.cpp85
-rw-r--r--apps/openmw/mwmechanics/objects.hpp45
-rw-r--r--apps/openmw/mwmechanics/pathfinding.cpp221
-rw-r--r--apps/openmw/mwmechanics/pathfinding.hpp34
-rw-r--r--apps/openmw/mwmechanics/repair.cpp110
-rw-r--r--apps/openmw/mwmechanics/repair.hpp23
-rw-r--r--apps/openmw/mwmechanics/security.cpp109
-rw-r--r--apps/openmw/mwmechanics/security.hpp27
-rw-r--r--apps/openmw/mwmechanics/spells.cpp103
-rw-r--r--apps/openmw/mwmechanics/spells.hpp63
-rw-r--r--apps/openmw/mwmechanics/spellsuccess.hpp118
-rw-r--r--apps/openmw/mwmechanics/stat.hpp198
-rw-r--r--apps/openmw/mwrender/activatoranimation.cpp31
-rw-r--r--apps/openmw/mwrender/activatoranimation.hpp21
-rw-r--r--apps/openmw/mwrender/actors.cpp190
-rw-r--r--apps/openmw/mwrender/actors.hpp58
-rw-r--r--apps/openmw/mwrender/animation.cpp1039
-rw-r--r--apps/openmw/mwrender/animation.hpp256
-rw-r--r--apps/openmw/mwrender/camera.cpp346
-rw-r--r--apps/openmw/mwrender/camera.hpp113
-rw-r--r--apps/openmw/mwrender/cell.hpp35
-rw-r--r--apps/openmw/mwrender/characterpreview.cpp272
-rw-r--r--apps/openmw/mwrender/characterpreview.hpp109
-rw-r--r--apps/openmw/mwrender/compositors.cpp108
-rw-r--r--apps/openmw/mwrender/compositors.hpp64
-rw-r--r--apps/openmw/mwrender/creatureanimation.cpp33
-rw-r--r--apps/openmw/mwrender/creatureanimation.hpp21
-rw-r--r--apps/openmw/mwrender/debugging.cpp296
-rw-r--r--apps/openmw/mwrender/debugging.hpp90
-rw-r--r--apps/openmw/mwrender/externalrendering.hpp23
-rw-r--r--apps/openmw/mwrender/globalmap.cpp235
-rw-r--r--apps/openmw/mwrender/globalmap.hpp51
-rw-r--r--apps/openmw/mwrender/localmap.cpp426
-rw-r--r--apps/openmw/mwrender/localmap.hpp125
-rw-r--r--apps/openmw/mwrender/npcanimation.cpp605
-rw-r--r--apps/openmw/mwrender/npcanimation.hpp98
-rw-r--r--apps/openmw/mwrender/objects.cpp283
-rw-r--r--apps/openmw/mwrender/objects.hpp66
-rw-r--r--apps/openmw/mwrender/occlusionquery.cpp205
-rw-r--r--apps/openmw/mwrender/occlusionquery.hpp77
-rw-r--r--apps/openmw/mwrender/refraction.cpp107
-rw-r--r--apps/openmw/mwrender/refraction.hpp45
-rw-r--r--apps/openmw/mwrender/renderconst.hpp70
-rw-r--r--apps/openmw/mwrender/renderinginterface.hpp17
-rw-r--r--apps/openmw/mwrender/renderingmanager.cpp1038
-rw-r--r--apps/openmw/mwrender/renderingmanager.hpp279
-rw-r--r--apps/openmw/mwrender/ripplesimulation.cpp263
-rw-r--r--apps/openmw/mwrender/ripplesimulation.hpp85
-rw-r--r--apps/openmw/mwrender/shadows.cpp195
-rw-r--r--apps/openmw/mwrender/shadows.hpp39
-rw-r--r--apps/openmw/mwrender/sky.cpp646
-rw-r--r--apps/openmw/mwrender/sky.hpp224
-rw-r--r--apps/openmw/mwrender/terrainstorage.cpp56
-rw-r--r--apps/openmw/mwrender/terrainstorage.hpp22
-rw-r--r--apps/openmw/mwrender/videoplayer.cpp1191
-rw-r--r--apps/openmw/mwrender/videoplayer.hpp54
-rw-r--r--apps/openmw/mwrender/water.cpp496
-rw-r--r--apps/openmw/mwrender/water.hpp180
-rw-r--r--apps/openmw/mwscript/aiextensions.cpp420
-rw-r--r--apps/openmw/mwscript/aiextensions.hpp23
-rw-r--r--apps/openmw/mwscript/animationextensions.cpp106
-rw-r--r--apps/openmw/mwscript/animationextensions.hpp24
-rw-r--r--apps/openmw/mwscript/cellextensions.cpp182
-rw-r--r--apps/openmw/mwscript/cellextensions.hpp25
-rw-r--r--apps/openmw/mwscript/compilercontext.cpp70
-rw-r--r--apps/openmw/mwscript/compilercontext.hpp41
-rw-r--r--apps/openmw/mwscript/consoleextensions.cpp20
-rw-r--r--apps/openmw/mwscript/consoleextensions.hpp23
-rw-r--r--apps/openmw/mwscript/containerextensions.cpp385
-rw-r--r--apps/openmw/mwscript/containerextensions.hpp23
-rw-r--r--apps/openmw/mwscript/controlextensions.cpp200
-rw-r--r--apps/openmw/mwscript/controlextensions.hpp23
-rw-r--r--apps/openmw/mwscript/dialogueextensions.cpp214
-rw-r--r--apps/openmw/mwscript/dialogueextensions.hpp23
-rw-r--r--apps/openmw/mwscript/docs/vmformat.txt355
-rw-r--r--apps/openmw/mwscript/extensions.cpp46
-rw-r--r--apps/openmw/mwscript/extensions.hpp20
-rw-r--r--apps/openmw/mwscript/globalscripts.cpp79
-rw-r--r--apps/openmw/mwscript/globalscripts.hpp38
-rw-r--r--apps/openmw/mwscript/guiextensions.cpp187
-rw-r--r--apps/openmw/mwscript/guiextensions.hpp24
-rw-r--r--apps/openmw/mwscript/interpretercontext.cpp506
-rw-r--r--apps/openmw/mwscript/interpretercontext.hpp158
-rw-r--r--apps/openmw/mwscript/locals.cpp68
-rw-r--r--apps/openmw/mwscript/locals.hpp30
-rw-r--r--apps/openmw/mwscript/miscextensions.cpp780
-rw-r--r--apps/openmw/mwscript/miscextensions.hpp24
-rw-r--r--apps/openmw/mwscript/ref.hpp40
-rw-r--r--apps/openmw/mwscript/scriptmanagerimp.cpp230
-rw-r--r--apps/openmw/mwscript/scriptmanagerimp.hpp82
-rw-r--r--apps/openmw/mwscript/skyextensions.cpp135
-rw-r--r--apps/openmw/mwscript/skyextensions.hpp23
-rw-r--r--apps/openmw/mwscript/soundextensions.cpp223
-rw-r--r--apps/openmw/mwscript/soundextensions.hpp24
-rw-r--r--apps/openmw/mwscript/statsextensions.cpp1213
-rw-r--r--apps/openmw/mwscript/statsextensions.hpp23
-rw-r--r--apps/openmw/mwscript/transformationextensions.cpp752
-rw-r--r--apps/openmw/mwscript/transformationextensions.hpp23
-rw-r--r--apps/openmw/mwscript/userextensions.cpp78
-rw-r--r--apps/openmw/mwscript/userextensions.hpp23
-rw-r--r--apps/openmw/mwsound/audiere_decoder.cpp137
-rw-r--r--apps/openmw/mwsound/audiere_decoder.hpp46
-rw-r--r--apps/openmw/mwsound/ffmpeg_decoder.cpp343
-rw-r--r--apps/openmw/mwsound/ffmpeg_decoder.hpp78
-rw-r--r--apps/openmw/mwsound/mpgsnd_decoder.cpp247
-rw-r--r--apps/openmw/mwsound/mpgsnd_decoder.hpp59
-rw-r--r--apps/openmw/mwsound/openal_output.cpp1025
-rw-r--r--apps/openmw/mwsound/openal_output.hpp79
-rw-r--r--apps/openmw/mwsound/sound.hpp55
-rw-r--r--apps/openmw/mwsound/sound_decoder.hpp54
-rw-r--r--apps/openmw/mwsound/sound_output.hpp61
-rw-r--r--apps/openmw/mwsound/soundmanagerimp.cpp708
-rw-r--r--apps/openmw/mwsound/soundmanagerimp.hpp154
-rw-r--r--apps/openmw/mwworld/action.cpp48
-rw-r--r--apps/openmw/mwworld/action.hpp42
-rw-r--r--apps/openmw/mwworld/actionalchemy.cpp12
-rw-r--r--apps/openmw/mwworld/actionalchemy.hpp14
-rw-r--r--apps/openmw/mwworld/actionapply.cpp28
-rw-r--r--apps/openmw/mwworld/actionapply.hpp38
-rw-r--r--apps/openmw/mwworld/actiondoor.cpp16
-rw-r--r--apps/openmw/mwworld/actiondoor.hpp18
-rw-r--r--apps/openmw/mwworld/actioneat.cpp48
-rw-r--r--apps/openmw/mwworld/actioneat.hpp19
-rw-r--r--apps/openmw/mwworld/actionequip.cpp87
-rw-r--r--apps/openmw/mwworld/actionequip.hpp19
-rw-r--r--apps/openmw/mwworld/actionopen.cpp27
-rw-r--r--apps/openmw/mwworld/actionopen.hpp25
-rw-r--r--apps/openmw/mwworld/actionread.cpp57
-rw-r--r--apps/openmw/mwworld/actionread.hpp19
-rw-r--r--apps/openmw/mwworld/actionrepair.cpp18
-rw-r--r--apps/openmw/mwworld/actionrepair.hpp17
-rw-r--r--apps/openmw/mwworld/actionsoulgem.cpp21
-rw-r--r--apps/openmw/mwworld/actionsoulgem.hpp19
-rw-r--r--apps/openmw/mwworld/actiontake.cpp24
-rw-r--r--apps/openmw/mwworld/actiontake.hpp19
-rw-r--r--apps/openmw/mwworld/actiontalk.cpp15
-rw-r--r--apps/openmw/mwworld/actiontalk.hpp20
-rw-r--r--apps/openmw/mwworld/actionteleport.cpp22
-rw-r--r--apps/openmw/mwworld/actionteleport.hpp26
-rw-r--r--apps/openmw/mwworld/cellfunctors.hpp33
-rw-r--r--apps/openmw/mwworld/cells.cpp263
-rw-r--r--apps/openmw/mwworld/cells.hpp55
-rw-r--r--apps/openmw/mwworld/cellstore.cpp277
-rw-r--r--apps/openmw/mwworld/cellstore.hpp157
-rw-r--r--apps/openmw/mwworld/class.cpp360
-rw-r--r--apps/openmw/mwworld/class.hpp309
-rw-r--r--apps/openmw/mwworld/containerstore.cpp749
-rw-r--r--apps/openmw/mwworld/containerstore.hpp197
-rw-r--r--apps/openmw/mwworld/customdata.hpp17
-rw-r--r--apps/openmw/mwworld/esmstore.cpp144
-rw-r--r--apps/openmw/mwworld/esmstore.hpp471
-rw-r--r--apps/openmw/mwworld/failedaction.cpp19
-rw-r--r--apps/openmw/mwworld/failedaction.hpp20
-rw-r--r--apps/openmw/mwworld/fallback.cpp49
-rw-r--r--apps/openmw/mwworld/fallback.hpp22
-rw-r--r--apps/openmw/mwworld/globals.cpp159
-rw-r--r--apps/openmw/mwworld/globals.hpp62
-rw-r--r--apps/openmw/mwworld/inventorystore.cpp327
-rw-r--r--apps/openmw/mwworld/inventorystore.hpp114
-rw-r--r--apps/openmw/mwworld/livecellref.hpp58
-rw-r--r--apps/openmw/mwworld/localscripts.cpp175
-rw-r--r--apps/openmw/mwworld/localscripts.hpp59
-rw-r--r--apps/openmw/mwworld/manualref.hpp86
-rw-r--r--apps/openmw/mwworld/nullaction.hpp15
-rw-r--r--apps/openmw/mwworld/physicssystem.cpp588
-rw-r--r--apps/openmw/mwworld/physicssystem.hpp102
-rw-r--r--apps/openmw/mwworld/player.cpp148
-rw-r--r--apps/openmw/mwworld/player.hpp69
-rw-r--r--apps/openmw/mwworld/ptr.cpp55
-rw-r--r--apps/openmw/mwworld/ptr.hpp111
-rw-r--r--apps/openmw/mwworld/recordcmp.hpp34
-rw-r--r--apps/openmw/mwworld/refdata.cpp167
-rw-r--r--apps/openmw/mwworld/refdata.hpp98
-rw-r--r--apps/openmw/mwworld/scene.cpp529
-rw-r--r--apps/openmw/mwworld/scene.hpp107
-rw-r--r--apps/openmw/mwworld/store.cpp97
-rw-r--r--apps/openmw/mwworld/store.hpp1080
-rw-r--r--apps/openmw/mwworld/timestamp.cpp108
-rw-r--r--apps/openmw/mwworld/timestamp.hpp44
-rw-r--r--apps/openmw/mwworld/weather.cpp709
-rw-r--r--apps/openmw/mwworld/weather.hpp217
-rw-r--r--apps/openmw/mwworld/worldimp.cpp1951
-rw-r--r--apps/openmw/mwworld/worldimp.hpp445
-rw-r--r--apps/openmw_test_suite/CMakeLists.txt29
-rw-r--r--apps/openmw_test_suite/components/file_finder/test_filefinder.cpp66
-rw-r--r--apps/openmw_test_suite/components/file_finder/test_search.cpp74
-rw-r--r--apps/openmw_test_suite/components/misc/test_slicearray.cpp33
-rw-r--r--apps/openmw_test_suite/components/misc/test_stringops.cpp79
-rw-r--r--apps/openmw_test_suite/openmw_test_suite.cpp12
-rw-r--r--cmake/COPYING-CMAKE-SCRIPTS27
-rw-r--r--cmake/FindAudiere.cmake60
-rw-r--r--cmake/FindBullet.cmake80
-rw-r--r--cmake/FindCg.cmake53
-rw-r--r--cmake/FindFFmpeg.cmake156
-rw-r--r--cmake/FindFreeImage.cmake47
-rw-r--r--cmake/FindFreetype.cmake69
-rw-r--r--cmake/FindGMock.cmake91
-rw-r--r--cmake/FindLIBUNSHIELD.cmake48
-rw-r--r--cmake/FindMPG123.cmake47
-rw-r--r--cmake/FindMyGUI.cmake164
-rw-r--r--cmake/FindOGRE.cmake554
-rw-r--r--cmake/FindPkgMacros.cmake161
-rw-r--r--cmake/FindSDL.cmake177
-rw-r--r--cmake/FindSDL2.cmake193
-rw-r--r--cmake/FindSNDFILE.cmake41
-rw-r--r--cmake/FindZZip.cmake48
-rw-r--r--cmake/OpenMWMacros.cmake95
-rw-r--r--cmake/PreprocessorUtils.cmake60
-rw-r--r--components/CMakeLists.txt102
-rw-r--r--components/bsa/bsa_archive.cpp382
-rw-r--r--components/bsa/bsa_archive.hpp42
-rw-r--r--components/bsa/bsa_file.cpp176
-rw-r--r--components/bsa/bsa_file.hpp128
-rw-r--r--components/bsa/tests/Makefile15
-rw-r--r--components/bsa/tests/bsa_file_test.cpp44
-rw-r--r--components/bsa/tests/ogre_archive_test.cpp34
-rw-r--r--components/bsa/tests/output/bsa_file_test.out17
-rw-r--r--components/bsa/tests/output/ogre_archive_test.out2
-rwxr-xr-xcomponents/bsa/tests/test.sh18
-rw-r--r--components/compiler/context.hpp44
-rw-r--r--components/compiler/controlparser.cpp260
-rw-r--r--components/compiler/controlparser.hpp74
-rw-r--r--components/compiler/errorhandler.cpp65
-rw-r--r--components/compiler/errorhandler.hpp68
-rw-r--r--components/compiler/exception.hpp39
-rw-r--r--components/compiler/exprparser.cpp756
-rw-r--r--components/compiler/exprparser.hpp109
-rw-r--r--components/compiler/extensions.cpp217
-rw-r--r--components/compiler/extensions.hpp87
-rw-r--r--components/compiler/extensions0.cpp467
-rw-r--r--components/compiler/extensions0.hpp81
-rw-r--r--components/compiler/fileparser.cpp134
-rw-r--r--components/compiler/fileparser.hpp60
-rw-r--r--components/compiler/generator.cpp903
-rw-r--r--components/compiler/generator.hpp130
-rw-r--r--components/compiler/lineparser.cpp451
-rw-r--r--components/compiler/lineparser.hpp79
-rw-r--r--components/compiler/literals.cpp94
-rw-r--r--components/compiler/literals.hpp49
-rw-r--r--components/compiler/locals.cpp110
-rw-r--r--components/compiler/locals.hpp45
-rw-r--r--components/compiler/nullerrorhandler.cpp6
-rw-r--r--components/compiler/nullerrorhandler.hpp21
-rw-r--r--components/compiler/opcodes.cpp13
-rw-r--r--components/compiler/opcodes.hpp416
-rw-r--r--components/compiler/output.cpp74
-rw-r--r--components/compiler/output.hpp44
-rw-r--r--components/compiler/parser.cpp185
-rw-r--r--components/compiler/parser.hpp112
-rw-r--r--components/compiler/scanner.cpp564
-rw-r--r--components/compiler/scanner.hpp126
-rw-r--r--components/compiler/scriptparser.cpp92
-rw-r--r--components/compiler/scriptparser.hpp54
-rw-r--r--components/compiler/skipparser.cpp41
-rw-r--r--components/compiler/skipparser.hpp42
-rw-r--r--components/compiler/streamerrorhandler.cpp39
-rw-r--r--components/compiler/streamerrorhandler.hpp37
-rw-r--r--components/compiler/stringparser.cpp64
-rw-r--r--components/compiler/stringparser.hpp50
-rw-r--r--components/compiler/tokenloc.hpp20
-rw-r--r--components/doc.hpp30
-rw-r--r--components/esm/aipackage.cpp77
-rw-r--r--components/esm/aipackage.hpp101
-rw-r--r--components/esm/attr.cpp58
-rw-r--r--components/esm/attr.hpp44
-rw-r--r--components/esm/cellref.cpp87
-rw-r--r--components/esm/cellref.hpp92
-rw-r--r--components/esm/defs.hpp87
-rw-r--r--components/esm/effectlist.cpp24
-rw-r--r--components/esm/effectlist.hpp43
-rw-r--r--components/esm/esmcommon.hpp94
-rw-r--r--components/esm/esmreader.cpp329
-rw-r--r--components/esm/esmreader.hpp278
-rw-r--r--components/esm/esmwriter.cpp186
-rw-r--r--components/esm/esmwriter.hpp104
-rw-r--r--components/esm/filter.cpp23
-rw-r--r--components/esm/filter.hpp27
-rw-r--r--components/esm/loadacti.cpp27
-rw-r--r--components/esm/loadacti.hpp24
-rw-r--r--components/esm/loadalch.cpp38
-rw-r--r--components/esm/loadalch.hpp39
-rw-r--r--components/esm/loadappa.cpp51
-rw-r--r--components/esm/loadappa.hpp44
-rw-r--r--components/esm/loadarmo.cpp68
-rw-r--r--components/esm/loadarmo.hpp98
-rw-r--r--components/esm/loadbody.cpp22
-rw-r--r--components/esm/loadbody.hpp63
-rw-r--r--components/esm/loadbook.cpp44
-rw-r--r--components/esm/loadbook.hpp34
-rw-r--r--components/esm/loadbsgn.cpp35
-rw-r--r--components/esm/loadbsgn.hpp28
-rw-r--r--components/esm/loadcell.cpp304
-rw-r--r--components/esm/loadcell.hpp154
-rw-r--r--components/esm/loadclas.cpp71
-rw-r--r--components/esm/loadclas.hpp80
-rw-r--r--components/esm/loadclot.cpp51
-rw-r--r--components/esm/loadclot.hpp54
-rw-r--r--components/esm/loadcont.cpp65
-rw-r--r--components/esm/loadcont.hpp55
-rw-r--r--components/esm/loadcrea.cpp82
-rw-r--r--components/esm/loadcrea.hpp96
-rw-r--r--components/esm/loadcrec.hpp47
-rw-r--r--components/esm/loaddial.cpp39
-rw-r--r--components/esm/loaddial.hpp40
-rw-r--r--components/esm/loaddoor.cpp35
-rw-r--r--components/esm/loaddoor.hpp23
-rw-r--r--components/esm/loadench.cpp21
-rw-r--r--components/esm/loadench.hpp45
-rw-r--r--components/esm/loadfact.cpp92
-rw-r--r--components/esm/loadfact.hpp72
-rw-r--r--components/esm/loadglob.cpp24
-rw-r--r--components/esm/loadglob.hpp33
-rw-r--r--components/esm/loadgmst.cpp39
-rw-r--r--components/esm/loadgmst.hpp46
-rw-r--r--components/esm/loadinfo.cpp156
-rw-r--r--components/esm/loadinfo.hpp106
-rw-r--r--components/esm/loadingr.cpp65
-rw-r--r--components/esm/loadingr.hpp37
-rw-r--r--components/esm/loadland.cpp245
-rw-r--r--components/esm/loadland.hpp126
-rw-r--r--components/esm/loadlevlist.cpp55
-rw-r--r--components/esm/loadlevlist.hpp77
-rw-r--r--components/esm/loadligh.cpp43
-rw-r--r--components/esm/loadligh.hpp53
-rw-r--r--components/esm/loadlock.cpp41
-rw-r--r--components/esm/loadlock.hpp34
-rw-r--r--components/esm/loadltex.cpp20
-rw-r--r--components/esm/loadltex.hpp37
-rw-r--r--components/esm/loadmgef.cpp278
-rw-r--r--components/esm/loadmgef.hpp227
-rw-r--r--components/esm/loadmisc.cpp36
-rw-r--r--components/esm/loadmisc.hpp38
-rw-r--r--components/esm/loadnpc.cpp147
-rw-r--r--components/esm/loadnpc.hpp130
-rw-r--r--components/esm/loadnpcc.hpp92
-rw-r--r--components/esm/loadpgrd.cpp99
-rw-r--r--components/esm/loadpgrd.hpp52
-rw-r--r--components/esm/loadprob.cpp41
-rw-r--r--components/esm/loadprob.hpp34
-rw-r--r--components/esm/loadrace.cpp54
-rw-r--r--components/esm/loadrace.hpp75
-rw-r--r--components/esm/loadregn.cpp62
-rw-r--r--components/esm/loadregn.hpp56
-rw-r--r--components/esm/loadrepa.cpp41
-rw-r--r--components/esm/loadrepa.hpp34
-rw-r--r--components/esm/loadscpt.cpp96
-rw-r--r--components/esm/loadscpt.hpp65
-rw-r--r--components/esm/loadskil.cpp174
-rw-r--r--components/esm/loadskil.hpp86
-rw-r--r--components/esm/loadsndg.cpp23
-rw-r--r--components/esm/loadsndg.hpp39
-rw-r--r--components/esm/loadsoun.cpp34
-rw-r--r--components/esm/loadsoun.hpp29
-rw-r--r--components/esm/loadspel.cpp33
-rw-r--r--components/esm/loadspel.hpp51
-rw-r--r--components/esm/loadsscr.cpp20
-rw-r--r--components/esm/loadsscr.hpp31
-rw-r--r--components/esm/loadstat.cpp22
-rw-r--r--components/esm/loadstat.hpp34
-rw-r--r--components/esm/loadtes3.cpp53
-rw-r--r--components/esm/loadtes3.hpp55
-rw-r--r--components/esm/loadweap.cpp48
-rw-r--r--components/esm/loadweap.hpp68
-rw-r--r--components/esm/records.hpp51
-rw-r--r--components/esm/spelllist.cpp22
-rw-r--r--components/esm/spelllist.hpp25
-rw-r--r--components/esm/variant.cpp282
-rw-r--r--components/esm/variant.hpp86
-rw-r--r--components/esm/variantimp.cpp280
-rw-r--r--components/esm/variantimp.hpp179
-rw-r--r--components/file_finder/file_finder.hpp142
-rw-r--r--components/file_finder/filename_less.hpp84
-rw-r--r--components/file_finder/search.cpp36
-rw-r--r--components/file_finder/search.hpp20
-rw-r--r--components/fileorderlist/model/datafilesmodel.cpp445
-rw-r--r--components/fileorderlist/model/datafilesmodel.hpp66
-rw-r--r--components/fileorderlist/model/esm/esmfile.cpp50
-rw-r--r--components/fileorderlist/model/esm/esmfile.hpp54
-rw-r--r--components/fileorderlist/model/modelitem.cpp57
-rw-r--r--components/fileorderlist/model/modelitem.hpp32
-rw-r--r--components/fileorderlist/model/pluginsproxymodel.cpp17
-rw-r--r--components/fileorderlist/model/pluginsproxymodel.hpp18
-rw-r--r--components/fileorderlist/utils/comboboxlineedit.cpp35
-rw-r--r--components/fileorderlist/utils/comboboxlineedit.hpp35
-rw-r--r--components/fileorderlist/utils/lineedit.cpp38
-rw-r--r--components/fileorderlist/utils/lineedit.hpp35
-rw-r--r--components/fileorderlist/utils/naturalsort.cpp105
-rw-r--r--components/fileorderlist/utils/naturalsort.hpp11
-rw-r--r--components/fileorderlist/utils/profilescombobox.cpp92
-rw-r--r--components/fileorderlist/utils/profilescombobox.hpp30
-rw-r--r--components/files/collections.cpp64
-rw-r--r--components/files/collections.hpp43
-rw-r--r--components/files/configurationmanager.cpp165
-rw-r--r--components/files/configurationmanager.hpp74
-rw-r--r--components/files/constrainedfiledatastream.cpp172
-rw-r--r--components/files/constrainedfiledatastream.hpp8
-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.hpp123
-rw-r--r--components/files/linuxpath.cpp164
-rw-r--r--components/files/linuxpath.hpp71
-rw-r--r--components/files/lowlevelfile.cpp299
-rw-r--r--components/files/lowlevelfile.hpp56
-rw-r--r--components/files/macospath.cpp165
-rw-r--r--components/files/macospath.hpp66
-rw-r--r--components/files/multidircollection.cpp112
-rw-r--r--components/files/multidircollection.hpp88
-rw-r--r--components/files/ogreplugin.cpp51
-rw-r--r--components/files/ogreplugin.hpp42
-rw-r--r--components/files/windowspath.cpp108
-rw-r--r--components/files/windowspath.hpp77
-rw-r--r--components/interpreter/context.hpp114
-rw-r--r--components/interpreter/controlopcodes.hpp76
-rw-r--r--components/interpreter/defines.cpp208
-rw-r--r--components/interpreter/defines.hpp13
-rw-r--r--components/interpreter/docs/vmformat.txt131
-rw-r--r--components/interpreter/genericopcodes.hpp93
-rw-r--r--components/interpreter/installopcodes.cpp115
-rw-r--r--components/interpreter/installopcodes.hpp11
-rw-r--r--components/interpreter/interpreter.cpp216
-rw-r--r--components/interpreter/interpreter.hpp63
-rw-r--r--components/interpreter/localopcodes.hpp321
-rw-r--r--components/interpreter/mathopcodes.hpp112
-rw-r--r--components/interpreter/miscopcodes.hpp242
-rw-r--r--components/interpreter/opcodes.hpp40
-rw-r--r--components/interpreter/runtime.cpp116
-rw-r--r--components/interpreter/runtime.hpp64
-rw-r--r--components/interpreter/scriptopcodes.hpp47
-rw-r--r--components/interpreter/spatialopcodes.hpp43
-rw-r--r--components/interpreter/types.hpp44
-rw-r--r--components/loadinglistener/loadinglistener.hpp35
-rw-r--r--components/misc/slice_array.hpp82
-rw-r--r--components/misc/stringops.cpp70
-rw-r--r--components/misc/stringops.hpp92
-rw-r--r--components/misc/tests/Makefile12
-rw-r--r--components/misc/tests/output/slice_test.out6
-rw-r--r--components/misc/tests/output/strops_test.out0
-rw-r--r--components/misc/tests/slice_test.cpp28
-rw-r--r--components/misc/tests/strops_test.cpp48
-rwxr-xr-xcomponents/misc/tests/test.sh18
-rw-r--r--components/misc/utf8stream.hpp116
-rw-r--r--components/nif/controlled.hpp151
-rw-r--r--components/nif/controller.hpp330
-rw-r--r--components/nif/data.hpp428
-rw-r--r--components/nif/effect.hpp105
-rw-r--r--components/nif/extra.hpp108
-rw-r--r--components/nif/niffile.cpp440
-rw-r--r--components/nif/niffile.hpp212
-rw-r--r--components/nif/nifstream.hpp181
-rw-r--r--components/nif/niftypes.hpp51
-rw-r--r--components/nif/node.hpp272
-rw-r--r--components/nif/property.hpp333
-rw-r--r--components/nif/record.hpp120
-rw-r--r--components/nif/recordptr.hpp181
-rw-r--r--components/nif/tests/Makefile12
-rw-r--r--components/nif/tests/nif_bsa_test.cpp30
-rw-r--r--components/nif/tests/niftool.cpp232
-rw-r--r--components/nif/tests/output/nif_bsa_test.out5799
-rwxr-xr-xcomponents/nif/tests/test.sh18
-rw-r--r--components/nifbullet/bulletnifloader.cpp303
-rw-r--r--components/nifbullet/bulletnifloader.hpp113
-rw-r--r--components/nifbullet/test/test.cpp209
-rw-r--r--components/nifogre/material.cpp394
-rw-r--r--components/nifogre/material.hpp57
-rw-r--r--components/nifogre/mesh.cpp387
-rw-r--r--components/nifogre/mesh.hpp55
-rw-r--r--components/nifogre/ogrenifloader.cpp998
-rw-r--r--components/nifogre/ogrenifloader.hpp103
-rw-r--r--components/nifogre/skeleton.cpp153
-rw-r--r--components/nifogre/skeleton.hpp62
-rw-r--r--components/nifogre/tests/Makefile19
-rw-r--r--components/nifogre/tests/ogre_common.cpp97
-rw-r--r--components/nifogre/tests/ogre_manualresource_test.cpp40
-rw-r--r--components/nifogre/tests/ogre_mesh_common.cpp69
-rw-r--r--components/nifogre/tests/ogre_nif_test.cpp50
-rw-r--r--components/nifogre/tests/ogre_skeleton_test.cpp21
-rw-r--r--components/nifogre/tests/output/ogre_manualresource_test.out3
-rw-r--r--components/nifogre/tests/output/ogre_nif_test.out0
-rw-r--r--components/nifogre/tests/output/ogre_skeleton_test.out1
-rw-r--r--components/nifogre/tests/plugins.cfg12
-rwxr-xr-xcomponents/nifogre/tests/test.sh18
-rw-r--r--components/nifoverrides/nifoverrides.cpp38
-rw-r--r--components/nifoverrides/nifoverrides.hpp23
-rw-r--r--components/settings/settings.cpp169
-rw-r--r--components/settings/settings.hpp52
-rw-r--r--components/terrain/chunk.cpp169
-rw-r--r--components/terrain/chunk.hpp63
-rw-r--r--components/terrain/material.cpp300
-rw-r--r--components/terrain/material.hpp56
-rw-r--r--components/terrain/quadtreenode.cpp512
-rw-r--r--components/terrain/quadtreenode.hpp161
-rw-r--r--components/terrain/storage.cpp470
-rw-r--r--components/terrain/storage.hpp84
-rw-r--r--components/terrain/world.cpp410
-rw-r--r--components/terrain/world.hpp154
-rw-r--r--components/to_utf8/Makefile8
-rw-r--r--components/to_utf8/gen_iconv.cpp118
-rw-r--r--components/to_utf8/tables_gen.hpp1056
-rw-r--r--components/to_utf8/tests/output/to_utf8_test.out4
-rwxr-xr-xcomponents/to_utf8/tests/test.sh18
-rw-r--r--components/to_utf8/tests/test_data/french-utf8.txt1
-rw-r--r--components/to_utf8/tests/test_data/french-win1252.txt1
-rw-r--r--components/to_utf8/tests/test_data/russian-utf8.txt1
-rw-r--r--components/to_utf8/tests/test_data/russian-win1251.txt1
-rw-r--r--components/to_utf8/tests/to_utf8_test.cpp59
-rw-r--r--components/to_utf8/to_utf8.cpp344
-rw-r--r--components/to_utf8/to_utf8.hpp55
-rw-r--r--components/translation/translation.cpp115
-rw-r--r--components/translation/translation.hpp42
-rw-r--r--credits.txt156
-rw-r--r--extern/oics/CMakeLists.txt20
-rw-r--r--extern/oics/ICSChannel.cpp258
-rw-r--r--extern/oics/ICSChannel.h122
-rw-r--r--extern/oics/ICSChannelListener.h46
-rw-r--r--extern/oics/ICSControl.cpp161
-rw-r--r--extern/oics/ICSControl.h107
-rw-r--r--extern/oics/ICSControlListener.h46
-rw-r--r--extern/oics/ICSInputControlSystem.cpp933
-rw-r--r--extern/oics/ICSInputControlSystem.h264
-rw-r--r--extern/oics/ICSInputControlSystem_joystick.cpp666
-rw-r--r--extern/oics/ICSInputControlSystem_keyboard.cpp156
-rw-r--r--extern/oics/ICSInputControlSystem_mouse.cpp397
-rw-r--r--extern/oics/ICSPrerequisites.cpp27
-rw-r--r--extern/oics/ICSPrerequisites.h109
-rw-r--r--extern/oics/tinystr.cpp116
-rw-r--r--extern/oics/tinystr.h319
-rw-r--r--extern/oics/tinyxml.cpp1888
-rw-r--r--extern/oics/tinyxml.h1802
-rw-r--r--extern/oics/tinyxmlerror.cpp53
-rw-r--r--extern/oics/tinyxmlparser.cpp1638
-rw-r--r--extern/sdl4ogre/CMakeLists.txt25
-rw-r--r--extern/sdl4ogre/OISCompat.h159
-rw-r--r--extern/sdl4ogre/cursormanager.hpp33
-rw-r--r--extern/sdl4ogre/events.h78
-rw-r--r--extern/sdl4ogre/osx_utils.h12
-rw-r--r--extern/sdl4ogre/osx_utils.mm15
-rw-r--r--extern/sdl4ogre/sdlcursormanager.cpp177
-rw-r--r--extern/sdl4ogre/sdlcursormanager.hpp41
-rw-r--r--extern/sdl4ogre/sdlinputwrapper.cpp418
-rw-r--r--extern/sdl4ogre/sdlinputwrapper.hpp76
-rw-r--r--extern/sdl4ogre/sdlwindowhelper.cpp119
-rw-r--r--extern/sdl4ogre/sdlwindowhelper.hpp31
-rw-r--r--extern/shiny/CMakeLists.txt54
-rw-r--r--extern/shiny/Docs/Configurations.dox32
-rw-r--r--extern/shiny/Docs/Doxyfile1826
-rw-r--r--extern/shiny/Docs/GettingStarted.dox65
-rw-r--r--extern/shiny/Docs/Lod.dox49
-rw-r--r--extern/shiny/Docs/Macros.dox283
-rw-r--r--extern/shiny/Docs/Mainpage.dox13
-rw-r--r--extern/shiny/Docs/Materials.dox131
-rw-r--r--extern/shiny/Editor/Actions.cpp195
-rw-r--r--extern/shiny/Editor/Actions.hpp307
-rw-r--r--extern/shiny/Editor/AddPropertyDialog.cpp31
-rw-r--r--extern/shiny/Editor/AddPropertyDialog.h22
-rw-r--r--extern/shiny/Editor/AddPropertyDialog.hpp29
-rw-r--r--extern/shiny/Editor/CMakeLists.txt61
-rw-r--r--extern/shiny/Editor/ColoredTabWidget.hpp24
-rw-r--r--extern/shiny/Editor/Editor.cpp117
-rw-r--r--extern/shiny/Editor/Editor.hpp73
-rw-r--r--extern/shiny/Editor/MainWindow.cpp952
-rw-r--r--extern/shiny/Editor/MainWindow.hpp139
-rw-r--r--extern/shiny/Editor/NewMaterialDialog.cpp14
-rw-r--r--extern/shiny/Editor/NewMaterialDialog.hpp22
-rw-r--r--extern/shiny/Editor/PropertySortModel.cpp35
-rw-r--r--extern/shiny/Editor/PropertySortModel.hpp21
-rw-r--r--extern/shiny/Editor/Query.cpp134
-rw-r--r--extern/shiny/Editor/Query.hpp121
-rw-r--r--extern/shiny/Editor/addpropertydialog.ui118
-rw-r--r--extern/shiny/Editor/mainwindow.ui420
-rw-r--r--extern/shiny/Editor/newmaterialdialog.ui98
-rw-r--r--extern/shiny/Extra/core.h168
-rw-r--r--extern/shiny/License.txt9
-rw-r--r--extern/shiny/Main/Factory.cpp803
-rw-r--r--extern/shiny/Main/Factory.hpp271
-rw-r--r--extern/shiny/Main/Language.hpp17
-rw-r--r--extern/shiny/Main/MaterialInstance.cpp260
-rw-r--r--extern/shiny/Main/MaterialInstance.hpp109
-rw-r--r--extern/shiny/Main/MaterialInstancePass.cpp35
-rw-r--r--extern/shiny/Main/MaterialInstancePass.hpp29
-rw-r--r--extern/shiny/Main/MaterialInstanceTextureUnit.cpp14
-rw-r--r--extern/shiny/Main/MaterialInstanceTextureUnit.hpp27
-rw-r--r--extern/shiny/Main/Platform.cpp89
-rw-r--r--extern/shiny/Main/Platform.hpp146
-rw-r--r--extern/shiny/Main/Preprocessor.cpp99
-rw-r--r--extern/shiny/Main/Preprocessor.hpp69
-rw-r--r--extern/shiny/Main/PropertyBase.cpp302
-rw-r--r--extern/shiny/Main/PropertyBase.hpp244
-rw-r--r--extern/shiny/Main/ScriptLoader.cpp414
-rw-r--r--extern/shiny/Main/ScriptLoader.hpp134
-rw-r--r--extern/shiny/Main/ShaderInstance.cpp698
-rw-r--r--extern/shiny/Main/ShaderInstance.hpp71
-rw-r--r--extern/shiny/Main/ShaderSet.cpp195
-rw-r--r--extern/shiny/Main/ShaderSet.hpp69
-rw-r--r--extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp69
-rw-r--r--extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp31
-rw-r--r--extern/shiny/Platforms/Ogre/OgreMaterial.cpp115
-rw-r--r--extern/shiny/Platforms/Ogre/OgreMaterial.hpp42
-rw-r--r--extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp85
-rw-r--r--extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp29
-rw-r--r--extern/shiny/Platforms/Ogre/OgrePass.cpp131
-rw-r--r--extern/shiny/Platforms/Ogre/OgrePass.hpp35
-rw-r--r--extern/shiny/Platforms/Ogre/OgrePlatform.cpp183
-rw-r--r--extern/shiny/Platforms/Ogre/OgrePlatform.hpp74
-rw-r--r--extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp41
-rw-r--r--extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp27
-rw-r--r--extern/shiny/Readme.txt33
-rw-r--r--files/CMakeLists.txt54
-rw-r--r--files/launcher/icons/tango/document-new.pngbin0 -> 477 bytes
-rw-r--r--files/launcher/icons/tango/edit-copy.pngbin0 -> 498 bytes
-rw-r--r--files/launcher/icons/tango/edit-delete.pngbin0 -> 793 bytes
-rw-r--r--files/launcher/icons/tango/go-bottom.pngbin0 -> 663 bytes
-rw-r--r--files/launcher/icons/tango/go-down.pngbin0 -> 683 bytes
-rw-r--r--files/launcher/icons/tango/go-top.pngbin0 -> 636 bytes
-rw-r--r--files/launcher/icons/tango/go-up.pngbin0 -> 652 bytes
-rw-r--r--files/launcher/icons/tango/index.theme8
-rw-r--r--files/launcher/icons/tango/video-display.pngbin0 -> 2547 bytes
-rw-r--r--files/launcher/images/clear.pngbin0 -> 590 bytes
-rw-r--r--files/launcher/images/down.pngbin0 -> 2899 bytes
-rw-r--r--files/launcher/images/openmw-header.pngbin0 -> 64052 bytes
-rw-r--r--files/launcher/images/openmw-plugin.pngbin0 -> 6557 bytes
-rw-r--r--files/launcher/images/openmw.icobin0 -> 83788 bytes
-rw-r--r--files/launcher/images/openmw.pngbin0 -> 60039 bytes
-rw-r--r--files/launcher/images/playpage-background.pngbin0 -> 241484 bytes
-rw-r--r--files/launcher/launcher.qrc21
-rw-r--r--files/launcher/launcher.rc1
-rw-r--r--files/mac/Info.plist30
-rw-r--r--files/mac/openmw.icnsbin0 -> 134980 bytes
-rw-r--r--files/materials/atmosphere.shader39
-rw-r--r--files/materials/atmosphere.shaderset15
-rw-r--r--files/materials/clouds.shader53
-rw-r--r--files/materials/clouds.shaderset15
-rw-r--r--files/materials/core.h181
-rw-r--r--files/materials/moon.shader53
-rw-r--r--files/materials/moon.shaderset15
-rw-r--r--files/materials/mygui.mat25
-rw-r--r--files/materials/mygui.shader45
-rw-r--r--files/materials/mygui.shaderset15
-rw-r--r--files/materials/objects.mat98
-rw-r--r--files/materials/objects.shader447
-rw-r--r--files/materials/objects.shaderset15
-rw-r--r--files/materials/openmw.configuration20
-rw-r--r--files/materials/quad.mat22
-rw-r--r--files/materials/quad.shader25
-rw-r--r--files/materials/quad.shaderset15
-rw-r--r--files/materials/selection.mat9
-rw-r--r--files/materials/selection.shader24
-rw-r--r--files/materials/selection.shaderset15
-rw-r--r--files/materials/shadowcaster.mat35
-rw-r--r--files/materials/shadowcaster.shader55
-rw-r--r--files/materials/shadowcaster.shaderset15
-rw-r--r--files/materials/shadows.h51
-rw-r--r--files/materials/sky.mat140
-rw-r--r--files/materials/stars.shader47
-rw-r--r--files/materials/stars.shaderset15
-rw-r--r--files/materials/sun.shader41
-rw-r--r--files/materials/sun.shaderset15
-rw-r--r--files/materials/terrain.shader348
-rw-r--r--files/materials/terrain.shaderset15
-rw-r--r--files/materials/underwater.h121
-rw-r--r--files/materials/water.mat77
-rw-r--r--files/materials/water.shader370
-rw-r--r--files/materials/water.shaderset15
-rw-r--r--files/materials/watersim.mat59
-rw-r--r--files/materials/watersim.shaderset31
-rw-r--r--files/materials/watersim_addimpulse.shader12
-rw-r--r--files/materials/watersim_common.h25
-rw-r--r--files/materials/watersim_heightmap.shader51
-rw-r--r--files/materials/watersim_heighttonormal.shader36
-rw-r--r--files/mygui/CMakeLists.txt91
-rw-r--r--files/mygui/DejaVuLGCSansMono.ttfbin0 -> 285304 bytes
-rw-r--r--files/mygui/EBGaramond-Regular.ttfbin0 -> 405476 bytes
-rw-r--r--files/mygui/Obliviontt.zipbin0 -> 138502 bytes
-rw-r--r--files/mygui/bigbars.pngbin0 -> 387 bytes
-rw-r--r--files/mygui/black.pngbin0 -> 103 bytes
-rw-r--r--files/mygui/core.skin19
-rw-r--r--files/mygui/core.xml27
-rw-r--r--files/mygui/core_layouteditor.xml25
-rw-r--r--files/mygui/markers.pngbin0 -> 245 bytes
-rw-r--r--files/mygui/openmw_alchemy_window.layout109
-rw-r--r--files/mygui/openmw_book.layout50
-rw-r--r--files/mygui/openmw_box.skin.xml76
-rw-r--r--files/mygui/openmw_button.skin.xml87
-rw-r--r--files/mygui/openmw_chargen_birth.layout30
-rw-r--r--files/mygui/openmw_chargen_class.layout81
-rw-r--r--files/mygui/openmw_chargen_class_description.layout22
-rw-r--r--files/mygui/openmw_chargen_create_class.layout84
-rw-r--r--files/mygui/openmw_chargen_generate_class_result.layout35
-rw-r--r--files/mygui/openmw_chargen_race.layout91
-rw-r--r--files/mygui/openmw_chargen_review.layout126
-rw-r--r--files/mygui/openmw_chargen_select_attribute.layout30
-rw-r--r--files/mygui/openmw_chargen_select_skill.layout65
-rw-r--r--files/mygui/openmw_chargen_select_specialization.layout32
-rw-r--r--files/mygui/openmw_companion_window.layout23
-rw-r--r--files/mygui/openmw_confirmation_dialog.layout30
-rw-r--r--files/mygui/openmw_console.layout18
-rw-r--r--files/mygui/openmw_console.skin.xml33
-rw-r--r--files/mygui/openmw_container_window.layout28
-rw-r--r--files/mygui/openmw_count_window.layout37
-rw-r--r--files/mygui/openmw_dialogue_window.layout32
-rw-r--r--files/mygui/openmw_dialogue_window_skin.xml19
-rw-r--r--files/mygui/openmw_edit.skin.xml50
-rw-r--r--files/mygui/openmw_edit_effect.layout99
-rw-r--r--files/mygui/openmw_enchanting_dialog.layout119
-rw-r--r--files/mygui/openmw_font.xml43
-rw-r--r--files/mygui/openmw_hud.layout177
-rw-r--r--files/mygui/openmw_hud_box.skin.xml35
-rw-r--r--files/mygui/openmw_hud_energybar.skin.xml77
-rw-r--r--files/mygui/openmw_infobox.layout16
-rw-r--r--files/mygui/openmw_interactive_messagebox.layout18
-rw-r--r--files/mygui/openmw_inventory_window.layout54
-rw-r--r--files/mygui/openmw_itemselection_dialog.layout17
-rw-r--r--files/mygui/openmw_journal.layout109
-rw-r--r--files/mygui/openmw_journal_skin.xml15
-rw-r--r--files/mygui/openmw_layers.xml13
-rw-r--r--files/mygui/openmw_levelup_dialog.layout162
-rw-r--r--files/mygui/openmw_list.skin.xml206
-rw-r--r--files/mygui/openmw_loading_screen.layout23
-rw-r--r--files/mygui/openmw_magicselection_dialog.layout20
-rw-r--r--files/mygui/openmw_mainmenu.layout6
-rw-r--r--files/mygui/openmw_mainmenu_skin.xml4
-rw-r--r--files/mygui/openmw_map_window.layout40
-rw-r--r--files/mygui/openmw_map_window_skin.xml11
-rw-r--r--files/mygui/openmw_merchantrepair.layout33
-rw-r--r--files/mygui/openmw_messagebox.layout18
-rw-r--r--files/mygui/openmw_persuasion_dialog.layout49
-rw-r--r--files/mygui/openmw_pointer.xml39
-rw-r--r--files/mygui/openmw_progress.skin.xml80
-rw-r--r--files/mygui/openmw_quickkeys_menu.layout39
-rw-r--r--files/mygui/openmw_quickkeys_menu_assign.layout28
-rw-r--r--files/mygui/openmw_repair.layout33
-rw-r--r--files/mygui/openmw_resources.xml304
-rw-r--r--files/mygui/openmw_scroll.layout29
-rw-r--r--files/mygui/openmw_scroll_skin.xml15
-rw-r--r--files/mygui/openmw_settings.xml11
-rw-r--r--files/mygui/openmw_settings_window.layout359
-rw-r--r--files/mygui/openmw_spell_buying_window.layout35
-rw-r--r--files/mygui/openmw_spell_window.layout20
-rw-r--r--files/mygui/openmw_spellcreation_dialog.layout78
-rw-r--r--files/mygui/openmw_stats_window.layout228
-rw-r--r--files/mygui/openmw_text.skin.xml183
-rw-r--r--files/mygui/openmw_text_input.layout18
-rw-r--r--files/mygui/openmw_tooltips.layout245
-rw-r--r--files/mygui/openmw_trade_window.layout70
-rw-r--r--files/mygui/openmw_trainingwindow.layout30
-rw-r--r--files/mygui/openmw_travel_window.layout35
-rw-r--r--files/mygui/openmw_wait_dialog.layout42
-rw-r--r--files/mygui/openmw_wait_dialog_progressbar.layout13
-rw-r--r--files/mygui/openmw_windows.skin.xml871
-rw-r--r--files/mygui/smallbars.pngbin0 -> 2985 bytes
-rw-r--r--files/opencs.cfg5
-rw-r--r--files/opencs.desktop9
-rwxr-xr-xfiles/opencs/activator.pngbin0 -> 2297 bytes
-rw-r--r--files/opencs/added.pngbin0 -> 862 bytes
-rwxr-xr-xfiles/opencs/apparatus.pngbin0 -> 1440 bytes
-rwxr-xr-xfiles/opencs/armor.pngbin0 -> 1641 bytes
-rw-r--r--files/opencs/base.pngbin0 -> 460 bytes
-rwxr-xr-xfiles/opencs/book.pngbin0 -> 1336 bytes
-rwxr-xr-xfiles/opencs/clothing.pngbin0 -> 1377 bytes
-rwxr-xr-xfiles/opencs/container.pngbin0 -> 1526 bytes
-rwxr-xr-xfiles/opencs/creature.pngbin0 -> 2297 bytes
-rwxr-xr-xfiles/opencs/door.pngbin0 -> 1627 bytes
-rwxr-xr-xfiles/opencs/ingredient.pngbin0 -> 1444 bytes
-rwxr-xr-xfiles/opencs/leveled-creature.pngbin0 -> 2150 bytes
-rwxr-xr-xfiles/opencs/leveled-item.pngbin0 -> 1698 bytes
-rwxr-xr-xfiles/opencs/light.pngbin0 -> 747 bytes
-rwxr-xr-xfiles/opencs/lockpick.pngbin0 -> 671 bytes
-rwxr-xr-xfiles/opencs/miscellaneous.pngbin0 -> 1716 bytes
-rw-r--r--files/opencs/modified.pngbin0 -> 2149 bytes
-rwxr-xr-xfiles/opencs/npc.pngbin0 -> 2143 bytes
-rw-r--r--files/opencs/opencs.pngbin0 -> 65168 bytes
-rwxr-xr-xfiles/opencs/potion.pngbin0 -> 1582 bytes
-rwxr-xr-xfiles/opencs/probe.pngbin0 -> 587 bytes
-rw-r--r--files/opencs/raster/Info.pngbin0 -> 1234 bytes
-rw-r--r--files/opencs/raster/LandTexture.pngbin0 -> 2662 bytes
-rw-r--r--files/opencs/raster/PathGrid.pngbin0 -> 1297 bytes
-rw-r--r--files/opencs/raster/activator.pngbin0 -> 2297 bytes
-rw-r--r--files/opencs/raster/added.pngbin0 -> 615 bytes
-rw-r--r--files/opencs/raster/apparatus.pngbin0 -> 1440 bytes
-rw-r--r--files/opencs/raster/armor.pngbin0 -> 1641 bytes
-rw-r--r--files/opencs/raster/base.pngbin0 -> 460 bytes
-rw-r--r--files/opencs/raster/birthsign.pngbin0 -> 2454 bytes
-rw-r--r--files/opencs/raster/body-part.pngbin0 -> 1788 bytes
-rw-r--r--files/opencs/raster/book.pngbin0 -> 1336 bytes
-rw-r--r--files/opencs/raster/cell.pngbin0 -> 1403 bytes
-rw-r--r--files/opencs/raster/class.pngbin0 -> 2283 bytes
-rw-r--r--files/opencs/raster/clothing.pngbin0 -> 1377 bytes
-rw-r--r--files/opencs/raster/container.pngbin0 -> 1526 bytes
-rw-r--r--files/opencs/raster/creature.pngbin0 -> 2297 bytes
-rw-r--r--files/opencs/raster/dialogoue-info.pngbin0 -> 1851 bytes
-rw-r--r--files/opencs/raster/dialogoue-journal.pngbin0 -> 1991 bytes
-rw-r--r--files/opencs/raster/dialogoue-regular.pngbin0 -> 1486 bytes
-rw-r--r--files/opencs/raster/dialogue-greeting.pngbin0 -> 1948 bytes
-rw-r--r--files/opencs/raster/dialogue-persuasion.pngbin0 -> 1987 bytes
-rw-r--r--files/opencs/raster/dialogue-speech.pngbin0 -> 1987 bytes
-rw-r--r--files/opencs/raster/door.pngbin0 -> 1627 bytes
-rw-r--r--files/opencs/raster/enchantment.pngbin0 -> 1812 bytes
-rw-r--r--files/opencs/raster/faction.pngbin0 -> 1858 bytes
-rw-r--r--files/opencs/raster/globvar.pngbin0 -> 2394 bytes
-rw-r--r--files/opencs/raster/ingredient.pngbin0 -> 1444 bytes
-rw-r--r--files/opencs/raster/land.pngbin0 -> 1220 bytes
-rw-r--r--files/opencs/raster/landpaint.pngbin0 -> 1361 bytes
-rw-r--r--files/opencs/raster/leveled-creature.pngbin0 -> 2150 bytes
-rw-r--r--files/opencs/raster/light.pngbin0 -> 747 bytes
-rw-r--r--files/opencs/raster/lockpick.pngbin0 -> 671 bytes
-rw-r--r--files/opencs/raster/magic-effect.pngbin0 -> 1702 bytes
-rw-r--r--files/opencs/raster/magicrabbit.pngbin0 -> 1820 bytes
-rw-r--r--files/opencs/raster/map.pngbin0 -> 1477 bytes
-rw-r--r--files/opencs/raster/miscellaneous.pngbin0 -> 1716 bytes
-rw-r--r--files/opencs/raster/modified.pngbin0 -> 1320 bytes
-rw-r--r--files/opencs/raster/npc.pngbin0 -> 2143 bytes
-rw-r--r--files/opencs/raster/potion.pngbin0 -> 1582 bytes
-rw-r--r--files/opencs/raster/probe.pngbin0 -> 587 bytes
-rw-r--r--files/opencs/raster/race.pngbin0 -> 1834 bytes
-rw-r--r--files/opencs/raster/random-item.pngbin0 -> 1698 bytes
-rw-r--r--files/opencs/raster/removed.pngbin0 -> 1251 bytes
-rw-r--r--files/opencs/raster/repair.pngbin0 -> 1115 bytes
-rw-r--r--files/opencs/raster/script.pngbin0 -> 952 bytes
-rw-r--r--files/opencs/raster/skill.pngbin0 -> 1676 bytes
-rw-r--r--files/opencs/raster/sound.pngbin0 -> 1144 bytes
-rw-r--r--files/opencs/raster/soundgen.pngbin0 -> 2041 bytes
-rw-r--r--files/opencs/raster/spell.pngbin0 -> 2071 bytes
-rw-r--r--files/opencs/raster/static.pngbin0 -> 1518 bytes
-rw-r--r--files/opencs/raster/weapon.pngbin0 -> 1003 bytes
-rw-r--r--files/opencs/removed.pngbin0 -> 1772 bytes
-rwxr-xr-xfiles/opencs/repair.pngbin0 -> 1115 bytes
-rw-r--r--files/opencs/resources.qrc29
-rw-r--r--files/opencs/scalable/Palette.svg569
-rw-r--r--files/opencs/scalable/referenceable-record/.directory7
-rw-r--r--files/opencs/scalable/referenceable-record/activator.svg1022
-rw-r--r--files/opencs/scalable/referenceable-record/apparatus.svg1058
-rw-r--r--files/opencs/scalable/referenceable-record/book.svg687
-rw-r--r--files/opencs/scalable/referenceable-record/container.svg1899
-rw-r--r--files/opencs/scalable/referenceable-record/ingredient.svg1107
-rw-r--r--files/opencs/scalable/referenceable-record/light.svg1508
-rw-r--r--files/opencs/scalable/referenceable-record/miscellaneous.svg965
-rw-r--r--files/opencs/scalable/referenceable-record/potion.svg1161
-rw-r--r--files/opencs/scalable/referenceable-record/random-item.svg156
-rw-r--r--files/opencs/scalable/referenceable-record/repair.svg1280
-rw-r--r--files/opencs/scalable/referenceable-record/static.svg1141
-rw-r--r--files/opencs/scalable/referenceable-record/weapon.svg1206
-rw-r--r--files/opencs/scalable/status/.directory5
-rw-r--r--files/opencs/scalable/status/added.svg932
-rw-r--r--files/opencs/scalable/status/base.svg942
-rw-r--r--files/opencs/scalable/status/modified.svg1155
-rw-r--r--files/opencs/scalable/status/removed.svg935
-rw-r--r--files/opencs/scalable/top-level/gmst.svg1047
-rw-r--r--files/opencs/scalable/top-level/topic-regular.svg1045
-rwxr-xr-xfiles/opencs/static.pngbin0 -> 1518 bytes
-rwxr-xr-xfiles/opencs/weapon.pngbin0 -> 1003 bytes
-rw-r--r--files/openmw.bmpbin0 -> 79926 bytes
-rw-r--r--files/openmw.cfg4
-rw-r--r--files/openmw.cfg.local2
-rw-r--r--files/openmw.desktop9
-rw-r--r--files/settings-default.cfg171
-rw-r--r--files/transparency-overrides.cfg623
-rw-r--r--files/ui/datafilespage.ui167
-rw-r--r--files/ui/graphicspage.ui152
-rw-r--r--files/ui/mainwindow.ui72
-rw-r--r--files/ui/playpage.ui192
-rw-r--r--files/water/circle.pngbin0 -> 753 bytes
-rw-r--r--files/water/water_nm.pngbin0 -> 24405 bytes
-rw-r--r--libs/openengine/README12
-rw-r--r--libs/openengine/bullet/BtOgre.cpp1094
-rw-r--r--libs/openengine/bullet/BtOgreExtras.h282
-rw-r--r--libs/openengine/bullet/BtOgreGP.h145
-rw-r--r--libs/openengine/bullet/BtOgrePG.h81
-rw-r--r--libs/openengine/bullet/BulletShapeLoader.cpp153
-rw-r--r--libs/openengine/bullet/BulletShapeLoader.h176
-rw-r--r--libs/openengine/bullet/CMotionState.cpp46
-rw-r--r--libs/openengine/bullet/CMotionState.h52
-rw-r--r--libs/openengine/bullet/btKinematicCharacterController.cpp643
-rw-r--r--libs/openengine/bullet/btKinematicCharacterController.h168
-rw-r--r--libs/openengine/bullet/physic.cpp799
-rw-r--r--libs/openengine/bullet/physic.hpp389
-rw-r--r--libs/openengine/bullet/trace.cpp130
-rw-r--r--libs/openengine/bullet/trace.h31
-rw-r--r--libs/openengine/gui/layout.hpp162
-rw-r--r--libs/openengine/gui/manager.cpp652
-rw-r--r--libs/openengine/gui/manager.hpp55
-rw-r--r--libs/openengine/misc/list.hpp178
-rw-r--r--libs/openengine/ogre/fader.cpp135
-rw-r--r--libs/openengine/ogre/fader.hpp59
-rw-r--r--libs/openengine/ogre/imagerotate.cpp88
-rw-r--r--libs/openengine/ogre/imagerotate.hpp27
-rw-r--r--libs/openengine/ogre/lights.cpp118
-rw-r--r--libs/openengine/ogre/lights.hpp51
-rw-r--r--libs/openengine/ogre/particles.cpp693
-rw-r--r--libs/openengine/ogre/particles.hpp41
-rw-r--r--libs/openengine/ogre/renderer.cpp319
-rw-r--r--libs/openengine/ogre/renderer.hpp213
-rw-r--r--libs/openengine/ogre/selectionbuffer.cpp134
-rw-r--r--libs/openengine/ogre/selectionbuffer.hpp54
-rwxr-xr-xlibs/openengine/testall.sh11
-rw-r--r--libs/platform/stdint.h21
-rw-r--r--libs/platform/string.h34
-rw-r--r--libs/platform/strings.h18
-rw-r--r--readme.txt756
1398 files changed, 212217 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..caf2e33899
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,39 @@
+language: cpp
+compiler:
+ - gcc
+branches:
+ only:
+ - master
+ - next
+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
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-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 mkdir /usr/src/gtest/build
+ - cd /usr/src/gtest/build
+ - sudo cmake .. -DBUILD_SHARED_LIBS=1
+ - sudo make -j4
+ - sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
+ - sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
+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
+script:
+ - make -j4
+after_script:
+ - ./openmw_test_suite
+notifications:
+ recipients:
+ - lgromanowski+travis.ci@gmail.com
+ email:
+ on_success: change
+ on_failure: always
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000..3f95f2d4e6
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,722 @@
+project(OpenMW)
+
+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
+
+set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
+
+include (OpenMWMacros)
+
+# Version
+
+set (OPENMW_VERSION_MAJOR 0)
+set (OPENMW_VERSION_MINOR 26)
+set (OPENMW_VERSION_RELEASE 0)
+
+set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
+
+# doxygen main page
+
+configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp")
+
+option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE)
+option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE)
+option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
+option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
+
+# Apps and tools
+option(BUILD_BSATOOL "build BSA extractor" OFF)
+option(BUILD_ESMTOOL "build ESM inspector" ON)
+option(BUILD_LAUNCHER "build Launcher" ON)
+option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
+option(BUILD_OPENCS "build OpenMW Construction Set" ON)
+option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
+option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF)
+
+# Sound source selection
+option(USE_FFMPEG "use ffmpeg for sound" ON)
+option(USE_AUDIERE "use audiere for sound" ON)
+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")
+
+# Location of morrowind data files
+if (APPLE)
+ set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files")
+ set(MORROWIND_RESOURCE_FILES "./resources" CACHE PATH "location of OpenMW resources files")
+elseif(UNIX)
+ set(MORROWIND_DATA_FILES "/usr/share/games/openmw/data/" CACHE PATH "location of Morrowind data files")
+ set(MORROWIND_RESOURCE_FILES "/usr/share/games/openmw/resources/" CACHE PATH "location of OpenMW resources files")
+else()
+ set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files")
+ set(MORROWIND_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files")
+endif(APPLE)
+
+if (WIN32)
+ option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON)
+endif()
+
+# We probably support older versions than this.
+cmake_minimum_required(VERSION 2.6)
+
+# source directory: libs
+
+set(LIBDIR ${CMAKE_SOURCE_DIR}/libs)
+
+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
+)
+
+set(OENGINE_GUI
+ ${LIBDIR}/openengine/gui/manager.cpp
+)
+
+set(OENGINE_BULLET
+ ${LIBDIR}/openengine/bullet/btKinematicCharacterController.cpp
+ ${LIBDIR}/openengine/bullet/btKinematicCharacterController.h
+ ${LIBDIR}/openengine/bullet/BtOgre.cpp
+ ${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
+ ${LIBDIR}/openengine/bullet/BulletShapeLoader.h
+ ${LIBDIR}/openengine/bullet/trace.cpp
+ ${LIBDIR}/openengine/bullet/trace.h
+
+)
+
+set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET})
+source_group(libs\\openengine FILES ${OENGINE_ALL})
+
+set(OPENMW_LIBS ${OENGINE_ALL})
+set(OPENMW_LIBS_HEADER)
+
+# Sound setup
+set(GOT_SOUND_INPUT 0)
+set(SOUND_INPUT_INCLUDES "")
+set(SOUND_INPUT_LIBRARY "")
+set(SOUND_DEFINE "")
+if (USE_FFMPEG)
+ set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE)
+ find_package(FFmpeg)
+ if (FFMPEG_FOUND)
+ set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS})
+ set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES})
+ set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG)
+ set(GOT_SOUND_INPUT 1)
+ endif (FFMPEG_FOUND)
+endif (USE_FFMPEG)
+
+if (USE_AUDIERE AND NOT GOT_SOUND_INPUT)
+ find_package(Audiere)
+ if (AUDIERE_FOUND)
+ set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR})
+ set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY})
+ set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE)
+ set(GOT_SOUND_INPUT 1)
+ endif (AUDIERE_FOUND)
+endif (USE_AUDIERE AND NOT GOT_SOUND_INPUT)
+
+if (USE_MPG123 AND NOT GOT_SOUND_INPUT)
+ find_package(MPG123 REQUIRED)
+ find_package(SNDFILE REQUIRED)
+ if (MPG123_FOUND AND SNDFILE_FOUND)
+ set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR})
+ set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY})
+ set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123)
+ set(GOT_SOUND_INPUT 1)
+ endif (MPG123_FOUND AND SNDFILE_FOUND)
+endif (USE_MPG123 AND NOT GOT_SOUND_INPUT)
+
+if (NOT GOT_SOUND_INPUT)
+ message(WARNING "--------------------")
+ message(WARNING "Failed to find any sound input packages")
+ message(WARNING "--------------------")
+endif (NOT GOT_SOUND_INPUT)
+
+if (NOT FFMPEG_FOUND)
+ message(WARNING "--------------------")
+ message(WARNING "FFmpeg not found, video playback will be disabled")
+ message(WARNING "--------------------")
+endif (NOT FFMPEG_FOUND)
+
+
+# Platform specific
+if (WIN32)
+ set(Boost_USE_STATIC_LIBS ON)
+ set(PLATFORM_INCLUDE_DIR "platform")
+ add_definitions(-DBOOST_ALL_NO_LIB)
+else (WIN32)
+ set(PLATFORM_INCLUDE_DIR "")
+ find_path (UUID_INCLUDE_DIR uuid/uuid.h)
+ include_directories(${UUID_INCLUDE_DIR})
+endif (WIN32)
+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
+if (UNIX AND NOT APPLE)
+ find_package (Threads)
+endif()
+
+include (CheckIncludeFileCXX)
+check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP)
+if (HAVE_UNORDERED_MAP)
+ add_definitions(-DHAVE_UNORDERED_MAP)
+endif ()
+
+
+set(BOOST_COMPONENTS system filesystem program_options thread date_time wave)
+
+IF(BOOST_STATIC)
+ set(Boost_USE_STATIC_LIBS ON)
+endif()
+
+find_package(OGRE REQUIRED)
+find_package(MyGUI REQUIRED)
+find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
+find_package(SDL2 REQUIRED)
+find_package(OpenAL REQUIRED)
+find_package(Bullet REQUIRED)
+IF(OGRE_STATIC)
+find_package(Cg)
+IF(WIN32)
+set(OGRE_PLUGIN_INCLUDE_DIRS ${OGRE_Plugin_CgProgramManager_INCLUDE_DIRS} ${OGRE_Plugin_OctreeSceneManager_INCLUDE_DIRS} ${OGRE_Plugin_ParticleFX_INCLUDE_DIRS} ${OGRE_RenderSystem_Direct3D9_INCLUDE_DIRS} ${OGRE_RenderSystem_GL_INCLUDE_DIRS})
+ELSE(WIN32)
+set(OGRE_PLUGIN_INCLUDE_DIRS ${OGRE_Plugin_CgProgramManager_INCLUDE_DIRS} ${OGRE_Plugin_OctreeSceneManager_INCLUDE_DIRS} ${OGRE_Plugin_ParticleFX_INCLUDE_DIRS} ${OGRE_RenderSystem_GL_INCLUDE_DIRS})
+ENDIF(WIN32)
+ENDIF(OGRE_STATIC)
+include_directories("."
+ ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_PLUGIN_INCLUDE_DIRS}
+ ${SDL2_INCLUDE_DIR}
+ ${Boost_INCLUDE_DIR}
+ ${PLATFORM_INCLUDE_DIR}
+ ${MYGUI_INCLUDE_DIRS}
+ ${MYGUI_PLATFORM_INCLUDE_DIRS}
+ ${OPENAL_INCLUDE_DIR}
+ ${UUID_INCLUDE_DIR}
+ ${LIBDIR}
+)
+
+link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR})
+
+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})
+
+ if (${OGRE_PLUGIN_DIR_REL}})
+ set(OGRE_PLUGINS_REL_FOUND TRUE)
+ endif ()
+
+ if (${OGRE_PLUGIN_DIR_DBG})
+ set(OGRE_PLUGINS_DBG_FOUND TRUE)
+ endif ()
+
+ if (${OGRE_PLUGINS_REL_FOUND})
+ set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL})
+ else ()
+ set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG})
+ endif ()
+
+ #set(OGRE_PLUGIN_DIR "${OGRE_PLUGIN_DIR}/")
+
+ configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist
+ "${APP_BUNDLE_DIR}/Contents/Info.plist")
+
+ configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns
+ "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
+endif (APPLE)
+
+# Set up DEBUG define
+set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1)
+
+# Set up Ogre plugin folder & debug suffix
+if (APPLE)
+ # Ogre on OS X doesn't use "_d" suffix (see Ogre's CMakeLists.txt)
+ add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="")
+else ()
+ add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d")
+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")
+else()
+ add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}")
+endif()
+
+
+add_subdirectory(files/)
+add_subdirectory(files/mygui)
+
+# Specify build paths
+
+if (APPLE)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS")
+else (APPLE)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}")
+endif (APPLE)
+
+# Other files
+
+configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
+ "${OpenMW_BINARY_DIR}/settings-default.cfg")
+
+configure_file(${OpenMW_SOURCE_DIR}/files/transparency-overrides.cfg
+ "${OpenMW_BINARY_DIR}/transparency-overrides.cfg")
+
+configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local
+ "${OpenMW_BINARY_DIR}/openmw.cfg")
+
+configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
+ "${OpenMW_BINARY_DIR}/openmw.cfg.install")
+
+configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg
+ "${OpenMW_BINARY_DIR}/opencs.cfg")
+
+if (NOT WIN32 AND NOT APPLE)
+ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop
+ "${OpenMW_BINARY_DIR}/openmw.desktop")
+ configure_file(${OpenMW_SOURCE_DIR}/files/opencs.desktop
+ "${OpenMW_BINARY_DIR}/opencs.desktop")
+endif()
+
+# Compiler settings
+if (CMAKE_COMPILER_IS_GNUCC)
+ SET(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wno-reorder -std=c++98 -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}")
+
+ # Silence warnings in OGRE headers. Remove once OGRE got fixed!
+ SET(CMAKE_CXX_FLAGS "-Wno-ignored-qualifiers ${CMAKE_CXX_FLAGS}")
+
+ execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion
+ OUTPUT_VARIABLE GCC_VERSION)
+ if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6)
+ SET(CMAKE_CXX_FLAGS "-Wno-unused-but-set-parameter ${CMAKE_CXX_FLAGS}")
+ endif("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6)
+endif (CMAKE_COMPILER_IS_GNUCC)
+
+IF(NOT WIN32 AND NOT APPLE)
+ ## Debian and non debian Linux building
+ # Paths
+ IF (DPKG_PROGRAM)
+ ## Debian specific
+ SET(CMAKE_INSTALL_PREFIX "/usr")
+ SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location")
+ SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location")
+ SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir")
+ SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir")
+ ELSE ()
+ ## Non debian specific
+ SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries")
+ SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location")
+ SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location")
+ SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir")
+ SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.")
+ SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir")
+
+ # Install binaries
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" )
+ IF(BUILD_LAUNCHER)
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" )
+ ENDIF(BUILD_LAUNCHER)
+ IF(BUILD_BSATOOL)
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" )
+ ENDIF(BUILD_BSATOOL)
+ IF(BUILD_ESMTOOL)
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" )
+ ENDIF(BUILD_ESMTOOL)
+ IF(BUILD_MWINIIMPORTER)
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" )
+ ENDIF(BUILD_MWINIIMPORTER)
+ IF(BUILD_OPENCS)
+ INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" )
+ ENDIF(BUILD_OPENCS)
+
+ # Install licenses
+ INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" )
+ INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" )
+ INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" )
+ INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" )
+ ENDIF (DPKG_PROGRAM)
+
+ # Install icon and desktop file
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw")
+ INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw")
+ IF(BUILD_OPENCS)
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs")
+ INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs")
+ ENDIF(BUILD_OPENCS)
+
+ # Install global configuration files
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw")
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw")
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw")
+ IF(BUILD_OPENCS)
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs")
+ ENDIF(BUILD_OPENCS)
+
+ # Install resources
+ INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources")
+ INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
+
+ IF (DPKG_PROGRAM)
+ ## Debian Specific
+ IF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git")
+ EXEC_PROGRAM("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe" OUTPUT_VARIABLE GIT_VERSION )
+ STRING(REGEX REPLACE "openmw-" "" VERSION_STRING "${GIT_VERSION}")
+ EXEC_PROGRAM("git" ARGS "config --get user.name" OUTPUT_VARIABLE GIT_NAME )
+ EXEC_PROGRAM("git" ARGS "config --get user.email" OUTPUT_VARIABLE GIT_EMAIL)
+ SET(PACKAGE_MAINTAINER "${GIT_NAME} <${GIT_EMAIL}>")
+ ELSE()
+ SET(VERSION_STRING "${OPENMW_VERSION}")
+ SET(PACKAGE_MAINTAINER "unknown")
+ ENDIF()
+
+ SET(CPACK_GENERATOR "DEB")
+ SET(CPACK_PACKAGE_NAME "openmw")
+ SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.org")
+ SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
+ SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PACKAGE_MAINTAINER}")
+ SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A reimplementation of The Elder Scrolls III: Morrowind
+ OpenMW is a reimplementation of the Bethesda Game Studios game The Elder Scrolls III: Morrowind.
+ Data files from the original game is required to run it.")
+ SET(CPACK_DEBIAN_PACKAGE_NAME "openmw")
+ SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}")
+ SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter")
+ SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)")
+
+ SET(CPACK_DEBIAN_PACKAGE_SECTION "Games")
+
+ STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
+ EXECUTE_PROCESS(
+ COMMAND ${DPKG_PROGRAM} --print-architecture
+ OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
+
+
+ INCLUDE(CPack)
+ ENDIF(DPKG_PROGRAM)
+ENDIF(NOT WIN32 AND NOT APPLE)
+
+if(WIN32)
+ FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll")
+ INSTALL(FILES ${dll_files} DESTINATION ".")
+ INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
+ INSTALL(FILES
+ "${OpenMW_SOURCE_DIR}/readme.txt"
+ "${OpenMW_SOURCE_DIR}/GPL3.txt"
+ "${OpenMW_SOURCE_DIR}/OFL.txt"
+ "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt"
+ "${OpenMW_SOURCE_DIR}/Daedric Font License.txt"
+ "${OpenMW_BINARY_DIR}/settings-default.cfg"
+ "${OpenMW_BINARY_DIR}/transparency-overrides.cfg"
+ "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe"
+ "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe"
+ "${OpenMW_BINARY_DIR}/Release/openmw.exe"
+ DESTINATION ".")
+ INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".")
+
+ SET(CPACK_GENERATOR "NSIS")
+ SET(CPACK_PACKAGE_NAME "OpenMW")
+ SET(CPACK_PACKAGE_VENDOR "OpenMW.org")
+ SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
+ SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
+ SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
+ SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
+ SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher")
+ SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'")
+ SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
+ !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
+ Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\"
+ ")
+ SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/readme.txt")
+ SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt")
+ SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
+ SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
+ SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org")
+ SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org")
+ SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe")
+ SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.ico")
+ SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.ico")
+ SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
+
+ SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
+ if(EXISTS ${VCREDIST32})
+ INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
+ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
+ endif(EXISTS ${VCREDIST32})
+
+ SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
+ if(EXISTS ${VCREDIST64})
+ INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
+ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
+ endif(EXISTS ${VCREDIST64})
+
+ SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
+ if(EXISTS ${OALREDIST})
+ INSTALL(FILES ${OALREDIST} DESTINATION "redist")
+ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
+ ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" )
+ endif(EXISTS ${OALREDIST})
+
+ if(CMAKE_CL_64)
+ SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
+ endif()
+
+ include(CPack)
+endif(WIN32)
+
+# Extern
+add_subdirectory (extern/shiny)
+add_subdirectory (extern/oics)
+add_subdirectory (extern/sdl4ogre)
+
+# Components
+add_subdirectory (components)
+
+# Apps and tools
+add_subdirectory( apps/openmw )
+
+if (BUILD_BSATOOL)
+ add_subdirectory( apps/bsatool )
+endif()
+
+if (BUILD_ESMTOOL)
+ add_subdirectory( apps/esmtool )
+endif()
+
+if (BUILD_LAUNCHER)
+ if(NOT WIN32)
+ find_package(LIBUNSHIELD REQUIRED)
+ if(NOT LIBUNSHIELD_FOUND)
+ message(SEND_ERROR "Failed to find libunshield")
+ endif(NOT LIBUNSHIELD_FOUND)
+ endif(NOT WIN32)
+
+ add_subdirectory( apps/launcher )
+endif()
+
+if (BUILD_MWINIIMPORTER)
+ add_subdirectory( apps/mwiniimporter )
+endif()
+
+if (BUILD_OPENCS)
+ add_subdirectory (apps/opencs)
+endif()
+
+# UnitTests
+if (BUILD_UNITTESTS)
+ add_subdirectory( apps/openmw_test_suite )
+endif()
+
+if (WIN32)
+ if (MSVC)
+ if (USE_DEBUG_CONSOLE)
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
+ set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
+ else()
+ # Turn off debug console, debug output will be written to visual studio output instead
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
+ endif()
+
+ # Release builds use the debug console
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE")
+ set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE")
+
+ # Play a bit with the warning levels
+
+ set(WARNINGS "/Wall") # Since windows can only disable specific warnings, not enable them
+
+ set(WARNINGS_DISABLE
+ # Warnings that aren't enabled normally and don't need to be enabled
+ # They're unneeded and sometimes completely retarded warnings that /Wall enables
+ # Not going to bother commenting them as they tend to warn on every standard library files
+ 4061 4263 4264 4266 4350 4371 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946
+
+ # Warnings that are thrown on standard libraries and not OpenMW
+ 4347 # Non-template function with same name and parameter count as template function
+ 4365 # Variable signed/unsigned mismatch
+ 4510 4512 # Unable to generate copy constructor/assignment operator as it's not public in the base
+ 4706 # Assignment in conditional expression
+ 4738 # Storing 32-bit float result in memory, possible loss of performance
+ 4986 # Undocumented warning that occurs in the crtdbg.h file
+ 4996 # Function was declared deprecated
+
+ # cause by ogre extensivly
+ 4193 # #pragma warning(pop) : no matching '#pragma warning(push)'
+ 4251 # class 'XXXX' needs to have dll-interface to be used by clients of class 'YYYY'
+ 4275 # non dll-interface struct 'XXXX' used as base for dll-interface class 'YYYY'
+
+ # OpenMW specific warnings
+ 4099 # Type mismatch, declared class or struct is defined with other type
+ 4100 # Unreferenced formal parameter (-Wunused-parameter)
+ 4127 # Conditional expression is constant
+ 4242 # Storing value in a variable of a smaller type, possible loss of data
+ 4244 # Storing value of one type in variable of another (size_t in int, for example)
+ 4305 # Truncating value (double to float, for example)
+ 4309 # Variable overflow, trying to store 128 in a signed char for example
+ 4355 # Using 'this' in member initialization list
+ 4701 # Potentially uninitialized local variable used
+ 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt
+ )
+
+ foreach(d ${WARNINGS_DISABLE})
+ set(WARNINGS "${WARNINGS} /wd${d}")
+ endforeach(d)
+
+ set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ if (BUILD_LAUNCHER)
+ set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ endif (BUILD_LAUNCHER)
+ set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ if (BUILD_BSATOOL)
+ set_target_properties(bsatool PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ endif (BUILD_BSATOOL)
+ if (BUILD_ESMTOOL)
+ set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS})
+ endif (BUILD_ESMTOOL)
+ endif(MSVC)
+
+ # Same for MinGW
+ if (MINGW)
+ if (USE_DEBUG_CONSOLE)
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "-Wl,-subsystem,console")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "-Wl,-subsystem,console")
+ set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
+ else(USE_DEBUG_CONSOLE)
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "-Wl,-subsystem,windows")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "-Wl,-subsystem,windows")
+ endif(USE_DEBUG_CONSOLE)
+
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "-Wl,-subsystem,console")
+ set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "-Wl,-subsystem,console")
+ set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE")
+ endif(MINGW)
+
+ # TODO: At some point release builds should not use the console but rather write to a log file
+ #set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
+ #set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
+endif()
+
+# Apple bundling
+if (APPLE)
+ set(INSTALL_SUBDIR OpenMW)
+
+ install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+ install(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime)
+
+ set(CPACK_GENERATOR "DragNDrop")
+ set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
+ set(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
+ set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO})
+ set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
+
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
+ set(PLUGINS "")
+ set(ABSOLUTE_PLUGINS "")
+
+ foreach (PLUGIN ${USED_OGRE_PLUGINS})
+ get_filename_component(PLUGIN_ABS ${PLUGIN} REALPATH)
+ 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 ()
+
+ #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 "")
+
+ # Overriding item resolving during installation, it needed if
+ # some library already has been "fixed up", i.e. its id name contains @executable_path,
+ # but library is not embedded in bundle. For example, it's Ogre.framework from Ogre SDK.
+ # Current implementation of GetPrerequsities/BundleUtilities doesn't handle that case.
+ #
+ # Current limitations:
+ # 1. Handles only frameworks, not simple libs
+ INSTALL(CODE "
+ set(CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES})
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
+ set(CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_SYSTEM_FRAMEWORK_PATH})
+
+ set(OPENMW_RESOLVED_ITEMS \"\")
+
+ function(gp_resolve_item_override context item exepath dirs resolved_item_var resolved_var)
+ if(item MATCHES \"@executable_path\" AND NOT \${\${resolved_var}})
+ if (item MATCHES \"Frameworks\") # if it is a framework
+ # get last segment of path
+ get_filename_component(fname \"\${item}\" NAME_WE)
+ find_library(ri NAMES \${fname} PATHS \${exepath} \${dirs} \${CMAKE_SYSTEM_FRAMEWORK_PATH})
+ if (ri)
+ string(REGEX REPLACE \"^.*/Frameworks/.*\\\\.framework\" \"\" item_part \${item})
+ set(ri \"\${ri}\${item_part}\")
+ set(\${resolved_item_var} \${ri} PARENT_SCOPE)
+ set(\${resolved_var} 1 PARENT_SCOPE)
+ endif()
+ else()
+ # code path for standard (non-framework) libs (ogre & qt pugins)
+ get_filename_component(fname \"\${item}\" NAME_WE)
+ string(REGEX REPLACE \"^lib\" \"\" fname \${fname})
+ find_library(ri NAMES \${fname} PATHS \${exepath} \${dirs} /usr/lib /usr/local/lib)
+ if (ri)
+ set(\${resolved_item_var} \${ri} PARENT_SCOPE)
+ set(\${resolved_var} 1 PARENT_SCOPE)
+ endif ()
+ endif()
+ endif()
+ endfunction(gp_resolve_item_override)
+
+ cmake_policy(SET CMP0009 OLD)
+ set(BU_CHMOD_BUNDLE_ITEMS ON)
+ include(BundleUtilities)
+ fixup_bundle(\"${APPS}\" \"${PLUGINS}\" \"${DIRS}\")
+ " COMPONENT Runtime)
+ include(CPack)
+endif (APPLE)
+
diff --git a/Daedric Font License.txt b/Daedric Font License.txt
new file mode 100644
index 0000000000..a1553d0b04
--- /dev/null
+++ b/Daedric Font License.txt
@@ -0,0 +1,10 @@
+Dongle's Oblivion Daedric font set
+http://www.uesp.net/wiki/Lore:Daedric_Alphabet#Daedric_Font
+
+---------------------------------------------------
+
+This was done entirely as a personal project. Bethesda Softworks graciously granted me the permission for it. I am not connected with them in any way.
+You may freely use these fonts to create anything you'd like. You may re-distribute the fonts freely, over the Internet, or by any other means. Always keep the .zip file intact, and this read me included.
+Please do not modify and redistribute the fonts without my permission.
+You may NOT sell any of these fonts under any circumstances. This includes putting them on compilation font CDs for sale, putting them in a "members only" pay-area of a website, or any other means of financial gain connected in ANY way with the redistribution of any of these fonts.
+You have my permission to create and sell any artwork made with these fonts, however you may need to contact Bethesda Softworks before doing so.
diff --git a/DejaVu Font License.txt b/DejaVu Font License.txt
new file mode 100644
index 0000000000..254e2cc42a
--- /dev/null
+++ b/DejaVu Font License.txt
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/Docs/Doxyfile b/Docs/Doxyfile
new file mode 100644
index 0000000000..43c3312ad3
--- /dev/null
+++ b/Docs/Doxyfile
@@ -0,0 +1,1543 @@
+# Doxyfile 1.5.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = OpenMW
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = Doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
+# Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = YES
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page. This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = NO
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = apps \
+ components \
+ libs \
+ Docs
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.idl \
+ *.odl \
+ *.cs \
+ *.php \
+ *.php3 \
+ *.inc \
+ *.m \
+ *.mm \
+ *.dox \
+ *.py \
+ *.f90 \
+ *.f \
+ *.vhd \
+ *.vhdl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to FRAME, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature. Other possible values
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list;
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
+# disables this behavior completely. For backwards compatibility with previous
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
+# respectively.
+
+GENERATE_TREEVIEW = NONE
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Options related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/Docs/DoxyfilePages b/Docs/DoxyfilePages
new file mode 100644
index 0000000000..5ce82a7c29
--- /dev/null
+++ b/Docs/DoxyfilePages
@@ -0,0 +1,1543 @@
+# Doxyfile 1.5.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = OpenMW
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ../doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
+# Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = YES
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page. This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = NO
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = apps \
+ components \
+ libs \
+ Docs
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.idl \
+ *.odl \
+ *.cs \
+ *.php \
+ *.php3 \
+ *.inc \
+ *.m \
+ *.mm \
+ *.dox \
+ *.py \
+ *.f90 \
+ *.f \
+ *.vhd \
+ *.vhdl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to FRAME, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature. Other possible values
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list;
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
+# disables this behavior completely. For backwards compatibility with previous
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
+# respectively.
+
+GENERATE_TREEVIEW = NONE
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = NO
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = NO
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = NO
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = NO
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = NO
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = NO
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = NO
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Options related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/Docs/mainpage.hpp.cmake b/Docs/mainpage.hpp.cmake
new file mode 100644
index 0000000000..f8cdf8a82a
--- /dev/null
+++ b/Docs/mainpage.hpp.cmake
@@ -0,0 +1,5 @@
+/// \mainpage
+///
+/// This is the source documentation for:
+///
+/// OpenMW @OPENMW_VERSION@
diff --git a/GPL3.txt b/GPL3.txt
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/GPL3.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/OFL.txt b/OFL.txt
new file mode 100644
index 0000000000..043e85e83b
--- /dev/null
+++ b/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/README_Mac.md b/README_Mac.md
new file mode 100644
index 0000000000..dc39183680
--- /dev/null
+++ b/README_Mac.md
@@ -0,0 +1,160 @@
+#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/CMakeLists.txt b/apps/bsatool/CMakeLists.txt
new file mode 100644
index 0000000000..3f1988a709
--- /dev/null
+++ b/apps/bsatool/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(BSATOOL
+ bsatool.cpp
+)
+source_group(apps\\bsatool FILES ${BSATOOL})
+
+# Main executable
+add_executable(bsatool
+ ${BSATOOL}
+)
+
+target_link_libraries(bsatool
+ ${Boost_LIBRARIES}
+ components
+)
+
+if (BUILD_WITH_CODE_COVERAGE)
+ add_definitions (--coverage)
+ target_link_libraries(bsatool gcov)
+endif()
diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp
new file mode 100644
index 0000000000..e6fcc2567d
--- /dev/null
+++ b/apps/bsatool/bsatool.cpp
@@ -0,0 +1,288 @@
+#include <iostream>
+#include <vector>
+#include <exception>
+
+#include <boost/program_options.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#include <components/bsa/bsa_file.hpp>
+
+#define BSATOOL_VERSION 1.1
+
+// Create local aliases for brevity
+namespace bpo = boost::program_options;
+namespace bfs = boost::filesystem;
+
+struct Arguments
+{
+ std::string mode;
+ std::string filename;
+ std::string extractfile;
+ std::string outdir;
+
+ bool longformat;
+ bool fullpath;
+};
+
+void replaceAll(std::string& str, const std::string& needle, const std::string& substitute)
+{
+ int pos = str.find(needle);
+ while(pos != -1)
+ {
+ str.replace(pos, needle.size(), substitute);
+ pos = str.find(needle);
+ }
+}
+
+bool parseOptions (int argc, char** argv, Arguments &info)
+{
+ bpo::options_description desc("Inspect and extract files from Bethesda BSA archives\n\n"
+ "Usages:\n"
+ " bsatool list [-l] archivefile\n"
+ " List the files presents in the input archive.\n\n"
+ " bsatool extract [-f] archivefile [file_to_extract] [output_directory]\n"
+ " Extract a file from the input archive.\n\n"
+ " bsatool extractall archivefile [output_directory]\n"
+ " Extract all files from the input archive.\n\n"
+ "Allowed options");
+
+ desc.add_options()
+ ("help,h", "print help message.")
+ ("version,v", "print version information and quit.")
+ ("long,l", "Include extra information in archive listing.")
+ ("full-path,f", "Create diretory hierarchy on file extraction "
+ "(always true for extractall).")
+ ;
+
+ // input-file is hidden and used as a positional argument
+ bpo::options_description hidden("Hidden Options");
+
+ hidden.add_options()
+ ( "mode,m", bpo::value<std::string>(), "bsatool mode")
+ ( "input-file,i", bpo::value< std::vector<std::string> >(), "input file")
+ ;
+
+ bpo::positional_options_description p;
+ p.add("mode", 1).add("input-file", 3);
+
+ // there might be a better way to do this
+ bpo::options_description all;
+ all.add(desc).add(hidden);
+
+ bpo::variables_map variables;
+ try
+ {
+ bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
+ .options(all).positional(p).run();
+ bpo::store(valid_opts, variables);
+ }
+ catch(std::exception &e)
+ {
+ std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
+ << desc << std::endl;
+ return false;
+ }
+
+ bpo::notify(variables);
+
+ if (variables.count ("help"))
+ {
+ std::cout << desc << std::endl;
+ return false;
+ }
+ if (variables.count ("version"))
+ {
+ std::cout << "BSATool version " << BSATOOL_VERSION << std::endl;
+ return false;
+ }
+ if (!variables.count("mode"))
+ {
+ std::cout << "ERROR: no mode specified!\n\n"
+ << desc << std::endl;
+ return false;
+ }
+
+ info.mode = variables["mode"].as<std::string>();
+ if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall"))
+ {
+ std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
+ << desc << std::endl;
+ return false;
+ }
+
+ if (!variables.count("input-file"))
+ {
+ std::cout << "\nERROR: missing BSA archive\n\n"
+ << desc << std::endl;
+ return false;
+ }
+ info.filename = variables["input-file"].as< std::vector<std::string> >()[0];
+
+ // Default output to the working directory
+ info.outdir = ".";
+
+ if (info.mode == "extract")
+ {
+ if (variables["input-file"].as< std::vector<std::string> >().size() < 2)
+ {
+ std::cout << "\nERROR: file to extract unspecified\n\n"
+ << desc << std::endl;
+ return false;
+ }
+ if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
+ info.extractfile = variables["input-file"].as< std::vector<std::string> >()[1];
+ if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
+ info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
+ }
+ else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
+ info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
+
+ info.longformat = variables.count("long");
+ info.fullpath = variables.count("full-path");
+
+ return true;
+}
+
+int list(Bsa::BSAFile& bsa, Arguments& info);
+int extract(Bsa::BSAFile& bsa, Arguments& info);
+int extractAll(Bsa::BSAFile& bsa, Arguments& info);
+
+int main(int argc, char** argv)
+{
+ Arguments info;
+ if(!parseOptions (argc, argv, info))
+ return 1;
+
+ // Open file
+ Bsa::BSAFile bsa;
+ try
+ {
+ bsa.open(info.filename);
+ }
+ catch(std::exception &e)
+ {
+ std::cout << "ERROR reading BSA archive '" << info.filename
+ << "'\nDetails:\n" << e.what() << std::endl;
+ return 2;
+ }
+
+ if (info.mode == "list")
+ return list(bsa, info);
+ else if (info.mode == "extract")
+ return extract(bsa, info);
+ else if (info.mode == "extractall")
+ return extractAll(bsa, info);
+ else
+ {
+ std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
+ return 1;
+ }
+}
+
+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++)
+ {
+ if(info.longformat)
+ {
+ // Long format
+ std::cout << std::setw(50) << std::left << files[i].name;
+ std::cout << std::setw(8) << std::left << std::dec << files[i].fileSize;
+ std::cout << "@ 0x" << std::hex << files[i].offset << std::endl;
+ }
+ else
+ std::cout << files[i].name << std::endl;
+ }
+
+ return 0;
+}
+
+int extract(Bsa::BSAFile& bsa, Arguments& info)
+{
+ std::string archivePath = info.extractfile;
+ replaceAll(archivePath, "/", "\\");
+
+ std::string extractPath = info.extractfile;
+ replaceAll(extractPath, "\\", "/");
+
+ if (!bsa.exists(archivePath.c_str()))
+ {
+ std::cout << "ERROR: file '" << archivePath << "' not found\n";
+ std::cout << "In archive: " << info.filename << std::endl;
+ return 3;
+ }
+
+ // Get the target path (the path the file will be extracted to)
+ bfs::path relPath (extractPath);
+ bfs::path outdir (info.outdir);
+
+ bfs::path target;
+ if (info.fullpath)
+ target = outdir / relPath;
+ else
+ target = outdir / relPath.filename();
+
+ // Create the directory hierarchy
+ bfs::create_directories(target.parent_path());
+
+ bfs::file_status s = bfs::status(target.parent_path());
+ if (!bfs::is_directory(s))
+ {
+ std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
+ return 3;
+ }
+
+ // Get a stream for the file to extract
+ Ogre::DataStreamPtr data = bsa.getFile(archivePath.c_str());
+ bfs::ofstream out(target, std::ios::binary);
+
+ // Write the file to disk
+ std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
+ out.write(data->getAsString().c_str(), data->size());
+ out.close();
+
+ return 0;
+}
+
+int extractAll(Bsa::BSAFile& bsa, Arguments& info)
+{
+ // Get the list of files present in the archive
+ Bsa::BSAFile::FileList list = bsa.getList();
+
+ // Iter on the list
+ for(Bsa::BSAFile::FileList::iterator it = list.begin(); it != list.end(); ++it) {
+ const char* archivePath = it->name;
+
+ std::string extractPath (archivePath);
+ replaceAll(extractPath, "\\", "/");
+
+ // Get the target path (the path the file will be extracted to)
+ bfs::path target (info.outdir);
+ target /= extractPath;
+
+ // Create the directory hierarchy
+ bfs::create_directories(target.parent_path());
+
+ bfs::file_status s = bfs::status(target.parent_path());
+ if (!bfs::is_directory(s))
+ {
+ std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
+ return 3;
+ }
+
+ // Get a stream for the file to extract
+ // (inefficient because getFile iter on the list again)
+ Ogre::DataStreamPtr data = bsa.getFile(archivePath);
+ bfs::ofstream out(target, std::ios::binary);
+
+ // Write the file to disk
+ std::cout << "Extracting " << target << std::endl;
+ out.write(data->getAsString().c_str(), data->size());
+ out.close();
+ }
+
+ return 0;
+}
diff --git a/apps/doc.hpp b/apps/doc.hpp
new file mode 100644
index 0000000000..e7fb1fa411
--- /dev/null
+++ b/apps/doc.hpp
@@ -0,0 +1,3 @@
+// Note: This is not a regular source file.
+
+/// \defgroup apps Applications
diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt
new file mode 100644
index 0000000000..1d00262151
--- /dev/null
+++ b/apps/esmtool/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(ESMTOOL
+ esmtool.cpp
+ labels.hpp
+ labels.cpp
+ record.hpp
+ record.cpp
+)
+source_group(apps\\esmtool FILES ${ESMTOOL})
+
+# Main executable
+add_executable(esmtool
+ ${ESMTOOL}
+)
+
+target_link_libraries(esmtool
+ ${Boost_LIBRARIES}
+ components
+)
+
+if (BUILD_WITH_CODE_COVERAGE)
+ add_definitions (--coverage)
+ target_link_libraries(esmtool gcov)
+endif()
diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp
new file mode 100644
index 0000000000..a60e9f0e20
--- /dev/null
+++ b/apps/esmtool/esmtool.cpp
@@ -0,0 +1,556 @@
+#include <iostream>
+#include <vector>
+#include <deque>
+#include <list>
+#include <map>
+#include <set>
+
+#include <boost/program_options.hpp>
+
+#include <components/esm/esmreader.hpp>
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/records.hpp>
+
+#include "record.hpp"
+
+#define ESMTOOL_VERSION 1.2
+
+// Create a local alias for brevity
+namespace bpo = boost::program_options;
+
+struct ESMData
+{
+ std::string author;
+ std::string description;
+ int version;
+ std::vector<ESM::Header::MasterData> masters;
+
+ std::deque<EsmTool::RecordBase *> mRecords;
+ std::map<ESM::Cell *, std::deque<ESM::CellRef> > mCellRefs;
+ std::map<int, int> mRecordStats;
+
+ static const std::set<int> sLabeledRec;
+};
+
+static const int sLabeledRecIds[] = {
+ ESM::REC_GLOB, ESM::REC_CLAS, ESM::REC_FACT, ESM::REC_RACE, ESM::REC_SOUN,
+ ESM::REC_REGN, ESM::REC_BSGN, ESM::REC_LTEX, ESM::REC_STAT, ESM::REC_DOOR,
+ ESM::REC_MISC, ESM::REC_WEAP, ESM::REC_CONT, ESM::REC_SPEL, ESM::REC_CREA,
+ ESM::REC_BODY, ESM::REC_LIGH, ESM::REC_ENCH, ESM::REC_NPC_, ESM::REC_ARMO,
+ ESM::REC_CLOT, ESM::REC_REPA, ESM::REC_ACTI, ESM::REC_APPA, ESM::REC_LOCK,
+ ESM::REC_PROB, ESM::REC_INGR, ESM::REC_BOOK, ESM::REC_ALCH, ESM::REC_LEVI,
+ ESM::REC_LEVC, ESM::REC_SNDG, ESM::REC_CELL, ESM::REC_DIAL
+};
+
+const std::set<int> ESMData::sLabeledRec =
+ std::set<int>(sLabeledRecIds, sLabeledRecIds + 34);
+
+// Based on the legacy struct
+struct Arguments
+{
+ unsigned int raw_given;
+ unsigned int quiet_given;
+ unsigned int loadcells_given;
+ bool plain_given;
+
+ std::string mode;
+ std::string encoding;
+ std::string filename;
+ std::string outname;
+
+ std::vector<std::string> types;
+
+ ESMData data;
+ ESM::ESMReader reader;
+ ESM::ESMWriter writer;
+};
+
+bool parseOptions (int argc, char** argv, Arguments &info)
+{
+ bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options");
+
+ desc.add_options()
+ ("help,h", "print help message.")
+ ("version,v", "print version information and quit.")
+ ("raw,r", "Show an unformatted list of all records and subrecords.")
+ // The intention is that this option would interact better
+ // with other modes including clone, dump, and raw.
+ ("type,t", bpo::value< std::vector<std::string> >(),
+ "Show only records of this type (four character record code). May "
+ "be specified multiple times. Only affects dump mode.")
+ ("plain,p", "Print contents of dialogs, books and scripts. "
+ "(skipped by default)"
+ "Only affects dump mode.")
+ ("quiet,q", "Supress all record information. Useful for speed tests.")
+ ("loadcells,C", "Browse through contents of all cells.")
+
+ ( "encoding,e", bpo::value<std::string>(&(info.encoding))->
+ default_value("win1252"),
+ "Character encoding used in ESMTool:\n"
+ "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
+ "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
+ "\n\twin1252 - Western European (Latin) alphabet, used by default")
+ ;
+
+ std::string finalText = "\nIf no option is given, the default action is to parse all records in the archive\nand display diagnostic information.";
+
+ // input-file is hidden and used as a positional argument
+ bpo::options_description hidden("Hidden Options");
+
+ hidden.add_options()
+ ( "mode,m", bpo::value<std::string>(), "esmtool mode")
+ ( "input-file,i", bpo::value< std::vector<std::string> >(), "input file")
+ ;
+
+ bpo::positional_options_description p;
+ p.add("mode", 1).add("input-file", 2);
+
+ // there might be a better way to do this
+ bpo::options_description all;
+ all.add(desc).add(hidden);
+ bpo::variables_map variables;
+
+ try
+ {
+ bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
+ .options(all).positional(p).run();
+
+ bpo::store(valid_opts, variables);
+ }
+ catch(boost::program_options::unknown_option & x)
+ {
+ std::cerr << "ERROR: " << x.what() << std::endl;
+ return false;
+ }
+ catch(boost::program_options::invalid_command_line_syntax & x)
+ {
+ std::cerr << "ERROR: " << x.what() << std::endl;
+ return false;
+ }
+
+ bpo::notify(variables);
+
+ if (variables.count ("help"))
+ {
+ std::cout << desc << finalText << std::endl;
+ return false;
+ }
+ if (variables.count ("version"))
+ {
+ std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl;
+ return false;
+ }
+ if (!variables.count("mode"))
+ {
+ std::cout << "No mode specified!" << std::endl << std::endl
+ << desc << finalText << std::endl;
+ return false;
+ }
+
+ if (variables.count("type") > 0)
+ info.types = variables["type"].as< std::vector<std::string> >();
+
+ info.mode = variables["mode"].as<std::string>();
+ if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp"))
+ {
+ std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"" << std::endl << std::endl
+ << desc << finalText << std::endl;
+ return false;
+ }
+
+ if ( !variables.count("input-file") )
+ {
+ std::cout << "\nERROR: missing ES file\n\n";
+ std::cout << desc << finalText << std::endl;
+ return false;
+ }
+
+ // handling gracefully the user adding multiple files
+/* if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
+ {
+ std::cout << "\nERROR: more than one ES file specified\n\n";
+ std::cout << desc << finalText << std::endl;
+ return false;
+ }*/
+
+ info.filename = variables["input-file"].as< std::vector<std::string> >()[0];
+ if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
+ info.outname = variables["input-file"].as< std::vector<std::string> >()[1];
+
+ info.raw_given = variables.count ("raw");
+ info.quiet_given = variables.count ("quiet");
+ info.loadcells_given = variables.count ("loadcells");
+ info.plain_given = (variables.count("plain") > 0);
+
+ // Font encoding settings
+ info.encoding = variables["encoding"].as<std::string>();
+ if(info.encoding != "win1250" && info.encoding != "win1251" && info.encoding != "win1252")
+ {
+ std::cout << info.encoding << " is not a valid encoding option." << std::endl;
+ info.encoding = "win1252";
+ }
+ std::cout << ToUTF8::encodingUsingMessage(info.encoding) << std::endl;
+
+ return true;
+}
+
+void printRaw(ESM::ESMReader &esm);
+void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info);
+
+int load(Arguments& info);
+int clone(Arguments& info);
+int comp(Arguments& info);
+
+int main(int argc, char**argv)
+{
+ Arguments info;
+ if(!parseOptions (argc, argv, info))
+ return 1;
+
+ if (info.mode == "dump")
+ return load(info);
+ else if (info.mode == "clone")
+ return clone(info);
+ else if (info.mode == "comp")
+ return comp(info);
+ else
+ {
+ std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
+{
+ bool quiet = (info.quiet_given || info.mode == "clone");
+ bool save = (info.mode == "clone");
+
+ // Skip back to the beginning of the reference list
+ // FIXME: Changes to the references backend required to support multiple plugins have
+ // almost certainly broken this following line. I'll leave it as is for now, so that
+ // the compiler does not complain.
+ cell.restore(esm, 0);
+
+ // Loop through all the references
+ ESM::CellRef ref;
+ if(!quiet) std::cout << " References:\n";
+ while(cell.getNextRef(esm, ref))
+ {
+ if (save) {
+ info.data.mCellRefs[&cell].push_back(ref);
+ }
+
+ if(quiet) continue;
+
+ std::cout << " Refnum: " << ref.mRefnum << std::endl;
+ std::cout << " ID: '" << ref.mRefID << "'\n";
+ std::cout << " Owner: '" << ref.mOwner << "'\n";
+ std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n";
+ std::cout << " Uses/health: '" << ref.mCharge << "'\n";
+ std::cout << " Gold value: '" << ref.mGoldValue << "'\n";
+ std::cout << " Blocked: '" << static_cast<int>(ref.mReferenceBlocked) << "'" << std::endl;
+ }
+}
+
+void printRaw(ESM::ESMReader &esm)
+{
+ while(esm.hasMoreRecs())
+ {
+ ESM::NAME n = esm.getRecName();
+ std::cout << "Record: " << n.toString() << std::endl;
+ esm.getRecHeader();
+ while(esm.hasMoreSubs())
+ {
+ uint64_t offs = esm.getOffset();
+ esm.getSubName();
+ esm.skipHSub();
+ n = esm.retSubName();
+ std::cout << " " << n.toString() << " - " << esm.getSubSize()
+ << " bytes @ 0x" << std::hex << offs << "\n";
+ }
+ }
+}
+
+int load(Arguments& info)
+{
+ ESM::ESMReader& esm = info.reader;
+ ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
+ esm.setEncoder(&encoder);
+
+ std::string filename = info.filename;
+ std::cout << "Loading file: " << filename << std::endl;
+
+ std::list<int> skipped;
+
+ try {
+
+ if(info.raw_given && info.mode == "dump")
+ {
+ std::cout << "RAW file listing:\n";
+
+ esm.openRaw(filename);
+
+ printRaw(esm);
+
+ return 0;
+ }
+
+ bool quiet = (info.quiet_given || info.mode == "clone");
+ bool loadCells = (info.loadcells_given || info.mode == "clone");
+ bool save = (info.mode == "clone");
+
+ esm.open(filename);
+
+ info.data.author = esm.getAuthor();
+ info.data.description = esm.getDesc();
+ info.data.masters = esm.getMasters();
+
+ if (!quiet)
+ {
+ std::cout << "Author: " << esm.getAuthor() << std::endl
+ << "Description: " << esm.getDesc() << std::endl
+ << "File format version: " << esm.getFVer() << std::endl;
+ std::vector<ESM::Header::MasterData> m = esm.getMasters();
+ if (!m.empty())
+ {
+ std::cout << "Masters:" << std::endl;
+ for(unsigned int i=0;i<m.size();i++)
+ std::cout << " " << m[i].name << ", " << m[i].size << " bytes" << std::endl;
+ }
+ }
+
+ // Loop through all records
+ while(esm.hasMoreRecs())
+ {
+ ESM::NAME n = esm.getRecName();
+ uint32_t flags;
+ esm.getRecHeader(flags);
+
+ // Is the user interested in this record type?
+ bool interested = true;
+ if (info.types.size() > 0)
+ {
+ std::vector<std::string>::iterator match;
+ match = std::find(info.types.begin(), info.types.end(),
+ n.toString());
+ if (match == info.types.end()) interested = false;
+ }
+
+ std::string id = esm.getHNOString("NAME");
+
+ if(!quiet && interested)
+ std::cout << "\nRecord: " << n.toString()
+ << " '" << id << "'\n";
+
+ EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
+
+ if (record == 0) {
+ if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
+ {
+ std::cout << "Skipping " << n.toString() << " records." << std::endl;
+ skipped.push_back(n.val);
+ }
+
+ esm.skipRecord();
+ if (quiet) break;
+ std::cout << " Skipping\n";
+ } else {
+ if (record->getType().val == ESM::REC_GMST) {
+ // preset id for GameSetting record
+ record->cast<ESM::GameSetting>()->get().mId = id;
+ }
+ record->setId(id);
+ record->setFlags((int) flags);
+ record->setPrintPlain(info.plain_given);
+ record->load(esm);
+ if (!quiet && interested) record->print();
+
+ if (record->getType().val == ESM::REC_CELL && loadCells) {
+ loadCell(record->cast<ESM::Cell>()->get(), esm, info);
+ }
+
+ if (save) {
+ info.data.mRecords.push_back(record);
+ } else {
+ delete record;
+ }
+ ++info.data.mRecordStats[n.val];
+ }
+ }
+
+ } catch(std::exception &e) {
+ std::cout << "\nERROR:\n\n " << e.what() << std::endl;
+
+ typedef std::deque<EsmTool::RecordBase *> RecStore;
+ RecStore &store = info.data.mRecords;
+ for (RecStore::iterator it = store.begin(); it != store.end(); ++it)
+ {
+ delete *it;
+ }
+ store.clear();
+ return 1;
+ }
+
+ return 0;
+}
+
+#include <iomanip>
+
+int clone(Arguments& info)
+{
+ if (info.outname.empty())
+ {
+ std::cout << "You need to specify an output name" << std::endl;
+ return 1;
+ }
+
+ if (load(info) != 0)
+ {
+ std::cout << "Failed to load, aborting." << std::endl;
+ return 1;
+ }
+
+ int recordCount = info.data.mRecords.size();
+
+ int digitCount = 1; // For a nicer output
+ if (recordCount > 9) ++digitCount;
+ if (recordCount > 99) ++digitCount;
+ if (recordCount > 999) ++digitCount;
+ if (recordCount > 9999) ++digitCount;
+ if (recordCount > 99999) ++digitCount;
+ if (recordCount > 999999) ++digitCount;
+
+ std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
+
+ ESM::NAME name;
+
+ int i = 0;
+ typedef std::map<int, int> Stats;
+ Stats &stats = info.data.mRecordStats;
+ for (Stats::iterator it = stats.begin(); it != stats.end(); ++it)
+ {
+ name.val = it->first;
+ float amount = it->second;
+ std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
+
+ if (++i % 3 == 0)
+ std::cout << std::endl;
+ }
+
+ if (i % 3 != 0)
+ std::cout << std::endl;
+
+ std::cout << std::endl << "Saving records to: " << info.outname << "..." << std::endl;
+
+ ESM::ESMWriter& esm = info.writer;
+ ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding));
+ esm.setEncoder(&encoder);
+ esm.setAuthor(info.data.author);
+ esm.setDescription(info.data.description);
+ esm.setVersion(info.data.version);
+ esm.setRecordCount (recordCount);
+
+ for (std::vector<ESM::Header::MasterData>::iterator it = info.data.masters.begin(); it != info.data.masters.end(); ++it)
+ esm.addMaster(it->name, it->size);
+
+ std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary);
+ esm.save(save);
+
+ int saved = 0;
+ typedef std::deque<EsmTool::RecordBase *> Records;
+ Records &records = info.data.mRecords;
+ for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
+ {
+ EsmTool::RecordBase *record = *it;
+
+ name.val = record->getType().val;
+
+ esm.startRecord(name.toString(), record->getFlags());
+
+ // TODO wrap this with std::set
+ if (ESMData::sLabeledRec.count(name.val) > 0) {
+ esm.writeHNCString("NAME", record->getId());
+ } else {
+ esm.writeHNOString("NAME", record->getId());
+ }
+
+ record->save(esm);
+
+ if (name.val == ESM::REC_CELL) {
+ ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
+ if (!info.data.mCellRefs[ptr].empty()) {
+ typedef std::deque<ESM::CellRef> RefList;
+ RefList &refs = info.data.mCellRefs[ptr];
+ for (RefList::iterator it = refs.begin(); it != refs.end(); ++it)
+ {
+ it->save(esm);
+ }
+ }
+ }
+
+ esm.endRecord(name.toString());
+
+ saved++;
+ int perc = (saved / (float)recordCount)*100;
+ if (perc % 10 == 0)
+ {
+ std::cerr << "\r" << perc << "%";
+ }
+ }
+
+ std::cout << "\rDone!" << std::endl;
+
+ esm.close();
+ save.close();
+
+ return 0;
+}
+
+int comp(Arguments& info)
+{
+ if (info.filename.empty() || info.outname.empty())
+ {
+ std::cout << "You need to specify two input files" << std::endl;
+ return 1;
+ }
+
+ Arguments fileOne;
+ Arguments fileTwo;
+
+ fileOne.raw_given = 0;
+ fileTwo.raw_given = 0;
+
+ fileOne.mode = "clone";
+ fileTwo.mode = "clone";
+
+ fileOne.encoding = info.encoding;
+ fileTwo.encoding = info.encoding;
+
+ fileOne.filename = info.filename;
+ fileTwo.filename = info.outname;
+
+ if (load(fileOne) != 0)
+ {
+ std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl;
+ return 1;
+ }
+
+ if (load(fileTwo) != 0)
+ {
+ std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl;
+ return 1;
+ }
+
+ if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size())
+ {
+ std::cout << "Not equal, different amount of records." << std::endl;
+ return 1;
+ }
+
+
+
+
+ return 0;
+}
diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp
new file mode 100644
index 0000000000..7b1fc7fb21
--- /dev/null
+++ b/apps/esmtool/labels.cpp
@@ -0,0 +1,880 @@
+#include "labels.hpp"
+
+#include <components/esm/loadbody.hpp>
+#include <components/esm/loadcell.hpp>
+#include <components/esm/loadcont.hpp>
+#include <components/esm/loadcrea.hpp>
+#include <components/esm/loadlevlist.hpp>
+#include <components/esm/loadligh.hpp>
+#include <components/esm/loadmgef.hpp>
+#include <components/esm/loadnpc.hpp>
+#include <components/esm/loadrace.hpp>
+#include <components/esm/loadspel.hpp>
+#include <components/esm/loadweap.hpp>
+#include <components/esm/aipackage.hpp>
+
+#include <iostream>
+#include <boost/format.hpp>
+
+std::string bodyPartLabel(int idx)
+{
+ const char *bodyPartLabels[] = {
+ "Head",
+ "Hair",
+ "Neck",
+ "Cuirass",
+ "Groin",
+ "Skirt",
+ "Right Hand",
+ "Left Hand",
+ "Right Wrist",
+ "Left Wrist",
+ "Shield",
+ "Right Forearm",
+ "Left Forearm",
+ "Right Upperarm",
+ "Left Upperarm",
+ "Right Foot",
+ "Left Foot",
+ "Right Ankle",
+ "Left Ankle",
+ "Right Knee",
+ "Left Knee",
+ "Right Leg",
+ "Left Leg",
+ "Right Shoulder",
+ "Left Shoulder",
+ "Weapon",
+ "Tail"
+ };
+
+ if (idx >= 0 && idx <= 26)
+ return bodyPartLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string meshPartLabel(int idx)
+{
+ const char *meshPartLabels[] = {
+ "Head",
+ "Hair",
+ "Neck",
+ "Chest",
+ "Groin",
+ "Hand",
+ "Wrist",
+ "Forearm",
+ "Upperarm",
+ "Foot",
+ "Ankle",
+ "Knee",
+ "Upper Leg",
+ "Clavicle",
+ "Tail"
+ };
+
+ if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail)
+ return meshPartLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string meshTypeLabel(int idx)
+{
+ const char *meshTypeLabels[] = {
+ "Skin",
+ "Clothing",
+ "Armor"
+ };
+
+ if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor)
+ return meshTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string clothingTypeLabel(int idx)
+{
+ const char *clothingTypeLabels[] = {
+ "Pants",
+ "Shoes",
+ "Shirt",
+ "Belt",
+ "Robe",
+ "Right Glove",
+ "Left Glove",
+ "Skirt",
+ "Ring",
+ "Amulet"
+ };
+
+ if (idx >= 0 && idx <= 9)
+ return clothingTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string armorTypeLabel(int idx)
+{
+ const char *armorTypeLabels[] = {
+ "Helmet",
+ "Cuirass",
+ "Left Pauldron",
+ "Right Pauldron",
+ "Greaves",
+ "Boots",
+ "Left Gauntlet",
+ "Right Gauntlet",
+ "Shield",
+ "Left Bracer",
+ "Right Bracer"
+ };
+
+ if (idx >= 0 && idx <= 10)
+ return armorTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string dialogTypeLabel(int idx)
+{
+ const char *dialogTypeLabels[] = {
+ "Topic",
+ "Voice",
+ "Greeting",
+ "Persuasion",
+ "Journal"
+ };
+
+ if (idx >= 0 && idx <= 4)
+ return dialogTypeLabels[idx];
+ else if (idx == -1)
+ return "Deleted";
+ else
+ return "Invalid";
+}
+
+std::string questStatusLabel(int idx)
+{
+ const char *questStatusLabels[] = {
+ "None",
+ "Name",
+ "Finished",
+ "Restart",
+ "Deleted"
+ };
+
+ if (idx >= 0 && idx <= 4)
+ return questStatusLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string creatureTypeLabel(int idx)
+{
+ const char *creatureTypeLabels[] = {
+ "Creature",
+ "Daedra",
+ "Undead",
+ "Humanoid",
+ };
+
+ if (idx >= 0 && idx <= 3)
+ return creatureTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string soundTypeLabel(int idx)
+{
+ const char *soundTypeLabels[] = {
+ "Left Foot",
+ "Right Foot",
+ "Swim Left",
+ "Swim Right",
+ "Moan",
+ "Roar",
+ "Scream",
+ "Land"
+ };
+
+ if (idx >= 0 && idx <= 7)
+ return soundTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string weaponTypeLabel(int idx)
+{
+ const char *weaponTypeLabels[] = {
+ "Short Blade One Hand",
+ "Long Blade One Hand",
+ "Long Blade Two Hand",
+ "Blunt One Hand",
+ "Blunt Two Close",
+ "Blunt Two Wide",
+ "Spear Two Wide",
+ "Axe One Hand",
+ "Axe Two Hand",
+ "Marksman Bow",
+ "Marksman Crossbow",
+ "Marksman Thrown",
+ "Arrow",
+ "Bolt"
+ };
+
+ if (idx >= 0 && idx <= 13)
+ return weaponTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string aiTypeLabel(int type)
+{
+ if (type == ESM::AI_Wander) return "Wander";
+ else if (type == ESM::AI_Travel) return "Travel";
+ else if (type == ESM::AI_Follow) return "Follow";
+ else if (type == ESM::AI_Escort) return "Escort";
+ else if (type == ESM::AI_Activate) return "Activate";
+ else return "Invalid";
+}
+
+std::string magicEffectLabel(int idx)
+{
+ const char* magicEffectLabels [] = {
+ "Water Breathing",
+ "Swift Swim",
+ "Water Walking",
+ "Shield",
+ "Fire Shield",
+ "Lightning Shield",
+ "Frost Shield",
+ "Burden",
+ "Feather",
+ "Jump",
+ "Levitate",
+ "SlowFall",
+ "Lock",
+ "Open",
+ "Fire Damage",
+ "Shock Damage",
+ "Frost Damage",
+ "Drain Attribute",
+ "Drain Health",
+ "Drain Magicka",
+ "Drain Fatigue",
+ "Drain Skill",
+ "Damage Attribute",
+ "Damage Health",
+ "Damage Magicka",
+ "Damage Fatigue",
+ "Damage Skill",
+ "Poison",
+ "Weakness to Fire",
+ "Weakness to Frost",
+ "Weakness to Shock",
+ "Weakness to Magicka",
+ "Weakness to Common Disease",
+ "Weakness to Blight Disease",
+ "Weakness to Corprus Disease",
+ "Weakness to Poison",
+ "Weakness to Normal Weapons",
+ "Disintegrate Weapon",
+ "Disintegrate Armor",
+ "Invisibility",
+ "Chameleon",
+ "Light",
+ "Sanctuary",
+ "Night Eye",
+ "Charm",
+ "Paralyze",
+ "Silence",
+ "Blind",
+ "Sound",
+ "Calm Humanoid",
+ "Calm Creature",
+ "Frenzy Humanoid",
+ "Frenzy Creature",
+ "Demoralize Humanoid",
+ "Demoralize Creature",
+ "Rally Humanoid",
+ "Rally Creature",
+ "Dispel",
+ "Soultrap",
+ "Telekinesis",
+ "Mark",
+ "Recall",
+ "Divine Intervention",
+ "Almsivi Intervention",
+ "Detect Animal",
+ "Detect Enchantment",
+ "Detect Key",
+ "Spell Absorption",
+ "Reflect",
+ "Cure Common Disease",
+ "Cure Blight Disease",
+ "Cure Corprus Disease",
+ "Cure Poison",
+ "Cure Paralyzation",
+ "Restore Attribute",
+ "Restore Health",
+ "Restore Magicka",
+ "Restore Fatigue",
+ "Restore Skill",
+ "Fortify Attribute",
+ "Fortify Health",
+ "Fortify Magicka",
+ "Fortify Fatigue",
+ "Fortify Skill",
+ "Fortify Maximum Magicka",
+ "Absorb Attribute",
+ "Absorb Health",
+ "Absorb Magicka",
+ "Absorb Fatigue",
+ "Absorb Skill",
+ "Resist Fire",
+ "Resist Frost",
+ "Resist Shock",
+ "Resist Magicka",
+ "Resist Common Disease",
+ "Resist Blight Disease",
+ "Resist Corprus Disease",
+ "Resist Poison",
+ "Resist Normal Weapons",
+ "Resist Paralysis",
+ "Remove Curse",
+ "Turn Undead",
+ "Summon Scamp",
+ "Summon Clannfear",
+ "Summon Daedroth",
+ "Summon Dremora",
+ "Summon Ancestral Ghost",
+ "Summon Skeletal Minion",
+ "Summon Bonewalker",
+ "Summon Greater Bonewalker",
+ "Summon Bonelord",
+ "Summon Winged Twilight",
+ "Summon Hunger",
+ "Summon Golden Saint",
+ "Summon Flame Atronach",
+ "Summon Frost Atronach",
+ "Summon Storm Atronach",
+ "Fortify Attack",
+ "Command Creature",
+ "Command Humanoid",
+ "Bound Dagger",
+ "Bound Longsword",
+ "Bound Mace",
+ "Bound Battle Axe",
+ "Bound Spear",
+ "Bound Longbow",
+ "EXTRA SPELL",
+ "Bound Cuirass",
+ "Bound Helm",
+ "Bound Boots",
+ "Bound Shield",
+ "Bound Gloves",
+ "Corprus",
+ "Vampirism",
+ "Summon Centurion Sphere",
+ "Sun Damage",
+ "Stunted Magicka",
+ "Summon Fabricant",
+ "sEffectSummonCreature01",
+ "sEffectSummonCreature02",
+ "sEffectSummonCreature03",
+ "sEffectSummonCreature04",
+ "sEffectSummonCreature05"
+ };
+ if (idx >= 0 && idx <= 143)
+ return magicEffectLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string attributeLabel(int idx)
+{
+ const char* attributeLabels [] = {
+ "Strength",
+ "Intelligence",
+ "Willpower",
+ "Agility",
+ "Speed",
+ "Endurance",
+ "Personality",
+ "Luck"
+ };
+ if (idx >= 0 && idx <= 7)
+ return attributeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string spellTypeLabel(int idx)
+{
+ const char* spellTypeLabels [] = {
+ "Spells",
+ "Abilities",
+ "Blight Disease",
+ "Disease",
+ "Curse",
+ "Powers"
+ };
+ if (idx >= 0 && idx <= 5)
+ return spellTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string specializationLabel(int idx)
+{
+ const char* specializationLabels [] = {
+ "Combat",
+ "Magic",
+ "Stealth"
+ };
+ if (idx >= 0 && idx <= 2)
+ return specializationLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string skillLabel(int idx)
+{
+ const char* skillLabels [] = {
+ "Block",
+ "Armorer",
+ "Medium Armor",
+ "Heavy Armor",
+ "Blunt Weapon",
+ "Long Blade",
+ "Axe",
+ "Spear",
+ "Athletics",
+ "Enchant",
+ "Destruction",
+ "Alteration",
+ "Illusion",
+ "Conjuration",
+ "Mysticism",
+ "Restoration",
+ "Alchemy",
+ "Unarmored",
+ "Security",
+ "Sneak",
+ "Acrobatics",
+ "Light Armor",
+ "Short Blade",
+ "Marksman",
+ "Mercantile",
+ "Speechcraft",
+ "Hand-to-hand"
+ };
+ if (idx >= 0 && idx <= 27)
+ return skillLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string apparatusTypeLabel(int idx)
+{
+ const char* apparatusTypeLabels [] = {
+ "Mortar",
+ "Alembic",
+ "Calcinator",
+ "Retort",
+ };
+ if (idx >= 0 && idx <= 3)
+ return apparatusTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string rangeTypeLabel(int idx)
+{
+ const char* rangeTypeLabels [] = {
+ "Self",
+ "Touch",
+ "Target"
+ };
+ if (idx >= 0 && idx <= 3)
+ return rangeTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string schoolLabel(int idx)
+{
+ const char* schoolLabels [] = {
+ "Alteration",
+ "Conjuration",
+ "Destruction",
+ "Illusion",
+ "Mysticism",
+ "Restoration"
+ };
+ if (idx >= 0 && idx <= 5)
+ return schoolLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string enchantTypeLabel(int idx)
+{
+ const char* enchantTypeLabels [] = {
+ "Cast Once",
+ "Cast When Strikes",
+ "Cast When Used",
+ "Constant Effect"
+ };
+ if (idx >= 0 && idx <= 3)
+ return enchantTypeLabels[idx];
+ else
+ return "Invalid";
+}
+
+std::string ruleFunction(int idx)
+{
+ std::string ruleFunctions[] = {
+ "Reaction Low",
+ "Reaction High",
+ "Rank Requirement",
+ "NPC? Reputation",
+ "Health Percent",
+ "Player Reputation",
+ "NPC Level",
+ "Player Health Percent",
+ "Player Magicka",
+ "Player Fatigue",
+ "Player Attribute Strength",
+ "Player Skill Block",
+ "Player Skill Armorer",
+ "Player Skill Medium Armor",
+ "Player Skill Heavy Armor",
+ "Player Skill Blunt Weapon",
+ "Player Skill Long Blade",
+ "Player Skill Axe",
+ "Player Skill Spear",
+ "Player Skill Athletics",
+ "Player Skill Enchant",
+ "Player Skill Destruction",
+ "Player Skill Alteration",
+ "Player Skill Illusion",
+ "Player Skill Conjuration",
+ "Player Skill Mysticism",
+ "Player SKill Restoration",
+ "Player Skill Alchemy",
+ "Player Skill Unarmored",
+ "Player Skill Security",
+ "Player Skill Sneak",
+ "Player Skill Acrobatics",
+ "Player Skill Light Armor",
+ "Player Skill Short Blade",
+ "Player Skill Marksman",
+ "Player Skill Mercantile",
+ "Player Skill Speechcraft",
+ "Player Skill Hand to Hand",
+ "Player Gender",
+ "Player Expelled from Faction",
+ "Player Diseased (Common)",
+ "Player Diseased (Blight)",
+ "Player Clothing Modifier",
+ "Player Crime Level",
+ "Player Same Sex",
+ "Player Same Race",
+ "Player Same Faction",
+ "Faction Rank Difference",
+ "Player Detected",
+ "Alarmed",
+ "Choice Selected",
+ "Player Attribute Intelligence",
+ "Player Attribute Willpower",
+ "Player Attribute Agility",
+ "Player Attribute Speed",
+ "Player Attribute Endurance",
+ "Player Attribute Personality",
+ "Player Attribute Luck",
+ "Player Diseased (Corprus)",
+ "Weather",
+ "Player is a Vampire",
+ "Player Level",
+ "Attacked",
+ "NPC Talked to Player",
+ "Player Health",
+ "Creature Target",
+ "Friend Hit",
+ "Fight",
+ "Hello",
+ "Alarm",
+ "Flee",
+ "Should Attack",
+ "Werewolf"
+ };
+ if (idx >= 0 && idx <= 72)
+ return ruleFunctions[idx];
+ else
+ return "Invalid";
+}
+
+// The "unused flag bits" should probably be defined alongside the
+// defined bits in the ESM component. The names of the flag bits are
+// very inconsistent.
+
+std::string bodyPartFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::BodyPart::BPF_Female) properties += "Female ";
+ if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::BodyPart::BPF_Female|
+ ESM::BodyPart::BPF_NotPlayable));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string cellFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::Cell::HasWater) properties += "HasWater ";
+ if (flags & ESM::Cell::Interior) properties += "Interior ";
+ if (flags & ESM::Cell::NoSleep) properties += "NoSleep ";
+ if (flags & ESM::Cell::QuasiEx) properties += "QuasiEx ";
+ // This used value is not in the ESM component.
+ if (flags & 0x00000040) properties += "Unknown ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Cell::HasWater|
+ ESM::Cell::Interior|
+ ESM::Cell::NoSleep|
+ ESM::Cell::QuasiEx|
+ 0x00000040));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string containerFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::Container::Unknown) properties += "Unknown ";
+ if (flags & ESM::Container::Organic) properties += "Organic ";
+ if (flags & ESM::Container::Respawn) properties += "Respawn ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Container::Unknown|
+ ESM::Container::Organic|
+ ESM::Container::Respawn));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string creatureFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::Creature::None) properties += "All ";
+ if (flags & ESM::Creature::Walks) properties += "Walks ";
+ if (flags & ESM::Creature::Swims) properties += "Swims ";
+ if (flags & ESM::Creature::Flies) properties += "Flies ";
+ if (flags & ESM::Creature::Biped) properties += "Biped ";
+ if (flags & ESM::Creature::Respawn) properties += "Respawn ";
+ if (flags & ESM::Creature::Weapon) properties += "Weapon ";
+ if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
+ if (flags & ESM::Creature::Metal) properties += "Metal ";
+ if (flags & ESM::Creature::Essential) properties += "Essential ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Creature::None|
+ ESM::Creature::Walks|
+ ESM::Creature::Swims|
+ ESM::Creature::Flies|
+ ESM::Creature::Biped|
+ ESM::Creature::Respawn|
+ ESM::Creature::Weapon|
+ ESM::Creature::Skeleton|
+ ESM::Creature::Metal|
+ ESM::Creature::Essential));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string landFlags(int flags)
+{
+ std::string properties = "";
+ // The ESM component says that this first four bits are used, but
+ // only the first three bits are used as far as I can tell.
+ // There's also no enumeration of the bit in the ESM component.
+ if (flags == 0) properties += "[None] ";
+ if (flags & 0x00000001) properties += "Unknown1 ";
+ if (flags & 0x00000004) properties += "Unknown3 ";
+ if (flags & 0x00000002) properties += "Unknown2 ";
+ if (flags & 0xFFFFFFF8) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string leveledListFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::LeveledListBase::AllLevels) properties += "AllLevels ";
+ // This flag apparently not present on creature lists...
+ if (flags & ESM::LeveledListBase::Each) properties += "Each ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::LeveledListBase::AllLevels|
+ ESM::LeveledListBase::Each));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string lightFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::Light::Dynamic) properties += "Dynamic ";
+ if (flags & ESM::Light::Fire) properties += "Fire ";
+ if (flags & ESM::Light::Carry) properties += "Carry ";
+ if (flags & ESM::Light::Flicker) properties += "Flicker ";
+ if (flags & ESM::Light::FlickerSlow) properties += "FlickerSlow ";
+ if (flags & ESM::Light::Pulse) properties += "Pulse ";
+ if (flags & ESM::Light::PulseSlow) properties += "PulseSlow ";
+ if (flags & ESM::Light::Negative) properties += "Negative ";
+ if (flags & ESM::Light::OffDefault) properties += "OffDefault ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Light::Dynamic|
+ ESM::Light::Fire|
+ ESM::Light::Carry|
+ ESM::Light::Flicker|
+ ESM::Light::FlickerSlow|
+ ESM::Light::Pulse|
+ ESM::Light::PulseSlow|
+ ESM::Light::Negative|
+ ESM::Light::OffDefault));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string magicEffectFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ // Enchanting & SpellMaking occur on the same list of effects.
+ // "EXTRA SPELL" appears in the construction set under both the
+ // spell making and enchanting tabs as an allowed effect. Since
+ // most of the effects without this flags are defective in various
+ // ways, it's still very unclear what these flag bits are.
+ if (flags & ESM::MagicEffect::SpellMaking) properties += "SpellMaking ";
+ if (flags & ESM::MagicEffect::Enchanting) properties += "Enchanting ";
+ if (flags & 0x00000040) properties += "RangeNoSelf ";
+ if (flags & 0x00000080) properties += "RangeTouch ";
+ if (flags & 0x00000100) properties += "RangeTarget ";
+ if (flags & 0x00001000) properties += "Unknown2 ";
+ if (flags & 0x00000001) properties += "AffectSkill ";
+ if (flags & 0x00000002) properties += "AffectAttribute ";
+ if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration ";
+ if (flags & 0x00000008) properties += "NoMagnitude ";
+ if (flags & 0x00000010) properties += "Negative ";
+ if (flags & 0x00000020) properties += "Unknown1 ";
+ // ESM componet says 0x800 is negative, but none of the magic
+ // effects have this flags set.
+ if (flags & ESM::MagicEffect::Negative) properties += "Unused ";
+ // Since only Chameleon has this flag it could be anything
+ // that uniquely distinguishes Chameleon.
+ if (flags & 0x00002000) properties += "Chameleon ";
+ if (flags & 0x00004000) properties += "Bound ";
+ if (flags & 0x00008000) properties += "Summon ";
+ // Calm, Demoralize, Frenzy, Lock, Open, Rally, Soultrap, Turn Unded
+ if (flags & 0x00010000) properties += "Unknown3 ";
+ if (flags & 0x00020000) properties += "Absorb ";
+ if (flags & 0xFFFC0000) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string npcFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ // Mythicmods and the ESM component differ. Mythicmods says
+ // 0x8=None and 0x10=AutoCalc, while our code defines 0x8 as
+ // AutoCalc. The former seems to be correct. All Bethesda
+ // records have bit 0x8 set. A suspiciously large portion of
+ // females have autocalc turned off.
+ if (flags & ESM::NPC::Autocalc) properties += "Unknown ";
+ if (flags & 0x00000010) properties += "Autocalc ";
+ if (flags & ESM::NPC::Female) properties += "Female ";
+ if (flags & ESM::NPC::Respawn) properties += "Respawn ";
+ if (flags & ESM::NPC::Essential) properties += "Essential ";
+ // These two flags do not appear on any NPCs and may have been
+ // confused with the flags for creatures.
+ if (flags & ESM::NPC::Skeleton) properties += "Skeleton ";
+ if (flags & ESM::NPC::Metal) properties += "Metal ";
+ // Whether corpses persist is a bit that is unaccounted for,
+ // however the only unknown bit occurs on ALL records, and
+ // relatively few NPCs have this bit set.
+ int unused = (0xFFFFFFFF ^
+ (ESM::NPC::Autocalc|
+ 0x00000010|
+ ESM::NPC::Female|
+ ESM::NPC::Respawn|
+ ESM::NPC::Essential|
+ ESM::NPC::Skeleton|
+ ESM::NPC::Metal));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string raceFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ // All races have the playable flag set in Bethesda files.
+ if (flags & ESM::Race::Playable) properties += "Playable ";
+ if (flags & ESM::Race::Beast) properties += "Beast ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Race::Playable|
+ ESM::Race::Beast));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string spellFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::Spell::F_Autocalc) properties += "Autocalc ";
+ if (flags & ESM::Spell::F_PCStart) properties += "PCStart ";
+ if (flags & ESM::Spell::F_Always) properties += "Always ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Spell::F_Autocalc|
+ ESM::Spell::F_PCStart|
+ ESM::Spell::F_Always));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string weaponFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ // The interpretation of the flags are still unclear to me.
+ // Apparently you can't be Silver without being Magical? Many of
+ // the "Magical" weapons don't have enchantments of any sort.
+ if (flags & ESM::Weapon::Magical) properties += "Magical ";
+ if (flags & ESM::Weapon::Silver) properties += "Silver ";
+ int unused = (0xFFFFFFFF ^
+ (ESM::Weapon::Magical|
+ ESM::Weapon::Silver));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp
new file mode 100644
index 0000000000..48d7b249bd
--- /dev/null
+++ b/apps/esmtool/labels.hpp
@@ -0,0 +1,64 @@
+#ifndef OPENMW_ESMTOOL_LABELS_H
+#define OPENMW_ESMTOOL_LABELS_H
+
+#include <string>
+
+std::string bodyPartLabel(int idx);
+std::string meshPartLabel(int idx);
+std::string meshTypeLabel(int idx);
+std::string clothingTypeLabel(int idx);
+std::string armorTypeLabel(int idx);
+std::string dialogTypeLabel(int idx);
+std::string questStatusLabel(int idx);
+std::string creatureTypeLabel(int idx);
+std::string soundTypeLabel(int idx);
+std::string weaponTypeLabel(int idx);
+
+// This function's a bit different because the types are record types,
+// not consecutive values.
+std::string aiTypeLabel(int type);
+
+// This one's also a bit different, because it enumerates dialog
+// select rule functions, not types. Structurally, it still converts
+// indexes to strings for display.
+std::string ruleFunction(int idx);
+
+// The labels below here can all be loaded from GMSTs, but are not
+// currently because among other things, that requires loading the
+// GMSTs before dumping any of the records.
+
+// If the data format supported ordered lists of GMSTs (post 1.0), the
+// lists could define the valid values, their localization strings,
+// and the indexes for referencing the types in other records in the
+// database. Then a single label function could work for all types.
+
+std::string magicEffectLabel(int idx);
+std::string attributeLabel(int idx);
+std::string spellTypeLabel(int idx);
+std::string specializationLabel(int idx);
+std::string skillLabel(int idx);
+std::string apparatusTypeLabel(int idx);
+std::string rangeTypeLabel(int idx);
+std::string schoolLabel(int idx);
+std::string enchantTypeLabel(int idx);
+
+// The are the flag functions that convert a bitmask into a list of
+// human readble strings representing the set bits.
+
+std::string bodyPartFlags(int flags);
+std::string cellFlags(int flags);
+std::string containerFlags(int flags);
+std::string creatureFlags(int flags);
+std::string landFlags(int flags);
+std::string leveledListFlags(int flags);
+std::string lightFlags(int flags);
+std::string magicEffectFlags(int flags);
+std::string npcFlags(int flags);
+std::string raceFlags(int flags);
+std::string spellFlags(int flags);
+std::string weaponFlags(int flags);
+
+// Missing flags functions:
+// aiServicesFlags, possibly more
+
+#endif
diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp
new file mode 100644
index 0000000000..68e0dcc09d
--- /dev/null
+++ b/apps/esmtool/record.cpp
@@ -0,0 +1,1300 @@
+#include "record.hpp"
+#include "labels.hpp"
+
+#include <iostream>
+#include <boost/format.hpp>
+
+void printAIPackage(ESM::AIPackage p)
+{
+ std::cout << " AI Type: " << aiTypeLabel(p.mType)
+ << " (" << boost::format("0x%08X") % p.mType << ")" << std::endl;
+ if (p.mType == ESM::AI_Wander)
+ {
+ std::cout << " Distance: " << p.mWander.mDistance << std::endl;
+ std::cout << " Duration: " << p.mWander.mDuration << std::endl;
+ std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl;
+ if (p.mWander.mUnk != 1)
+ std::cout << " Unknown: " << (int)p.mWander.mUnk << std::endl;
+
+ std::cout << " Idle: ";
+ for (int i = 0; i != 8; i++)
+ std::cout << (int)p.mWander.mIdle[i] << " ";
+ std::cout << std::endl;
+ }
+ else if (p.mType == ESM::AI_Travel)
+ {
+ std::cout << " Travel Coordinates: (" << p.mTravel.mX << ","
+ << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl;
+ std::cout << " Travel Unknown: " << (int)p.mTravel.mUnk << std::endl;
+ }
+ else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort)
+ {
+ std::cout << " Follow Coordinates: (" << p.mTarget.mX << ","
+ << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl;
+ std::cout << " Duration: " << p.mTarget.mDuration << std::endl;
+ std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl;
+ std::cout << " Unknown: " << (int)p.mTarget.mUnk << std::endl;
+ }
+ else if (p.mType == ESM::AI_Activate)
+ {
+ std::cout << " Name: " << p.mActivate.mName.toString() << std::endl;
+ std::cout << " Activate Unknown: " << (int)p.mActivate.mUnk << std::endl;
+ }
+ else {
+ std::cout << " BadPackage: " << boost::format("0x%08x") % p.mType << std::endl;
+ }
+
+ if (p.mCellName != "")
+ std::cout << " Cell Name: " << p.mCellName << std::endl;
+}
+
+std::string ruleString(ESM::DialInfo::SelectStruct ss)
+{
+ std::string rule = ss.mSelectRule;
+
+ if (rule.length() < 5)
+ return "INVALID";
+
+ char type = rule[1];
+ char indicator = rule[2];
+
+ std::string type_str = "INVALID";
+ std::string func_str = str(boost::format("INVALID=%s") % rule.substr(1,3));
+ int func;
+ std::istringstream iss(rule.substr(2,2));
+ iss >> func;
+
+ switch(type)
+ {
+ case '1':
+ type_str = "Function";
+ func_str = ruleFunction(func);
+ break;
+ case '2':
+ if (indicator == 's') type_str = "Global short";
+ else if (indicator == 'l') type_str = "Global long";
+ else if (indicator == 'f') type_str = "Global float";
+ break;
+ case '3':
+ if (indicator == 's') type_str = "Local short";
+ else if (indicator == 'l') type_str = "Local long";
+ else if (indicator == 'f') type_str = "Local float";
+ break;
+ case '4': if (indicator == 'J') type_str = "Journal"; break;
+ case '5': if (indicator == 'I') type_str = "Item type"; break;
+ case '6': if (indicator == 'D') type_str = "NPC Dead"; break;
+ case '7': if (indicator == 'X') type_str = "Not ID"; break;
+ case '8': if (indicator == 'F') type_str = "Not Faction"; break;
+ case '9': if (indicator == 'C') type_str = "Not Class"; break;
+ case 'A': if (indicator == 'R') type_str = "Not Race"; break;
+ case 'B': if (indicator == 'L') type_str = "Not Cell"; break;
+ case 'C': if (indicator == 's') type_str = "Not Local"; break;
+ }
+
+ // Append the variable name to the function string if any.
+ if (type != '1') func_str = rule.substr(5);
+
+ // In the previous switch, we assumed that the second char was X
+ // for all types not qual to one. If this wasn't true, go back to
+ // the error message.
+ if (type != '1' && rule[3] != 'X')
+ func_str = str(boost::format("INVALID=%s") % rule.substr(1,3));
+
+ char oper = rule[4];
+ std::string oper_str = "??";
+ switch (oper)
+ {
+ case '0': oper_str = "=="; break;
+ case '1': oper_str = "!="; break;
+ case '2': oper_str = "> "; break;
+ case '3': oper_str = ">="; break;
+ case '4': oper_str = "< "; break;
+ case '5': oper_str = "<="; break;
+ }
+
+ std::ostringstream stream;
+ stream << ss.mValue;
+
+ std::string result = str(boost::format("%-12s %-32s %2s %s")
+ % type_str % func_str % oper_str % stream.str());
+ return result;
+}
+
+void printEffectList(ESM::EffectList effects)
+{
+ int i = 0;
+ std::vector<ESM::ENAMstruct>::iterator eit;
+ for (eit = effects.mList.begin(); eit != effects.mList.end(); eit++)
+ {
+ std::cout << " Effect[" << i << "]: " << magicEffectLabel(eit->mEffectID)
+ << " (" << eit->mEffectID << ")" << std::endl;
+ if (eit->mSkill != -1)
+ std::cout << " Skill: " << skillLabel(eit->mSkill)
+ << " (" << (int)eit->mSkill << ")" << std::endl;
+ if (eit->mAttribute != -1)
+ std::cout << " Attribute: " << attributeLabel(eit->mAttribute)
+ << " (" << (int)eit->mAttribute << ")" << std::endl;
+ std::cout << " Range: " << rangeTypeLabel(eit->mRange)
+ << " (" << eit->mRange << ")" << std::endl;
+ // Area is always zero if range type is "Self"
+ if (eit->mRange != ESM::RT_Self)
+ std::cout << " Area: " << eit->mArea << std::endl;
+ std::cout << " Duration: " << eit->mDuration << std::endl;
+ std::cout << " Magnitude: " << eit->mMagnMin << "-" << eit->mMagnMax << std::endl;
+ i++;
+ }
+}
+
+namespace EsmTool {
+
+RecordBase *
+RecordBase::create(ESM::NAME type)
+{
+ RecordBase *record = 0;
+
+ switch (type.val) {
+ case ESM::REC_ACTI:
+ {
+ record = new EsmTool::Record<ESM::Activator>;
+ break;
+ }
+ case ESM::REC_ALCH:
+ {
+ record = new EsmTool::Record<ESM::Potion>;
+ break;
+ }
+ case ESM::REC_APPA:
+ {
+ record = new EsmTool::Record<ESM::Apparatus>;
+ break;
+ }
+ case ESM::REC_ARMO:
+ {
+ record = new EsmTool::Record<ESM::Armor>;
+ break;
+ }
+ case ESM::REC_BODY:
+ {
+ record = new EsmTool::Record<ESM::BodyPart>;
+ break;
+ }
+ case ESM::REC_BOOK:
+ {
+ record = new EsmTool::Record<ESM::Book>;
+ break;
+ }
+ case ESM::REC_BSGN:
+ {
+ record = new EsmTool::Record<ESM::BirthSign>;
+ break;
+ }
+ case ESM::REC_CELL:
+ {
+ record = new EsmTool::Record<ESM::Cell>;
+ break;
+ }
+ case ESM::REC_CLAS:
+ {
+ record = new EsmTool::Record<ESM::Class>;
+ break;
+ }
+ case ESM::REC_CLOT:
+ {
+ record = new EsmTool::Record<ESM::Clothing>;
+ break;
+ }
+ case ESM::REC_CONT:
+ {
+ record = new EsmTool::Record<ESM::Container>;
+ break;
+ }
+ case ESM::REC_CREA:
+ {
+ record = new EsmTool::Record<ESM::Creature>;
+ break;
+ }
+ case ESM::REC_DIAL:
+ {
+ record = new EsmTool::Record<ESM::Dialogue>;
+ break;
+ }
+ case ESM::REC_DOOR:
+ {
+ record = new EsmTool::Record<ESM::Door>;
+ break;
+ }
+ case ESM::REC_ENCH:
+ {
+ record = new EsmTool::Record<ESM::Enchantment>;
+ break;
+ }
+ case ESM::REC_FACT:
+ {
+ record = new EsmTool::Record<ESM::Faction>;
+ break;
+ }
+ case ESM::REC_GLOB:
+ {
+ record = new EsmTool::Record<ESM::Global>;
+ break;
+ }
+ case ESM::REC_GMST:
+ {
+ record = new EsmTool::Record<ESM::GameSetting>;
+ break;
+ }
+ case ESM::REC_INFO:
+ {
+ record = new EsmTool::Record<ESM::DialInfo>;
+ break;
+ }
+ case ESM::REC_INGR:
+ {
+ record = new EsmTool::Record<ESM::Ingredient>;
+ break;
+ }
+ case ESM::REC_LAND:
+ {
+ record = new EsmTool::Record<ESM::Land>;
+ break;
+ }
+ case ESM::REC_LEVI:
+ {
+ record = new EsmTool::Record<ESM::ItemLevList>;
+ break;
+ }
+ case ESM::REC_LEVC:
+ {
+ record = new EsmTool::Record<ESM::CreatureLevList>;
+ break;
+ }
+ case ESM::REC_LIGH:
+ {
+ record = new EsmTool::Record<ESM::Light>;
+ break;
+ }
+ case ESM::REC_LOCK:
+ {
+ record = new EsmTool::Record<ESM::Lockpick>;
+ break;
+ }
+ case ESM::REC_LTEX:
+ {
+ record = new EsmTool::Record<ESM::LandTexture>;
+ break;
+ }
+ case ESM::REC_MISC:
+ {
+ record = new EsmTool::Record<ESM::Miscellaneous>;
+ break;
+ }
+ case ESM::REC_MGEF:
+ {
+ record = new EsmTool::Record<ESM::MagicEffect>;
+ break;
+ }
+ case ESM::REC_NPC_:
+ {
+ record = new EsmTool::Record<ESM::NPC>;
+ break;
+ }
+ case ESM::REC_PGRD:
+ {
+ record = new EsmTool::Record<ESM::Pathgrid>;
+ break;
+ }
+ case ESM::REC_PROB:
+ {
+ record = new EsmTool::Record<ESM::Probe>;
+ break;
+ }
+ case ESM::REC_RACE:
+ {
+ record = new EsmTool::Record<ESM::Race>;
+ break;
+ }
+ case ESM::REC_REGN:
+ {
+ record = new EsmTool::Record<ESM::Region>;
+ break;
+ }
+ case ESM::REC_REPA:
+ {
+ record = new EsmTool::Record<ESM::Repair>;
+ break;
+ }
+ case ESM::REC_SCPT:
+ {
+ record = new EsmTool::Record<ESM::Script>;
+ break;
+ }
+ case ESM::REC_SKIL:
+ {
+ record = new EsmTool::Record<ESM::Skill>;
+ break;
+ }
+ case ESM::REC_SNDG:
+ {
+ record = new EsmTool::Record<ESM::SoundGenerator>;
+ break;
+ }
+ case ESM::REC_SOUN:
+ {
+ record = new EsmTool::Record<ESM::Sound>;
+ break;
+ }
+ case ESM::REC_SPEL:
+ {
+ record = new EsmTool::Record<ESM::Spell>;
+ break;
+ }
+ case ESM::REC_STAT:
+ {
+ record = new EsmTool::Record<ESM::Static>;
+ break;
+ }
+ case ESM::REC_WEAP:
+ {
+ record = new EsmTool::Record<ESM::Weapon>;
+ break;
+ }
+ case ESM::REC_SSCR:
+ {
+ record = new EsmTool::Record<ESM::StartScript>;
+ break;
+ }
+ default:
+ record = 0;
+ }
+ if (record) {
+ record->mType = type;
+ }
+ return record;
+}
+
+template<>
+void Record<ESM::Activator>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Script: " << mData.mScript << std::endl;
+}
+
+template<>
+void Record<ESM::Potion>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
+ printEffectList(mData.mEffects);
+}
+
+template<>
+void Record<ESM::Armor>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ if (mData.mEnchant != "")
+ std::cout << " Enchantment: " << mData.mEnchant << std::endl;
+ std::cout << " Type: " << armorTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Health: " << mData.mData.mHealth << std::endl;
+ std::cout << " Armor: " << mData.mData.mArmor << std::endl;
+ std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
+ std::vector<ESM::PartReference>::iterator pit;
+ for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); pit++)
+ {
+ std::cout << " Body Part: " << bodyPartLabel(pit->mPart)
+ << " (" << (int)(pit->mPart) << ")" << std::endl;
+ std::cout << " Male Name: " << pit->mMale << std::endl;
+ if (pit->mFemale != "")
+ std::cout << " Female Name: " << pit->mFemale << std::endl;
+ }
+}
+
+template<>
+void Record<ESM::Apparatus>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType)
+ << " (" << (int)mData.mData.mType << ")" << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Quality: " << mData.mData.mQuality << std::endl;
+}
+
+template<>
+void Record<ESM::BodyPart>::print()
+{
+ std::cout << " Race: " << mData.mRace << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Type: " << meshTypeLabel(mData.mData.mType)
+ << " (" << (int)mData.mData.mType << ")" << std::endl;
+ std::cout << " Flags: " << bodyPartFlags(mData.mData.mFlags) << std::endl;
+ std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
+ << " (" << (int)mData.mData.mPart << ")" << std::endl;
+ std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
+}
+
+template<>
+void Record<ESM::Book>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ if (mData.mEnchant != "")
+ std::cout << " Enchantment: " << mData.mEnchant << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl;
+ std::cout << " SkillID: " << mData.mData.mSkillID << std::endl;
+ std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
+ if (mPrintPlain)
+ {
+ std::cout << " Text:" << std::endl;
+ std::cout << "START--------------------------------------" << std::endl;
+ std::cout << mData.mText << std::endl;
+ std::cout << "END----------------------------------------" << std::endl;
+ }
+ else
+ {
+ std::cout << " Text: [skipped]" << std::endl;
+ }
+}
+
+template<>
+void Record<ESM::BirthSign>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Texture: " << mData.mTexture << std::endl;
+ std::cout << " Description: " << mData.mDescription << std::endl;
+ std::vector<std::string>::iterator pit;
+ for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); pit++)
+ std::cout << " Power: " << *pit << std::endl;
+}
+
+template<>
+void Record<ESM::Cell>::print()
+{
+ // None of the cells have names...
+ if (mData.mName != "")
+ std::cout << " Name: " << mData.mName << std::endl;
+ if (mData.mRegion != "")
+ std::cout << " Region: " << mData.mRegion << std::endl;
+ std::cout << " Flags: " << cellFlags(mData.mData.mFlags) << std::endl;
+
+ std::cout << " Coordinates: " << " (" << mData.getGridX() << ","
+ << mData.getGridY() << ")" << std::endl;
+
+ if (mData.mData.mFlags & ESM::Cell::Interior &&
+ !(mData.mData.mFlags & ESM::Cell::QuasiEx))
+ {
+ std::cout << " Ambient Light Color: " << mData.mAmbi.mAmbient << std::endl;
+ std::cout << " Sunlight Color: " << mData.mAmbi.mSunlight << std::endl;
+ std::cout << " Fog Color: " << mData.mAmbi.mFog << std::endl;
+ std::cout << " Fog Density: " << mData.mAmbi.mFogDensity << std::endl;
+ std::cout << " Water Level: " << mData.mWater << std::endl;
+ }
+ else
+ std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
+ std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
+ std::cout << " NAM0: " << mData.mNAM0 << std::endl;
+
+}
+
+template<>
+void Record<ESM::Class>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Description: " << mData.mDescription << std::endl;
+ std::cout << " Playable: " << mData.mData.mIsPlayable << std::endl;
+ std::cout << " AutoCalc: " << mData.mData.mCalc << std::endl;
+ std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0])
+ << " (" << mData.mData.mAttribute[0] << ")" << std::endl;
+ std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1])
+ << " (" << mData.mData.mAttribute[1] << ")" << std::endl;
+ std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization)
+ << " (" << mData.mData.mSpecialization << ")" << std::endl;
+ for (int i = 0; i != 5; i++)
+ std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][0])
+ << " (" << mData.mData.mSkills[i][0] << ")" << std::endl;
+ for (int i = 0; i != 5; i++)
+ std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][1])
+ << " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
+}
+
+template<>
+void Record<ESM::Clothing>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ if (mData.mEnchant != "")
+ std::cout << " Enchantment: " << mData.mEnchant << std::endl;
+ std::cout << " Type: " << clothingTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
+ std::vector<ESM::PartReference>::iterator pit;
+ for (pit = mData.mParts.mParts.begin(); pit != mData.mParts.mParts.end(); pit++)
+ {
+ std::cout << " Body Part: " << bodyPartLabel(pit->mPart)
+ << " (" << (int)(pit->mPart) << ")" << std::endl;
+ std::cout << " Male Name: " << pit->mMale << std::endl;
+ if (pit->mFemale != "")
+ std::cout << " Female Name: " << pit->mFemale << std::endl;
+ }
+}
+
+template<>
+void Record<ESM::Container>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl;
+ std::cout << " Weight: " << mData.mWeight << std::endl;
+ std::vector<ESM::ContItem>::iterator cit;
+ for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++)
+ std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
+ << " Item: " << cit->mItem.toString() << std::endl;
+}
+
+template<>
+void Record<ESM::Creature>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Flags: " << creatureFlags(mData.mFlags) << std::endl;
+ std::cout << " Original: " << mData.mOriginal << std::endl;
+ std::cout << " Scale: " << mData.mScale << std::endl;
+
+ std::cout << " Type: " << creatureTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Level: " << mData.mData.mLevel << std::endl;
+
+ std::cout << " Attributes:" << std::endl;
+ std::cout << " Strength: " << mData.mData.mStrength << std::endl;
+ std::cout << " Intelligence: " << mData.mData.mIntelligence << std::endl;
+ std::cout << " Willpower: " << mData.mData.mWillpower << std::endl;
+ std::cout << " Agility: " << mData.mData.mAgility << std::endl;
+ std::cout << " Speed: " << mData.mData.mSpeed << std::endl;
+ std::cout << " Endurance: " << mData.mData.mEndurance << std::endl;
+ std::cout << " Personality: " << mData.mData.mPersonality << std::endl;
+ std::cout << " Luck: " << mData.mData.mLuck << std::endl;
+
+ std::cout << " Health: " << mData.mData.mHealth << std::endl;
+ std::cout << " Magicka: " << mData.mData.mMana << std::endl;
+ std::cout << " Fatigue: " << mData.mData.mFatigue << std::endl;
+ std::cout << " Soul: " << mData.mData.mSoul << std::endl;
+ std::cout << " Combat: " << mData.mData.mCombat << std::endl;
+ std::cout << " Magic: " << mData.mData.mMagic << std::endl;
+ std::cout << " Stealth: " << mData.mData.mStealth << std::endl;
+ std::cout << " Attack1: " << mData.mData.mAttack[0]
+ << "-" << mData.mData.mAttack[1] << std::endl;
+ std::cout << " Attack2: " << mData.mData.mAttack[2]
+ << "-" << mData.mData.mAttack[3] << std::endl;
+ std::cout << " Attack3: " << mData.mData.mAttack[4]
+ << "-" << mData.mData.mAttack[5] << std::endl;
+ std::cout << " Gold: " << mData.mData.mGold << std::endl;
+
+ std::vector<ESM::ContItem>::iterator cit;
+ for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++)
+ std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
+ << " Item: " << cit->mItem.toString() << std::endl;
+
+ std::vector<std::string>::iterator sit;
+ for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); sit++)
+ std::cout << " Spell: " << *sit << std::endl;
+
+ std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl;
+ std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl;
+ std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl;
+ std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
+ std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
+ std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
+ std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
+ std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
+ std::cout << " AI U4:" << (int)mData.mAiData.mU4 << std::endl;
+ std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl;
+
+ std::vector<ESM::AIPackage>::iterator pit;
+ for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); pit++)
+ printAIPackage(*pit);
+}
+
+template<>
+void Record<ESM::Dialogue>::print()
+{
+ std::cout << " Type: " << dialogTypeLabel(mData.mType)
+ << " (" << (int)mData.mType << ")" << std::endl;
+ // Sadly, there are no DialInfos, because the loader dumps as it
+ // loads, rather than loading and then dumping. :-( Anyone mind if
+ // I change this?
+ std::vector<ESM::DialInfo>::iterator iit;
+ for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); iit++)
+ std::cout << "INFO!" << iit->mId << std::endl;
+}
+
+template<>
+void Record<ESM::Door>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
+ std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
+}
+
+template<>
+void Record<ESM::Enchantment>::print()
+{
+ std::cout << " Type: " << enchantTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Cost: " << mData.mData.mCost << std::endl;
+ std::cout << " Charge: " << mData.mData.mCharge << std::endl;
+ std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
+ printEffectList(mData.mEffects);
+}
+
+template<>
+void Record<ESM::Faction>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl;
+ if (mData.mData.mUnknown != -1)
+ std::cout << " Unknown: " << mData.mData.mUnknown << std::endl;
+ std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0])
+ << " (" << mData.mData.mAttribute[0] << ")" << std::endl;
+ std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1])
+ << " (" << mData.mData.mAttribute[1] << ")" << std::endl;
+ for (int i = 0; i != 6; i++)
+ if (mData.mData.mSkills[i] != -1)
+ std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i])
+ << " (" << mData.mData.mSkills[i] << ")" << std::endl;
+ for (int i = 0; i != 10; i++)
+ if (mData.mRanks[i] != "")
+ {
+ std::cout << " Rank: " << mData.mRanks[i] << std::endl;
+ std::cout << " Attribute1 Requirement: "
+ << mData.mData.mRankData[i].mAttribute1 << std::endl;
+ std::cout << " Attribute2 Requirement: "
+ << mData.mData.mRankData[i].mAttribute2 << std::endl;
+ std::cout << " One Skill at Level: "
+ << mData.mData.mRankData[i].mSkill1 << std::endl;
+ std::cout << " Two Skills at Level: "
+ << mData.mData.mRankData[i].mSkill2 << std::endl;
+ std::cout << " Faction Reaction: "
+ << mData.mData.mRankData[i].mFactReaction << std::endl;
+ }
+ std::vector<ESM::Faction::Reaction>::iterator rit;
+ for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); rit++)
+ std::cout << " Reaction: " << rit->mReaction << " = " << rit->mFaction << std::endl;
+}
+
+template<>
+void Record<ESM::Global>::print()
+{
+ std::cout << " " << mData.mValue << std::endl;
+}
+
+template<>
+void Record<ESM::GameSetting>::print()
+{
+ std::cout << " " << mData.mValue << std::endl;
+}
+
+template<>
+void Record<ESM::DialInfo>::print()
+{
+ std::cout << " Id: " << mData.mId << std::endl;
+ if (mData.mPrev != "")
+ std::cout << " Previous ID: " << mData.mPrev << std::endl;
+ if (mData.mNext != "")
+ std::cout << " Next ID: " << mData.mNext << std::endl;
+ std::cout << " Text: " << mData.mResponse << std::endl;
+ if (mData.mActor != "")
+ std::cout << " Actor: " << mData.mActor << std::endl;
+ if (mData.mRace != "")
+ std::cout << " Race: " << mData.mRace << std::endl;
+ 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.mData.mRank != -1)
+ std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl;
+ if (mData.mPcFaction != "")
+ std::cout << " PC Faction: " << mData.mPcFaction << std::endl;
+ // CHANGE? non-standard capitalization mPCrank -> mPCRank (mPcRank?)
+ if (mData.mData.mPCrank != -1)
+ std::cout << " PC Rank: " << (int)mData.mData.mPCrank << std::endl;
+ if (mData.mCell != "")
+ std::cout << " Cell: " << mData.mCell << std::endl;
+ if (mData.mData.mDisposition > 0)
+ std::cout << " Disposition: " << mData.mData.mDisposition << std::endl;
+ if (mData.mData.mGender != ESM::DialInfo::NA)
+ std::cout << " Gender: " << mData.mData.mGender << std::endl;
+ if (mData.mSound != "")
+ std::cout << " Sound File: " << mData.mSound << std::endl;
+
+
+ std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus)
+ << " (" << mData.mQuestStatus << ")" << std::endl;
+ std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl;
+ std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl;
+
+ std::vector<ESM::DialInfo::SelectStruct>::iterator sit;
+ for (sit = mData.mSelects.begin(); sit != mData.mSelects.end(); sit++)
+ std::cout << " Select Rule: " << ruleString(*sit) << std::endl;
+
+ if (mData.mResultScript != "")
+ {
+ if (mPrintPlain)
+ {
+ std::cout << " Result Script:" << std::endl;
+ std::cout << "START--------------------------------------" << std::endl;
+ std::cout << mData.mResultScript << std::endl;
+ std::cout << "END----------------------------------------" << std::endl;
+ }
+ else
+ {
+ std::cout << " Result Script: [skipped]" << std::endl;
+ }
+ }
+}
+
+template<>
+void Record<ESM::Ingredient>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ for (int i = 0; i !=4; i++)
+ {
+ // A value of -1 means no effect
+ if (mData.mData.mEffectID[i] == -1) continue;
+ std::cout << " Effect: " << magicEffectLabel(mData.mData.mEffectID[i])
+ << " (" << mData.mData.mEffectID[i] << ")" << std::endl;
+ std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i])
+ << " (" << mData.mData.mSkills[i] << ")" << std::endl;
+ std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
+ << " (" << mData.mData.mAttributes[i] << ")" << std::endl;
+ }
+}
+
+template<>
+void Record<ESM::Land>::print()
+{
+ std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl;
+ std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
+ std::cout << " HasData: " << mData.mHasData << std::endl;
+ std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
+
+ // Seems like this should done with reference counting in the
+ // loader to me. But I'm not really knowledgable about this
+ // record type yet. --Cory
+ bool wasLoaded = mData.mDataLoaded;
+ if (mData.mDataTypes) mData.loadData(mData.mDataTypes);
+ if (mData.mDataLoaded)
+ {
+ std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl;
+ // Lots of missing members.
+ std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl;
+ std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl;
+ }
+ if (!wasLoaded) mData.unloadData();
+}
+
+template<>
+void Record<ESM::CreatureLevList>::print()
+{
+ std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
+ std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl;
+ std::cout << " Number of items: " << mData.mList.size() << std::endl;
+ std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
+ for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++)
+ std::cout << " Creature: Level: " << iit->mLevel
+ << " Creature: " << iit->mId << std::endl;
+}
+
+template<>
+void Record<ESM::ItemLevList>::print()
+{
+ std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
+ std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl;
+ std::cout << " Number of items: " << mData.mList.size() << std::endl;
+ std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
+ for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++)
+ std::cout << " Inventory: Count: " << iit->mLevel
+ << " Item: " << iit->mId << std::endl;
+}
+
+template<>
+void Record<ESM::Light>::print()
+{
+ if (mData.mName != "")
+ std::cout << " Name: " << mData.mName << std::endl;
+ if (mData.mModel != "")
+ std::cout << " Model: " << mData.mModel << std::endl;
+ if (mData.mIcon != "")
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Flags: " << lightFlags(mData.mData.mFlags) << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Sound: " << mData.mSound << std::endl;
+ std::cout << " Duration: " << mData.mData.mTime << std::endl;
+ std::cout << " Radius: " << mData.mData.mRadius << std::endl;
+ std::cout << " Color: " << mData.mData.mColor << std::endl;
+}
+
+template<>
+void Record<ESM::Lockpick>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Quality: " << mData.mData.mQuality << std::endl;
+ std::cout << " Uses: " << mData.mData.mUses << std::endl;
+}
+
+template<>
+void Record<ESM::Probe>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Quality: " << mData.mData.mQuality << std::endl;
+ std::cout << " Uses: " << mData.mData.mUses << std::endl;
+}
+
+template<>
+void Record<ESM::Repair>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Quality: " << mData.mData.mQuality << std::endl;
+ std::cout << " Uses: " << mData.mData.mUses << std::endl;
+}
+
+template<>
+void Record<ESM::LandTexture>::print()
+{
+ std::cout << " Id: " << mData.mId << std::endl;
+ std::cout << " Index: " << mData.mIndex << std::endl;
+ std::cout << " Texture: " << mData.mTexture << std::endl;
+}
+
+template<>
+void Record<ESM::MagicEffect>::print()
+{
+ std::cout << " Index: " << magicEffectLabel(mData.mIndex)
+ << " (" << mData.mIndex << ")" << std::endl;
+ std::cout << " Description: " << mData.mDescription << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ std::cout << " Flags: " << magicEffectFlags(mData.mData.mFlags) << std::endl;
+ std::cout << " Particle Texture: " << mData.mParticle << std::endl;
+ if (mData.mCasting != "")
+ std::cout << " Casting Static: " << mData.mCasting << std::endl;
+ if (mData.mCastSound != "")
+ std::cout << " Casting Sound: " << mData.mCastSound << std::endl;
+ if (mData.mBolt != "")
+ std::cout << " Bolt Static: " << mData.mBolt << std::endl;
+ if (mData.mBoltSound != "")
+ std::cout << " Bolt Sound: " << mData.mBoltSound << std::endl;
+ if (mData.mHit != "")
+ std::cout << " Hit Static: " << mData.mHit << std::endl;
+ if (mData.mHitSound != "")
+ std::cout << " Hit Sound: " << mData.mHitSound << std::endl;
+ if (mData.mArea != "")
+ std::cout << " Area Static: " << mData.mArea << std::endl;
+ if (mData.mAreaSound != "")
+ std::cout << " Area Sound: " << mData.mAreaSound << std::endl;
+ std::cout << " School: " << schoolLabel(mData.mData.mSchool)
+ << " (" << mData.mData.mSchool << ")" << std::endl;
+ std::cout << " Base Cost: " << mData.mData.mBaseCost << std::endl;
+ std::cout << " Speed: " << mData.mData.mSpeed << std::endl;
+ std::cout << " Size: " << mData.mData.mSize << std::endl;
+ std::cout << " Size Cap: " << mData.mData.mSizeCap << std::endl;
+ std::cout << " RGB Color: " << "("
+ << mData.mData.mRed << ","
+ << mData.mData.mGreen << ","
+ << mData.mData.mGreen << ")" << std::endl;
+}
+
+template<>
+void Record<ESM::Miscellaneous>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
+}
+
+template<>
+void Record<ESM::NPC>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Animation: " << mData.mModel << std::endl;
+ std::cout << " Hair Model: " << mData.mHair << std::endl;
+ std::cout << " Head Model: " << mData.mHead << std::endl;
+ std::cout << " Race: " << mData.mRace << std::endl;
+ std::cout << " Class: " << mData.mClass << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ if (mData.mFaction != "")
+ std::cout << " Faction: " << mData.mFaction << std::endl;
+ std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl;
+
+ // Seriously?
+ if (mData.mNpdt52.mGold == -10)
+ {
+ std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl;
+ std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl;
+ std::cout << " Disposition: " << (int)mData.mNpdt12.mDisposition << std::endl;
+ std::cout << " Faction: " << (int)mData.mNpdt52.mFactionID << std::endl;
+ std::cout << " Rank: " << (int)mData.mNpdt12.mRank << std::endl;
+ std::cout << " Unknown1: "
+ << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl;
+ std::cout << " Unknown2: "
+ << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl;
+ std::cout << " Unknown3: "
+ << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl;
+ std::cout << " Gold: " << (int)mData.mNpdt12.mGold << std::endl;
+ }
+ else {
+ std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl;
+ std::cout << " Reputation: " << (int)mData.mNpdt52.mReputation << std::endl;
+ std::cout << " Disposition: " << (int)mData.mNpdt52.mDisposition << std::endl;
+ std::cout << " Rank: " << (int)mData.mNpdt52.mRank << std::endl;
+
+ std::cout << " Attributes:" << std::endl;
+ std::cout << " Strength: " << (int)mData.mNpdt52.mStrength << std::endl;
+ std::cout << " Intelligence: " << (int)mData.mNpdt52.mIntelligence << std::endl;
+ std::cout << " Willpower: " << (int)mData.mNpdt52.mWillpower << std::endl;
+ std::cout << " Agility: " << (int)mData.mNpdt52.mAgility << std::endl;
+ std::cout << " Speed: " << (int)mData.mNpdt52.mSpeed << std::endl;
+ std::cout << " Endurance: " << (int)mData.mNpdt52.mEndurance << std::endl;
+ std::cout << " Personality: " << (int)mData.mNpdt52.mPersonality << std::endl;
+ std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl;
+
+ std::cout << " Skills:" << std::endl;
+ for (int i = 0; i != 27; i++)
+ std::cout << " " << skillLabel(i) << ": "
+ << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl;
+
+ std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl;
+ std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl;
+ std::cout << " Fatigue: " << mData.mNpdt52.mFatigue << std::endl;
+ std::cout << " Unknown: " << (int)mData.mNpdt52.mUnknown << std::endl;
+ std::cout << " Gold: " << mData.mNpdt52.mGold << std::endl;
+ }
+
+ std::vector<ESM::ContItem>::iterator cit;
+ for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); cit++)
+ std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
+ << " Item: " << cit->mItem.toString() << std::endl;
+
+ std::vector<std::string>::iterator sit;
+ for (sit = mData.mSpells.mList.begin(); sit != mData.mSpells.mList.end(); sit++)
+ std::cout << " Spell: " << *sit << std::endl;
+
+ std::vector<ESM::NPC::Dest>::iterator dit;
+ for (dit = mData.mTransport.begin(); dit != mData.mTransport.end(); dit++)
+ {
+ std::cout << " Destination Position: "
+ << boost::format("%12.3f") % dit->mPos.pos[0] << ","
+ << boost::format("%12.3f") % dit->mPos.pos[1] << ","
+ << boost::format("%12.3f") % dit->mPos.pos[2] << ")" << std::endl;
+ std::cout << " Destination Rotation: "
+ << boost::format("%9.6f") % dit->mPos.rot[0] << ","
+ << boost::format("%9.6f") % dit->mPos.rot[1] << ","
+ << boost::format("%9.6f") % dit->mPos.rot[2] << ")" << std::endl;
+ if (dit->mCellName != "")
+ std::cout << " Destination Cell: " << dit->mCellName << std::endl;
+ }
+
+ std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl;
+ std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl;
+ std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl;
+ std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl;
+ std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl;
+ std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl;
+ std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl;
+ std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl;
+ std::cout << " AI U4:" << (int)mData.mAiData.mU4 << std::endl;
+ std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl;
+
+ std::vector<ESM::AIPackage>::iterator pit;
+ for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); pit++)
+ printAIPackage(*pit);
+}
+
+template<>
+void Record<ESM::Pathgrid>::print()
+{
+ std::cout << " Cell: " << mData.mCell << std::endl;
+ std::cout << " Coordinates: (" << mData.mData.mX << "," << mData.mData.mY << ")" << std::endl;
+ std::cout << " Unknown S1: " << mData.mData.mS1 << std::endl;
+ if ((unsigned int)mData.mData.mS2 != mData.mPoints.size())
+ std::cout << " Reported Point Count: " << mData.mData.mS2 << std::endl;
+ std::cout << " Point Count: " << mData.mPoints.size() << std::endl;
+ std::cout << " Edge Count: " << mData.mEdges.size() << std::endl;
+
+ int i = 0;
+ ESM::Pathgrid::PointList::iterator pit;
+ for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); pit++)
+ {
+ std::cout << " Point[" << i << "]:" << std::endl;
+ std::cout << " Coordinates: (" << pit->mX << ","
+ << pit->mY << "," << pit->mZ << ")" << std::endl;
+ std::cout << " Auto-Generated: " << (int)pit->mAutogenerated << std::endl;
+ std::cout << " Connections: " << (int)pit->mConnectionNum << std::endl;
+ std::cout << " Unknown: " << pit->mUnknown << std::endl;
+ i++;
+ }
+ i = 0;
+ ESM::Pathgrid::EdgeList::iterator eit;
+ for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); eit++)
+ {
+ std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl;
+ if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2)
+ std::cout << " BAD POINT IN EDGE!" << std::endl;
+ i++;
+ }
+}
+
+template<>
+void Record<ESM::Race>::print()
+{
+ static const char *sAttributeNames[8] =
+ {
+ "Strength", "Intelligence", "Willpower", "Agility",
+ "Speed", "Endurance", "Personality", "Luck"
+ };
+
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Description: " << mData.mDescription << std::endl;
+ std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl;
+
+ for (int i=0; i<2; ++i)
+ {
+ bool male = i==0;
+
+ std::cout << (male ? " Male:" : " Female:") << std::endl;
+
+ for (int i=0; i<8; ++i)
+ std::cout << " " << sAttributeNames[i] << ": "
+ << mData.mData.mAttributeValues[i].getValue (male) << std::endl;
+
+ std::cout << " Height: " << mData.mData.mHeight.getValue (male) << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight.getValue (male) << std::endl;
+ }
+
+ for (int i = 0; i != 7; i++)
+ // Not all races have 7 skills.
+ if (mData.mData.mBonus[i].mSkill != -1)
+ std::cout << " Skill: "
+ << skillLabel(mData.mData.mBonus[i].mSkill)
+ << " (" << mData.mData.mBonus[i].mSkill << ") = "
+ << mData.mData.mBonus[i].mBonus << std::endl;
+
+ std::vector<std::string>::iterator sit;
+ for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); sit++)
+ std::cout << " Power: " << *sit << std::endl;
+}
+
+template<>
+void Record<ESM::Region>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+
+ std::cout << " Weather:" << std::endl;
+ std::cout << " Clear: " << (int)mData.mData.mClear << std::endl;
+ std::cout << " Cloudy: " << (int)mData.mData.mCloudy << std::endl;
+ std::cout << " Foggy: " << (int)mData.mData.mFoggy << std::endl;
+ std::cout << " Overcast: " << (int)mData.mData.mOvercast << std::endl;
+ std::cout << " Rain: " << (int)mData.mData.mOvercast << std::endl;
+ std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl;
+ std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl;
+ std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl;
+ std::cout << " UnknownA: " << (int)mData.mData.mA << std::endl;
+ std::cout << " UnknownB: " << (int)mData.mData.mB << std::endl;
+ std::cout << " Map Color: " << mData.mMapColor << std::endl;
+ if (mData.mSleepList != "")
+ std::cout << " Sleep List: " << mData.mSleepList << std::endl;
+ std::vector<ESM::Region::SoundRef>::iterator sit;
+ for (sit = mData.mSoundList.begin(); sit != mData.mSoundList.end(); sit++)
+ std::cout << " Sound: " << (int)sit->mChance << " = " << sit->mSound.toString() << std::endl;
+}
+
+template<>
+void Record<ESM::Script>::print()
+{
+ std::cout << " Name: " << mData.mId << std::endl;
+
+ std::cout << " Num Shorts: " << mData.mData.mNumShorts << std::endl;
+ std::cout << " Num Longs: " << mData.mData.mNumLongs << std::endl;
+ std::cout << " Num Floats: " << mData.mData.mNumFloats << std::endl;
+ std::cout << " Script Data Size: " << mData.mData.mScriptDataSize << std::endl;
+ std::cout << " Table Size: " << mData.mData.mStringTableSize << std::endl;
+
+
+ std::vector<std::string>::iterator vit;
+ for (vit = mData.mVarNames.begin(); vit != mData.mVarNames.end(); vit++)
+ std::cout << " Variable: " << *vit << std::endl;
+
+ std::cout << " ByteCode: ";
+ std::vector<unsigned char>::iterator cit;
+ for (cit = mData.mScriptData.begin(); cit != mData.mScriptData.end(); cit++)
+ std::cout << boost::format("%02X") % (int)(*cit);
+ std::cout << std::endl;
+
+ if (mPrintPlain)
+ {
+ std::cout << " Script:" << std::endl;
+ std::cout << "START--------------------------------------" << std::endl;
+ std::cout << mData.mScriptText << std::endl;
+ std::cout << "END----------------------------------------" << std::endl;
+ }
+ else
+ {
+ std::cout << " Script: [skipped]" << std::endl;
+ }
+}
+
+template<>
+void Record<ESM::Skill>::print()
+{
+ std::cout << " ID: " << skillLabel(mData.mIndex)
+ << " (" << mData.mIndex << ")" << std::endl;
+ std::cout << " Description: " << mData.mDescription << std::endl;
+ std::cout << " Governing Attribute: " << attributeLabel(mData.mData.mAttribute)
+ << " (" << mData.mData.mAttribute << ")" << std::endl;
+ std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization)
+ << " (" << mData.mData.mSpecialization << ")" << std::endl;
+ for (int i = 0; i != 4; i++)
+ std::cout << " UseValue[" << i << "]:" << mData.mData.mUseValue[i] << std::endl;
+}
+
+template<>
+void Record<ESM::SoundGenerator>::print()
+{
+ std::cout << " Creature: " << mData.mCreature << std::endl;
+ std::cout << " Sound: " << mData.mSound << std::endl;
+ std::cout << " Type: " << soundTypeLabel(mData.mType)
+ << " (" << mData.mType << ")" << std::endl;
+}
+
+template<>
+void Record<ESM::Sound>::print()
+{
+ std::cout << " Sound: " << mData.mSound << std::endl;
+ std::cout << " Volume: " << (int)mData.mData.mVolume << std::endl;
+ if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
+ std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
+ << (int)mData.mData.mMaxRange << std::endl;
+}
+
+template<>
+void Record<ESM::Spell>::print()
+{
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Type: " << spellTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
+ std::cout << " Cost: " << mData.mData.mCost << std::endl;
+ printEffectList(mData.mEffects);
+}
+
+template<>
+void Record<ESM::StartScript>::print()
+{
+ std::cout << "Start Script: " << mData.mScript << std::endl;
+ std::cout << "Start Data: " << mData.mData << std::endl;
+}
+
+template<>
+void Record<ESM::Static>::print()
+{
+ std::cout << " Model: " << mData.mModel << std::endl;
+}
+
+template<>
+void Record<ESM::Weapon>::print()
+{
+ // No names on VFX bolts
+ if (mData.mName != "")
+ std::cout << " Name: " << mData.mName << std::endl;
+ std::cout << " Model: " << mData.mModel << std::endl;
+ // No icons on VFX bolts or magic bolts
+ if (mData.mIcon != "")
+ std::cout << " Icon: " << mData.mIcon << std::endl;
+ if (mData.mScript != "")
+ std::cout << " Script: " << mData.mScript << std::endl;
+ if (mData.mEnchant != "")
+ std::cout << " Enchantment: " << mData.mEnchant << std::endl;
+ std::cout << " Type: " << weaponTypeLabel(mData.mData.mType)
+ << " (" << mData.mData.mType << ")" << std::endl;
+ std::cout << " Flags: " << weaponFlags(mData.mData.mFlags) << std::endl;
+ std::cout << " Weight: " << mData.mData.mWeight << std::endl;
+ std::cout << " Value: " << mData.mData.mValue << std::endl;
+ std::cout << " Health: " << mData.mData.mHealth << std::endl;
+ std::cout << " Speed: " << mData.mData.mSpeed << std::endl;
+ std::cout << " Reach: " << mData.mData.mReach << std::endl;
+ std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
+ if (mData.mData.mChop[0] != 0 && mData.mData.mChop[1] != 0)
+ std::cout << " Chop: " << (int)mData.mData.mChop[0] << "-"
+ << (int)mData.mData.mChop[1] << std::endl;
+ if (mData.mData.mSlash[0] != 0 && mData.mData.mSlash[1] != 0)
+ std::cout << " Slash: " << (int)mData.mData.mSlash[0] << "-"
+ << (int)mData.mData.mSlash[1] << std::endl;
+ if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
+ std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
+ << (int)mData.mData.mThrust[1] << std::endl;
+}
+
+} // end namespace
diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp
new file mode 100644
index 0000000000..78cf5d436e
--- /dev/null
+++ b/apps/esmtool/record.hpp
@@ -0,0 +1,136 @@
+#ifndef OPENMW_ESMTOOL_RECORD_H
+#define OPENMW_ESMTOOL_RECORD_H
+
+#include <string>
+
+#include <components/esm/records.hpp>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+}
+
+namespace EsmTool
+{
+ template <class T> class Record;
+
+ class RecordBase
+ {
+ protected:
+ std::string mId;
+ int mFlags;
+ ESM::NAME mType;
+ bool mPrintPlain;
+
+ public:
+ RecordBase () { mPrintPlain = false; }
+ virtual ~RecordBase() {}
+
+ const std::string &getId() const {
+ return mId;
+ }
+
+ void setId(const std::string &id) {
+ mId = id;
+ }
+
+ int getFlags() const {
+ return mFlags;
+ }
+
+ void setFlags(int flags) {
+ mFlags = flags;
+ }
+
+ ESM::NAME getType() const {
+ return mType;
+ }
+
+ bool getPrintPlain() const {
+ return mPrintPlain;
+ }
+
+ void setPrintPlain(bool plain) {
+ mPrintPlain = plain;
+ }
+
+ virtual void load(ESM::ESMReader &esm) = 0;
+ virtual void save(ESM::ESMWriter &esm) = 0;
+ virtual void print() = 0;
+
+ static RecordBase *create(ESM::NAME type);
+
+ // just make it a bit shorter
+ template <class T>
+ Record<T> *cast() {
+ return static_cast<Record<T> *>(this);
+ }
+ };
+
+ template <class T>
+ class Record : public RecordBase
+ {
+ T mData;
+
+ public:
+ T &get() {
+ return mData;
+ }
+
+ void save(ESM::ESMWriter &esm) {
+ mData.save(esm);
+ }
+
+ void load(ESM::ESMReader &esm) {
+ mData.load(esm);
+ }
+
+ void print();
+ };
+
+ template<> void Record<ESM::Activator>::print();
+ template<> void Record<ESM::Potion>::print();
+ template<> void Record<ESM::Armor>::print();
+ template<> void Record<ESM::Apparatus>::print();
+ template<> void Record<ESM::BodyPart>::print();
+ template<> void Record<ESM::Book>::print();
+ template<> void Record<ESM::BirthSign>::print();
+ template<> void Record<ESM::Cell>::print();
+ template<> void Record<ESM::Class>::print();
+ template<> void Record<ESM::Clothing>::print();
+ template<> void Record<ESM::Container>::print();
+ template<> void Record<ESM::Creature>::print();
+ template<> void Record<ESM::Dialogue>::print();
+ template<> void Record<ESM::Door>::print();
+ template<> void Record<ESM::Enchantment>::print();
+ template<> void Record<ESM::Faction>::print();
+ template<> void Record<ESM::Global>::print();
+ template<> void Record<ESM::GameSetting>::print();
+ template<> void Record<ESM::DialInfo>::print();
+ template<> void Record<ESM::Ingredient>::print();
+ template<> void Record<ESM::Land>::print();
+ template<> void Record<ESM::CreatureLevList>::print();
+ template<> void Record<ESM::ItemLevList>::print();
+ template<> void Record<ESM::Light>::print();
+ template<> void Record<ESM::Lockpick>::print();
+ template<> void Record<ESM::Probe>::print();
+ template<> void Record<ESM::Repair>::print();
+ template<> void Record<ESM::LandTexture>::print();
+ template<> void Record<ESM::MagicEffect>::print();
+ template<> void Record<ESM::Miscellaneous>::print();
+ template<> void Record<ESM::NPC>::print();
+ template<> void Record<ESM::Pathgrid>::print();
+ template<> void Record<ESM::Race>::print();
+ template<> void Record<ESM::Region>::print();
+ template<> void Record<ESM::Script>::print();
+ template<> void Record<ESM::Skill>::print();
+ template<> void Record<ESM::SoundGenerator>::print();
+ template<> void Record<ESM::Sound>::print();
+ template<> void Record<ESM::Spell>::print();
+ template<> void Record<ESM::StartScript>::print();
+ template<> void Record<ESM::Static>::print();
+ template<> void Record<ESM::Weapon>::print();
+}
+
+#endif
diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt
new file mode 100644
index 0000000000..5908deb907
--- /dev/null
+++ b/apps/launcher/CMakeLists.txt
@@ -0,0 +1,139 @@
+set(LAUNCHER
+ datafilespage.cpp
+ graphicspage.cpp
+ main.cpp
+ maindialog.cpp
+ playpage.cpp
+ textslotmsgbox.cpp
+
+ settings/gamesettings.cpp
+ settings/graphicssettings.cpp
+ settings/launchersettings.cpp
+
+ utils/checkablemessagebox.cpp
+ utils/textinputdialog.cpp
+
+ ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc
+)
+if(NOT WIN32)
+ LIST(APPEND LAUNCHER unshieldthread.cpp)
+endif(NOT WIN32)
+
+set(LAUNCHER_HEADER
+ datafilespage.hpp
+ graphicspage.hpp
+ maindialog.hpp
+ playpage.hpp
+ unshieldthread.hpp
+ textslotmsgbox.hpp
+
+ settings/gamesettings.hpp
+ settings/graphicssettings.hpp
+ settings/launchersettings.hpp
+ settings/settingsbase.hpp
+
+ utils/checkablemessagebox.hpp
+ utils/textinputdialog.hpp
+
+)
+if(NOT WIN32)
+ LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp)
+endif(NOT WIN32)
+
+
+# Headers that must be pre-processed
+set(LAUNCHER_HEADER_MOC
+ datafilespage.hpp
+ graphicspage.hpp
+ maindialog.hpp
+ playpage.hpp
+ unshieldthread.hpp
+ textslotmsgbox.hpp
+
+ utils/checkablemessagebox.hpp
+ utils/textinputdialog.hpp
+)
+
+if(NOT WIN32)
+ LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp)
+endif(NOT WIN32)
+
+
+set(LAUNCHER_UI
+ ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
+ ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
+ ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui
+ ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui
+)
+
+source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
+
+find_package(Qt4 REQUIRED)
+set(QT_USE_QTGUI 1)
+
+# Set some platform specific settings
+if(WIN32)
+ set(GUI_TYPE WIN32)
+ set(QT_USE_QTMAIN TRUE)
+endif(WIN32)
+
+QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
+QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
+QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
+
+
+include(${QT_USE_FILE})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+if(NOT WIN32)
+ include_directories(${LIBUNSHIELD_INCLUDE})
+endif(NOT WIN32)
+
+# Main executable
+IF(OGRE_STATIC)
+IF(WIN32)
+ADD_DEFINITIONS(-DENABLE_PLUGIN_Direct3D9 -DENABLE_PLUGIN_GL)
+set(OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_Direct3D9_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES})
+ELSE(WIN32)
+ADD_DEFINITIONS(-DENABLE_PLUGIN_GL)
+set(OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_GL_LIBRARIES})
+ENDIF(WIN32)
+ENDIF(OGRE_STATIC)
+add_executable(omwlauncher
+ ${GUI_TYPE}
+ ${LAUNCHER}
+ ${LAUNCHER_HEADER}
+ ${RCC_SRCS}
+ ${MOC_SRCS}
+ ${UI_HDRS}
+)
+
+target_link_libraries(omwlauncher
+ ${Boost_LIBRARIES}
+ ${OGRE_LIBRARIES}
+ ${OGRE_STATIC_PLUGINS}
+ ${SDL2_LIBRARY}
+ ${QT_LIBRARIES}
+ components
+)
+if(NOT WIN32)
+ target_link_libraries(omwlauncher
+ ${LIBUNSHIELD_LIBRARY}
+ )
+endif(NOT WIN32)
+
+
+
+if(DPKG_PROGRAM)
+ INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher)
+endif()
+
+if (BUILD_WITH_CODE_COVERAGE)
+ add_definitions (--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
new file mode 100644
index 0000000000..add3dea40e
--- /dev/null
+++ b/apps/launcher/datafilespage.cpp
@@ -0,0 +1,551 @@
+#include "datafilespage.hpp"
+
+#include <QPushButton>
+#include <QMessageBox>
+#include <QCheckBox>
+#include <QMenu>
+
+#include <components/files/configurationmanager.hpp>
+
+#include <components/fileorderlist/model/datafilesmodel.hpp>
+#include <components/fileorderlist/model/pluginsproxymodel.hpp>
+#include <components/fileorderlist/model/esm/esmfile.hpp>
+
+#include <components/fileorderlist/utils/lineedit.hpp>
+#include <components/fileorderlist/utils/naturalsort.hpp>
+#include <components/fileorderlist/utils/profilescombobox.hpp>
+
+#include "settings/gamesettings.hpp"
+#include "settings/launchersettings.hpp"
+
+#include "utils/textinputdialog.hpp"
+
+DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent)
+ : mCfgMgr(cfg)
+ , mGameSettings(gameSettings)
+ , mLauncherSettings(launcherSettings)
+ , QWidget(parent)
+{
+ setupUi(this);
+
+ // Models
+ mDataFilesModel = new DataFilesModel(this);
+
+ mMastersProxyModel = new QSortFilterProxyModel();
+ mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm"));
+ mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ mMastersProxyModel->setSourceModel(mDataFilesModel);
+
+ mPluginsProxyModel = new PluginsProxyModel();
+ mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp"));
+ mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ mPluginsProxyModel->setSourceModel(mDataFilesModel);
+
+ mFilterProxyModel = new QSortFilterProxyModel();
+ mFilterProxyModel->setDynamicSortFilter(true);
+ mFilterProxyModel->setSourceModel(mPluginsProxyModel);
+
+ QCheckBox checkBox;
+ unsigned int height = checkBox.sizeHint().height() + 4;
+
+ mastersTable->setModel(mMastersProxyModel);
+ mastersTable->setObjectName("MastersTable");
+ mastersTable->setContextMenuPolicy(Qt::CustomContextMenu);
+ mastersTable->setSortingEnabled(false);
+ mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ mastersTable->setAlternatingRowColors(true);
+ mastersTable->horizontalHeader()->setStretchLastSection(true);
+ mastersTable->horizontalHeader()->hide();
+
+ // Set the row height to the size of the checkboxes
+ mastersTable->verticalHeader()->setDefaultSectionSize(height);
+ mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
+ mastersTable->verticalHeader()->hide();
+
+ pluginsTable->setModel(mFilterProxyModel);
+ pluginsTable->setObjectName("PluginsTable");
+ pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
+ pluginsTable->setSortingEnabled(false);
+ pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ pluginsTable->setAlternatingRowColors(true);
+ pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
+ pluginsTable->horizontalHeader()->setStretchLastSection(true);
+ pluginsTable->horizontalHeader()->hide();
+
+ pluginsTable->verticalHeader()->setDefaultSectionSize(height);
+ pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
+
+ // Adjust the tableview widths inside the splitter
+ QList<int> sizeList;
+ sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt();
+ sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt();
+
+ splitter->setSizes(sizeList);
+
+ // Create a dialog for the new profile name input
+ mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
+
+ connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
+
+ connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString)));
+
+ connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
+ connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
+
+ connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
+ connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
+
+ connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews()));
+
+ connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
+
+ connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter()));
+
+ createActions();
+ setupDataFiles();
+}
+
+void DataFilesPage::createActions()
+{
+
+ // Add the actions to the toolbuttons
+ newProfileButton->setDefaultAction(newProfileAction);
+ deleteProfileButton->setDefaultAction(deleteProfileAction);
+
+ // Context menu actions
+ mContextMenu = new QMenu(this);
+ mContextMenu->addAction(checkAction);
+ mContextMenu->addAction(uncheckAction);
+}
+
+void DataFilesPage::setupDataFiles()
+{
+ // Set the encoding to the one found in openmw.cfg or the default
+ mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252")));
+
+ QStringList paths = mGameSettings.getDataDirs();
+
+ foreach (const QString &path, paths) {
+ mDataFilesModel->addFiles(path);
+ }
+
+ QString dataLocal = mGameSettings.getDataLocal();
+ if (!dataLocal.isEmpty())
+ mDataFilesModel->addFiles(dataLocal);
+
+ // Sort by date accessed for now
+ mDataFilesModel->sort(3);
+
+ QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/"));
+ QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
+
+ if (!profiles.isEmpty())
+ profilesComboBox->addItems(profiles);
+
+ // Add the current profile if empty
+ if (profilesComboBox->findText(profile) == -1 && !profile.isEmpty())
+ profilesComboBox->addItem(profile);
+
+ if (profilesComboBox->findText(QString("Default")) == -1)
+ profilesComboBox->addItem(QString("Default"));
+
+ if (profile.isEmpty() || profile == QLatin1String("Default")) {
+ deleteProfileAction->setEnabled(false);
+ profilesComboBox->setEditEnabled(false);
+ profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default")));
+ } else {
+ profilesComboBox->setEditEnabled(true);
+ profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
+ }
+
+ // We do this here to prevent deletion of profiles when initializing the combobox
+ connect(profilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString)));
+ connect(profilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString)));
+
+ loadSettings();
+
+}
+
+void DataFilesPage::loadSettings()
+{
+ QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
+
+ if (profile.isEmpty())
+ return;
+
+ mDataFilesModel->uncheckAll();
+
+ QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly);
+ QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly);
+
+ foreach (const QString &master, masters) {
+ QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master));
+ if (index.isValid())
+ mDataFilesModel->setCheckState(index, Qt::Checked);
+ }
+
+ foreach (const QString &plugin, plugins) {
+ QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin));
+ if (index.isValid())
+ mDataFilesModel->setCheckState(index, Qt::Checked);
+ }
+}
+
+void DataFilesPage::saveSettings()
+{
+ if (mDataFilesModel->rowCount() < 1)
+ return;
+
+ QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
+
+ if (profile.isEmpty()) {
+ profile = profilesComboBox->currentText();
+ mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile);
+ }
+
+ mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
+ mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
+
+ mGameSettings.remove(QString("master"));
+ mGameSettings.remove(QString("plugin"));
+
+ QStringList items = mDataFilesModel->checkedItems();
+
+ foreach(const QString &item, items) {
+
+ if (item.endsWith(QString(".esm"), Qt::CaseInsensitive)) {
+ mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item);
+ mGameSettings.setMultiValue(QString("master"), item);
+
+ } else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) {
+ mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item);
+ mGameSettings.setMultiValue(QString("plugin"), item);
+ }
+ }
+
+}
+
+void DataFilesPage::updateOkButton(const QString &text)
+{
+ // We do this here because we need the profiles combobox text
+ if (text.isEmpty()) {
+ mNewProfileDialog->setOkButtonEnabled(false);
+ return;
+ }
+
+ (profilesComboBox->findText(text) == -1)
+ ? mNewProfileDialog->setOkButtonEnabled(true)
+ : mNewProfileDialog->setOkButtonEnabled(false);
+}
+
+void DataFilesPage::updateSplitter()
+{
+ // Sigh, update the saved splitter size in settings only when moved
+ // Since getting mSplitter->sizes() if page is hidden returns invalid values
+ QList<int> sizes = splitter->sizes();
+
+ mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0)));
+ mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1)));
+}
+
+void DataFilesPage::updateViews()
+{
+ // Ensure the columns are hidden because sort() re-enables them
+ mastersTable->setColumnHidden(1, true);
+ mastersTable->setColumnHidden(2, true);
+ mastersTable->setColumnHidden(3, true);
+ mastersTable->setColumnHidden(4, true);
+ mastersTable->setColumnHidden(5, true);
+ mastersTable->setColumnHidden(6, true);
+ mastersTable->setColumnHidden(7, true);
+ mastersTable->setColumnHidden(8, true);
+
+ pluginsTable->setColumnHidden(1, true);
+ pluginsTable->setColumnHidden(2, true);
+ pluginsTable->setColumnHidden(3, true);
+ pluginsTable->setColumnHidden(4, true);
+ pluginsTable->setColumnHidden(5, true);
+ pluginsTable->setColumnHidden(6, true);
+ pluginsTable->setColumnHidden(7, true);
+ pluginsTable->setColumnHidden(8, true);
+}
+
+void DataFilesPage::setProfilesComboBoxIndex(int index)
+{
+ profilesComboBox->setCurrentIndex(index);
+}
+
+void DataFilesPage::slotCurrentIndexChanged(int index)
+{
+ emit profileChanged(index);
+}
+
+QAbstractItemModel* DataFilesPage::profilesComboBoxModel()
+{
+ return profilesComboBox->model();
+}
+
+int DataFilesPage::profilesComboBoxIndex()
+{
+ return profilesComboBox->currentIndex();
+}
+
+void DataFilesPage::on_newProfileAction_triggered()
+{
+ if (mNewProfileDialog->exec() == QDialog::Accepted) {
+ QString profile = mNewProfileDialog->lineEdit()->text();
+ profilesComboBox->addItem(profile);
+ profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
+ }
+}
+
+void DataFilesPage::on_deleteProfileAction_triggered()
+{
+ QString profile = profilesComboBox->currentText();
+
+ if (profile.isEmpty())
+ return;
+
+ QMessageBox msgBox(this);
+ msgBox.setWindowTitle(tr("Delete Profile"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Cancel);
+ msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(profile));
+
+ QAbstractButton *deleteButton =
+ msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
+
+ msgBox.exec();
+
+ if (msgBox.clickedButton() == deleteButton) {
+ mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
+ mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
+
+ // Remove the profile from the combobox
+ profilesComboBox->removeItem(profilesComboBox->findText(profile));
+ }
+}
+
+void DataFilesPage::on_checkAction_triggered()
+{
+ if (pluginsTable->hasFocus())
+ setPluginsCheckstates(Qt::Checked);
+
+ if (mastersTable->hasFocus())
+ setMastersCheckstates(Qt::Checked);
+
+}
+
+void DataFilesPage::on_uncheckAction_triggered()
+{
+ if (pluginsTable->hasFocus())
+ setPluginsCheckstates(Qt::Unchecked);
+
+ if (mastersTable->hasFocus())
+ setMastersCheckstates(Qt::Unchecked);
+}
+
+void DataFilesPage::setMastersCheckstates(Qt::CheckState state)
+{
+ if (!mastersTable->selectionModel()->hasSelection()) {
+ return;
+ }
+
+ QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
+
+ foreach (const QModelIndex &index, indexes)
+ {
+ if (!index.isValid())
+ return;
+
+ QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
+
+ if (!sourceIndex.isValid())
+ return;
+
+ mDataFilesModel->setCheckState(sourceIndex, state);
+ }
+}
+
+void DataFilesPage::setPluginsCheckstates(Qt::CheckState state)
+{
+ if (!pluginsTable->selectionModel()->hasSelection()) {
+ return;
+ }
+
+ QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
+
+ foreach (const QModelIndex &index, indexes)
+ {
+ if (!index.isValid())
+ return;
+
+ QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
+ mFilterProxyModel->mapToSource(index));
+
+ if (!sourceIndex.isValid())
+ return;
+
+ mDataFilesModel->setCheckState(sourceIndex, state);
+ }
+}
+
+void DataFilesPage::setCheckState(QModelIndex index)
+{
+ if (!index.isValid())
+ return;
+
+ QObject *object = QObject::sender();
+
+ // Not a signal-slot call
+ if (!object)
+ return;
+
+
+ if (object->objectName() == QLatin1String("PluginsTable")) {
+ QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
+ mFilterProxyModel->mapToSource(index));
+
+ if (sourceIndex.isValid()) {
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
+ : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
+ }
+ }
+
+ if (object->objectName() == QLatin1String("MastersTable")) {
+ QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
+
+ if (sourceIndex.isValid()) {
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
+ : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
+ }
+ }
+
+ return;
+}
+
+void DataFilesPage::filterChanged(const QString filter)
+{
+ QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
+ mFilterProxyModel->setFilterRegExp(regExp);
+}
+
+void DataFilesPage::profileChanged(const QString &previous, const QString &current)
+{
+ // Prevent the deletion of the default profile
+ if (current == QLatin1String("Default")) {
+ deleteProfileAction->setEnabled(false);
+ profilesComboBox->setEditEnabled(false);
+ } else {
+ deleteProfileAction->setEnabled(true);
+ profilesComboBox->setEditEnabled(true);
+ }
+
+ if (previous.isEmpty())
+ return;
+
+ if (profilesComboBox->findText(previous) == -1)
+ return; // Profile was deleted
+
+ // Store the previous profile
+ mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous);
+ saveSettings();
+ mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
+
+ loadSettings();
+}
+
+void DataFilesPage::profileRenamed(const QString &previous, const QString &current)
+{
+ if (previous.isEmpty())
+ return;
+
+ // Save the new profile name
+ mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
+ saveSettings();
+
+ // Remove the old one
+ mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master"));
+ mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin"));
+
+ // Remove the profile from the combobox
+ profilesComboBox->removeItem(profilesComboBox->findText(previous));
+
+ loadSettings();
+
+}
+
+void DataFilesPage::showContextMenu(const QPoint &point)
+{
+ QObject *object = QObject::sender();
+
+ // Not a signal-slot call
+ if (!object)
+ return;
+
+ if (object->objectName() == QLatin1String("PluginsTable")) {
+ if (!pluginsTable->selectionModel()->hasSelection())
+ return;
+
+ QPoint globalPos = pluginsTable->mapToGlobal(point);
+ QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
+
+ // Show the check/uncheck actions depending on the state of the selected items
+ uncheckAction->setEnabled(false);
+ checkAction->setEnabled(false);
+
+ foreach (const QModelIndex &index, indexes)
+ {
+ if (!index.isValid())
+ return;
+
+ QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
+ mFilterProxyModel->mapToSource(index));
+
+ if (!sourceIndex.isValid())
+ return;
+
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? uncheckAction->setEnabled(true)
+ : checkAction->setEnabled(true);
+ }
+
+ // Show menu
+ mContextMenu->exec(globalPos);
+ }
+
+ if (object->objectName() == QLatin1String("MastersTable")) {
+ if (!mastersTable->selectionModel()->hasSelection())
+ return;
+
+ QPoint globalPos = mastersTable->mapToGlobal(point);
+ QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
+
+ // Show the check/uncheck actions depending on the state of the selected items
+ uncheckAction->setEnabled(false);
+ checkAction->setEnabled(false);
+
+ foreach (const QModelIndex &index, indexes)
+ {
+ if (!index.isValid())
+ return;
+
+ QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
+
+ if (!sourceIndex.isValid())
+ return;
+
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? uncheckAction->setEnabled(true)
+ : checkAction->setEnabled(true);
+ }
+
+ mContextMenu->exec(globalPos);
+ }
+}
diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp
new file mode 100644
index 0000000000..a0b0293309
--- /dev/null
+++ b/apps/launcher/datafilespage.hpp
@@ -0,0 +1,88 @@
+#ifndef DATAFILESPAGE_H
+#define DATAFILESPAGE_H
+
+#include <QWidget>
+#include <QModelIndex>
+
+#include "ui_datafilespage.h"
+
+class QSortFilterProxyModel;
+class QAbstractItemModel;
+class QAction;
+class QMenu;
+
+class DataFilesModel;
+class TextInputDialog;
+class GameSettings;
+class LauncherSettings;
+class PluginsProxyModel;
+
+namespace Files { struct ConfigurationManager; }
+
+class DataFilesPage : public QWidget, private Ui::DataFilesPage
+{
+ Q_OBJECT
+
+public:
+ DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0);
+
+ QAbstractItemModel* profilesComboBoxModel();
+ int profilesComboBoxIndex();
+
+ void writeConfig(QString profile = QString());
+ void saveSettings();
+
+signals:
+ void profileChanged(int index);
+
+public slots:
+ void setCheckState(QModelIndex index);
+ void setProfilesComboBoxIndex(int index);
+
+ void filterChanged(const QString filter);
+ void showContextMenu(const QPoint &point);
+ void profileChanged(const QString &previous, const QString &current);
+ void profileRenamed(const QString &previous, const QString &current);
+ void updateOkButton(const QString &text);
+ void updateSplitter();
+ void updateViews();
+
+ // Action slots
+ void on_newProfileAction_triggered();
+ void on_deleteProfileAction_triggered();
+ void on_checkAction_triggered();
+ void on_uncheckAction_triggered();
+
+private slots:
+ void slotCurrentIndexChanged(int index);
+
+private:
+ DataFilesModel *mDataFilesModel;
+
+ PluginsProxyModel *mPluginsProxyModel;
+ QSortFilterProxyModel *mMastersProxyModel;
+
+ QSortFilterProxyModel *mFilterProxyModel;
+
+ QMenu *mContextMenu;
+
+ Files::ConfigurationManager &mCfgMgr;
+
+ GameSettings &mGameSettings;
+ LauncherSettings &mLauncherSettings;
+
+ TextInputDialog *mNewProfileDialog;
+
+ void setMastersCheckstates(Qt::CheckState state);
+ void setPluginsCheckstates(Qt::CheckState state);
+
+ void createActions();
+ void setupDataFiles();
+ void setupConfig();
+ void readConfig();
+
+ void loadSettings();
+
+};
+
+#endif
diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp
new file mode 100644
index 0000000000..9308c1d572
--- /dev/null
+++ b/apps/launcher/graphicspage.cpp
@@ -0,0 +1,385 @@
+#include "graphicspage.hpp"
+
+#include <QDesktopWidget>
+#include <QMessageBox>
+#include <QDir>
+
+#ifdef __APPLE__
+// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
+#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
+#endif
+#include <SDL.h>
+
+#include <cstdlib>
+
+#include <boost/math/common_factor.hpp>
+
+#include <components/files/configurationmanager.hpp>
+#include <components/files/ogreplugin.hpp>
+
+#include <components/fileorderlist/utils/naturalsort.hpp>
+
+#include "settings/graphicssettings.hpp"
+
+QString getAspect(int x, int y)
+{
+ int gcd = boost::math::gcd (x, y);
+ int xaspect = x / gcd;
+ int yaspect = y / gcd;
+ // special case: 8 : 5 is usually referred to as 16:10
+ if (xaspect == 8 && yaspect == 5)
+ return QString("16:10");
+
+ return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
+}
+
+GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent)
+ : mCfgMgr(cfg)
+ , mGraphicsSettings(graphicsSetting)
+ , QWidget(parent)
+{
+ setupUi(this);
+
+ // Set the maximum res we can set in windowed mode
+ QRect res = getMaximumResolution();
+ customWidthSpinBox->setMaximum(res.width());
+ customHeightSpinBox->setMaximum(res.height());
+
+ connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&)));
+ connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
+ connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
+ connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
+
+}
+
+bool 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");
+ }
+ catch(Ogre::Exception &ex)
+ {
+ QString ogreError = QString::fromStdString(ex.getFullDescription().c_str());
+ QMessageBox msgBox;
+ msgBox.setWindowTitle("Error creating Ogre::Root");
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Failed to create the Ogre::Root object</b><br><br> \
+ Press \"Show Details...\" for more information.<br>"));
+ msgBox.setDetailedText(ogreError);
+ msgBox.exec();
+
+ qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError));
+ 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();
+
+ for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) {
+ mSelectedRenderSystem = *r;
+ rendererComboBox->addItem((*r)->getName().c_str());
+ }
+
+ QString openGLName = QString("OpenGL Rendering Subsystem");
+ QString direct3DName = QString("Direct3D9 Rendering Subsystem");
+
+ // Create separate rendersystems
+ mOpenGLRenderSystem = mOgre->getRenderSystemByName(openGLName.toStdString());
+ mDirect3DRenderSystem = mOgre->getRenderSystemByName(direct3DName.toStdString());
+
+ if (!mOpenGLRenderSystem && !mDirect3DRenderSystem) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error creating renderer"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \
+ Please make sure the plugins.cfg file exists and contains a valid rendering plugin.<br>"));
+ msgBox.exec();
+ return false;
+ }
+
+ // Now fill the GUI elements
+ int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system")));
+ if ( index != -1) {
+ rendererComboBox->setCurrentIndex(index);
+ } else {
+#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName));
+#else
+ rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName));
+#endif
+ }
+
+ antiAliasingComboBox->clear();
+ antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
+
+ return true;
+}
+
+bool GraphicsPage::setupSDL()
+{
+ int displays = SDL_GetNumVideoDisplays();
+
+ if (displays < 0)
+ {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error receiving number of screens"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
+ msgBox.exec();
+ return false;
+ }
+
+ for (int i = 0; i < displays; i++)
+ {
+ screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
+ }
+
+ return true;
+}
+
+bool GraphicsPage::loadSettings()
+{
+ if (!setupSDL())
+ return false;
+ if (!setupOgre())
+ return false;
+
+ if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true"))
+ vSyncCheckBox->setCheckState(Qt::Checked);
+
+ if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true"))
+ fullScreenCheckBox->setCheckState(Qt::Checked);
+
+ int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing")));
+ if (aaIndex != -1)
+ antiAliasingComboBox->setCurrentIndex(aaIndex);
+
+ QString width = mGraphicsSettings.value(QString("Video/resolution x"));
+ QString height = mGraphicsSettings.value(QString("Video/resolution y"));
+ QString resolution = width + QString(" x ") + height;
+ QString screen = mGraphicsSettings.value(QString("Video/screen"));
+
+ screenComboBox->setCurrentIndex(screen.toInt());
+
+ int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
+
+ if (resIndex != -1) {
+ standardRadioButton->toggle();
+ resolutionComboBox->setCurrentIndex(resIndex);
+ } else {
+ customRadioButton->toggle();
+ customWidthSpinBox->setValue(width.toInt());
+ customHeightSpinBox->setValue(height.toInt());
+
+ }
+
+ return true;
+}
+
+void GraphicsPage::saveSettings()
+{
+ vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
+ : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
+
+ fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true"))
+ : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false"));
+
+ mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText());
+ mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText());
+
+
+ if (standardRadioButton->isChecked()) {
+ QRegExp resolutionRe(QString("(\\d+) x (\\d+).*"));
+
+ if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) {
+ mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1));
+ mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2));
+ }
+ } else {
+ mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value()));
+ mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value()));
+ }
+
+ mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex()));
+}
+
+QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer)
+{
+ QStringList result;
+
+ uint row = 0;
+ Ogre::ConfigOptionMap options = renderer->getConfigOptions();
+
+ for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); i++, row++)
+ {
+ Ogre::StringVector::iterator opt_it;
+ uint idx = 0;
+
+ for (opt_it = i->second.possibleValues.begin();
+ opt_it != i->second.possibleValues.end(); opt_it++, idx++)
+ {
+ if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) {
+ result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified();
+ }
+ }
+ }
+
+ // Sort ascending
+ qSort(result.begin(), result.end(), naturalSortLessThanCI);
+
+ // Replace the zero option with Off
+ int index = result.indexOf("MSAA 0");
+
+ if (index != -1)
+ result.replace(index, tr("Off"));
+
+ return result;
+}
+
+QStringList GraphicsPage::getAvailableResolutions(int screen)
+{
+ QStringList result;
+ SDL_DisplayMode mode;
+ int modeIndex, modes = SDL_GetNumDisplayModes(screen);
+
+ if (modes < 0)
+ {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error receiving resolutions"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>SDL_GetNumDisplayModes failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
+ msgBox.exec();
+ return result;
+ }
+
+ for (modeIndex = 0; modeIndex < modes; modeIndex++)
+ {
+ if (SDL_GetDisplayMode(screen, modeIndex, &mode) < 0)
+ {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error receiving resolutions"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>SDL_GetDisplayMode failed:</b><br><br>") + QString::fromStdString(SDL_GetError()) + "<br>");
+ msgBox.exec();
+ return result;
+ }
+
+ QString aspect = getAspect(mode.w, mode.h);
+ QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
+
+ if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
+ resolution.append(tr("\t(Wide ") + aspect + ")");
+
+ } else if (aspect == QLatin1String("4:3")) {
+ resolution.append(tr("\t(Standard 4:3)"));
+ }
+
+ result.append(resolution);
+ }
+
+ result.removeDuplicates();
+ return result;
+}
+
+QRect GraphicsPage::getMaximumResolution()
+{
+ QRect max;
+ int screens = QApplication::desktop()->screenCount();
+ for (int i = 0; i < screens; ++i)
+ {
+ QRect res = QApplication::desktop()->screenGeometry(i);
+ if (res.width() > max.width())
+ max.setWidth(res.width());
+ if (res.height() > max.height())
+ max.setHeight(res.height());
+ }
+ return max;
+}
+
+void GraphicsPage::rendererChanged(const QString &renderer)
+{
+ mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
+
+ antiAliasingComboBox->clear();
+
+ antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
+}
+
+void GraphicsPage::screenChanged(int screen)
+{
+ if (screen >= 0) {
+ resolutionComboBox->clear();
+ resolutionComboBox->addItems(getAvailableResolutions(screen));
+ }
+}
+
+void GraphicsPage::slotFullScreenChanged(int state)
+{
+ if (state == Qt::Checked) {
+ standardRadioButton->toggle();
+ customRadioButton->setEnabled(false);
+ customWidthSpinBox->setEnabled(false);
+ customHeightSpinBox->setEnabled(false);
+ } else {
+ customRadioButton->setEnabled(true);
+ customWidthSpinBox->setEnabled(true);
+ customHeightSpinBox->setEnabled(true);
+ }
+}
+
+void GraphicsPage::slotStandardToggled(bool checked)
+{
+ if (checked) {
+ resolutionComboBox->setEnabled(true);
+ customWidthSpinBox->setEnabled(false);
+ customHeightSpinBox->setEnabled(false);
+ } else {
+ resolutionComboBox->setEnabled(false);
+ customWidthSpinBox->setEnabled(true);
+ customHeightSpinBox->setEnabled(true);
+ }
+}
diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp
new file mode 100644
index 0000000000..d233ea12e2
--- /dev/null
+++ b/apps/launcher/graphicspage.hpp
@@ -0,0 +1,66 @@
+#ifndef GRAPHICSPAGE_H
+#define GRAPHICSPAGE_H
+
+#include <QWidget>
+
+#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 "ui_graphicspage.h"
+
+class GraphicsSettings;
+
+namespace Files { struct ConfigurationManager; }
+
+class GraphicsPage : public QWidget, private Ui::GraphicsPage
+{
+ Q_OBJECT
+
+public:
+ GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0);
+
+ void saveSettings();
+ bool loadSettings();
+
+public slots:
+ void rendererChanged(const QString &renderer);
+ void screenChanged(int screen);
+
+private slots:
+ void slotFullScreenChanged(int state);
+ void slotStandardToggled(bool checked);
+
+private:
+ 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;
+
+ QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer);
+ QStringList getAvailableResolutions(int screen);
+ QRect getMaximumResolution();
+
+ bool setupOgre();
+ bool setupSDL();
+};
+
+#endif
diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp
new file mode 100644
index 0000000000..f67f5edcff
--- /dev/null
+++ b/apps/launcher/main.cpp
@@ -0,0 +1,64 @@
+#include <QApplication>
+#include <QTextCodec>
+#include <QDir>
+#include <QDebug>
+
+#ifdef __APPLE__
+// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
+#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
+#endif
+#include <SDL.h>
+
+#include "maindialog.hpp"
+// SDL workaround
+#include "graphicspage.hpp"
+
+int main(int argc, char *argv[])
+{
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
+ return 0;
+ }
+
+ QApplication app(argc, argv);
+
+ // Now we make sure the current dir is set to application path
+ QDir dir(QCoreApplication::applicationDirPath());
+
+ #ifdef Q_OS_MAC
+ if (dir.dirName() == "MacOS") {
+ dir.cdUp();
+ dir.cdUp();
+ dir.cdUp();
+ }
+
+ // force Qt to load only LOCAL plugins, don't touch system Qt installation
+ QDir pluginsPath(QCoreApplication::applicationDirPath());
+ pluginsPath.cdUp();
+ pluginsPath.cd("Plugins");
+
+ QStringList libraryPaths;
+ libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
+ app.setLibraryPaths(libraryPaths);
+ #endif
+
+ QDir::setCurrent(dir.absolutePath());
+
+ // Support non-latin characters
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
+
+ MainDialog mainWin;
+
+ if (mainWin.setup()) {
+ mainWin.show();
+ } else {
+ return 0;
+ }
+
+ int returnValue = app.exec();
+ SDL_Quit();
+ return returnValue;
+}
+
diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp
new file mode 100644
index 0000000000..032f70916f
--- /dev/null
+++ b/apps/launcher/maindialog.cpp
@@ -0,0 +1,854 @@
+#include "maindialog.hpp"
+
+#include <QFontDatabase>
+#include <QInputDialog>
+#include <QFileDialog>
+#include <QCloseEvent>
+#include <QTextCodec>
+#include <QProcess>
+#include <QFile>
+#include <QDir>
+
+#include <QDebug>
+
+#ifndef WIN32
+ #include "unshieldthread.hpp"
+#endif
+
+#include "textslotmsgbox.hpp"
+
+#include "utils/checkablemessagebox.hpp"
+
+#include "playpage.hpp"
+#include "graphicspage.hpp"
+#include "datafilespage.hpp"
+
+MainDialog::MainDialog()
+ : mGameSettings(mCfgMgr)
+{
+ // Install the stylesheet font
+ QFile file;
+ QFontDatabase fontDatabase;
+
+ const QStringList fonts = fontDatabase.families();
+
+ // Check if the font is installed
+ if (!fonts.contains("EB Garamond")) {
+
+ QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf");
+ file.setFileName(font);
+
+ if (!file.exists()) {
+ font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf");
+ }
+
+ fontDatabase.addApplicationFont(font);
+ }
+
+ setupUi(this);
+
+ iconWidget->setViewMode(QListView::IconMode);
+ iconWidget->setWrapping(false);
+ iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure
+ iconWidget->setIconSize(QSize(48, 48));
+ iconWidget->setMovement(QListView::Static);
+
+ iconWidget->setSpacing(4);
+ iconWidget->setCurrentRow(0);
+ iconWidget->setFlow(QListView::LeftToRight);
+
+ QPushButton *playButton = new QPushButton(tr("Play"));
+ buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole);
+
+ connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
+ connect(buttonBox, SIGNAL(accepted()), this, SLOT(play()));
+
+ // Remove what's this? button
+ setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ createIcons();
+}
+
+void MainDialog::createIcons()
+{
+ if (!QIcon::hasThemeIcon("document-new"))
+ QIcon::setThemeName("tango");
+
+ // We create a fallback icon because the default fallback doesn't work
+ QIcon graphicsIcon = QIcon(":/icons/tango/video-display.png");
+
+ QListWidgetItem *playButton = new QListWidgetItem(iconWidget);
+ playButton->setIcon(QIcon(":/images/openmw.png"));
+ playButton->setText(tr("Play"));
+ playButton->setTextAlignment(Qt::AlignCenter);
+ playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
+ graphicsButton->setIcon(QIcon::fromTheme("video-display", graphicsIcon));
+ graphicsButton->setText(tr("Graphics"));
+ graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
+ graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget);
+ dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png"));
+ dataFilesButton->setText(tr("Data Files"));
+ dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
+ dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ connect(iconWidget,
+ SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
+ this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*)));
+
+}
+
+void MainDialog::createPages()
+{
+ mPlayPage = new PlayPage(this);
+ mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
+ mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
+
+ // Set the combobox of the play page to imitate the combobox on the datafilespage
+ mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel());
+ mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex());
+
+ // Add the pages to the stacked widget
+ pagesWidget->addWidget(mPlayPage);
+ pagesWidget->addWidget(mGraphicsPage);
+ pagesWidget->addWidget(mDataFilesPage);
+
+ // Select the first page
+ iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select);
+
+ connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play()));
+
+ connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int)));
+ connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int)));
+
+}
+
+bool MainDialog::showFirstRunDialog()
+{
+ QStringList iniPaths;
+
+ foreach (const QString &path, mGameSettings.getDataDirs()) {
+ QDir dir(path);
+ dir.setPath(dir.canonicalPath()); // Resolve symlinks
+
+ if (dir.exists(QString("Morrowind.ini")))
+ iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini")));
+ else
+ {
+ if (!dir.cdUp())
+ continue; // Cannot move from Data Files
+
+ if (dir.exists(QString("Morrowind.ini")))
+ iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini")));
+ }
+ }
+
+ // Ask the user where the Morrowind.ini is
+ if (iniPaths.empty()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error detecting Morrowind configuration"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Cancel);
+ msgBox.setText(QObject::tr("<br><b>Could not find Morrowind.ini</b><br><br> \
+ OpenMW needs to import settings from this file.<br><br> \
+ Press \"Browse...\" to specify the location manually.<br>"));
+
+ QAbstractButton *dirSelectButton =
+ msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole);
+
+ msgBox.exec();
+
+ QString iniFile;
+ if (msgBox.clickedButton() == dirSelectButton) {
+ iniFile = QFileDialog::getOpenFileName(
+ NULL,
+ QObject::tr("Select configuration file"),
+ QDir::currentPath(),
+ QString(tr("Morrowind configuration file (*.ini)")));
+ }
+
+ if (iniFile.isEmpty())
+ return false; // Cancel was clicked;
+
+ QFileInfo info(iniFile);
+ iniPaths.clear();
+ iniPaths.append(info.absoluteFilePath());
+ }
+
+ CheckableMessageBox msgBox(this);
+ msgBox.setWindowTitle(tr("Morrowind installation detected"));
+
+ QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion);
+ int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize);
+ msgBox.setIconPixmap(icon.pixmap(size, size));
+
+ QAbstractButton *importerButton =
+ msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?!
+ QAbstractButton *skipButton =
+ msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole);
+
+ Q_UNUSED(skipButton); // Surpress compiler unused warning
+
+ msgBox.setStandardButtons(QDialogButtonBox::NoButton);
+ msgBox.setText(tr("<br><b>An existing Morrowind configuration was detected</b><br> \
+ <br>Would you like to import settings from Morrowind.ini?<br> \
+ <br><b>Warning: In most cases OpenMW needs these settings to run properly</b><br>"));
+ msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)"));
+ msgBox.exec();
+
+
+ if (msgBox.clickedButton() == importerButton) {
+
+ if (iniPaths.count() > 1) {
+ // Multiple Morrowind.ini files found
+ bool ok;
+ QString path = QInputDialog::getItem(this, tr("Multiple configurations found"),
+ tr("<br><b>There are multiple Morrowind.ini files found.</b><br><br> \
+ Please select the one you wish to import from:"), iniPaths, 0, false, &ok);
+ if (ok && !path.isEmpty()) {
+ iniPaths.clear();
+ iniPaths.append(path);
+ } else {
+ // Cancel was clicked
+ return false;
+ }
+ }
+
+ // Create the file if it doesn't already exist, else the importer will fail
+ QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg");
+ QFile file(path);
+
+ if (!file.exists()) {
+ if (!file.open(QIODevice::ReadWrite)) {
+ // File cannot be created
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
+ Please make sure you have the right permissions \
+ and try again.<br>").arg(file.fileName()));
+ msgBox.exec();
+ return false;
+ }
+
+ file.close();
+ }
+
+ // Construct the arguments to run the importer
+ QStringList arguments;
+
+ if (msgBox.isChecked())
+ arguments.append(QString("--game-files"));
+
+ arguments.append(QString("--encoding"));
+ arguments.append(mGameSettings.value(QString("encoding"), QString("win1252")));
+ arguments.append(QString("--ini"));
+ arguments.append(iniPaths.first());
+ arguments.append(QString("--cfg"));
+ arguments.append(path);
+
+ if (!startProgram(QString("mwiniimport"), arguments, false))
+ return false;
+
+ // Re-read the game settings
+ if (!setupGameSettings())
+ return false;
+
+ // Add a new profile
+ if (msgBox.isChecked()) {
+ mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported"));
+
+ mLauncherSettings.remove(QString("Profiles/Imported/master"));
+ mLauncherSettings.remove(QString("Profiles/Imported/plugin"));
+
+ QStringList masters = mGameSettings.values(QString("master"));
+ QStringList plugins = mGameSettings.values(QString("plugin"));
+
+ foreach (const QString &master, masters) {
+ mLauncherSettings.setMultiValue(QString("Profiles/Imported/master"), master);
+ }
+
+ foreach (const QString &plugin, plugins) {
+ mLauncherSettings.setMultiValue(QString("Profiles/Imported/plugin"), plugin);
+ }
+ }
+
+ }
+
+ return true;
+}
+
+bool MainDialog::setup()
+{
+ if (!setupLauncherSettings())
+ return false;
+
+ if (!setupGameSettings())
+ return false;
+
+ if (!setupGraphicsSettings())
+ return false;
+
+ // Check if we need to show the importer
+ if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true"))
+ {
+ if (!showFirstRunDialog())
+ return false;
+ }
+
+ // Now create the pages as they need the settings
+ createPages();
+
+ // Call this so we can exit on Ogre/SDL errors before mainwindow is shown
+ if (!mGraphicsPage->loadSettings())
+ return false;
+
+ loadSettings();
+ return true;
+}
+
+void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
+{
+ if (!current)
+ current = previous;
+
+ pagesWidget->setCurrentIndex(iconWidget->row(current));
+}
+
+bool MainDialog::setupLauncherSettings()
+{
+ mLauncherSettings.setMultiValueEnabled(true);
+
+ QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+
+ QStringList paths;
+ paths.append(QString("launcher.cfg"));
+ paths.append(userPath + QString("launcher.cfg"));
+
+ foreach (const QString &path, paths) {
+ qDebug() << "Loading config file:" << qPrintable(path);
+ QFile file(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"));
+
+ mLauncherSettings.readFile(stream);
+ }
+ file.close();
+ }
+
+ return true;
+}
+
+#ifndef WIN32
+bool expansions(UnshieldThread& cd)
+{
+ if(cd.BloodmoonDone())
+ {
+ cd.Done();
+ return false;
+ }
+
+ QMessageBox expansionsBox;
+ expansionsBox.setText(QObject::tr("<br>Would you like to install expansions now ? (make sure you have the disc)<br> \
+ If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.<br>"));
+
+ QAbstractButton* tribunalButton = NULL;
+ if(!cd.TribunalDone())
+ tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole);
+
+ QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole);
+ QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole);
+
+ expansionsBox.exec();
+
+ if(expansionsBox.clickedButton() == noneButton)
+ {
+ cd.Done();
+ return false;
+ }
+ else if(expansionsBox.clickedButton() == tribunalButton)
+ {
+
+ TextSlotMsgBox cdbox;
+ cdbox.setStandardButtons(QMessageBox::Cancel);
+
+ QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
+ QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
+
+ cd.SetTribunalPath(
+ QFileDialog::getOpenFileName(
+ NULL,
+ QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"),
+ QDir::currentPath(),
+ QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
+
+ cd.start();
+ cdbox.exec();
+ }
+ else if(expansionsBox.clickedButton() == bloodmoonButton)
+ {
+
+ TextSlotMsgBox cdbox;
+ cdbox.setStandardButtons(QMessageBox::Cancel);
+
+ QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
+ QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
+
+ cd.SetBloodmoonPath(
+ QFileDialog::getOpenFileName(
+ NULL,
+ QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"),
+ QDir::currentPath(),
+ QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
+
+ cd.start();
+ cdbox.exec();
+ }
+
+
+
+ return true;
+}
+#endif // WIN32
+
+bool MainDialog::setupGameSettings()
+{
+ QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
+
+ QStringList paths;
+ paths.append(userPath + QString("openmw.cfg"));
+ paths.append(QString("openmw.cfg"));
+ paths.append(globalPath + QString("openmw.cfg"));
+
+ foreach (const QString &path, paths) {
+ qDebug() << "Loading config file:" << qPrintable(path);
+
+ QFile file(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.readFile(stream);
+ }
+ file.close();
+ }
+
+ QStringList dataDirs;
+
+ // Check if the paths actually contain data files
+ foreach (const QString path, mGameSettings.getDataDirs()) {
+ QDir dir(path);
+ QStringList filters;
+ filters << "*.esp" << "*.esm";
+
+ if (!dir.entryList(filters).isEmpty())
+ dataDirs.append(path);
+ }
+
+ if (dataDirs.isEmpty())
+ {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Cancel);
+ msgBox.setText(QObject::tr("<br><b>Could not find the Data Files location</b><br><br> \
+ The directory containing the data files was not found.<br><br> \
+ Press \"Browse...\" to specify the location manually.<br>"));
+
+ QAbstractButton *dirSelectButton =
+ msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole);
+
+ #ifndef WIN32
+ QAbstractButton *cdSelectButton =
+ msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole);
+ #endif
+
+
+ msgBox.exec();
+
+ QString selectedFile;
+ if (msgBox.clickedButton() == dirSelectButton) {
+ selectedFile = QFileDialog::getOpenFileName(
+ NULL,
+ QObject::tr("Select master file"),
+ QDir::currentPath(),
+ QString(tr("Morrowind master file (*.esm)")));
+ }
+ #ifndef WIN32
+ else if(msgBox.clickedButton() == cdSelectButton) {
+ UnshieldThread cd;
+
+ {
+ TextSlotMsgBox cdbox;
+ cdbox.setStandardButtons(QMessageBox::Cancel);
+
+ QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
+ QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
+
+ cd.SetMorrowindPath(
+ QFileDialog::getOpenFileName(
+ NULL,
+ QObject::tr("Select data1.hdr from Morrowind Installation CD"),
+ QDir::currentPath(),
+ QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData());
+
+ cd.SetOutputPath(
+ QFileDialog::getExistingDirectory(
+ NULL,
+ QObject::tr("Select where to extract files to"),
+ QDir::currentPath(),
+ QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData());
+
+ cd.start();
+ cdbox.exec();
+ }
+
+ while(expansions(cd));
+
+ selectedFile = QString::fromStdString(cd.GetMWEsmPath());
+ }
+ #endif // WIN32
+
+ if (selectedFile.isEmpty())
+ return false; // Cancel was clicked;
+
+ QFileInfo info(selectedFile);
+
+ // Add the new dir to the settings file and to the data dir container
+ mGameSettings.setMultiValue(QString("data"), info.absolutePath());
+ mGameSettings.addDataDir(info.absolutePath());
+ }
+
+ return true;
+}
+
+bool MainDialog::setupGraphicsSettings()
+{
+ mGraphicsSettings.setMultiValueEnabled(false);
+
+ QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
+
+ QFile localDefault(QString("settings-default.cfg"));
+ QFile globalDefault(globalPath + QString("settings-default.cfg"));
+
+ if (!localDefault.exists() && !globalDefault.exists()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error reading OpenMW configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(QObject::tr("<br><b>Could not find settings-default.cfg</b><br><br> \
+ The problem may be due to an incomplete installation of OpenMW.<br> \
+ Reinstalling OpenMW may resolve the problem."));
+ msgBox.exec();
+ return false;
+ }
+
+
+ QStringList paths;
+ paths.append(globalPath + QString("settings-default.cfg"));
+ paths.append(QString("settings-default.cfg"));
+ paths.append(userPath + QString("settings.cfg"));
+
+ foreach (const QString &path, paths) {
+ qDebug() << "Loading config file:" << qPrintable(path);
+ QFile file(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"));
+
+ mGraphicsSettings.readFile(stream);
+ }
+ file.close();
+ }
+
+ return true;
+}
+
+void MainDialog::loadSettings()
+{
+ int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt();
+ int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt();
+
+ int posX = mLauncherSettings.value(QString("General/MainWindow/posx")).toInt();
+ int posY = mLauncherSettings.value(QString("General/MainWindow/posy")).toInt();
+
+ resize(width, height);
+ move(posX, posY);
+}
+
+void MainDialog::saveSettings()
+{
+ QString width = QString::number(this->width());
+ QString height = QString::number(this->height());
+
+ mLauncherSettings.setValue(QString("General/MainWindow/width"), width);
+ mLauncherSettings.setValue(QString("General/MainWindow/height"), height);
+
+ QString posX = QString::number(this->pos().x());
+ QString posY = QString::number(this->pos().y());
+
+ mLauncherSettings.setValue(QString("General/MainWindow/posx"), posX);
+ mLauncherSettings.setValue(QString("General/MainWindow/posy"), posY);
+
+ mLauncherSettings.setValue(QString("General/firstrun"), QString("false"));
+
+}
+
+bool MainDialog::writeSettings()
+{
+ // Now write all config files
+ saveSettings();
+ mGraphicsPage->saveSettings();
+ mDataFilesPage->saveSettings();
+
+ QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
+ QDir dir(userPath);
+
+ if (!dir.exists()) {
+ if (!dir.mkpath(userPath)) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not create %0</b><br><br> \
+ Please make sure you have the right permissions \
+ and try again.<br>").arg(userPath));
+ msgBox.exec();
+ return false;
+ }
+ }
+
+ // Game settings
+ QFile file(userPath + QString("openmw.cfg"));
+
+ if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
+ // File cannot be opened or created
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not open or create %0 for writing</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.writeFile(stream);
+ file.close();
+
+ // Graphics settings
+ file.setFileName(userPath + QString("settings.cfg"));
+
+ if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
+ // File cannot be opened or created
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
+ Please make sure you have the right permissions \
+ and try again.<br>").arg(file.fileName()));
+ msgBox.exec();
+ return false;
+ }
+
+ stream.setDevice(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
+
+ mGraphicsSettings.writeFile(stream);
+ file.close();
+
+ // Launcher settings
+ file.setFileName(userPath + QString("launcher.cfg"));
+
+ if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
+ // File cannot be opened or created
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error writing Launcher configuration file"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
+ Please make sure you have the right permissions \
+ and try again.<br>").arg(file.fileName()));
+ msgBox.exec();
+ return false;
+ }
+
+ stream.setDevice(&file);
+ stream.setCodec(QTextCodec::codecForName("UTF-8"));
+
+ mLauncherSettings.writeFile(stream);
+ file.close();
+
+ return true;
+}
+
+void MainDialog::closeEvent(QCloseEvent *event)
+{
+ writeSettings();
+ event->accept();
+}
+
+void MainDialog::play()
+{
+ if (!writeSettings()) {
+ qApp->quit();
+ return;
+ }
+
+ if(!mGameSettings.hasMaster()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("No master file selected"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>You do not have any master files selected.</b><br><br> \
+ OpenMW will not start without a master file selected.<br>"));
+ msgBox.exec();
+ return;
+ }
+
+ // Launch the game detached
+ startProgram(QString("openmw"), true);
+ qApp->quit();
+}
+
+bool MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached)
+{
+ QString path = name;
+#ifdef Q_OS_WIN
+ path.append(QString(".exe"));
+#elif defined(Q_OS_MAC)
+ QDir dir(QCoreApplication::applicationDirPath());
+ path = dir.absoluteFilePath(name);
+#else
+ path.prepend(QString("./"));
+#endif
+
+ QFile file(path);
+
+ QProcess process;
+ QFileInfo info(file);
+
+ if (!file.exists()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error starting executable"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not find %1</b><br><br> \
+ The application is not found.<br> \
+ Please make sure OpenMW is installed correctly and try again.<br>").arg(info.fileName()));
+ msgBox.exec();
+
+ return false;
+ }
+
+ if (!info.isExecutable()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error starting executable"));
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not start %1</b><br><br> \
+ The application is not executable.<br> \
+ Please make sure you have the right permissions and try again.<br>").arg(info.fileName()));
+ msgBox.exec();
+
+ return false;
+ }
+
+ // Start the executable
+ if (detached) {
+ if (!process.startDetached(path, arguments)) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error starting executable"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not start %1</b><br><br> \
+ An error occurred while starting %1.<br><br> \
+ Press \"Show Details...\" for more information.<br>").arg(info.fileName()));
+ msgBox.setDetailedText(process.errorString());
+ msgBox.exec();
+
+ return false;
+ }
+ } else {
+ process.start(path, arguments);
+ if (!process.waitForFinished()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error starting executable"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Could not start %1</b><br><br> \
+ An error occurred while starting %1.<br><br> \
+ Press \"Show Details...\" for more information.<br>").arg(info.fileName()));
+ msgBox.setDetailedText(process.errorString());
+ msgBox.exec();
+
+ return false;
+ }
+
+ if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit) {
+ QString error(process.readAllStandardError());
+ error.append(tr("\nArguments:\n"));
+ error.append(arguments.join(" "));
+
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(tr("Error running executable"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+ msgBox.setText(tr("<br><b>Executable %1 returned an error</b><br><br> \
+ An error occurred while running %1.<br><br> \
+ Press \"Show Details...\" for more information.<br>").arg(info.fileName()));
+ msgBox.setDetailedText(error);
+ msgBox.exec();
+
+ return false;
+ }
+ }
+
+ return true;
+
+}
diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp
new file mode 100644
index 0000000000..824dff6e82
--- /dev/null
+++ b/apps/launcher/maindialog.hpp
@@ -0,0 +1,67 @@
+#ifndef MAINDIALOG_H
+#define MAINDIALOG_H
+
+#include <QMainWindow>
+#ifndef Q_MOC_RUN
+#include <components/files/configurationmanager.hpp>
+#endif
+#include "settings/gamesettings.hpp"
+#include "settings/graphicssettings.hpp"
+#include "settings/launchersettings.hpp"
+
+#include "ui_mainwindow.h"
+
+class QListWidget;
+class QListWidgetItem;
+class QStackedWidget;
+class QStringList;
+class QStringListModel;
+class QString;
+
+class PlayPage;
+class GraphicsPage;
+class DataFilesPage;
+
+class MainDialog : public QMainWindow, private Ui::MainWindow
+{
+ Q_OBJECT
+
+public:
+ MainDialog();
+ bool setup();
+ bool showFirstRunDialog();
+
+public slots:
+ void changePage(QListWidgetItem *current, QListWidgetItem *previous);
+ void play();
+
+private:
+ void createIcons();
+ void createPages();
+
+ bool setupLauncherSettings();
+ bool setupGameSettings();
+ bool setupGraphicsSettings();
+
+ void loadSettings();
+ void saveSettings();
+ bool writeSettings();
+
+ inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); }
+ bool startProgram(const QString &name, const QStringList &arguments, bool detached = false);
+
+ void closeEvent(QCloseEvent *event);
+
+ PlayPage *mPlayPage;
+ GraphicsPage *mGraphicsPage;
+ DataFilesPage *mDataFilesPage;
+
+ Files::ConfigurationManager mCfgMgr;
+
+ GameSettings mGameSettings;
+ GraphicsSettings mGraphicsSettings;
+ LauncherSettings mLauncherSettings;
+
+};
+
+#endif
diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp
new file mode 100644
index 0000000000..46900c5958
--- /dev/null
+++ b/apps/launcher/playpage.cpp
@@ -0,0 +1,43 @@
+#include "playpage.hpp"
+
+#include <QListView>
+
+#ifdef Q_OS_MAC
+#include <QPlastiqueStyle>
+#endif
+
+PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
+{
+ setupUi(this);
+
+ // Hacks to get the stylesheet look properly
+#ifdef Q_OS_MAC
+ QPlastiqueStyle *style = new QPlastiqueStyle;
+ profilesComboBox->setStyle(style);
+#endif
+ profilesComboBox->setView(new QListView());
+
+ connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
+ connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked()));
+
+}
+
+void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model)
+{
+ profilesComboBox->setModel(model);
+}
+
+void PlayPage::setProfilesComboBoxIndex(int index)
+{
+ profilesComboBox->setCurrentIndex(index);
+}
+
+void PlayPage::slotCurrentIndexChanged(int index)
+{
+ emit profileChanged(index);
+}
+
+void PlayPage::slotPlayClicked()
+{
+ emit playButtonClicked();
+}
diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp
new file mode 100644
index 0000000000..4306396bd2
--- /dev/null
+++ b/apps/launcher/playpage.hpp
@@ -0,0 +1,35 @@
+#ifndef PLAYPAGE_H
+#define PLAYPAGE_H
+
+#include <QWidget>
+
+#include "ui_playpage.h"
+
+class QComboBox;
+class QPushButton;
+class QAbstractItemModel;
+
+class PlayPage : public QWidget, private Ui::PlayPage
+{
+ Q_OBJECT
+
+public:
+ PlayPage(QWidget *parent = 0);
+ void setProfilesComboBoxModel(QAbstractItemModel *model);
+
+signals:
+ void profileChanged(int index);
+ void playButtonClicked();
+
+public slots:
+ void setProfilesComboBoxIndex(int index);
+
+private slots:
+ void slotCurrentIndexChanged(int index);
+ void slotPlayClicked();
+
+
+
+};
+
+#endif
diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp
new file mode 100644
index 0000000000..205879bc37
--- /dev/null
+++ b/apps/launcher/settings/gamesettings.cpp
@@ -0,0 +1,175 @@
+#include "gamesettings.hpp"
+
+#include <QTextStream>
+#include <QDir>
+#include <QString>
+#include <QRegExp>
+#include <QMap>
+
+#include <components/files/configurationmanager.hpp>
+
+#include <boost/version.hpp>
+/**
+ * Workaround for problems with whitespaces in paths in older versions of Boost library
+ */
+#if (BOOST_VERSION <= 104600)
+namespace boost
+{
+
+ template<>
+ inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
+ {
+ return boost::filesystem::path(arg);
+ }
+
+} /* namespace boost */
+#endif /* (BOOST_VERSION <= 104600) */
+
+
+GameSettings::GameSettings(Files::ConfigurationManager &cfg)
+ : mCfgMgr(cfg)
+{
+}
+
+GameSettings::~GameSettings()
+{
+}
+
+void GameSettings::validatePaths()
+{
+ if (mSettings.isEmpty() || !mDataDirs.isEmpty())
+ return; // Don't re-validate paths if they are already parsed
+
+ QStringList paths = mSettings.values(QString("data"));
+ Files::PathContainer dataDirs;
+
+ foreach (const QString &path, paths) {
+ dataDirs.push_back(Files::PathContainer::value_type(path.toStdString()));
+ }
+
+ // Parse the data dirs to convert the tokenized paths
+ mCfgMgr.processPaths(dataDirs);
+ mDataDirs.clear();
+
+ for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) {
+ QString path = QString::fromStdString(it->string());
+ path.remove(QChar('\"'));
+
+ QDir dir(path);
+ if (dir.exists())
+ mDataDirs.append(path);
+ }
+
+ // Do the same for data-local
+ QString local = mSettings.value(QString("data-local"));
+
+ if (local.isEmpty())
+ return;
+
+ dataDirs.clear();
+ dataDirs.push_back(Files::PathContainer::value_type(local.toStdString()));
+
+ mCfgMgr.processPaths(dataDirs);
+
+ if (!dataDirs.empty()) {
+ QString path = QString::fromStdString(dataDirs.front().string());
+ path.remove(QChar('\"'));
+
+ QDir dir(path);
+ if (dir.exists())
+ mDataLocal = path;
+ }
+}
+
+QStringList GameSettings::values(const QString &key, const QStringList &defaultValues)
+{
+ if (!mSettings.values(key).isEmpty())
+ return mSettings.values(key);
+ return defaultValues;
+}
+
+bool GameSettings::readFile(QTextStream &stream)
+{
+ QMap<QString, QString> cache;
+ QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
+
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+
+ if (line.isEmpty() || line.startsWith("#"))
+ continue;
+
+ if (keyRe.indexIn(line) != -1) {
+
+ QString key = keyRe.cap(1).trimmed();
+ QString value = keyRe.cap(2).trimmed();
+
+ // Don't remove existing data entries
+ if (key != QLatin1String("data"))
+ mSettings.remove(key);
+
+ QStringList values = cache.values(key);
+ values.append(mSettings.values(key));
+
+ if (!values.contains(value)) {
+ cache.insertMulti(key, value);
+ }
+ }
+ }
+
+ if (mSettings.isEmpty()) {
+ mSettings = 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);
+ validatePaths();
+
+ return true;
+}
+
+bool GameSettings::writeFile(QTextStream &stream)
+{
+ // Iterate in reverse order to preserve insertion order
+ QMapIterator<QString, QString> i(mSettings);
+ i.toBack();
+
+ while (i.hasPrevious()) {
+ i.previous();
+
+ if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin"))
+ continue;
+
+ // Quote paths with spaces
+ if (i.key() == QLatin1String("data")
+ || i.key() == QLatin1String("data-local")
+ || i.key() == QLatin1String("resources"))
+ {
+ if (i.value().contains(QChar(' ')))
+ {
+ QString stripped = i.value();
+ stripped.remove(QChar('\"')); // Remove quotes
+
+ stream << i.key() << "=\"" << stripped << "\"\n";
+ continue;
+ }
+ }
+
+ stream << i.key() << "=" << i.value() << "\n";
+
+ }
+
+ QStringList masters = mSettings.values(QString("master"));
+ for (int i = masters.count(); i--;) {
+ stream << "master=" << masters.at(i) << "\n";
+ }
+
+ QStringList plugins = mSettings.values(QString("plugin"));
+ for (int i = plugins.count(); i--;) {
+ stream << "plugin=" << plugins.at(i) << "\n";
+ }
+
+ return true;
+}
diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp
new file mode 100644
index 0000000000..55b2107e2a
--- /dev/null
+++ b/apps/launcher/settings/gamesettings.hpp
@@ -0,0 +1,62 @@
+#ifndef GAMESETTINGS_HPP
+#define GAMESETTINGS_HPP
+
+#include <QTextStream>
+#include <QStringList>
+#include <QString>
+#include <QMap>
+
+#include <boost/filesystem/path.hpp>
+
+namespace Files { typedef std::vector<boost::filesystem::path> PathContainer;
+ struct ConfigurationManager;}
+
+class GameSettings
+{
+public:
+ GameSettings(Files::ConfigurationManager &cfg);
+ ~GameSettings();
+
+ inline QString value(const QString &key, const QString &defaultValue = QString())
+ {
+ return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
+ }
+
+
+ inline void setValue(const QString &key, const QString &value)
+ {
+ mSettings.insert(key, value);
+ }
+
+ inline void setMultiValue(const QString &key, const QString &value)
+ {
+ QStringList values = mSettings.values(key);
+ if (!values.contains(value))
+ mSettings.insertMulti(key, value);
+ }
+
+ inline void remove(const QString &key)
+ {
+ mSettings.remove(key);
+ }
+
+ inline QStringList getDataDirs() { return mDataDirs; }
+ inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
+ inline QString getDataLocal() {return mDataLocal; }
+ inline bool hasMaster() { return mSettings.count(QString("master")) > 0; }
+
+ QStringList values(const QString &key, const QStringList &defaultValues = QStringList());
+ bool readFile(QTextStream &stream);
+ bool writeFile(QTextStream &stream);
+
+private:
+ Files::ConfigurationManager &mCfgMgr;
+
+ void validatePaths();
+ QMap<QString, QString> mSettings;
+
+ QStringList mDataDirs;
+ QString mDataLocal;
+};
+
+#endif // GAMESETTINGS_HPP
diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp
new file mode 100644
index 0000000000..0c55800917
--- /dev/null
+++ b/apps/launcher/settings/graphicssettings.cpp
@@ -0,0 +1,44 @@
+#include "graphicssettings.hpp"
+
+#include <QTextStream>
+#include <QString>
+#include <QRegExp>
+#include <QMap>
+
+GraphicsSettings::GraphicsSettings()
+{
+}
+
+GraphicsSettings::~GraphicsSettings()
+{
+}
+
+bool GraphicsSettings::writeFile(QTextStream &stream)
+{
+ QString sectionPrefix;
+ QRegExp sectionRe("([^/]+)/(.+)$");
+ QMap<QString, QString> settings = SettingsBase::getSettings();
+
+ QMapIterator<QString, QString> i(settings);
+ while (i.hasNext()) {
+ i.next();
+
+ QString prefix;
+ QString key;
+
+ if (sectionRe.exactMatch(i.key())) {
+ prefix = sectionRe.cap(1);
+ key = sectionRe.cap(2);
+ }
+
+ if (sectionPrefix != prefix) {
+ sectionPrefix = prefix;
+ stream << "\n[" << prefix << "]\n";
+ }
+
+ stream << key << " = " << i.value() << "\n";
+ }
+
+ return true;
+
+}
diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp
new file mode 100644
index 0000000000..3e8617849e
--- /dev/null
+++ b/apps/launcher/settings/graphicssettings.hpp
@@ -0,0 +1,16 @@
+#ifndef GRAPHICSSETTINGS_HPP
+#define GRAPHICSSETTINGS_HPP
+
+#include "settingsbase.hpp"
+
+class GraphicsSettings : public SettingsBase<QMap<QString, QString> >
+{
+public:
+ GraphicsSettings();
+ ~GraphicsSettings();
+
+ bool writeFile(QTextStream &stream);
+
+};
+
+#endif // GRAPHICSSETTINGS_HPP
diff --git a/apps/launcher/settings/launchersettings.cpp b/apps/launcher/settings/launchersettings.cpp
new file mode 100644
index 0000000000..5d298e814e
--- /dev/null
+++ b/apps/launcher/settings/launchersettings.cpp
@@ -0,0 +1,101 @@
+#include "launchersettings.hpp"
+
+#include <QTextStream>
+#include <QString>
+#include <QRegExp>
+#include <QMap>
+
+LauncherSettings::LauncherSettings()
+{
+}
+
+LauncherSettings::~LauncherSettings()
+{
+}
+
+QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags)
+{
+ QMap<QString, QString> settings = SettingsBase::getSettings();
+
+ if (flags == Qt::MatchExactly)
+ return settings.values(key);
+
+ QStringList result;
+
+ if (flags == Qt::MatchStartsWith) {
+ QStringList keys = settings.keys();
+
+ foreach (const QString &currentKey, keys) {
+ if (currentKey.startsWith(key))
+ result.append(settings.value(currentKey));
+ }
+ }
+
+ return result;
+}
+
+QStringList LauncherSettings::subKeys(const QString &key)
+{
+ QMap<QString, QString> settings = SettingsBase::getSettings();
+ QStringList keys = settings.uniqueKeys();
+
+ QRegExp keyRe("(.+)/");
+
+ QStringList result;
+
+ foreach (const QString &currentKey, keys) {
+
+ if (keyRe.indexIn(currentKey) != -1) {
+
+ QString prefixedKey = keyRe.cap(1);
+ if(prefixedKey.startsWith(key)) {
+
+ QString subKey = prefixedKey.remove(key);
+ if (!subKey.isEmpty())
+ result.append(subKey);
+ }
+ }
+ }
+
+ result.removeDuplicates();
+ return result;
+}
+
+bool LauncherSettings::writeFile(QTextStream &stream)
+{
+ QString sectionPrefix;
+ QRegExp sectionRe("([^/]+)/(.+)$");
+ QMap<QString, QString> settings = SettingsBase::getSettings();
+
+ QMapIterator<QString, QString> i(settings);
+ i.toBack();
+
+ while (i.hasPrevious()) {
+ i.previous();
+
+ QString prefix;
+ QString key;
+
+ if (sectionRe.exactMatch(i.key())) {
+ prefix = sectionRe.cap(1);
+ key = sectionRe.cap(2);
+ }
+
+ // Get rid of legacy settings
+ if (key.contains(QChar('\\')))
+ continue;
+
+ if (key == QLatin1String("CurrentProfile"))
+ continue;
+
+ if (sectionPrefix != prefix) {
+ sectionPrefix = prefix;
+ stream << "\n[" << prefix << "]\n";
+ }
+
+ stream << key << "=" << i.value() << "\n";
+ }
+
+ return true;
+
+}
diff --git a/apps/launcher/settings/launchersettings.hpp b/apps/launcher/settings/launchersettings.hpp
new file mode 100644
index 0000000000..60c6f86bc7
--- /dev/null
+++ b/apps/launcher/settings/launchersettings.hpp
@@ -0,0 +1,19 @@
+#ifndef LAUNCHERSETTINGS_HPP
+#define LAUNCHERSETTINGS_HPP
+
+#include "settingsbase.hpp"
+
+class LauncherSettings : public SettingsBase<QMap<QString, QString> >
+{
+public:
+ LauncherSettings();
+ ~LauncherSettings();
+
+ QStringList subKeys(const QString &key);
+ QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly);
+
+ bool writeFile(QTextStream &stream);
+
+};
+
+#endif // LAUNCHERSETTINGS_HPP
diff --git a/apps/launcher/settings/settingsbase.hpp b/apps/launcher/settings/settingsbase.hpp
new file mode 100644
index 0000000000..ed8ada56c3
--- /dev/null
+++ b/apps/launcher/settings/settingsbase.hpp
@@ -0,0 +1,109 @@
+#ifndef SETTINGSBASE_HPP
+#define SETTINGSBASE_HPP
+
+#include <QTextStream>
+#include <QStringList>
+#include <QString>
+#include <QRegExp>
+#include <QMap>
+
+template <class Map>
+class SettingsBase
+{
+
+public:
+ SettingsBase() { mMultiValue = false; }
+ ~SettingsBase() {}
+
+ inline QString value(const QString &key, const QString &defaultValue = QString())
+ {
+ return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
+ }
+
+ inline void setValue(const QString &key, const QString &value)
+ {
+ QStringList values = mSettings.values(key);
+ if (!values.contains(value))
+ mSettings.insert(key, value);
+ }
+
+ inline void setMultiValue(const QString &key, const QString &value)
+ {
+ QStringList values = mSettings.values(key);
+ if (!values.contains(value))
+ mSettings.insertMulti(key, value);
+ }
+
+ inline void setMultiValueEnabled(bool enable)
+ {
+ mMultiValue = enable;
+ }
+
+ inline void remove(const QString &key)
+ {
+ mSettings.remove(key);
+ }
+
+ Map getSettings() {return mSettings;}
+
+ bool readFile(QTextStream &stream)
+ {
+ mCache.clear();
+
+ QString sectionPrefix;
+
+ QRegExp sectionRe("^\\[([^]]+)\\]");
+ QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
+
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+
+ if (line.isEmpty() || line.startsWith("#"))
+ continue;
+
+ if (sectionRe.exactMatch(line)) {
+ sectionPrefix = sectionRe.cap(1);
+ sectionPrefix.append("/");
+ continue;
+ }
+
+ if (keyRe.indexIn(line) != -1) {
+
+ QString key = keyRe.cap(1).trimmed();
+ QString value = keyRe.cap(2).trimmed();
+
+ if (!sectionPrefix.isEmpty())
+ key.prepend(sectionPrefix);
+
+ mSettings.remove(key);
+
+ QStringList values = mCache.values(key);
+
+ if (!values.contains(value)) {
+ if (mMultiValue) {
+ mCache.insertMulti(key, value);
+ } else {
+ mCache.insert(key, value);
+ }
+ }
+ }
+ }
+
+ if (mSettings.isEmpty()) {
+ mSettings = mCache; // This is the first time we read a file
+ return true;
+ }
+
+ // Merge the changed keys with those which didn't
+ mSettings.unite(mCache);
+ return true;
+ }
+
+private:
+ Map mSettings;
+ Map mCache;
+
+ bool mMultiValue;
+};
+
+#endif // SETTINGSBASE_HPP
diff --git a/apps/launcher/textslotmsgbox.cpp b/apps/launcher/textslotmsgbox.cpp
new file mode 100644
index 0000000000..0607d1cc6e
--- /dev/null
+++ b/apps/launcher/textslotmsgbox.cpp
@@ -0,0 +1,6 @@
+#include "textslotmsgbox.hpp"
+
+void TextSlotMsgBox::setTextSlot(const QString& string)
+{
+ setText(string);
+}
diff --git a/apps/launcher/textslotmsgbox.hpp b/apps/launcher/textslotmsgbox.hpp
new file mode 100644
index 0000000000..a29e2c3543
--- /dev/null
+++ b/apps/launcher/textslotmsgbox.hpp
@@ -0,0 +1,13 @@
+#ifndef TEXT_SLOT_MSG_BOX
+#define TEXT_SLOT_MSG_BOX
+
+#include <QMessageBox>
+
+class TextSlotMsgBox : public QMessageBox
+{
+Q_OBJECT
+ public slots:
+ void setTextSlot(const QString& string);
+};
+
+#endif
diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp
new file mode 100644
index 0000000000..ab9d984e1a
--- /dev/null
+++ b/apps/launcher/unshieldthread.cpp
@@ -0,0 +1,487 @@
+#include "unshieldthread.hpp"
+
+#include <fstream>
+#include <components/misc/stringops.hpp>
+
+namespace bfs = boost::filesystem;
+
+namespace
+{
+ static bool make_sure_directory_exists(bfs::path directory)
+ {
+
+ if(!bfs::exists(directory))
+ {
+ bfs::create_directories(directory);
+ }
+
+ return bfs::exists(directory);
+ }
+
+ void fill_path(bfs::path& path, const std::string& name)
+ {
+ size_t start = 0;
+
+ size_t i;
+ for(i = 0; i < name.length(); i++)
+ {
+ switch(name[i])
+ {
+ case '\\':
+ path /= name.substr(start, i-start);
+ start = i+1;
+ break;
+ }
+ }
+
+ path /= name.substr(start, i-start);
+ }
+
+ std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx)
+ {
+ size_t start = inx.find(category);
+ start = inx.find(setting, start) + setting.length() + 3;
+
+ size_t end = inx.find("!", start);
+
+ return inx.substr(start, end-start);
+ }
+
+ std::string read_to_string(const bfs::path& path)
+ {
+ std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary);
+ std::string str;
+
+ strstream.seekg(0, std::ios::end);
+ str.resize(strstream.tellg());
+ strstream.seekg(0, std::ios::beg);
+ strstream.read(&str[0], str.size());
+ strstream.close();
+
+ return str;
+ }
+
+ void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini)
+ {
+ size_t loc;
+ loc = ini.find("[" + category + "]");
+
+ // If category is not found, create it
+ if(loc == std::string::npos)
+ {
+ loc = ini.size() + 2;
+ ini += ("\r\n[" + category + "]\r\n");
+ }
+
+ loc += category.length() +2 +2;
+ ini.insert(loc, setting + "=" + val + "\r\n");
+ }
+
+ void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath)
+ {
+ std::string inx = read_to_string(inxPath);
+
+ // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones)
+ size_t start = ini.find("[Weather Blight]");
+ start = ini.find("Ambient Loop Sound ID", start);
+ size_t end = ini.find("\r\n", start) +2;
+ ini.erase(start, end-start);
+
+ std::string category;
+ std::string setting;
+
+ category = "General";
+ {
+ setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ category = "Moons";
+ {
+ setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ category = "Weather";
+ {
+ setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ category = "Weather Blight";
+ {
+ setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ category = "Weather Snow";
+ {
+ setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ category = "Weather Blizzard";
+ {
+ setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini);
+ }
+ }
+
+
+ void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon)
+ {
+ bfs::path ini_path = output_dir;
+ ini_path /= "Morrowind.ini";
+
+ std::string ini = read_to_string(ini_path.string());
+
+ if(tribunal)
+ {
+ add_setting("Game Files", "GameFile1", "Tribunal.esm", ini);
+ add_setting("Archives", "Archive 0", "Tribunal.bsa", ini);
+ }
+ if(bloodmoon)
+ {
+ bloodmoon_fix_ini(ini, cdPath / "setup.inx");
+ add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini);
+ add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini);
+ }
+
+ std::ofstream inistream(ini_path.c_str());
+ inistream << ini;
+ inistream.close();
+ }
+
+ void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false)
+ {
+ make_sure_directory_exists(to);
+
+ for ( bfs::directory_iterator end, dir(from); dir != end; ++dir )
+ {
+ if(bfs::is_directory(dir->path()))
+ installToPath(dir->path(), to / dir->path().filename(), copy);
+ else
+ {
+ if(copy)
+ bfs::copy_file(dir->path(), to / dir->path().filename());
+ else
+ bfs::rename(dir->path(), to / dir->path().filename());
+ }
+ }
+ }
+
+ bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true)
+ {
+ if(recursive)
+ {
+ for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir )
+ {
+ if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ return dir->path();
+ }
+ }
+ else
+ {
+ for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
+ {
+ if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ return dir->path();
+ }
+ }
+
+ return "";
+ }
+
+ bool contains(const bfs::path& in, std::string filename)
+ {
+ for(bfs::directory_iterator end, dir(in); dir != end; ++dir)
+ {
+ if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ return true;
+ }
+
+ return false;
+ }
+
+ time_t getTime(const char* time)
+ {
+ struct tm tms;
+ memset(&tms, 0, sizeof(struct tm));
+ strptime(time, "%d %B %Y", &tms);
+ return mktime(&tms);
+ }
+}
+
+bool UnshieldThread::SetMorrowindPath(const std::string& path)
+{
+ mMorrowindPath = path;
+ return true;
+}
+
+bool UnshieldThread::SetTribunalPath(const std::string& path)
+{
+ mTribunalPath = path;
+ return true;
+}
+
+bool UnshieldThread::SetBloodmoonPath(const std::string& path)
+{
+ mBloodmoonPath = path;
+ return true;
+}
+
+void UnshieldThread::SetOutputPath(const std::string& path)
+{
+ mOutputPath = path;
+}
+
+bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index)
+{
+ bool success;
+ bfs::path dirname;
+ bfs::path filename;
+ int directory = unshield_file_directory(unshield, index);
+
+ dirname = output_dir;
+
+ if (prefix && prefix[0])
+ dirname /= prefix;
+
+ if (directory >= 0)
+ {
+ const char* tmp = unshield_directory_name(unshield, directory);
+ if (tmp && tmp[0])
+ fill_path(dirname, tmp);
+ }
+
+ make_sure_directory_exists(dirname);
+
+ filename = dirname;
+ filename /= unshield_file_name(unshield, index);
+
+ emit signalGUI(QString("Extracting: ") + QString(filename.c_str()));
+
+ success = unshield_file_save(unshield, index, filename.c_str());
+
+ if (!success)
+ bfs::remove(filename);
+
+ return success;
+}
+
+void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini)
+{
+ Unshield * unshield;
+ unshield = unshield_open(cab.c_str());
+
+ int i;
+ for (i = 0; i < unshield_file_group_count(unshield); i++)
+ {
+ UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i);
+
+ for (size_t j = file_group->first_file; j <= file_group->last_file; j++)
+ {
+ if (unshield_file_is_valid(unshield, j))
+ extract_file(unshield, output_dir, file_group->name, j);
+ }
+ }
+ unshield_close(unshield);
+}
+
+
+bool UnshieldThread::extract()
+{
+ bfs::path outputDataFilesDir = mOutputPath;
+ outputDataFilesDir /= "Data Files";
+ bfs::path extractPath = mOutputPath;
+ extractPath /= "extract-temp";
+
+ if(!mMorrowindDone && mMorrowindPath.string().length() > 0)
+ {
+ mMorrowindDone = true;
+
+ bfs::path mwExtractPath = extractPath / "morrowind";
+ extract_cab(mMorrowindPath, mwExtractPath, true);
+
+ bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path();
+
+ installToPath(dFilesDir, outputDataFilesDir);
+
+ // Videos are often kept uncompressed on the cd
+ bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false);
+ if(videosPath.string() != "")
+ {
+ emit signalGUI(QString("Installing Videos..."));
+ installToPath(videosPath, outputDataFilesDir / "Video", true);
+ }
+
+ bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false);
+ if(cdDFiles.string() != "")
+ {
+ emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
+ installToPath(cdDFiles, outputDataFilesDir, true);
+ }
+
+
+ bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini");
+
+ mTribunalDone = contains(outputDataFilesDir, "tribunal.esm");
+ mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
+
+ }
+
+ else if(!mTribunalDone && mTribunalPath.string().length() > 0)
+ {
+ mTribunalDone = true;
+
+ bfs::path tbExtractPath = extractPath / "tribunal";
+ extract_cab(mTribunalPath, tbExtractPath, true);
+
+ bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path();
+
+ installToPath(dFilesDir, outputDataFilesDir);
+
+ // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files
+ bfs::path soundsPath = findFile(tbExtractPath, "sounds", false);
+ if(soundsPath.string() != "")
+ installToPath(soundsPath, outputDataFilesDir / "Sounds");
+
+ bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false);
+ if(cdDFiles.string() != "")
+ {
+ emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
+ installToPath(cdDFiles, outputDataFilesDir, true);
+ }
+
+ mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
+
+ fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone);
+ }
+
+ else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0)
+ {
+ mBloodmoonDone = true;
+
+ bfs::path bmExtractPath = extractPath / "bloodmoon";
+ extract_cab(mBloodmoonPath, bmExtractPath, true);
+
+ bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path();
+
+ installToPath(dFilesDir, outputDataFilesDir);
+
+ // My GOTY CD contains a folder within cab files called Tribunal patch,
+ // which contains Tribunal.esm
+ bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm");
+ if(tbPatchPath.string() != "")
+ bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm");
+
+ bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false);
+ if(cdDFiles.string() != "")
+ {
+ emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
+ installToPath(cdDFiles, outputDataFilesDir, true);
+ }
+
+ fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone);
+ }
+
+
+ return true;
+}
+
+void UnshieldThread::Done()
+{
+ // Get rid of unnecessary files
+ bfs::remove_all(mOutputPath / "extract-temp");
+
+ // Set modified time to release dates, to preserve load order
+ if(mMorrowindDone)
+ bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002"));
+
+ if(mTribunalDone)
+ bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002"));
+
+ if(mBloodmoonDone)
+ bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003"));
+}
+
+std::string UnshieldThread::GetMWEsmPath()
+{
+ return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
+}
+
+bool UnshieldThread::TribunalDone()
+{
+ return mTribunalDone;
+}
+
+bool UnshieldThread::BloodmoonDone()
+{
+ return mBloodmoonDone;
+}
+
+void UnshieldThread::run()
+{
+ extract();
+ emit close();
+}
+
+UnshieldThread::UnshieldThread()
+{
+ mMorrowindDone = false;
+ mTribunalDone = false;
+ mBloodmoonDone = false;
+}
diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp
new file mode 100644
index 0000000000..b48d3d9878
--- /dev/null
+++ b/apps/launcher/unshieldthread.hpp
@@ -0,0 +1,57 @@
+#ifndef UNSHIELD_THREAD_H
+#define UNSHIELD_THREAD_H
+
+#include <QThread>
+
+#include <boost/filesystem.hpp>
+
+#include <libunshield.h>
+
+
+class UnshieldThread : public QThread
+{
+ Q_OBJECT
+
+ public:
+ bool SetMorrowindPath(const std::string& path);
+ bool SetTribunalPath(const std::string& path);
+ bool SetBloodmoonPath(const std::string& path);
+
+ void SetOutputPath(const std::string& path);
+
+ bool extract();
+
+ bool TribunalDone();
+ bool BloodmoonDone();
+
+ void Done();
+
+ std::string GetMWEsmPath();
+
+ UnshieldThread();
+
+ private:
+
+ void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false);
+ bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index);
+
+ boost::filesystem::path mMorrowindPath;
+ boost::filesystem::path mTribunalPath;
+ boost::filesystem::path mBloodmoonPath;
+
+ bool mMorrowindDone;
+ bool mTribunalDone;
+ bool mBloodmoonDone;
+
+ boost::filesystem::path mOutputPath;
+
+
+ protected:
+ virtual void run();
+
+ signals:
+ void signalGUI(QString);
+ void close();
+};
+
+#endif
diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp
new file mode 100644
index 0000000000..41207a8ded
--- /dev/null
+++ b/apps/launcher/utils/checkablemessagebox.cpp
@@ -0,0 +1,269 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "checkablemessagebox.hpp"
+
+#include <QVariant>
+
+#include <QPushButton>
+#include <QAction>
+#include <QApplication>
+#include <QButtonGroup>
+#include <QCheckBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QLabel>
+#include <QSpacerItem>
+#include <QVBoxLayout>
+
+
+/*!
+ \class Utils::CheckableMessageBox
+
+ \brief A messagebox suitable for questions with a
+ "Do not ask me again" checkbox.
+
+ Emulates the QMessageBox API with
+ static conveniences. The message label can open external URLs.
+*/
+
+class CheckableMessageBoxPrivate
+{
+public:
+ CheckableMessageBoxPrivate(QDialog *q)
+ : clickedButton(0)
+ {
+ QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+
+ pixmapLabel = new QLabel(q);
+ sizePolicy.setHorizontalStretch(0);
+ sizePolicy.setVerticalStretch(0);
+ sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth());
+ pixmapLabel->setSizePolicy(sizePolicy);
+ pixmapLabel->setVisible(false);
+
+ QSpacerItem *pixmapSpacer =
+ new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+
+ messageLabel = new QLabel(q);
+ messageLabel->setMinimumSize(QSize(300, 0));
+ messageLabel->setWordWrap(true);
+ messageLabel->setOpenExternalLinks(true);
+ messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse);
+
+ QSpacerItem *checkBoxRightSpacer =
+ new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ QSpacerItem *buttonSpacer =
+ new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ checkBox = new QCheckBox(q);
+ checkBox->setText(CheckableMessageBox::tr("Do not ask again"));
+
+ buttonBox = new QDialogButtonBox(q);
+ buttonBox->setOrientation(Qt::Horizontal);
+ buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
+
+ QVBoxLayout *verticalLayout = new QVBoxLayout();
+ verticalLayout->addWidget(pixmapLabel);
+ verticalLayout->addItem(pixmapSpacer);
+
+ QHBoxLayout *horizontalLayout_2 = new QHBoxLayout();
+ horizontalLayout_2->addLayout(verticalLayout);
+ horizontalLayout_2->addWidget(messageLabel);
+
+ QHBoxLayout *horizontalLayout = new QHBoxLayout();
+ horizontalLayout->addWidget(checkBox);
+ horizontalLayout->addItem(checkBoxRightSpacer);
+
+ QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q);
+ verticalLayout_2->addLayout(horizontalLayout_2);
+ verticalLayout_2->addLayout(horizontalLayout);
+ verticalLayout_2->addItem(buttonSpacer);
+ verticalLayout_2->addWidget(buttonBox);
+ }
+
+ QLabel *pixmapLabel;
+ QLabel *messageLabel;
+ QCheckBox *checkBox;
+ QDialogButtonBox *buttonBox;
+ QAbstractButton *clickedButton;
+};
+
+CheckableMessageBox::CheckableMessageBox(QWidget *parent) :
+ QDialog(parent),
+ d(new CheckableMessageBoxPrivate(this))
+{
+ setModal(true);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ connect(d->buttonBox, SIGNAL(accepted()), SLOT(accept()));
+ connect(d->buttonBox, SIGNAL(rejected()), SLOT(reject()));
+ connect(d->buttonBox, SIGNAL(clicked(QAbstractButton*)),
+ SLOT(slotClicked(QAbstractButton*)));
+}
+
+CheckableMessageBox::~CheckableMessageBox()
+{
+ delete d;
+}
+
+void CheckableMessageBox::slotClicked(QAbstractButton *b)
+{
+ d->clickedButton = b;
+}
+
+QAbstractButton *CheckableMessageBox::clickedButton() const
+{
+ return d->clickedButton;
+}
+
+QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const
+{
+ if (d->clickedButton)
+ return d->buttonBox->standardButton(d->clickedButton);
+ return QDialogButtonBox::NoButton;
+}
+
+QString CheckableMessageBox::text() const
+{
+ return d->messageLabel->text();
+}
+
+void CheckableMessageBox::setText(const QString &t)
+{
+ d->messageLabel->setText(t);
+}
+
+QPixmap CheckableMessageBox::iconPixmap() const
+{
+ if (const QPixmap *p = d->pixmapLabel->pixmap())
+ return QPixmap(*p);
+ return QPixmap();
+}
+
+void CheckableMessageBox::setIconPixmap(const QPixmap &p)
+{
+ d->pixmapLabel->setPixmap(p);
+ d->pixmapLabel->setVisible(!p.isNull());
+}
+
+bool CheckableMessageBox::isChecked() const
+{
+ return d->checkBox->isChecked();
+}
+
+void CheckableMessageBox::setChecked(bool s)
+{
+ d->checkBox->setChecked(s);
+}
+
+QString CheckableMessageBox::checkBoxText() const
+{
+ return d->checkBox->text();
+}
+
+void CheckableMessageBox::setCheckBoxText(const QString &t)
+{
+ d->checkBox->setText(t);
+}
+
+bool CheckableMessageBox::isCheckBoxVisible() const
+{
+ return d->checkBox->isVisible();
+}
+
+void CheckableMessageBox::setCheckBoxVisible(bool v)
+{
+ d->checkBox->setVisible(v);
+}
+
+QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const
+{
+ return d->buttonBox->standardButtons();
+}
+
+void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s)
+{
+ d->buttonBox->setStandardButtons(s);
+}
+
+QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const
+{
+ return d->buttonBox->button(b);
+}
+
+QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role)
+{
+ return d->buttonBox->addButton(text, role);
+}
+
+QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const
+{
+ foreach (QAbstractButton *b, d->buttonBox->buttons())
+ if (QPushButton *pb = qobject_cast<QPushButton *>(b))
+ if (pb->isDefault())
+ return d->buttonBox->standardButton(pb);
+ return QDialogButtonBox::NoButton;
+}
+
+void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s)
+{
+ if (QPushButton *b = d->buttonBox->button(s)) {
+ b->setDefault(true);
+ b->setFocus();
+ }
+}
+
+QDialogButtonBox::StandardButton
+CheckableMessageBox::question(QWidget *parent,
+ const QString &title,
+ const QString &question,
+ const QString &checkBoxText,
+ bool *checkBoxSetting,
+ QDialogButtonBox::StandardButtons buttons,
+ QDialogButtonBox::StandardButton defaultButton)
+{
+ CheckableMessageBox mb(parent);
+ mb.setWindowTitle(title);
+ mb.setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question));
+ mb.setText(question);
+ mb.setCheckBoxText(checkBoxText);
+ mb.setChecked(*checkBoxSetting);
+ mb.setStandardButtons(buttons);
+ mb.setDefaultButton(defaultButton);
+ mb.exec();
+ *checkBoxSetting = mb.isChecked();
+ return mb.clickedStandardButton();
+}
+
+QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db)
+{
+ return static_cast<QMessageBox::StandardButton>(int(db));
+}
diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp
new file mode 100644
index 0000000000..93fd43fe1f
--- /dev/null
+++ b/apps/launcher/utils/checkablemessagebox.hpp
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef CHECKABLEMESSAGEBOX_HPP
+#define CHECKABLEMESSAGEBOX_HPP
+
+#include <QDialogButtonBox>
+#include <QMessageBox>
+#include <QDialog>
+
+class CheckableMessageBoxPrivate;
+
+class CheckableMessageBox : public QDialog
+{
+ Q_OBJECT
+ Q_PROPERTY(QString text READ text WRITE setText)
+ Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap)
+ Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked)
+ Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText)
+ Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons)
+ Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton)
+
+public:
+ explicit CheckableMessageBox(QWidget *parent);
+ virtual ~CheckableMessageBox();
+
+ static QDialogButtonBox::StandardButton
+ question(QWidget *parent,
+ const QString &title,
+ const QString &question,
+ const QString &checkBoxText,
+ bool *checkBoxSetting,
+ QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No,
+ QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No);
+
+ QString text() const;
+ void setText(const QString &);
+
+ bool isChecked() const;
+ void setChecked(bool s);
+
+ QString checkBoxText() const;
+ void setCheckBoxText(const QString &);
+
+ bool isCheckBoxVisible() const;
+ void setCheckBoxVisible(bool);
+
+ QDialogButtonBox::StandardButtons standardButtons() const;
+ void setStandardButtons(QDialogButtonBox::StandardButtons s);
+ QPushButton *button(QDialogButtonBox::StandardButton b) const;
+ QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role);
+
+ QDialogButtonBox::StandardButton defaultButton() const;
+ void setDefaultButton(QDialogButtonBox::StandardButton s);
+
+ // See static QMessageBox::standardPixmap()
+ QPixmap iconPixmap() const;
+ void setIconPixmap (const QPixmap &p);
+
+ // Query the result
+ QAbstractButton *clickedButton() const;
+ QDialogButtonBox::StandardButton clickedStandardButton() const;
+
+ // Conversion convenience
+ static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton);
+
+private slots:
+ void slotClicked(QAbstractButton *b);
+
+private:
+ CheckableMessageBoxPrivate *d;
+};
+
+#endif // CHECKABLEMESSAGEBOX_HPP
diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp
new file mode 100644
index 0000000000..a4b36b95ea
--- /dev/null
+++ b/apps/launcher/utils/textinputdialog.cpp
@@ -0,0 +1,71 @@
+#include "textinputdialog.hpp"
+
+#include <QDialogButtonBox>
+#include <QApplication>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include <QValidator>
+#include <QLabel>
+
+#include <components/fileorderlist/utils/lineedit.hpp>
+
+TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
+ QDialog(parent)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ mButtonBox = new QDialogButtonBox(this);
+ mButtonBox->addButton(QDialogButtonBox::Ok);
+ mButtonBox->addButton(QDialogButtonBox::Cancel);
+
+ // Line edit
+ QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
+ mLineEdit = new LineEdit(this);
+ mLineEdit->setValidator(validator);
+ mLineEdit->setCompleter(0);
+
+ QLabel *label = new QLabel(this);
+ label->setText(text);
+
+ QVBoxLayout *dialogLayout = new QVBoxLayout(this);
+ dialogLayout->addWidget(label);
+ dialogLayout->addWidget(mLineEdit);
+ dialogLayout->addWidget(mButtonBox);
+
+ // Messageboxes on mac have no title
+#ifndef Q_OS_MAC
+ setWindowTitle(title);
+#else
+ Q_UNUSED(title);
+#endif
+
+ setOkButtonEnabled(false);
+ setModal(true);
+
+ connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject()));
+
+}
+
+int TextInputDialog::exec()
+{
+ mLineEdit->clear();
+ mLineEdit->setFocus();
+ return QDialog::exec();
+}
+
+void TextInputDialog::setOkButtonEnabled(bool enabled)
+{
+ QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok);
+ okButton->setEnabled(enabled);
+
+ QPalette *palette = new QPalette();
+ palette->setColor(QPalette::Text,Qt::red);
+
+ if (enabled) {
+ mLineEdit->setPalette(QApplication::palette());
+ } else {
+ // Existing profile name, make the text red
+ mLineEdit->setPalette(*palette);
+ }
+
+}
diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp
new file mode 100644
index 0000000000..cbb453ac83
--- /dev/null
+++ b/apps/launcher/utils/textinputdialog.hpp
@@ -0,0 +1,28 @@
+#ifndef TEXTINPUTDIALOG_HPP
+#define TEXTINPUTDIALOG_HPP
+
+#include <QDialog>
+//#include "lineedit.hpp"
+
+class QDialogButtonBox;
+class LineEdit;
+
+class TextInputDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0);
+ inline LineEdit *lineEdit() { return mLineEdit; }
+ void setOkButtonEnabled(bool enabled);
+
+ LineEdit *mLineEdit;
+
+ int exec();
+
+private:
+ QDialogButtonBox *mButtonBox;
+
+
+};
+
+#endif // TEXTINPUTDIALOG_HPP
diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt
new file mode 100644
index 0000000000..702f665138
--- /dev/null
+++ b/apps/mwiniimporter/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(MWINIIMPORT
+ main.cpp
+ importer.cpp
+)
+
+set(MWINIIMPORT_HEADER
+ importer.hpp
+)
+
+source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
+
+add_executable(mwiniimport
+ ${MWINIIMPORT}
+)
+
+target_link_libraries(mwiniimport
+ ${Boost_LIBRARIES}
+ components
+)
+
+if (BUILD_WITH_CODE_COVERAGE)
+ add_definitions (--coverage)
+ target_link_libraries(mwiniimport gcov)
+endif()
+
+if(DPKG_PROGRAM)
+ INSTALL(TARGETS mwiniimport RUNTIME DESTINATION games COMPONENT mwiniimport)
+endif()
+
diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp
new file mode 100644
index 0000000000..8732b3eab3
--- /dev/null
+++ b/apps/mwiniimporter/importer.cpp
@@ -0,0 +1,873 @@
+#include "importer.hpp"
+#include <boost/iostreams/device/file.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <iostream>
+#include <string>
+#include <map>
+#include <vector>
+#include <algorithm>
+#include <sstream>
+#include <components/misc/stringops.hpp>
+
+
+MwIniImporter::MwIniImporter()
+ : mVerbose(false)
+{
+ const char *map[][2] =
+ {
+ { "fps", "General:Show FPS" },
+ { "nosound", "General:Disable Audio" },
+ { 0, 0 }
+ };
+ const char *fallback[] = {
+
+ // light
+ "LightAttenuation:UseConstant",
+ "LightAttenuation:ConstantValue",
+ "LightAttenuation:UseLinear",
+ "LightAttenuation:LinearMethod",
+ "LightAttenuation:LinearValue",
+ "LightAttenuation:LinearRadiusMult",
+ "LightAttenuation:UseQuadratic",
+ "LightAttenuation:QuadraticMethod",
+ "LightAttenuation:QuadraticValue",
+ "LightAttenuation:QuadraticRadiusMult",
+ "LightAttenuation:OutQuadInLin",
+
+ // inventory
+ "Inventory:DirectionalDiffuseR",
+ "Inventory:DirectionalDiffuseG",
+ "Inventory:DirectionalDiffuseB",
+ "Inventory:DirectionalAmbientR",
+ "Inventory:DirectionalAmbientG",
+ "Inventory:DirectionalAmbientB",
+ "Inventory:DirectionalRotationX",
+ "Inventory:DirectionalRotationY",
+ "Inventory:UniformScaling",
+
+ // map
+ "Map:Travel Siltstrider Red",
+ "Map:Travel Siltstrider Green",
+ "Map:Travel Siltstrider Blue",
+ "Map:Travel Boat Red",
+ "Map:Travel Boat Green",
+ "Map:Travel Boat Blue",
+ "Map:Travel Magic Red",
+ "Map:Travel Magic Green",
+ "Map:Travel Magic Blue",
+ "Map:Show Travel Lines",
+
+ // water
+ "Water:Map Alpha",
+ "Water:World Alpha",
+ "Water:SurfaceTextureSize",
+ "Water:SurfaceTileCount",
+ "Water:SurfaceFPS",
+ "Water:SurfaceTexture",
+ "Water:SurfaceFrameCount",
+ "Water:TileTextureDivisor",
+ "Water:RippleTexture",
+ "Water:RippleFrameCount",
+ "Water:RippleLifetime",
+ "Water:MaxNumberRipples",
+ "Water:RippleScale",
+ "Water:RippleRotSpeed",
+ "Water:RippleAlphas",
+ "Water:PSWaterReflectTerrain",
+ "Water:PSWaterReflectUpdate",
+ "Water:NearWaterRadius",
+ "Water:NearWaterPoints",
+ "Water:NearWaterUnderwaterFreq",
+ "Water:NearWaterUnderwaterVolume",
+ "Water:NearWaterIndoorTolerance",
+ "Water:NearWaterOutdoorTolerance",
+ "Water:NearWaterIndoorID",
+ "Water:NearWaterOutdoorID",
+ "Water:UnderwaterSunriseFog",
+ "Water:UnderwaterDayFog",
+ "Water:UnderwaterSunsetFog",
+ "Water:UnderwaterNightFog",
+ "Water:UnderwaterIndoorFog",
+ "Water:UnderwaterColor",
+ "Water:UnderwaterColorWeight",
+
+ // pixelwater
+ "PixelWater:SurfaceFPS",
+ "PixelWater:TileCount",
+ "PixelWater:Resolution",
+
+ // fonts
+ "Fonts:Font 0",
+ "Fonts:Font 1",
+ "Fonts:Font 2",
+
+ // UI colors
+ "FontColor:color_normal",
+ "FontColor:color_normal_over",
+ "FontColor:color_normal_pressed",
+ "FontColor:color_active",
+ "FontColor:color_active_over",
+ "FontColor:color_active_pressed",
+ "FontColor:color_disabled",
+ "FontColor:color_disabled_over",
+ "FontColor:color_disabled_pressed",
+ "FontColor:color_link",
+ "FontColor:color_link_over",
+ "FontColor:color_link_pressed",
+ "FontColor:color_journal_link",
+ "FontColor:color_journal_link_over",
+ "FontColor:color_journal_link_pressed",
+ "FontColor:color_journal_topic",
+ "FontColor:color_journal_topic_over",
+ "FontColor:color_journal_topic_pressed",
+ "FontColor:color_answer",
+ "FontColor:color_answer_over",
+ "FontColor:color_answer_pressed",
+ "FontColor:color_header",
+ "FontColor:color_notify",
+ "FontColor:color_big_normal",
+ "FontColor:color_big_normal_over",
+ "FontColor:color_big_normal_pressed",
+ "FontColor:color_big_link",
+ "FontColor:color_big_link_over",
+ "FontColor:color_big_link_pressed",
+ "FontColor:color_big_answer",
+ "FontColor:color_big_answer_over",
+ "FontColor:color_big_answer_pressed",
+ "FontColor:color_big_header",
+ "FontColor:color_big_notify",
+ "FontColor:color_background",
+ "FontColor:color_focus",
+ "FontColor:color_health",
+ "FontColor:color_magic",
+ "FontColor:color_fatigue",
+ "FontColor:color_misc",
+ "FontColor:color_weapon_fill",
+ "FontColor:color_magic_fill",
+ "FontColor:color_positive",
+ "FontColor:color_negative",
+ "FontColor:color_count",
+
+ // level up messages
+ "Level Up:Level2",
+ "Level Up:Level3",
+ "Level Up:Level4",
+ "Level Up:Level5",
+ "Level Up:Level6",
+ "Level Up:Level7",
+ "Level Up:Level8",
+ "Level Up:Level9",
+ "Level Up:Level10",
+ "Level Up:Level11",
+ "Level Up:Level12",
+ "Level Up:Level13",
+ "Level Up:Level14",
+ "Level Up:Level15",
+ "Level Up:Level16",
+ "Level Up:Level17",
+ "Level Up:Level18",
+ "Level Up:Level19",
+ "Level Up:Level20",
+ "Level Up:Default",
+
+ // character creation multiple choice test
+ "Question 1:Question",
+ "Question 1:AnswerOne",
+ "Question 1:AnswerTwo",
+ "Question 1:AnswerThree",
+ "Question 1:Sound",
+ "Question 2:Question",
+ "Question 2:AnswerOne",
+ "Question 2:AnswerTwo",
+ "Question 2:AnswerThree",
+ "Question 2:Sound",
+ "Question 3:Question",
+ "Question 3:AnswerOne",
+ "Question 3:AnswerTwo",
+ "Question 3:AnswerThree",
+ "Question 3:Sound",
+ "Question 4:Question",
+ "Question 4:AnswerOne",
+ "Question 4:AnswerTwo",
+ "Question 4:AnswerThree",
+ "Question 4:Sound",
+ "Question 5:Question",
+ "Question 5:AnswerOne",
+ "Question 5:AnswerTwo",
+ "Question 5:AnswerThree",
+ "Question 5:Sound",
+ "Question 6:Question",
+ "Question 6:AnswerOne",
+ "Question 6:AnswerTwo",
+ "Question 6:AnswerThree",
+ "Question 6:Sound",
+ "Question 7:Question",
+ "Question 7:AnswerOne",
+ "Question 7:AnswerTwo",
+ "Question 7:AnswerThree",
+ "Question 7:Sound",
+ "Question 8:Question",
+ "Question 8:AnswerOne",
+ "Question 8:AnswerTwo",
+ "Question 8:AnswerThree",
+ "Question 8:Sound",
+ "Question 9:Question",
+ "Question 9:AnswerOne",
+ "Question 9:AnswerTwo",
+ "Question 9:AnswerThree",
+ "Question 9:Sound",
+ "Question 10:Question",
+ "Question 10:AnswerOne",
+ "Question 10:AnswerTwo",
+ "Question 10:AnswerThree",
+ "Question 10:Sound",
+
+ // blood textures and models
+ "Blood:Model 0",
+ "Blood:Model 1",
+ "Blood:Model 2",
+ "Blood:Texture 0",
+ "Blood:Texture 1",
+ "Blood:Texture 2",
+ "Blood:Texture Name 0",
+ "Blood:Texture Name 1",
+ "Blood:Texture Name 2",
+
+ // movies
+ "Movies:Company Logo",
+ "Movies:Morrowind Logo",
+ "Movies:New Game",
+ "Movies:Loading",
+ "Movies:Options Menu",
+
+ // weather related values
+
+ "Weather Thunderstorm:Thunder Sound ID 0",
+ "Weather Thunderstorm:Thunder Sound ID 1",
+ "Weather Thunderstorm:Thunder Sound ID 2",
+ "Weather Thunderstorm:Thunder Sound ID 3",
+ "Weather:Sunrise Time",
+ "Weather:Sunset Time",
+ "Weather:Sunrise Duration",
+ "Weather:Sunset Duration",
+ "Weather:Hours Between Weather Changes", // AKA weather update time
+ "Weather Thunderstorm:Thunder Frequency",
+ "Weather Thunderstorm:Thunder Threshold",
+
+ "Weather:EnvReduceColor",
+ "Weather:LerpCloseColor",
+ "Weather:BumpFadeColor",
+ "Weather:AlphaReduce",
+ "Weather:Minimum Time Between Environmental Sounds",
+ "Weather:Maximum Time Between Environmental Sounds",
+ "Weather:Sun Glare Fader Max",
+ "Weather:Sun Glare Fader Angle Max",
+ "Weather:Sun Glare Fader Color",
+ "Weather:Timescale Clouds",
+ "Weather:Precip Gravity",
+ "Weather:Rain Ripples",
+ "Weather:Rain Ripple Radius",
+ "Weather:Rain Ripples Per Drop",
+ "Weather:Rain Ripple Scale",
+ "Weather:Rain Ripple Speed",
+ "Weather:Fog Depth Change Speed",
+ "Weather:Sky Pre-Sunrise Time",
+ "Weather:Sky Post-Sunrise Time",
+ "Weather:Sky Pre-Sunset Time",
+ "Weather:Sky Post-Sunset Time",
+ "Weather:Ambient Pre-Sunrise Time",
+ "Weather:Ambient Post-Sunrise Time",
+ "Weather:Ambient Pre-Sunset Time",
+ "Weather:Ambient Post-Sunset Time",
+ "Weather:Fog Pre-Sunrise Time",
+ "Weather:Fog Post-Sunrise Time",
+ "Weather:Fog Pre-Sunset Time",
+ "Weather:Fog Post-Sunset Time",
+ "Weather:Sun Pre-Sunrise Time",
+ "Weather:Sun Post-Sunrise Time",
+ "Weather:Sun Pre-Sunset Time",
+ "Weather:Sun Post-Sunset Time",
+ "Weather:Stars Post-Sunset Start",
+ "Weather:Stars Pre-Sunrise Finish",
+ "Weather:Stars Fading Duration",
+ "Weather:Snow Ripples",
+ "Weather:Snow Ripple Radius",
+ "Weather:Snow Ripples Per Flake",
+ "Weather:Snow Ripple Scale",
+ "Weather:Snow Ripple Speed",
+ "Weather:Snow Gravity Scale",
+ "Weather:Snow High Kill",
+ "Weather:Snow Low Kill",
+
+ "Weather Clear:Cloud Texture",
+ "Weather Clear:Clouds Maximum Percent",
+ "Weather Clear:Transition Delta",
+ "Weather Clear:Sky Sunrise Color",
+ "Weather Clear:Sky Day Color",
+ "Weather Clear:Sky Sunset Color",
+ "Weather Clear:Sky Night Color",
+ "Weather Clear:Fog Sunrise Color",
+ "Weather Clear:Fog Day Color",
+ "Weather Clear:Fog Sunset Color",
+ "Weather Clear:Fog Night Color",
+ "Weather Clear:Ambient Sunrise Color",
+ "Weather Clear:Ambient Day Color",
+ "Weather Clear:Ambient Sunset Color",
+ "Weather Clear:Ambient Night Color",
+ "Weather Clear:Sun Sunrise Color",
+ "Weather Clear:Sun Day Color",
+ "Weather Clear:Sun Sunset Color",
+ "Weather Clear:Sun Night Color",
+ "Weather Clear:Sun Disc Sunset Color",
+ "Weather Clear:Land Fog Day Depth",
+ "Weather Clear:Land Fog Night Depth",
+ "Weather Clear:Wind Speed",
+ "Weather Clear:Cloud Speed",
+ "Weather Clear:Glare View",
+ "Weather Clear:Ambient Loop Sound ID",
+
+ "Weather Cloudy:Cloud Texture",
+ "Weather Cloudy:Clouds Maximum Percent",
+ "Weather Cloudy:Transition Delta",
+ "Weather Cloudy:Sky Sunrise Color",
+ "Weather Cloudy:Sky Day Color",
+ "Weather Cloudy:Sky Sunset Color",
+ "Weather Cloudy:Sky Night Color",
+ "Weather Cloudy:Fog Sunrise Color",
+ "Weather Cloudy:Fog Day Color",
+ "Weather Cloudy:Fog Sunset Color",
+ "Weather Cloudy:Fog Night Color",
+ "Weather Cloudy:Ambient Sunrise Color",
+ "Weather Cloudy:Ambient Day Color",
+ "Weather Cloudy:Ambient Sunset Color",
+ "Weather Cloudy:Ambient Night Color",
+ "Weather Cloudy:Sun Sunrise Color",
+ "Weather Cloudy:Sun Day Color",
+ "Weather Cloudy:Sun Sunset Color",
+ "Weather Cloudy:Sun Night Color",
+ "Weather Cloudy:Sun Disc Sunset Color",
+ "Weather Cloudy:Land Fog Day Depth",
+ "Weather Cloudy:Land Fog Night Depth",
+ "Weather Cloudy:Wind Speed",
+ "Weather Cloudy:Cloud Speed",
+ "Weather Cloudy:Glare View",
+ "Weather Cloudy:Ambient Loop Sound ID",
+
+ "Weather Foggy:Cloud Texture",
+ "Weather Foggy:Clouds Maximum Percent",
+ "Weather Foggy:Transition Delta",
+ "Weather Foggy:Sky Sunrise Color",
+ "Weather Foggy:Sky Day Color",
+ "Weather Foggy:Sky Sunset Color",
+ "Weather Foggy:Sky Night Color",
+ "Weather Foggy:Fog Sunrise Color",
+ "Weather Foggy:Fog Day Color",
+ "Weather Foggy:Fog Sunset Color",
+ "Weather Foggy:Fog Night Color",
+ "Weather Foggy:Ambient Sunrise Color",
+ "Weather Foggy:Ambient Day Color",
+ "Weather Foggy:Ambient Sunset Color",
+ "Weather Foggy:Ambient Night Color",
+ "Weather Foggy:Sun Sunrise Color",
+ "Weather Foggy:Sun Day Color",
+ "Weather Foggy:Sun Sunset Color",
+ "Weather Foggy:Sun Night Color",
+ "Weather Foggy:Sun Disc Sunset Color",
+ "Weather Foggy:Land Fog Day Depth",
+ "Weather Foggy:Land Fog Night Depth",
+ "Weather Foggy:Wind Speed",
+ "Weather Foggy:Cloud Speed",
+ "Weather Foggy:Glare View",
+ "Weather Foggy:Ambient Loop Sound ID",
+
+ "Weather Thunderstorm:Cloud Texture",
+ "Weather Thunderstorm:Clouds Maximum Percent",
+ "Weather Thunderstorm:Transition Delta",
+ "Weather Thunderstorm:Sky Sunrise Color",
+ "Weather Thunderstorm:Sky Day Color",
+ "Weather Thunderstorm:Sky Sunset Color",
+ "Weather Thunderstorm:Sky Night Color",
+ "Weather Thunderstorm:Fog Sunrise Color",
+ "Weather Thunderstorm:Fog Day Color",
+ "Weather Thunderstorm:Fog Sunset Color",
+ "Weather Thunderstorm:Fog Night Color",
+ "Weather Thunderstorm:Ambient Sunrise Color",
+ "Weather Thunderstorm:Ambient Day Color",
+ "Weather Thunderstorm:Ambient Sunset Color",
+ "Weather Thunderstorm:Ambient Night Color",
+ "Weather Thunderstorm:Sun Sunrise Color",
+ "Weather Thunderstorm:Sun Day Color",
+ "Weather Thunderstorm:Sun Sunset Color",
+ "Weather Thunderstorm:Sun Night Color",
+ "Weather Thunderstorm:Sun Disc Sunset Color",
+ "Weather Thunderstorm:Land Fog Day Depth",
+ "Weather Thunderstorm:Land Fog Night Depth",
+ "Weather Thunderstorm:Wind Speed",
+ "Weather Thunderstorm:Cloud Speed",
+ "Weather Thunderstorm:Glare View",
+ "Weather Thunderstorm:Rain Loop Sound ID",
+ "Weather Thunderstorm:Using Precip",
+ "Weather Thunderstorm:Rain Diameter",
+ "Weather Thunderstorm:Rain Height Min",
+ "Weather Thunderstorm:Rain Height Max",
+ "Weather Thunderstorm:Rain Threshold",
+ "Weather Thunderstorm:Max Raindrops",
+ "Weather Thunderstorm:Rain Entrance Speed",
+ "Weather Thunderstorm:Ambient Loop Sound ID",
+ "Weather Thunderstorm:Flash Decrement",
+
+ "Weather Rain:Cloud Texture",
+ "Weather Rain:Clouds Maximum Percent",
+ "Weather Rain:Transition Delta",
+ "Weather Rain:Sky Sunrise Color",
+ "Weather Rain:Sky Day Color",
+ "Weather Rain:Sky Sunset Color",
+ "Weather Rain:Sky Night Color",
+ "Weather Rain:Fog Sunrise Color",
+ "Weather Rain:Fog Day Color",
+ "Weather Rain:Fog Sunset Color",
+ "Weather Rain:Fog Night Color",
+ "Weather Rain:Ambient Sunrise Color",
+ "Weather Rain:Ambient Day Color",
+ "Weather Rain:Ambient Sunset Color",
+ "Weather Rain:Ambient Night Color",
+ "Weather Rain:Sun Sunrise Color",
+ "Weather Rain:Sun Day Color",
+ "Weather Rain:Sun Sunset Color",
+ "Weather Rain:Sun Night Color",
+ "Weather Rain:Sun Disc Sunset Color",
+ "Weather Rain:Land Fog Day Depth",
+ "Weather Rain:Land Fog Night Depth",
+ "Weather Rain:Wind Speed",
+ "Weather Rain:Cloud Speed",
+ "Weather Rain:Glare View",
+ "Weather Rain:Rain Loop Sound ID",
+ "Weather Rain:Using Precip",
+ "Weather Rain:Rain Diameter",
+ "Weather Rain:Rain Height Min",
+ "Weather Rain:Rain Height Max",
+ "Weather Rain:Rain Threshold",
+ "Weather Rain:Rain Entrance Speed",
+ "Weather Rain:Ambient Loop Sound ID",
+ "Weather Rain:Max Raindrops",
+
+ "Weather Overcast:Cloud Texture",
+ "Weather Overcast:Clouds Maximum Percent",
+ "Weather Overcast:Transition Delta",
+ "Weather Overcast:Sky Sunrise Color",
+ "Weather Overcast:Sky Day Color",
+ "Weather Overcast:Sky Sunset Color",
+ "Weather Overcast:Sky Night Color",
+ "Weather Overcast:Fog Sunrise Color",
+ "Weather Overcast:Fog Day Color",
+ "Weather Overcast:Fog Sunset Color",
+ "Weather Overcast:Fog Night Color",
+ "Weather Overcast:Ambient Sunrise Color",
+ "Weather Overcast:Ambient Day Color",
+ "Weather Overcast:Ambient Sunset Color",
+ "Weather Overcast:Ambient Night Color",
+ "Weather Overcast:Sun Sunrise Color",
+ "Weather Overcast:Sun Day Color",
+ "Weather Overcast:Sun Sunset Color",
+ "Weather Overcast:Sun Night Color",
+ "Weather Overcast:Sun Disc Sunset Color",
+ "Weather Overcast:Land Fog Day Depth",
+ "Weather Overcast:Land Fog Night Depth",
+ "Weather Overcast:Wind Speed",
+ "Weather Overcast:Cloud Speed",
+ "Weather Overcast:Glare View",
+ "Weather Overcast:Ambient Loop Sound ID",
+
+ "Weather Ashstorm:Cloud Texture",
+ "Weather Ashstorm:Clouds Maximum Percent",
+ "Weather Ashstorm:Transition Delta",
+ "Weather Ashstorm:Sky Sunrise Color",
+ "Weather Ashstorm:Sky Day Color",
+ "Weather Ashstorm:Sky Sunset Color",
+ "Weather Ashstorm:Sky Night Color",
+ "Weather Ashstorm:Fog Sunrise Color",
+ "Weather Ashstorm:Fog Day Color",
+ "Weather Ashstorm:Fog Sunset Color",
+ "Weather Ashstorm:Fog Night Color",
+ "Weather Ashstorm:Ambient Sunrise Color",
+ "Weather Ashstorm:Ambient Day Color",
+ "Weather Ashstorm:Ambient Sunset Color",
+ "Weather Ashstorm:Ambient Night Color",
+ "Weather Ashstorm:Sun Sunrise Color",
+ "Weather Ashstorm:Sun Day Color",
+ "Weather Ashstorm:Sun Sunset Color",
+ "Weather Ashstorm:Sun Night Color",
+ "Weather Ashstorm:Sun Disc Sunset Color",
+ "Weather Ashstorm:Land Fog Day Depth",
+ "Weather Ashstorm:Land Fog Night Depth",
+ "Weather Ashstorm:Wind Speed",
+ "Weather Ashstorm:Cloud Speed",
+ "Weather Ashstorm:Glare View",
+ "Weather Ashstorm:Ambient Loop Sound ID",
+ "Weather Ashstorm:Storm Threshold",
+
+ "Weather Blight:Cloud Texture",
+ "Weather Blight:Clouds Maximum Percent",
+ "Weather Blight:Transition Delta",
+ "Weather Blight:Sky Sunrise Color",
+ "Weather Blight:Sky Day Color",
+ "Weather Blight:Sky Sunset Color",
+ "Weather Blight:Sky Night Color",
+ "Weather Blight:Fog Sunrise Color",
+ "Weather Blight:Fog Day Color",
+ "Weather Blight:Fog Sunset Color",
+ "Weather Blight:Fog Night Color",
+ "Weather Blight:Ambient Sunrise Color",
+ "Weather Blight:Ambient Day Color",
+ "Weather Blight:Ambient Sunset Color",
+ "Weather Blight:Ambient Night Color",
+ "Weather Blight:Sun Sunrise Color",
+ "Weather Blight:Sun Day Color",
+ "Weather Blight:Sun Sunset Color",
+ "Weather Blight:Sun Night Color",
+ "Weather Blight:Sun Disc Sunset Color",
+ "Weather Blight:Land Fog Day Depth",
+ "Weather Blight:Land Fog Night Depth",
+ "Weather Blight:Wind Speed",
+ "Weather Blight:Cloud Speed",
+ "Weather Blight:Glare View",
+ "Weather Blight:Ambient Loop Sound ID",
+ "Weather Blight:Storm Threshold",
+ "Weather Blight:Disease Chance",
+
+ // for Bloodmoon
+ "Weather Snow:Cloud Texture",
+ "Weather Snow:Clouds Maximum Percent",
+ "Weather Snow:Transition Delta",
+ "Weather Snow:Sky Sunrise Color",
+ "Weather Snow:Sky Day Color",
+ "Weather Snow:Sky Sunset Color",
+ "Weather Snow:Sky Night Color",
+ "Weather Snow:Fog Sunrise Color",
+ "Weather Snow:Fog Day Color",
+ "Weather Snow:Fog Sunset Color",
+ "Weather Snow:Fog Night Color",
+ "Weather Snow:Ambient Sunrise Color",
+ "Weather Snow:Ambient Day Color",
+ "Weather Snow:Ambient Sunset Color",
+ "Weather Snow:Ambient Night Color",
+ "Weather Snow:Sun Sunrise Color",
+ "Weather Snow:Sun Day Color",
+ "Weather Snow:Sun Sunset Color",
+ "Weather Snow:Sun Night Color",
+ "Weather Snow:Sun Disc Sunset Color",
+ "Weather Snow:Land Fog Day Depth",
+ "Weather Snow:Land Fog Night Depth",
+ "Weather Snow:Wind Speed",
+ "Weather Snow:Cloud Speed",
+ "Weather Snow:Glare View",
+ "Weather Snow:Snow Diameter",
+ "Weather Snow:Snow Height Min",
+ "Weather Snow:Snow Height Max",
+ "Weather Snow:Snow Entrance Speed",
+ "Weather Snow:Max Snowflakes",
+ "Weather Snow:Ambient Loop Sound ID",
+ "Weather Snow:Snow Threshold",
+
+ // for Bloodmoon
+ "Weather Blizzard:Cloud Texture",
+ "Weather Blizzard:Clouds Maximum Percent",
+ "Weather Blizzard:Transition Delta",
+ "Weather Blizzard:Sky Sunrise Color",
+ "Weather Blizzard:Sky Day Color",
+ "Weather Blizzard:Sky Sunset Color",
+ "Weather Blizzard:Sky Night Color",
+ "Weather Blizzard:Fog Sunrise Color",
+ "Weather Blizzard:Fog Day Color",
+ "Weather Blizzard:Fog Sunset Color",
+ "Weather Blizzard:Fog Night Color",
+ "Weather Blizzard:Ambient Sunrise Color",
+ "Weather Blizzard:Ambient Day Color",
+ "Weather Blizzard:Ambient Sunset Color",
+ "Weather Blizzard:Ambient Night Color",
+ "Weather Blizzard:Sun Sunrise Color",
+ "Weather Blizzard:Sun Day Color",
+ "Weather Blizzard:Sun Sunset Color",
+ "Weather Blizzard:Sun Night Color",
+ "Weather Blizzard:Sun Disc Sunset Color",
+ "Weather Blizzard:Land Fog Day Depth",
+ "Weather Blizzard:Land Fog Night Depth",
+ "Weather Blizzard:Wind Speed",
+ "Weather Blizzard:Cloud Speed",
+ "Weather Blizzard:Glare View",
+ "Weather Blizzard:Ambient Loop Sound ID",
+ "Weather Blizzard:Storm Threshold",
+
+ // moons
+ "Moons:Secunda Size",
+ "Moons:Secunda Axis Offset",
+ "Moons:Secunda Speed",
+ "Moons:Secunda Daily Increment",
+ "Moons:Secunda Moon Shadow Early Fade Angle",
+ "Moons:Secunda Fade Start Angle",
+ "Moons:Secunda Fade End Angle",
+ "Moons:Secunda Fade In Start",
+ "Moons:Secunda Fade In Finish",
+ "Moons:Secunda Fade Out Start",
+ "Moons:Secunda Fade Out Finish",
+ "Moons:Masser Size",
+ "Moons:Masser Axis Offset",
+ "Moons:Masser Speed",
+ "Moons:Masser Daily Increment",
+ "Moons:Masser Moon Shadow Early Fade Angle",
+ "Moons:Masser Fade Start Angle",
+ "Moons:Masser Fade End Angle",
+ "Moons:Masser Fade In Start",
+ "Moons:Masser Fade In Finish",
+ "Moons:Masser Fade Out Start",
+ "Moons:Masser Fade Out Finish",
+ "Moons:Script Color",
+
+ 0
+ };
+
+ for(int i=0; map[i][0]; i++) {
+ mMergeMap.insert(std::make_pair<std::string, std::string>(map[i][0], map[i][1]));
+ }
+
+ for(int i=0; fallback[i]; i++) {
+ mMergeFallback.push_back(fallback[i]);
+ }
+}
+
+void MwIniImporter::setVerbose(bool verbose) {
+ mVerbose = verbose;
+}
+
+std::string MwIniImporter::numberToString(int n) {
+ std::stringstream str;
+ str << n;
+ return str.str();
+}
+
+MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filename) const {
+ std::cout << "load ini file: " << filename << std::endl;
+
+ std::string section("");
+ MwIniImporter::multistrmap map;
+ boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
+ ToUTF8::Utf8Encoder encoder(mEncoding);
+
+ std::string line;
+ while (std::getline(file, line)) {
+
+ line = encoder.getUtf8(line);
+
+ // unify Unix-style and Windows file ending
+ if (!(line.empty()) && (line[line.length()-1]) == '\r') {
+ line = line.substr(0, line.length()-1);
+ }
+
+ if(line[0] == '[') {
+ int pos = line.find(']');
+ if(pos < 2) {
+ std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl;
+ continue;
+ }
+
+ section = line.substr(1, line.find(']')-1);
+ continue;
+ }
+
+ int comment_pos = line.find(";");
+ if(comment_pos > 0) {
+ line = line.substr(0,comment_pos);
+ }
+
+ if(line.empty()) {
+ continue;
+ }
+
+ int pos = line.find("=");
+ if(pos < 1) {
+ continue;
+ }
+
+ std::string key(section + ":" + line.substr(0,pos));
+ std::string value(line.substr(pos+1));
+ if(value.empty()) {
+ std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl;
+ continue;
+ }
+
+ multistrmap::iterator it;
+ if((it = map.find(key)) == map.end()) {
+ map.insert( std::make_pair (key, std::vector<std::string>() ) );
+ }
+ map[key].push_back(value);
+ }
+
+ return map;
+}
+
+MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filename) {
+ std::cout << "load cfg file: " << filename << std::endl;
+
+ MwIniImporter::multistrmap map;
+ boost::iostreams::stream<boost::iostreams::file_source>file(filename.c_str());
+
+ std::string line;
+ while (std::getline(file, line)) {
+
+ // we cant say comment by only looking at first char anymore
+ int comment_pos = line.find("#");
+ if(comment_pos > 0) {
+ line = line.substr(0,comment_pos);
+ }
+
+ if(line.empty()) {
+ continue;
+ }
+
+ int pos = line.find("=");
+ if(pos < 1) {
+ continue;
+ }
+
+ std::string key(line.substr(0,pos));
+ std::string value(line.substr(pos+1));
+
+ multistrmap::iterator it;
+ if((it = map.find(key)) == map.end()) {
+ map.insert( std::make_pair (key, std::vector<std::string>() ) );
+ }
+ map[key].push_back(value);
+ }
+
+ return map;
+}
+
+void MwIniImporter::merge(multistrmap &cfg, const multistrmap &ini) const {
+ multistrmap::const_iterator iniIt;
+ for(strmap::const_iterator it=mMergeMap.begin(); it!=mMergeMap.end(); ++it) {
+ if((iniIt = ini.find(it->second)) != ini.end()) {
+ for(std::vector<std::string>::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) {
+ cfg.erase(it->first);
+ insertMultistrmap(cfg, it->first, *vc);
+ }
+ }
+ }
+}
+
+void MwIniImporter::mergeFallback(multistrmap &cfg, const multistrmap &ini) const {
+ cfg.erase("fallback");
+
+ multistrmap::const_iterator iniIt;
+ for(std::vector<std::string>::const_iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); ++it) {
+ if((iniIt = ini.find(*it)) != ini.end()) {
+ for(std::vector<std::string>::const_iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) {
+ std::string value(*it);
+ std::replace( value.begin(), value.end(), ' ', '_' );
+ std::replace( value.begin(), value.end(), ':', '_' );
+ value.append(",").append(vc->substr(0,vc->length()));
+ insertMultistrmap(cfg, "fallback", value);
+ }
+ }
+ }
+}
+
+void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) {
+ const multistrmap::const_iterator it = cfg.find(key);
+ if(it == cfg.end()) {
+ cfg.insert(std::make_pair (key, std::vector<std::string>() ));
+ }
+ cfg[key].push_back(value);
+}
+
+void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) const {
+ std::vector<std::string> archives;
+ std::string baseArchive("Archives:Archive ");
+ std::string archive;
+
+ // Search archives listed in ini file
+ multistrmap::const_iterator it = ini.begin();
+ for(int i=0; it != ini.end(); i++) {
+ archive = baseArchive;
+ archive.append(this->numberToString(i));
+
+ it = ini.find(archive);
+ if(it == ini.end()) {
+ break;
+ }
+
+ for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
+ archives.push_back(*entry);
+ }
+ }
+
+ cfg.erase("fallback-archive");
+ cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("fallback-archive", std::vector<std::string>()));
+
+ // Add Morrowind.bsa by default, since Vanilla loads this archive even if it
+ // does not appears in the ini file
+ cfg["fallback-archive"].push_back("Morrowind.bsa");
+
+ for(std::vector<std::string>::const_iterator it=archives.begin(); it!=archives.end(); ++it) {
+ cfg["fallback-archive"].push_back(*it);
+ }
+}
+
+void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const {
+ std::vector<std::string> esmFiles;
+ std::vector<std::string> espFiles;
+ std::string baseGameFile("Game Files:GameFile");
+ std::string gameFile("");
+
+ multistrmap::const_iterator it = ini.begin();
+ for(int i=0; it != ini.end(); i++) {
+ gameFile = baseGameFile;
+ gameFile.append(this->numberToString(i));
+
+ it = ini.find(gameFile);
+ if(it == ini.end()) {
+ break;
+ }
+
+ for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
+ std::string filetype(entry->substr(entry->length()-3));
+ Misc::StringUtils::toLower(filetype);
+
+ if(filetype.compare("esm") == 0) {
+ esmFiles.push_back(*entry);
+ }
+ else if(filetype.compare("esp") == 0) {
+ espFiles.push_back(*entry);
+ }
+ }
+
+ gameFile = "";
+ }
+
+ cfg.erase("master");
+ cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("master", std::vector<std::string>() ) );
+
+ for(std::vector<std::string>::const_iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) {
+ cfg["master"].push_back(*it);
+ }
+
+ cfg.erase("plugin");
+ cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("plugin", std::vector<std::string>() ) );
+
+ for(std::vector<std::string>::const_iterator it=espFiles.begin(); it!=espFiles.end(); ++it) {
+ cfg["plugin"].push_back(*it);
+ }
+}
+
+void MwIniImporter::writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, const multistrmap &cfg) {
+
+ for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) {
+ for(std::vector<std::string>::const_iterator entry=it->second.begin(); entry != it->second.end(); ++entry) {
+ out << (it->first) << "=" << (*entry) << std::endl;
+ }
+ }
+}
+
+void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding)
+{
+ mEncoding = encoding;
+}
diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp
new file mode 100644
index 0000000000..784980e090
--- /dev/null
+++ b/apps/mwiniimporter/importer.hpp
@@ -0,0 +1,39 @@
+#ifndef MWINIIMPORTER_IMPORTER
+#define MWINIIMPORTER_IMPORTER 1
+
+#include <boost/iostreams/device/file.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <string>
+#include <map>
+#include <vector>
+#include <exception>
+
+#include <components/to_utf8/to_utf8.hpp>
+
+class MwIniImporter {
+ public:
+ typedef std::map<std::string, std::string> strmap;
+ typedef std::map<std::string, std::vector<std::string> > multistrmap;
+
+ MwIniImporter();
+ void setInputEncoding(const ToUTF8::FromType& encoding);
+ void setVerbose(bool verbose);
+ multistrmap loadIniFile(const std::string& filename) const;
+ static multistrmap loadCfgFile(const std::string& filename);
+ void merge(multistrmap &cfg, const multistrmap &ini) const;
+ void mergeFallback(multistrmap &cfg, const multistrmap &ini) const;
+ void importGameFiles(multistrmap &cfg, const multistrmap &ini) const;
+ void importArchives(multistrmap &cfg, const multistrmap &ini) const;
+ static void writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, const multistrmap &cfg);
+
+ private:
+ static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
+ static std::string numberToString(int n);
+ bool mVerbose;
+ strmap mMergeMap;
+ std::vector<std::string> mMergeFallback;
+ ToUTF8::FromType mEncoding;
+};
+
+
+#endif
diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp
new file mode 100644
index 0000000000..364a6b1a4a
--- /dev/null
+++ b/apps/mwiniimporter/main.cpp
@@ -0,0 +1,101 @@
+#include "importer.hpp"
+
+#include <iostream>
+#include <string>
+#include <boost/program_options.hpp>
+#include <boost/filesystem.hpp>
+
+namespace bpo = boost::program_options;
+
+int main(int argc, char *argv[]) {
+
+ bpo::options_description desc("Syntax: mwiniimporter <options> inifile configfile\nAllowed options");
+ bpo::positional_options_description p_desc;
+ desc.add_options()
+ ("help,h", "produce help message")
+ ("verbose,v", "verbose output")
+ ("ini,i", bpo::value<std::string>(), "morrowind.ini file")
+ ("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
+ ("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
+ ("game-files,g", "import esm and esp files")
+ ("no-archives,A", "disable bsa archives import")
+ ("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
+ "Character encoding used in OpenMW game messages:\n"
+ "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
+ "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
+ "\n\twin1252 - Western European (Latin) alphabet, used by default")
+ ;
+ p_desc.add("ini", 1).add("cfg", 1);
+
+ bpo::variables_map vm;
+
+ try
+ {
+ bpo::parsed_options parsed = bpo::command_line_parser(argc, argv)
+ .options(desc)
+ .positional(p_desc)
+ .run();
+
+ bpo::store(parsed, vm);
+ }
+ catch(boost::program_options::unknown_option & x)
+ {
+ std::cerr << "ERROR: " << x.what() << std::endl;
+ return false;
+ }
+ catch(boost::program_options::invalid_command_line_syntax & x)
+ {
+ std::cerr << "ERROR: " << x.what() << std::endl;
+ return false;
+ }
+
+ if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) {
+ std::cout << desc;
+ return 0;
+ }
+
+ bpo::notify(vm);
+
+ std::string iniFile = vm["ini"].as<std::string>();
+ std::string cfgFile = vm["cfg"].as<std::string>();
+
+ // if no output is given, write back to cfg file
+ std::string outputFile(vm["output"].as<std::string>());
+ if(vm["output"].defaulted()) {
+ outputFile = vm["cfg"].as<std::string>();
+ }
+
+ if(!boost::filesystem::exists(iniFile)) {
+ std::cerr << "ini file does not exist" << std::endl;
+ return -3;
+ }
+ if(!boost::filesystem::exists(cfgFile))
+ std::cerr << "cfg file does not exist" << std::endl;
+
+ MwIniImporter importer;
+ importer.setVerbose(vm.count("verbose"));
+
+ // Font encoding settings
+ std::string encoding(vm["encoding"].as<std::string>());
+ importer.setInputEncoding(ToUTF8::calculateEncoding(encoding));
+
+ MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
+ MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
+
+ importer.merge(cfg, ini);
+ importer.mergeFallback(cfg, ini);
+
+ if(vm.count("game-files")) {
+ importer.importGameFiles(cfg, ini);
+ }
+
+ if(!vm.count("no-archives")) {
+ importer.importArchives(cfg, ini);
+ }
+
+ std::cout << "write to: " << outputFile << std::endl;
+ boost::iostreams::stream<boost::iostreams::file_sink> file(outputFile);
+ importer.writeToFile(file, cfg);
+
+ return 0;
+}
diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
new file mode 100644
index 0000000000..fe0415ac08
--- /dev/null
+++ b/apps/opencs/CMakeLists.txt
@@ -0,0 +1,158 @@
+set (OPENCS_SRC main.cpp)
+
+opencs_units (. editor)
+
+set (CMAKE_BUILD_TYPE DEBUG)
+
+opencs_units (model/doc
+ document
+ )
+
+opencs_units_noqt (model/doc
+ documentmanager
+ )
+
+opencs_hdrs_noqt (model/doc
+ state
+ )
+
+
+opencs_units (model/world
+ idtable idtableproxymodel regionmap
+ )
+
+
+opencs_units_noqt (model/world
+ universalid data record commands columnbase scriptcontext cell refidcollection
+ refidadapter refiddata refidadapterimp ref collectionbase refcollection columns
+ )
+
+opencs_hdrs_noqt (model/world
+ columnimp idcollection collection
+ )
+
+
+opencs_units (model/tools
+ tools operation reportmodel
+ )
+
+opencs_units_noqt (model/tools
+ stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
+ birthsigncheck spellcheck
+ )
+
+
+opencs_units (view/doc
+ viewmanager view operations operation subview startup filedialog
+ )
+
+
+opencs_units_noqt (view/doc
+ subviewfactory
+ )
+
+opencs_hdrs_noqt (view/doc
+ subviewfactoryimp
+ )
+
+
+opencs_units (view/world
+ table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
+ cellcreator referenceablecreator referencecreator
+ )
+
+opencs_units_noqt (view/world
+ dialoguesubview subviews
+ enumdelegate vartypedelegate recordstatusdelegate refidtypedelegate datadisplaydelegate
+ scripthighlighter idvalidator
+ )
+
+
+opencs_units (view/tools
+ reportsubview
+ )
+
+opencs_units_noqt (view/tools
+ subviews
+ )
+
+opencs_units (view/settings
+ abstractblock
+ proxyblock
+ abstractwidget
+ usersettingsdialog
+ datadisplayformatpage
+ windowpage
+ )
+
+opencs_units_noqt (view/settings
+ abstractpage
+ blankpage
+ groupblock
+ customblock
+ groupbox
+ itemblock
+ settingwidget
+ toggleblock
+ support
+ )
+
+opencs_units (model/settings
+ usersettings
+ settingcontainer
+ )
+
+opencs_units_noqt (model/settings
+ support
+ settingsitem
+ )
+
+opencs_units_noqt (model/filter
+ node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
+ )
+
+opencs_hdrs_noqt (model/filter
+ filter
+ )
+
+opencs_units (view/filter
+ filtercreator filterbox recordfilterbox editwidget
+ )
+
+set (OPENCS_US
+ )
+
+set (OPENCS_RES ../../files/opencs/resources.qrc
+ ../../files/launcher/launcher.qrc
+ )
+
+set (OPENCS_UI ../../files/ui/datafilespage.ui
+ )
+
+source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR})
+
+if(WIN32)
+ set(QT_USE_QTMAIN TRUE)
+endif(WIN32)
+
+find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED)
+include(${QT_USE_FILE})
+
+qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
+qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
+qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(opencs
+ ${OPENCS_SRC}
+ ${OPENCS_UI_HDR}
+ ${OPENCS_MOC_SRC}
+ ${OPENCS_RES_SRC}
+)
+
+target_link_libraries(opencs
+ ${Boost_LIBRARIES}
+ ${QT_LIBRARIES}
+ components
+)
diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp
new file mode 100644
index 0000000000..36d4f9735c
--- /dev/null
+++ b/apps/opencs/editor.cpp
@@ -0,0 +1,157 @@
+
+#include "editor.hpp"
+
+#include <QApplication>
+#include <QLocalServer>
+#include <QLocalSocket>
+
+#include "model/doc/document.hpp"
+#include "model/world/data.hpp"
+
+CS::Editor::Editor() : mViewManager (mDocumentManager)
+{
+ mIpcServerName = "org.openmw.OpenCS";
+
+ connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ()));
+ connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
+
+ connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ()));
+ connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ()));
+
+ connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles()));
+ connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile()));
+
+ setupDataFiles();
+}
+
+void CS::Editor::setupDataFiles()
+{
+ boost::program_options::variables_map variables;
+ boost::program_options::options_description desc;
+
+ desc.add_options()
+ ("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
+ ("data-local", boost::program_options::value<std::string>()->default_value(""))
+ ("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
+ ("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
+
+ boost::program_options::notify(variables);
+
+ mCfgMgr.readConfiguration(variables, desc);
+
+ Files::PathContainer mDataDirs, mDataLocal;
+ if (!variables["data"].empty()) {
+ mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
+ }
+
+ std::string local = variables["data-local"].as<std::string>();
+ if (!local.empty()) {
+ mDataLocal.push_back(Files::PathContainer::value_type(local));
+ }
+
+ mCfgMgr.processPaths(mDataDirs);
+ mCfgMgr.processPaths(mDataLocal);
+
+ // Set the charset for reading the esm/esp files
+ QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
+ mFileDialog.setEncoding(encoding);
+
+ Files::PathContainer dataDirs;
+ dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end());
+ dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end());
+
+ for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
+ {
+ QString path = QString::fromStdString(iter->string());
+ mFileDialog.addFiles(path);
+ }
+
+ //load the settings into the userSettings instance.
+ const QString settingFileName = "opencs.cfg";
+ CSMSettings::UserSettings::instance().loadSettings(settingFileName);
+
+}
+
+void CS::Editor::createDocument()
+{
+ mStartup.hide();
+
+ mFileDialog.newFile();
+}
+
+void CS::Editor::loadDocument()
+{
+ mStartup.hide();
+
+ mFileDialog.openFile();
+}
+
+void CS::Editor::openFiles()
+{
+ std::vector<boost::filesystem::path> files;
+ QStringList paths = mFileDialog.checkedItemsPaths();
+
+ foreach (const QString &path, paths) {
+ files.push_back(path.toStdString());
+ }
+
+ CSMDoc::Document *document = mDocumentManager.addDocument(files, false);
+
+ mViewManager.addView (document);
+ mFileDialog.hide();
+}
+
+void CS::Editor::createNewFile()
+{
+ std::vector<boost::filesystem::path> files;
+ QStringList paths = mFileDialog.checkedItemsPaths();
+
+ foreach (const QString &path, paths) {
+ files.push_back(path.toStdString());
+ }
+
+ files.push_back(mFileDialog.fileName().toStdString());
+
+ CSMDoc::Document *document = mDocumentManager.addDocument (files, true);
+
+ mViewManager.addView (document);
+ mFileDialog.hide();
+}
+
+void CS::Editor::showStartup()
+{
+ if(mStartup.isHidden())
+ mStartup.show();
+ mStartup.raise();
+ mStartup.activateWindow();
+}
+
+bool CS::Editor::makeIPCServer()
+{
+ mServer = new QLocalServer(this);
+
+ if(mServer->listen(mIpcServerName))
+ {
+ connect(mServer, SIGNAL(newConnection()), this, SLOT(showStartup()));
+ return true;
+ }
+
+ mServer->close();
+ return false;
+}
+
+void CS::Editor::connectToIPCServer()
+{
+ mClientSocket = new QLocalSocket(this);
+ mClientSocket->connectToServer(mIpcServerName);
+ mClientSocket->close();
+}
+
+int CS::Editor::run()
+{
+ mStartup.show();
+
+ QApplication::setQuitOnLastWindowClosed (true);
+
+ return QApplication::exec();
+}
diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp
new file mode 100644
index 0000000000..80336d66f8
--- /dev/null
+++ b/apps/opencs/editor.hpp
@@ -0,0 +1,66 @@
+#ifndef CS_EDITOR_H
+#define CS_EDITOR_H
+
+#include <QObject>
+#include <QString>
+#include <QLocalServer>
+#include <QLocalSocket>
+
+#ifndef Q_MOC_RUN
+#include <components/files/configurationmanager.hpp>
+#endif
+#include "model/doc/documentmanager.hpp"
+
+#include "view/doc/viewmanager.hpp"
+#include "view/doc/startup.hpp"
+#include "view/doc/filedialog.hpp"
+#include "model/settings/usersettings.hpp"
+
+namespace CS
+{
+ class Editor : public QObject
+ {
+ Q_OBJECT
+
+ CSMSettings::UserSettings mUserSettings;
+ CSMDoc::DocumentManager mDocumentManager;
+ CSVDoc::ViewManager mViewManager;
+ CSVDoc::StartupDialogue mStartup;
+ FileDialog mFileDialog;
+
+ Files::ConfigurationManager mCfgMgr;
+ void setupDataFiles();
+
+ // not implemented
+ Editor (const Editor&);
+ Editor& operator= (const Editor&);
+
+ public:
+
+ Editor();
+
+ bool makeIPCServer();
+ void connectToIPCServer();
+
+ int run();
+ ///< \return error status
+
+ private slots:
+
+ void createDocument();
+
+ void loadDocument();
+ void openFiles();
+ void createNewFile();
+
+ void showStartup();
+
+ private:
+
+ QString mIpcServerName;
+ QLocalServer *mServer;
+ QLocalSocket *mClientSocket;
+ };
+}
+
+#endif
diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp
new file mode 100644
index 0000000000..eddeb1983b
--- /dev/null
+++ b/apps/opencs/main.cpp
@@ -0,0 +1,49 @@
+
+#include "editor.hpp"
+
+#include <exception>
+#include <iostream>
+
+#include <QApplication>
+#include <QIcon>
+
+class Application : public QApplication
+{
+ private:
+
+ bool notify (QObject *receiver, QEvent *event)
+ {
+ try
+ {
+ return QApplication::notify (receiver, event);
+ }
+ catch (const std::exception& exception)
+ {
+ std::cerr << "An exception has been caught: " << exception.what() << std::endl;
+ }
+
+ return false;
+ }
+
+ public:
+
+ Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
+};
+
+int main(int argc, char *argv[])
+{
+ Q_INIT_RESOURCE (resources);
+ Application mApplication (argc, argv);
+
+ mApplication.setWindowIcon (QIcon (":./opencs.png"));
+
+ CS::Editor editor;
+
+ if(!editor.makeIPCServer())
+ {
+ editor.connectToIPCServer();
+ return 0;
+ }
+
+ return editor.run();
+}
diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp
new file mode 100644
index 0000000000..30e9c21d12
--- /dev/null
+++ b/apps/opencs/model/doc/document.cpp
@@ -0,0 +1,2284 @@
+#include "document.hpp"
+#include <cassert>
+
+void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_iterator& begin,
+ const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified)
+{
+ assert (begin!=end);
+
+ std::vector<boost::filesystem::path>::const_iterator end2 (end);
+
+ if (lastAsModified)
+ --end2;
+
+ for (std::vector<boost::filesystem::path>::const_iterator iter (begin); iter!=end2; ++iter)
+ getData().loadFile (*iter, true);
+
+ if (lastAsModified)
+ getData().loadFile (*end2, false);
+}
+
+void CSMDoc::Document::addGmsts()
+{
+ static const char *gmstFloats[] =
+ {
+ "fAIFleeFleeMult",
+ "fAIFleeHealthMult",
+ "fAIMagicSpellMult",
+ "fAIMeleeArmorMult",
+ "fAIMeleeSummWeaponMult",
+ "fAIMeleeWeaponMult",
+ "fAIRangeMagicSpellMult",
+ "fAIRangeMeleeWeaponMult",
+ "fAlarmRadius",
+ "fAthleticsRunBonus",
+ "fAudioDefaultMaxDistance",
+ "fAudioDefaultMinDistance",
+ "fAudioMaxDistanceMult",
+ "fAudioMinDistanceMult",
+ "fAudioVoiceDefaultMaxDistance",
+ "fAudioVoiceDefaultMinDistance",
+ "fAutoPCSpellChance",
+ "fAutoSpellChance",
+ "fBargainOfferBase",
+ "fBargainOfferMulti",
+ "fBarterGoldResetDelay",
+ "fBaseRunMultiplier",
+ "fBlockStillBonus",
+ "fBribe1000Mod",
+ "fBribe100Mod",
+ "fBribe10Mod",
+ "fCombatAngleXY",
+ "fCombatAngleZ",
+ "fCombatArmorMinMult",
+ "fCombatBlockLeftAngle",
+ "fCombatBlockRightAngle",
+ "fCombatCriticalStrikeMult",
+ "fCombatDelayCreature",
+ "fCombatDelayNPC",
+ "fCombatDistance",
+ "fCombatDistanceWerewolfMod",
+ "fCombatForceSideAngle",
+ "fCombatInvisoMult",
+ "fCombatKODamageMult",
+ "fCombatTorsoSideAngle",
+ "fCombatTorsoStartPercent",
+ "fCombatTorsoStopPercent",
+ "fConstantEffectMult",
+ "fCorpseClearDelay",
+ "fCorpseRespawnDelay",
+ "fCrimeGoldDiscountMult",
+ "fCrimeGoldTurnInMult",
+ "fCrimeStealing",
+ "fDamageStrengthBase",
+ "fDamageStrengthMult",
+ "fDifficultyMult",
+ "fDiseaseXferChance",
+ "fDispAttacking",
+ "fDispBargainFailMod",
+ "fDispBargainSuccessMod",
+ "fDispCrimeMod",
+ "fDispDiseaseMod",
+ "fDispFactionMod",
+ "fDispFactionRankBase",
+ "fDispFactionRankMult",
+ "fDispositionMod",
+ "fDispPersonalityBase",
+ "fDispPersonalityMult",
+ "fDispPickPocketMod",
+ "fDispRaceMod",
+ "fDispStealing",
+ "fDispWeaponDrawn",
+ "fEffectCostMult",
+ "fElementalShieldMult",
+ "fEnchantmentChanceMult",
+ "fEnchantmentConstantChanceMult",
+ "fEnchantmentConstantDurationMult",
+ "fEnchantmentMult",
+ "fEnchantmentValueMult",
+ "fEncumberedMoveEffect",
+ "fEncumbranceStrMult",
+ "fEndFatigueMult",
+ "fFallAcroBase",
+ "fFallAcroMult",
+ "fFallDamageDistanceMin",
+ "fFallDistanceBase",
+ "fFallDistanceMult",
+ "fFatigueAttackBase",
+ "fFatigueAttackMult",
+ "fFatigueBase",
+ "fFatigueBlockBase",
+ "fFatigueBlockMult",
+ "fFatigueJumpBase",
+ "fFatigueJumpMult",
+ "fFatigueMult",
+ "fFatigueReturnBase",
+ "fFatigueReturnMult",
+ "fFatigueRunBase",
+ "fFatigueRunMult",
+ "fFatigueSneakBase",
+ "fFatigueSneakMult",
+ "fFatigueSpellBase",
+ "fFatigueSpellCostMult",
+ "fFatigueSpellMult",
+ "fFatigueSwimRunBase",
+ "fFatigueSwimRunMult",
+ "fFatigueSwimWalkBase",
+ "fFatigueSwimWalkMult",
+ "fFightDispMult",
+ "fFightDistanceMultiplier",
+ "fFightStealing",
+ "fFleeDistance",
+ "fGreetDistanceReset",
+ "fHandtoHandHealthPer",
+ "fHandToHandReach",
+ "fHoldBreathEndMult",
+ "fHoldBreathTime",
+ "fIdleChanceMultiplier",
+ "fIngredientMult",
+ "fInteriorHeadTrackMult",
+ "fJumpAcrobaticsBase",
+ "fJumpAcroMultiplier",
+ "fJumpEncumbranceBase",
+ "fJumpEncumbranceMultiplier",
+ "fJumpMoveBase",
+ "fJumpMoveMult",
+ "fJumpRunMultiplier",
+ "fKnockDownMult",
+ "fLevelMod",
+ "fLevelUpHealthEndMult",
+ "fLightMaxMod",
+ "fLuckMod",
+ "fMagesGuildTravel",
+ "fMagicCreatureCastDelay",
+ "fMagicDetectRefreshRate",
+ "fMagicItemConstantMult",
+ "fMagicItemCostMult",
+ "fMagicItemOnceMult",
+ "fMagicItemPriceMult",
+ "fMagicItemRechargePerSecond",
+ "fMagicItemStrikeMult",
+ "fMagicItemUsedMult",
+ "fMagicStartIconBlink",
+ "fMagicSunBlockedMult",
+ "fMajorSkillBonus",
+ "fMaxFlySpeed",
+ "fMaxHandToHandMult",
+ "fMaxHeadTrackDistance",
+ "fMaxWalkSpeed",
+ "fMaxWalkSpeedCreature",
+ "fMedMaxMod",
+ "fMessageTimePerChar",
+ "fMinFlySpeed",
+ "fMinHandToHandMult",
+ "fMinorSkillBonus",
+ "fMinWalkSpeed",
+ "fMinWalkSpeedCreature",
+ "fMiscSkillBonus",
+ "fNPCbaseMagickaMult",
+ "fNPCHealthBarFade",
+ "fNPCHealthBarTime",
+ "fPCbaseMagickaMult",
+ "fPerDieRollMult",
+ "fPersonalityMod",
+ "fPerTempMult",
+ "fPickLockMult",
+ "fPickPocketMod",
+ "fPotionMinUsefulDuration",
+ "fPotionStrengthMult",
+ "fPotionT1DurMult",
+ "fPotionT1MagMult",
+ "fPotionT4BaseStrengthMult",
+ "fPotionT4EquipStrengthMult",
+ "fProjectileMaxSpeed",
+ "fProjectileMinSpeed",
+ "fProjectileThrownStoreChance",
+ "fRepairAmountMult",
+ "fRepairMult",
+ "fReputationMod",
+ "fRestMagicMult",
+ "fSeriousWoundMult",
+ "fSleepRandMod",
+ "fSleepRestMod",
+ "fSneakBootMult",
+ "fSneakDistanceBase",
+ "fSneakDistanceMultiplier",
+ "fSneakNoViewMult",
+ "fSneakSkillMult",
+ "fSneakSpeedMultiplier",
+ "fSneakUseDelay",
+ "fSneakUseDist",
+ "fSneakViewMult",
+ "fSoulGemMult",
+ "fSpecialSkillBonus",
+ "fSpellMakingValueMult",
+ "fSpellPriceMult",
+ "fSpellValueMult",
+ "fStromWalkMult",
+ "fStromWindSpeed",
+ "fSuffocationDamage",
+ "fSwimHeightScale",
+ "fSwimRunAthleticsMult",
+ "fSwimRunBase",
+ "fSwimWalkAthleticsMult",
+ "fSwimWalkBase",
+ "fSwingBlockBase",
+ "fSwingBlockMult",
+ "fTargetSpellMaxSpeed",
+ "fThrownWeaponMaxSpeed",
+ "fThrownWeaponMinSpeed",
+ "fTrapCostMult",
+ "fTravelMult",
+ "fTravelTimeMult",
+ "fUnarmoredBase1",
+ "fUnarmoredBase2",
+ "fVanityDelay",
+ "fVoiceIdleOdds",
+ "fWaterReflectUpdateAlways",
+ "fWaterReflectUpdateSeldom",
+ "fWeaponDamageMult",
+ "fWeaponFatigueBlockMult",
+ "fWeaponFatigueMult",
+ "fWereWolfAcrobatics",
+ "fWereWolfAgility",
+ "fWereWolfAlchemy",
+ "fWereWolfAlteration",
+ "fWereWolfArmorer",
+ "fWereWolfAthletics",
+ "fWereWolfAxe",
+ "fWereWolfBlock",
+ "fWereWolfBluntWeapon",
+ "fWereWolfConjuration",
+ "fWereWolfDestruction",
+ "fWereWolfEnchant",
+ "fWereWolfEndurance",
+ "fWereWolfFatigue",
+ "fWereWolfHandtoHand",
+ "fWereWolfHealth",
+ "fWereWolfHeavyArmor",
+ "fWereWolfIllusion",
+ "fWereWolfIntellegence",
+ "fWereWolfLightArmor",
+ "fWereWolfLongBlade",
+ "fWereWolfLuck",
+ "fWereWolfMagicka",
+ "fWereWolfMarksman",
+ "fWereWolfMediumArmor",
+ "fWereWolfMerchantile",
+ "fWereWolfMysticism",
+ "fWereWolfPersonality",
+ "fWereWolfRestoration",
+ "fWereWolfRunMult",
+ "fWereWolfSecurity",
+ "fWereWolfShortBlade",
+ "fWereWolfSilverWeaponDamageMult",
+ "fWereWolfSneak",
+ "fWereWolfSpear",
+ "fWereWolfSpeechcraft",
+ "fWereWolfSpeed",
+ "fWereWolfStrength",
+ "fWereWolfUnarmored",
+ "fWereWolfWillPower",
+ "fWortChanceValue",
+ 0
+ };
+
+ static const float gmstFloatsValues[] =
+ {
+ 0.3, // fAIFleeFleeMult
+ 7.0, // fAIFleeHealthMult
+ 3.0, // fAIMagicSpellMult
+ 1.0, // fAIMeleeArmorMult
+ 1.0, // fAIMeleeSummWeaponMult
+ 2.0, // fAIMeleeWeaponMult
+ 5.0, // fAIRangeMagicSpellMult
+ 5.0, // fAIRangeMeleeWeaponMult
+ 2000.0, // fAlarmRadius
+ 1.0, // fAthleticsRunBonus
+ 40.0, // fAudioDefaultMaxDistance
+ 5.0, // fAudioDefaultMinDistance
+ 50.0, // fAudioMaxDistanceMult
+ 20.0, // fAudioMinDistanceMult
+ 60.0, // fAudioVoiceDefaultMaxDistance
+ 10.0, // fAudioVoiceDefaultMinDistance
+ 50.0, // fAutoPCSpellChance
+ 80.0, // fAutoSpellChance
+ 50.0, // fBargainOfferBase
+ -4.0, // fBargainOfferMulti
+ 24.0, // fBarterGoldResetDelay
+ 1.75, // fBaseRunMultiplier
+ 1.25, // fBlockStillBonus
+ 150.0, // fBribe1000Mod
+ 75.0, // fBribe100Mod
+ 35.0, // fBribe10Mod
+ 60.0, // fCombatAngleXY
+ 60.0, // fCombatAngleZ
+ 0.25, // fCombatArmorMinMult
+ -90.0, // fCombatBlockLeftAngle
+ 30.0, // fCombatBlockRightAngle
+ 4.0, // fCombatCriticalStrikeMult
+ 0.1, // fCombatDelayCreature
+ 0.1, // fCombatDelayNPC
+ 128.0, // fCombatDistance
+ 0.3, // fCombatDistanceWerewolfMod
+ 30.0, // fCombatForceSideAngle
+ 0.2, // fCombatInvisoMult
+ 1.5, // fCombatKODamageMult
+ 45.0, // fCombatTorsoSideAngle
+ 0.3, // fCombatTorsoStartPercent
+ 0.8, // fCombatTorsoStopPercent
+ 15.0, // fConstantEffectMult
+ 72.0, // fCorpseClearDelay
+ 72.0, // fCorpseRespawnDelay
+ 0.5, // fCrimeGoldDiscountMult
+ 0.9, // fCrimeGoldTurnInMult
+ 1.0, // fCrimeStealing
+ 0.5, // fDamageStrengthBase
+ 0.1, // fDamageStrengthMult
+ 5.0, // fDifficultyMult
+ 2.5, // fDiseaseXferChance
+ -10.0, // fDispAttacking
+ -1.0, // fDispBargainFailMod
+ 1.0, // fDispBargainSuccessMod
+ 0.0, // fDispCrimeMod
+ -10.0, // fDispDiseaseMod
+ 3.0, // fDispFactionMod
+ 1.0, // fDispFactionRankBase
+ 0.5, // fDispFactionRankMult
+ 1.0, // fDispositionMod
+ 50.0, // fDispPersonalityBase
+ 0.5, // fDispPersonalityMult
+ -25.0, // fDispPickPocketMod
+ 5.0, // fDispRaceMod
+ -0.5, // fDispStealing
+ -5.0, // fDispWeaponDrawn
+ 0.5, // fEffectCostMult
+ 0.1, // fElementalShieldMult
+ 3.0, // fEnchantmentChanceMult
+ 0.5, // fEnchantmentConstantChanceMult
+ 100.0, // fEnchantmentConstantDurationMult
+ 0.1, // fEnchantmentMult
+ 1000.0, // fEnchantmentValueMult
+ 0.3, // fEncumberedMoveEffect
+ 5.0, // fEncumbranceStrMult
+ 0.04, // fEndFatigueMult
+ 0.25, // fFallAcroBase
+ 0.01, // fFallAcroMult
+ 400.0, // fFallDamageDistanceMin
+ 0.0, // fFallDistanceBase
+ 0.07, // fFallDistanceMult
+ 2.0, // fFatigueAttackBase
+ 0.0, // fFatigueAttackMult
+ 1.25, // fFatigueBase
+ 4.0, // fFatigueBlockBase
+ 0.0, // fFatigueBlockMult
+ 5.0, // fFatigueJumpBase
+ 0.0, // fFatigueJumpMult
+ 0.5, // fFatigueMult
+ 2.5, // fFatigueReturnBase
+ 0.02, // fFatigueReturnMult
+ 5.0, // fFatigueRunBase
+ 2.0, // fFatigueRunMult
+ 1.5, // fFatigueSneakBase
+ 1.5, // fFatigueSneakMult
+ 0.0, // fFatigueSpellBase
+ 0.0, // fFatigueSpellCostMult
+ 0.0, // fFatigueSpellMult
+ 7.0, // fFatigueSwimRunBase
+ 0.0, // fFatigueSwimRunMult
+ 2.5, // fFatigueSwimWalkBase
+ 0.0, // fFatigueSwimWalkMult
+ 0.2, // fFightDispMult
+ 0.005, // fFightDistanceMultiplier
+ 50.0, // fFightStealing
+ 3000.0, // fFleeDistance
+ 512.0, // fGreetDistanceReset
+ 0.1, // fHandtoHandHealthPer
+ 1.0, // fHandToHandReach
+ 0.5, // fHoldBreathEndMult
+ 20.0, // fHoldBreathTime
+ 0.75, // fIdleChanceMultiplier
+ 1.0, // fIngredientMult
+ 0.5, // fInteriorHeadTrackMult
+ 128.0, // fJumpAcrobaticsBase
+ 4.0, // fJumpAcroMultiplier
+ 0.5, // fJumpEncumbranceBase
+ 1.0, // fJumpEncumbranceMultiplier
+ 0.5, // fJumpMoveBase
+ 0.5, // fJumpMoveMult
+ 1.0, // fJumpRunMultiplier
+ 0.5, // fKnockDownMult
+ 5.0, // fLevelMod
+ 0.1, // fLevelUpHealthEndMult
+ 0.6, // fLightMaxMod
+ 10.0, // fLuckMod
+ 10.0, // fMagesGuildTravel
+ 1.5, // fMagicCreatureCastDelay
+ 0.0167, // fMagicDetectRefreshRate
+ 1.0, // fMagicItemConstantMult
+ 1.0, // fMagicItemCostMult
+ 1.0, // fMagicItemOnceMult
+ 1.0, // fMagicItemPriceMult
+ 0.05, // fMagicItemRechargePerSecond
+ 1.0, // fMagicItemStrikeMult
+ 1.0, // fMagicItemUsedMult
+ 3.0, // fMagicStartIconBlink
+ 0.5, // fMagicSunBlockedMult
+ 0.75, // fMajorSkillBonus
+ 300.0, // fMaxFlySpeed
+ 0.5, // fMaxHandToHandMult
+ 400.0, // fMaxHeadTrackDistance
+ 200.0, // fMaxWalkSpeed
+ 300.0, // fMaxWalkSpeedCreature
+ 0.9, // fMedMaxMod
+ 0.1, // fMessageTimePerChar
+ 5.0, // fMinFlySpeed
+ 0.1, // fMinHandToHandMult
+ 1.0, // fMinorSkillBonus
+ 100.0, // fMinWalkSpeed
+ 5.0, // fMinWalkSpeedCreature
+ 1.25, // fMiscSkillBonus
+ 2.0, // fNPCbaseMagickaMult
+ 0.5, // fNPCHealthBarFade
+ 3.0, // fNPCHealthBarTime
+ 1.0, // fPCbaseMagickaMult
+ 0.3, // fPerDieRollMult
+ 5.0, // fPersonalityMod
+ 1.0, // fPerTempMult
+ -1.0, // fPickLockMult
+ 0.3, // fPickPocketMod
+ 20.0, // fPotionMinUsefulDuration
+ 0.5, // fPotionStrengthMult
+ 0.5, // fPotionT1DurMult
+ 1.5, // fPotionT1MagMult
+ 20.0, // fPotionT4BaseStrengthMult
+ 12.0, // fPotionT4EquipStrengthMult
+ 3000.0, // fProjectileMaxSpeed
+ 400.0, // fProjectileMinSpeed
+ 25.0, // fProjectileThrownStoreChance
+ 3.0, // fRepairAmountMult
+ 1.0, // fRepairMult
+ 1.0, // fReputationMod
+ 0.15, // fRestMagicMult
+ 0.0, // fSeriousWoundMult
+ 0.25, // fSleepRandMod
+ 0.3, // fSleepRestMod
+ -1.0, // fSneakBootMult
+ 0.5, // fSneakDistanceBase
+ 0.002, // fSneakDistanceMultiplier
+ 0.5, // fSneakNoViewMult
+ 1.0, // fSneakSkillMult
+ 0.75, // fSneakSpeedMultiplier
+ 1.0, // fSneakUseDelay
+ 500.0, // fSneakUseDist
+ 1.5, // fSneakViewMult
+ 3.0, // fSoulGemMult
+ 0.8, // fSpecialSkillBonus
+ 7.0, // fSpellMakingValueMult
+ 2.0, // fSpellPriceMult
+ 10.0, // fSpellValueMult
+ 0.25, // fStromWalkMult
+ 0.7, // fStromWindSpeed
+ 3.0, // fSuffocationDamage
+ 0.9, // fSwimHeightScale
+ 0.1, // fSwimRunAthleticsMult
+ 0.5, // fSwimRunBase
+ 0.02, // fSwimWalkAthleticsMult
+ 0.5, // fSwimWalkBase
+ 1.0, // fSwingBlockBase
+ 1.0, // fSwingBlockMult
+ 1000.0, // fTargetSpellMaxSpeed
+ 1000.0, // fThrownWeaponMaxSpeed
+ 300.0, // fThrownWeaponMinSpeed
+ 0.0, // fTrapCostMult
+ 4000.0, // fTravelMult
+ 16000.0,// fTravelTimeMult
+ 0.1, // fUnarmoredBase1
+ 0.065, // fUnarmoredBase2
+ 30.0, // fVanityDelay
+ 10.0, // fVoiceIdleOdds
+ 0.0, // fWaterReflectUpdateAlways
+ 10.0, // fWaterReflectUpdateSeldom
+ 0.1, // fWeaponDamageMult
+ 1.0, // fWeaponFatigueBlockMult
+ 0.25, // fWeaponFatigueMult
+ 150.0, // fWereWolfAcrobatics
+ 150.0, // fWereWolfAgility
+ 1.0, // fWereWolfAlchemy
+ 1.0, // fWereWolfAlteration
+ 1.0, // fWereWolfArmorer
+ 150.0, // fWereWolfAthletics
+ 1.0, // fWereWolfAxe
+ 1.0, // fWereWolfBlock
+ 1.0, // fWereWolfBluntWeapon
+ 1.0, // fWereWolfConjuration
+ 1.0, // fWereWolfDestruction
+ 1.0, // fWereWolfEnchant
+ 150.0, // fWereWolfEndurance
+ 400.0, // fWereWolfFatigue
+ 100.0, // fWereWolfHandtoHand
+ 2.0, // fWereWolfHealth
+ 1.0, // fWereWolfHeavyArmor
+ 1.0, // fWereWolfIllusion
+ 1.0, // fWereWolfIntellegence
+ 1.0, // fWereWolfLightArmor
+ 1.0, // fWereWolfLongBlade
+ 1.0, // fWereWolfLuck
+ 100.0, // fWereWolfMagicka
+ 1.0, // fWereWolfMarksman
+ 1.0, // fWereWolfMediumArmor
+ 1.0, // fWereWolfMerchantile
+ 1.0, // fWereWolfMysticism
+ 1.0, // fWereWolfPersonality
+ 1.0, // fWereWolfRestoration
+ 1.5, // fWereWolfRunMult
+ 1.0, // fWereWolfSecurity
+ 1.0, // fWereWolfShortBlade
+ 1.5, // fWereWolfSilverWeaponDamageMult
+ 1.0, // fWereWolfSneak
+ 1.0, // fWereWolfSpear
+ 1.0, // fWereWolfSpeechcraft
+ 150.0, // fWereWolfSpeed
+ 150.0, // fWereWolfStrength
+ 100.0, // fWereWolfUnarmored
+ 1.0, // fWereWolfWillPower
+ 15.0, // fWortChanceValue
+ };
+
+ static const char *gmstIntegers[] =
+ {
+ "i1stPersonSneakDelta",
+ "iAlarmAttack",
+ "iAlarmKilling",
+ "iAlarmPickPocket",
+ "iAlarmStealing",
+ "iAlarmTresspass",
+ "iAlchemyMod",
+ "iAutoPCSpellMax",
+ "iAutoRepFacMod",
+ "iAutoRepLevMod",
+ "iAutoSpellAlterationMax",
+ "iAutoSpellAttSkillMin",
+ "iAutoSpellConjurationMax",
+ "iAutoSpellDestructionMax",
+ "iAutoSpellIllusionMax",
+ "iAutoSpellMysticismMax",
+ "iAutoSpellRestorationMax",
+ "iAutoSpellTimesCanCast",
+ "iBarterFailDisposition",
+ "iBarterSuccessDisposition",
+ "iBaseArmorSkill",
+ "iBlockMaxChance",
+ "iBlockMinChance",
+ "iBootsWeight",
+ "iCrimeAttack",
+ "iCrimeKilling",
+ "iCrimePickPocket",
+ "iCrimeThreshold",
+ "iCrimeThresholdMultiplier",
+ "iCrimeTresspass",
+ "iCuirassWeight",
+ "iDaysinPrisonMod",
+ "iDispAttackMod",
+ "iDispKilling",
+ "iDispTresspass",
+ "iFightAlarmMult",
+ "iFightAttack",
+ "iFightAttacking",
+ "iFightDistanceBase",
+ "iFightKilling",
+ "iFightPickpocket",
+ "iFightTrespass",
+ "iFlee",
+ "iGauntletWeight",
+ "iGreavesWeight",
+ "iGreetDistanceMultiplier",
+ "iGreetDuration",
+ "iHelmWeight",
+ "iKnockDownOddsBase",
+ "iKnockDownOddsMult",
+ "iLevelUp01Mult",
+ "iLevelUp02Mult",
+ "iLevelUp03Mult",
+ "iLevelUp04Mult",
+ "iLevelUp05Mult",
+ "iLevelUp06Mult",
+ "iLevelUp07Mult",
+ "iLevelUp08Mult",
+ "iLevelUp09Mult",
+ "iLevelUp10Mult",
+ "iLevelupMajorMult",
+ "iLevelupMajorMultAttribute",
+ "iLevelupMinorMult",
+ "iLevelupMinorMultAttribute",
+ "iLevelupMiscMultAttriubte",
+ "iLevelupSpecialization",
+ "iLevelupTotal",
+ "iMagicItemChargeConst",
+ "iMagicItemChargeOnce",
+ "iMagicItemChargeStrike",
+ "iMagicItemChargeUse",
+ "iMaxActivateDist",
+ "iMaxInfoDist",
+ "iMonthsToRespawn",
+ "iNumberCreatures",
+ "iPauldronWeight",
+ "iPerMinChance",
+ "iPerMinChange",
+ "iPickMaxChance",
+ "iPickMinChance",
+ "iShieldWeight",
+ "iSoulAmountForConstantEffect",
+ "iTrainingMod",
+ "iVoiceAttackOdds",
+ "iVoiceHitOdds",
+ "iWereWolfBounty",
+ "iWereWolfFightMod",
+ "iWereWolfFleeMod",
+ "iWereWolfLevelToAttack",
+ 0
+ };
+
+ static const int gmstIntegersValues[] =
+ {
+ 10, // i1stPersonSneakDelta
+ 50, // iAlarmAttack
+ 90, // iAlarmKilling
+ 20, // iAlarmPickPocket
+ 1, // iAlarmStealing
+ 5, // iAlarmTresspass
+ 2, // iAlchemyMod
+ 100, // iAutoPCSpellMax
+ 2, // iAutoRepFacMod
+ 0, // iAutoRepLevMod
+ 5, // iAutoSpellAlterationMax
+ 70, // iAutoSpellAttSkillMin
+ 2, // iAutoSpellConjurationMax
+ 5, // iAutoSpellDestructionMax
+ 5, // iAutoSpellIllusionMax
+ 5, // iAutoSpellMysticismMax
+ 5, // iAutoSpellRestorationMax
+ 3, // iAutoSpellTimesCanCast
+ -1, // iBarterFailDisposition
+ 1, // iBarterSuccessDisposition
+ 30, // iBaseArmorSkill
+ 50, // iBlockMaxChance
+ 10, // iBlockMinChance
+ 20, // iBootsWeight
+ 40, // iCrimeAttack
+ 1000, // iCrimeKilling
+ 25, // iCrimePickPocket
+ 1000, // iCrimeThreshold
+ 10, // iCrimeThresholdMultiplier
+ 5, // iCrimeTresspass
+ 30, // iCuirassWeight
+ 100, // iDaysinPrisonMod
+ -50, // iDispAttackMod
+ -50, // iDispKilling
+ -20, // iDispTresspass
+ 1, // iFightAlarmMult
+ 100, // iFightAttack
+ 50, // iFightAttacking
+ 20, // iFightDistanceBase
+ 50, // iFightKilling
+ 25, // iFightPickpocket
+ 25, // iFightTrespass
+ 0, // iFlee
+ 5, // iGauntletWeight
+ 15, // iGreavesWeight
+ 6, // iGreetDistanceMultiplier
+ 4, // iGreetDuration
+ 5, // iHelmWeight
+ 50, // iKnockDownOddsBase
+ 50, // iKnockDownOddsMult
+ 2, // iLevelUp01Mult
+ 2, // iLevelUp02Mult
+ 2, // iLevelUp03Mult
+ 2, // iLevelUp04Mult
+ 3, // iLevelUp05Mult
+ 3, // iLevelUp06Mult
+ 3, // iLevelUp07Mult
+ 4, // iLevelUp08Mult
+ 4, // iLevelUp09Mult
+ 5, // iLevelUp10Mult
+ 1, // iLevelupMajorMult
+ 1, // iLevelupMajorMultAttribute
+ 1, // iLevelupMinorMult
+ 1, // iLevelupMinorMultAttribute
+ 1, // iLevelupMiscMultAttriubte
+ 1, // iLevelupSpecialization
+ 10, // iLevelupTotal
+ 10, // iMagicItemChargeConst
+ 1, // iMagicItemChargeOnce
+ 10, // iMagicItemChargeStrike
+ 5, // iMagicItemChargeUse
+ 192, // iMaxActivateDist
+ 192, // iMaxInfoDist
+ 4, // iMonthsToRespawn
+ 1, // iNumberCreatures
+ 10, // iPauldronWeight
+ 5, // iPerMinChance
+ 10, // iPerMinChange
+ 75, // iPickMaxChance
+ 5, // iPickMinChance
+ 15, // iShieldWeight
+ 400, // iSoulAmountForConstantEffect
+ 10, // iTrainingMod
+ 10, // iVoiceAttackOdds
+ 30, // iVoiceHitOdds
+ 10000, // iWereWolfBounty
+ 100, // iWereWolfFightMod
+ 100, // iWereWolfFleeMod
+ 20, // iWereWolfLevelToAttack
+ };
+
+ static const char *gmstStrings[] =
+ {
+ "s3dAudio",
+ "s3dHardware",
+ "s3dSoftware",
+ "sAbsorb",
+ "sAcrobat",
+ "sActivate",
+ "sActivateXbox",
+ "sActorInCombat",
+ "sAdmire",
+ "sAdmireFail",
+ "sAdmireSuccess",
+ "sAgent",
+ "sAgiDesc",
+ "sAIDistance",
+ "sAlembic",
+ "sAllTab",
+ "sAlways",
+ "sAlways_Run",
+ "sand",
+ "sApparatus",
+ "sApparelTab",
+ "sArcher",
+ "sArea",
+ "sAreaDes",
+ "sArmor",
+ "sArmorRating",
+ "sAsk",
+ "sAssassin",
+ "sAt",
+ "sAttack",
+ "sAttributeAgility",
+ "sAttributeEndurance",
+ "sAttributeIntelligence",
+ "sAttributeListTitle",
+ "sAttributeLuck",
+ "sAttributePersonality",
+ "sAttributesMenu1",
+ "sAttributeSpeed",
+ "sAttributeStrength",
+ "sAttributeWillpower",
+ "sAudio",
+ "sAuto_Run",
+ "sBack",
+ "sBackspace",
+ "sBackXbox",
+ "sBarbarian",
+ "sBard",
+ "sBarter",
+ "sBarterDialog1",
+ "sBarterDialog10",
+ "sBarterDialog11",
+ "sBarterDialog12",
+ "sBarterDialog2",
+ "sBarterDialog3",
+ "sBarterDialog4",
+ "sBarterDialog5",
+ "sBarterDialog6",
+ "sBarterDialog7",
+ "sBarterDialog8",
+ "sBarterDialog9",
+ "sBattlemage",
+ "sBestAttack",
+ "sBirthSign",
+ "sBirthsignmenu1",
+ "sBirthsignmenu2",
+ "sBlocks",
+ "sBonusSkillTitle",
+ "sBookPageOne",
+ "sBookPageTwo",
+ "sBookSkillMessage",
+ "sBounty",
+ "sBreath",
+ "sBribe",
+ "sBribe",
+ "sBribe",
+ "sBribeFail",
+ "sBribeSuccess",
+ "sBuy",
+ "sBye",
+ "sCalcinator",
+ "sCancel",
+ "sCantEquipWeapWarning",
+ "sCastCost",
+ "sCaughtStealingMessage",
+ "sCenter",
+ "sChangedMastersMsg",
+ "sCharges",
+ "sChooseClassMenu1",
+ "sChooseClassMenu2",
+ "sChooseClassMenu3",
+ "sChooseClassMenu4",
+ "sChop",
+ "sClass",
+ "sClassChoiceMenu1",
+ "sClassChoiceMenu2",
+ "sClassChoiceMenu3",
+ "sClose",
+ "sCompanionShare",
+ "sCompanionWarningButtonOne",
+ "sCompanionWarningButtonTwo",
+ "sCompanionWarningMessage",
+ "sCondition",
+ "sConsoleTitle",
+ "sContainer",
+ "sContentsMessage1",
+ "sContentsMessage2",
+ "sContentsMessage3",
+ "sControlerVibration",
+ "sControls",
+ "sControlsMenu1",
+ "sControlsMenu2",
+ "sControlsMenu3",
+ "sControlsMenu4",
+ "sControlsMenu5",
+ "sControlsMenu6",
+ "sCostChance",
+ "sCostCharge",
+ "sCreate",
+ "sCreateClassMenu1",
+ "sCreateClassMenu2",
+ "sCreateClassMenu3",
+ "sCreateClassMenuHelp1",
+ "sCreateClassMenuHelp2",
+ "sCreateClassMenuWarning",
+ "sCreatedEffects",
+ "sCrimeHelp",
+ "sCrimeMessage",
+ "sCrouch_Sneak",
+ "sCrouchXbox",
+ "sCrusader",
+ "sCursorOff",
+ "sCustom",
+ "sCustomClassName",
+ "sDamage",
+ "sDark_Gamma",
+ "sDay",
+ "sDefaultCellname",
+ "sDelete",
+ "sDeleteGame",
+ "sDeleteNote",
+ "sDeleteSpell",
+ "sDeleteSpellError",
+ "sDetail_Level",
+ "sDialogMenu1",
+ "sDialogText1Xbox",
+ "sDialogText2Xbox",
+ "sDialogText3Xbox",
+ "sDifficulty",
+ "sDisposeCorpseFail",
+ "sDisposeofCorpse",
+ "sDone",
+ "sDoYouWantTo",
+ "sDrain",
+ "sDrop",
+ "sDuration",
+ "sDurationDes",
+ "sEasy",
+ "sEditNote",
+ "sEffectAbsorbAttribute",
+ "sEffectAbsorbFatigue",
+ "sEffectAbsorbHealth",
+ "sEffectAbsorbSkill",
+ "sEffectAbsorbSpellPoints",
+ "sEffectAlmsiviIntervention",
+ "sEffectBlind",
+ "sEffectBoundBattleAxe",
+ "sEffectBoundBoots",
+ "sEffectBoundCuirass",
+ "sEffectBoundDagger",
+ "sEffectBoundGloves",
+ "sEffectBoundHelm",
+ "sEffectBoundLongbow",
+ "sEffectBoundLongsword",
+ "sEffectBoundMace",
+ "sEffectBoundShield",
+ "sEffectBoundSpear",
+ "sEffectBurden",
+ "sEffectCalmCreature",
+ "sEffectCalmHumanoid",
+ "sEffectChameleon",
+ "sEffectCharm",
+ "sEffectCommandCreatures",
+ "sEffectCommandHumanoids",
+ "sEffectCorpus",
+ "sEffectCureBlightDisease",
+ "sEffectCureCommonDisease",
+ "sEffectCureCorprusDisease",
+ "sEffectCureParalyzation",
+ "sEffectCurePoison",
+ "sEffectDamageAttribute",
+ "sEffectDamageFatigue",
+ "sEffectDamageHealth",
+ "sEffectDamageMagicka",
+ "sEffectDamageSkill",
+ "sEffectDemoralizeCreature",
+ "sEffectDemoralizeHumanoid",
+ "sEffectDetectAnimal",
+ "sEffectDetectEnchantment",
+ "sEffectDetectKey",
+ "sEffectDisintegrateArmor",
+ "sEffectDisintegrateWeapon",
+ "sEffectDispel",
+ "sEffectDivineIntervention",
+ "sEffectDrainAttribute",
+ "sEffectDrainFatigue",
+ "sEffectDrainHealth",
+ "sEffectDrainSkill",
+ "sEffectDrainSpellpoints",
+ "sEffectExtraSpell",
+ "sEffectFeather",
+ "sEffectFireDamage",
+ "sEffectFireShield",
+ "sEffectFortifyAttackBonus",
+ "sEffectFortifyAttribute",
+ "sEffectFortifyFatigue",
+ "sEffectFortifyHealth",
+ "sEffectFortifyMagickaMultiplier",
+ "sEffectFortifySkill",
+ "sEffectFortifySpellpoints",
+ "sEffectFrenzyCreature",
+ "sEffectFrenzyHumanoid",
+ "sEffectFrostDamage",
+ "sEffectFrostShield",
+ "sEffectInvisibility",
+ "sEffectJump",
+ "sEffectLevitate",
+ "sEffectLight",
+ "sEffectLightningShield",
+ "sEffectLock",
+ "sEffectMark",
+ "sEffectNightEye",
+ "sEffectOpen",
+ "sEffectParalyze",
+ "sEffectPoison",
+ "sEffectRallyCreature",
+ "sEffectRallyHumanoid",
+ "sEffectRecall",
+ "sEffectReflect",
+ "sEffectRemoveCurse",
+ "sEffectResistBlightDisease",
+ "sEffectResistCommonDisease",
+ "sEffectResistCorprusDisease",
+ "sEffectResistFire",
+ "sEffectResistFrost",
+ "sEffectResistMagicka",
+ "sEffectResistNormalWeapons",
+ "sEffectResistParalysis",
+ "sEffectResistPoison",
+ "sEffectResistShock",
+ "sEffectRestoreAttribute",
+ "sEffectRestoreFatigue",
+ "sEffectRestoreHealth",
+ "sEffectRestoreSkill",
+ "sEffectRestoreSpellPoints",
+ "sEffects",
+ "sEffectSanctuary",
+ "sEffectShield",
+ "sEffectShockDamage",
+ "sEffectSilence",
+ "sEffectSlowFall",
+ "sEffectSoultrap",
+ "sEffectSound",
+ "sEffectSpellAbsorption",
+ "sEffectStuntedMagicka",
+ "sEffectSummonAncestralGhost",
+ "sEffectSummonBonelord",
+ "sEffectSummonCenturionSphere",
+ "sEffectSummonClannfear",
+ "sEffectSummonCreature01",
+ "sEffectSummonCreature02",
+ "sEffectSummonCreature03",
+ "sEffectSummonCreature04",
+ "sEffectSummonCreature05",
+ "sEffectSummonDaedroth",
+ "sEffectSummonDremora",
+ "sEffectSummonFabricant",
+ "sEffectSummonFlameAtronach",
+ "sEffectSummonFrostAtronach",
+ "sEffectSummonGoldensaint",
+ "sEffectSummonGreaterBonewalker",
+ "sEffectSummonHunger",
+ "sEffectSummonLeastBonewalker",
+ "sEffectSummonScamp",
+ "sEffectSummonSkeletalMinion",
+ "sEffectSummonStormAtronach",
+ "sEffectSummonWingedTwilight",
+ "sEffectSunDamage",
+ "sEffectSwiftSwim",
+ "sEffectTelekinesis",
+ "sEffectTurnUndead",
+ "sEffectVampirism",
+ "sEffectWaterBreathing",
+ "sEffectWaterWalking",
+ "sEffectWeaknessToBlightDisease",
+ "sEffectWeaknessToCommonDisease",
+ "sEffectWeaknessToCorprusDisease",
+ "sEffectWeaknessToFire",
+ "sEffectWeaknessToFrost",
+ "sEffectWeaknessToMagicka",
+ "sEffectWeaknessToNormalWeapons",
+ "sEffectWeaknessToPoison",
+ "sEffectWeaknessToShock",
+ "sEnableJoystick",
+ "sEnchanting",
+ "sEnchantItems",
+ "sEnchantmentHelp1",
+ "sEnchantmentHelp10",
+ "sEnchantmentHelp2",
+ "sEnchantmentHelp3",
+ "sEnchantmentHelp4",
+ "sEnchantmentHelp5",
+ "sEnchantmentHelp6",
+ "sEnchantmentHelp7",
+ "sEnchantmentHelp8",
+ "sEnchantmentHelp9",
+ "sEnchantmentMenu1",
+ "sEnchantmentMenu10",
+ "sEnchantmentMenu11",
+ "sEnchantmentMenu12",
+ "sEnchantmentMenu2",
+ "sEnchantmentMenu3",
+ "sEnchantmentMenu4",
+ "sEnchantmentMenu5",
+ "sEnchantmentMenu6",
+ "sEnchantmentMenu7",
+ "sEnchantmentMenu8",
+ "sEnchantmentMenu9",
+ "sEncumbrance",
+ "sEndDesc",
+ "sEquip",
+ "sExitGame",
+ "sExpelled",
+ "sExpelledMessage",
+ "sFace",
+ "sFaction",
+ "sFar",
+ "sFast",
+ "sFatDesc",
+ "sFatigue",
+ "sFavoriteSkills",
+ "sfeet",
+ "sFileSize",
+ "sfootarea",
+ "sFootsteps",
+ "sfor",
+ "sFortify",
+ "sForward",
+ "sForwardXbox",
+ "sFull",
+ "sGame",
+ "sGameWithoutLauncherXbox",
+ "sGamma_Correction",
+ "sGeneralMastPlugMismatchMsg",
+ "sGold",
+ "sGoodbye",
+ "sGoverningAttribute",
+ "sgp",
+ "sHair",
+ "sHard",
+ "sHeal",
+ "sHealer",
+ "sHealth",
+ "sHealthDesc",
+ "sHealthPerHourOfRest",
+ "sHealthPerLevel",
+ "sHeavy",
+ "sHigh",
+ "sin",
+ "sInfo",
+ "sInfoRefusal",
+ "sIngredients",
+ "sInPrisonTitle",
+ "sInputMenu1",
+ "sIntDesc",
+ "sIntimidate",
+ "sIntimidateFail",
+ "sIntimidateSuccess",
+ "sInvalidSaveGameMsg",
+ "sInvalidSaveGameMsgXBOX",
+ "sInventory",
+ "sInventoryMenu1",
+ "sInventoryMessage1",
+ "sInventoryMessage2",
+ "sInventoryMessage3",
+ "sInventoryMessage4",
+ "sInventoryMessage5",
+ "sInventorySelectNoIngredients",
+ "sInventorySelectNoItems",
+ "sInventorySelectNoSoul",
+ "sItem",
+ "sItemCastConstant",
+ "sItemCastOnce",
+ "sItemCastWhenStrikes",
+ "sItemCastWhenUsed",
+ "sItemName",
+ "sJournal",
+ "sJournalCmd",
+ "sJournalEntry",
+ "sJournalXbox",
+ "sJoystickHatShort",
+ "sJoystickNotFound",
+ "sJoystickShort",
+ "sJump",
+ "sJumpXbox",
+ "sKeyName_00",
+ "sKeyName_01",
+ "sKeyName_02",
+ "sKeyName_03",
+ "sKeyName_04",
+ "sKeyName_05",
+ "sKeyName_06",
+ "sKeyName_07",
+ "sKeyName_08",
+ "sKeyName_09",
+ "sKeyName_0A",
+ "sKeyName_0B",
+ "sKeyName_0C",
+ "sKeyName_0D",
+ "sKeyName_0E",
+ "sKeyName_0F",
+ "sKeyName_10",
+ "sKeyName_11",
+ "sKeyName_12",
+ "sKeyName_13",
+ "sKeyName_14",
+ "sKeyName_15",
+ "sKeyName_16",
+ "sKeyName_17",
+ "sKeyName_18",
+ "sKeyName_19",
+ "sKeyName_1A",
+ "sKeyName_1B",
+ "sKeyName_1C",
+ "sKeyName_1D",
+ "sKeyName_1E",
+ "sKeyName_1F",
+ "sKeyName_20",
+ "sKeyName_21",
+ "sKeyName_22",
+ "sKeyName_23",
+ "sKeyName_24",
+ "sKeyName_25",
+ "sKeyName_26",
+ "sKeyName_27",
+ "sKeyName_28",
+ "sKeyName_29",
+ "sKeyName_2A",
+ "sKeyName_2B",
+ "sKeyName_2C",
+ "sKeyName_2D",
+ "sKeyName_2E",
+ "sKeyName_2F",
+ "sKeyName_30",
+ "sKeyName_31",
+ "sKeyName_32",
+ "sKeyName_33",
+ "sKeyName_34",
+ "sKeyName_35",
+ "sKeyName_36",
+ "sKeyName_37",
+ "sKeyName_38",
+ "sKeyName_39",
+ "sKeyName_3A",
+ "sKeyName_3B",
+ "sKeyName_3C",
+ "sKeyName_3D",
+ "sKeyName_3E",
+ "sKeyName_3F",
+ "sKeyName_40",
+ "sKeyName_41",
+ "sKeyName_42",
+ "sKeyName_43",
+ "sKeyName_44",
+ "sKeyName_45",
+ "sKeyName_46",
+ "sKeyName_47",
+ "sKeyName_48",
+ "sKeyName_49",
+ "sKeyName_4A",
+ "sKeyName_4B",
+ "sKeyName_4C",
+ "sKeyName_4D",
+ "sKeyName_4E",
+ "sKeyName_4F",
+ "sKeyName_50",
+ "sKeyName_51",
+ "sKeyName_52",
+ "sKeyName_53",
+ "sKeyName_54",
+ "sKeyName_55",
+ "sKeyName_56",
+ "sKeyName_57",
+ "sKeyName_58",
+ "sKeyName_59",
+ "sKeyName_5A",
+ "sKeyName_5B",
+ "sKeyName_5C",
+ "sKeyName_5D",
+ "sKeyName_5E",
+ "sKeyName_5F",
+ "sKeyName_60",
+ "sKeyName_61",
+ "sKeyName_62",
+ "sKeyName_63",
+ "sKeyName_64",
+ "sKeyName_65",
+ "sKeyName_66",
+ "sKeyName_67",
+ "sKeyName_68",
+ "sKeyName_69",
+ "sKeyName_6A",
+ "sKeyName_6B",
+ "sKeyName_6C",
+ "sKeyName_6D",
+ "sKeyName_6E",
+ "sKeyName_6F",
+ "sKeyName_70",
+ "sKeyName_71",
+ "sKeyName_72",
+ "sKeyName_73",
+ "sKeyName_74",
+ "sKeyName_75",
+ "sKeyName_76",
+ "sKeyName_77",
+ "sKeyName_78",
+ "sKeyName_79",
+ "sKeyName_7A",
+ "sKeyName_7B",
+ "sKeyName_7C",
+ "sKeyName_7D",
+ "sKeyName_7E",
+ "sKeyName_7F",
+ "sKeyName_80",
+ "sKeyName_81",
+ "sKeyName_82",
+ "sKeyName_83",
+ "sKeyName_84",
+ "sKeyName_85",
+ "sKeyName_86",
+ "sKeyName_87",
+ "sKeyName_88",
+ "sKeyName_89",
+ "sKeyName_8A",
+ "sKeyName_8B",
+ "sKeyName_8C",
+ "sKeyName_8D",
+ "sKeyName_8E",
+ "sKeyName_8F",
+ "sKeyName_90",
+ "sKeyName_91",
+ "sKeyName_92",
+ "sKeyName_93",
+ "sKeyName_94",
+ "sKeyName_95",
+ "sKeyName_96",
+ "sKeyName_97",
+ "sKeyName_98",
+ "sKeyName_99",
+ "sKeyName_9A",
+ "sKeyName_9B",
+ "sKeyName_9C",
+ "sKeyName_9D",
+ "sKeyName_9E",
+ "sKeyName_9F",
+ "sKeyName_A0",
+ "sKeyName_A1",
+ "sKeyName_A2",
+ "sKeyName_A3",
+ "sKeyName_A4",
+ "sKeyName_A5",
+ "sKeyName_A6",
+ "sKeyName_A7",
+ "sKeyName_A8",
+ "sKeyName_A9",
+ "sKeyName_AA",
+ "sKeyName_AB",
+ "sKeyName_AC",
+ "sKeyName_AD",
+ "sKeyName_AE",
+ "sKeyName_AF",
+ "sKeyName_B0",
+ "sKeyName_B1",
+ "sKeyName_B2",
+ "sKeyName_B3",
+ "sKeyName_B4",
+ "sKeyName_B5",
+ "sKeyName_B6",
+ "sKeyName_B7",
+ "sKeyName_B8",
+ "sKeyName_B9",
+ "sKeyName_BA",
+ "sKeyName_BB",
+ "sKeyName_BC",
+ "sKeyName_BD",
+ "sKeyName_BE",
+ "sKeyName_BF",
+ "sKeyName_C0",
+ "sKeyName_C1",
+ "sKeyName_C2",
+ "sKeyName_C3",
+ "sKeyName_C4",
+ "sKeyName_C5",
+ "sKeyName_C6",
+ "sKeyName_C7",
+ "sKeyName_C8",
+ "sKeyName_C9",
+ "sKeyName_CA",
+ "sKeyName_CB",
+ "sKeyName_CC",
+ "sKeyName_CD",
+ "sKeyName_CE",
+ "sKeyName_CF",
+ "sKeyName_D0",
+ "sKeyName_D1",
+ "sKeyName_D2",
+ "sKeyName_D3",
+ "sKeyName_D4",
+ "sKeyName_D5",
+ "sKeyName_D6",
+ "sKeyName_D7",
+ "sKeyName_D8",
+ "sKeyName_D9",
+ "sKeyName_DA",
+ "sKeyName_DB",
+ "sKeyName_DC",
+ "sKeyName_DD",
+ "sKeyName_DE",
+ "sKeyName_DF",
+ "sKeyName_E0",
+ "sKeyName_E1",
+ "sKeyName_E2",
+ "sKeyName_E3",
+ "sKeyName_E4",
+ "sKeyName_E5",
+ "sKeyName_E6",
+ "sKeyName_E7",
+ "sKeyName_E8",
+ "sKeyName_E9",
+ "sKeyName_EA",
+ "sKeyName_EB",
+ "sKeyName_EC",
+ "sKeyName_ED",
+ "sKeyName_EE",
+ "sKeyName_EF",
+ "sKeyName_F0",
+ "sKeyName_F1",
+ "sKeyName_F2",
+ "sKeyName_F3",
+ "sKeyName_F4",
+ "sKeyName_F5",
+ "sKeyName_F6",
+ "sKeyName_F7",
+ "sKeyName_F8",
+ "sKeyName_F9",
+ "sKeyName_FA",
+ "sKeyName_FB",
+ "sKeyName_FC",
+ "sKeyName_FD",
+ "sKeyName_FE",
+ "sKeyName_FF",
+ "sKeyUsed",
+ "sKilledEssential",
+ "sKnight",
+ "sLeft",
+ "sLess",
+ "sLevel",
+ "sLevelProgress",
+ "sLevels",
+ "sLevelUp",
+ "sLevelUpMenu1",
+ "sLevelUpMenu2",
+ "sLevelUpMenu3",
+ "sLevelUpMenu4",
+ "sLevelUpMsg",
+ "sLevitateDisabled",
+ "sLight",
+ "sLight_Gamma",
+ "sLoadFailedMessage",
+ "sLoadGame",
+ "sLoadingErrorsMsg",
+ "sLoadingMessage1",
+ "sLoadingMessage14",
+ "sLoadingMessage15",
+ "sLoadingMessage2",
+ "sLoadingMessage3",
+ "sLoadingMessage4",
+ "sLoadingMessage5",
+ "sLoadingMessage9",
+ "sLoadLastSaveMsg",
+ "sLocal",
+ "sLockFail",
+ "sLockImpossible",
+ "sLockLevel",
+ "sLockSuccess",
+ "sLookDownXbox",
+ "sLookUpXbox",
+ "sLow",
+ "sLucDesc",
+ "sMagDesc",
+ "sMage",
+ "sMagic",
+ "sMagicAncestralGhostID",
+ "sMagicBonelordID",
+ "sMagicBoundBattleAxeID",
+ "sMagicBoundBootsID",
+ "sMagicBoundCuirassID",
+ "sMagicBoundDaggerID",
+ "sMagicBoundHelmID",
+ "sMagicBoundLeftGauntletID",
+ "sMagicBoundLongbowID",
+ "sMagicBoundLongswordID",
+ "sMagicBoundMaceID",
+ "sMagicBoundRightGauntletID",
+ "sMagicBoundShieldID",
+ "sMagicBoundSpearID",
+ "sMagicCannotRecast",
+ "sMagicCenturionSphereID",
+ "sMagicClannfearID",
+ "sMagicContractDisease",
+ "sMagicCorprusWorsens",
+ "sMagicCreature01ID",
+ "sMagicCreature02ID",
+ "sMagicCreature03ID",
+ "sMagicCreature04ID",
+ "sMagicCreature05ID",
+ "sMagicDaedrothID",
+ "sMagicDremoraID",
+ "sMagicEffects",
+ "sMagicFabricantID",
+ "sMagicFlameAtronachID",
+ "sMagicFrostAtronachID",
+ "sMagicGoldenSaintID",
+ "sMagicGreaterBonewalkerID",
+ "sMagicHungerID",
+ "sMagicInsufficientCharge",
+ "sMagicInsufficientSP",
+ "sMagicInvalidEffect",
+ "sMagicInvalidTarget",
+ "sMagicItem",
+ "sMagicLeastBonewalkerID",
+ "sMagicLockSuccess",
+ "sMagicMenu",
+ "sMagicOpenSuccess",
+ "sMagicPCResisted",
+ "sMagicScampID",
+ "sMagicSelectTitle",
+ "sMagicSkeletalMinionID",
+ "sMagicSkillFail",
+ "sMagicStormAtronachID",
+ "sMagicTab",
+ "sMagicTargetResisted",
+ "sMagicTargetResistsWeapons",
+ "sMagicWingedTwilightID",
+ "sMagnitude",
+ "sMagnitudeDes",
+ "sMake",
+ "sMap",
+ "sMaster",
+ "sMastPlugMismatchMsg",
+ "sMaximumSaveGameMessage",
+ "sMaxSale",
+ "sMedium",
+ "sMenu_Help_Delay",
+ "sMenu_Mode",
+ "sMenuModeXbox",
+ "sMenuNextXbox",
+ "sMenuPrevXbox",
+ "sMenus",
+ "sMessage1",
+ "sMessage2",
+ "sMessage3",
+ "sMessage4",
+ "sMessage5",
+ "sMessageQuestionAnswer1",
+ "sMessageQuestionAnswer2",
+ "sMessageQuestionAnswer3",
+ "sMiscTab",
+ "sMissingMastersMsg",
+ "sMonk",
+ "sMonthEveningstar",
+ "sMonthFirstseed",
+ "sMonthFrostfall",
+ "sMonthHeartfire",
+ "sMonthLastseed",
+ "sMonthMidyear",
+ "sMonthMorningstar",
+ "sMonthRainshand",
+ "sMonthSecondseed",
+ "sMonthSunsdawn",
+ "sMonthSunsdusk",
+ "sMonthSunsheight",
+ "sMore",
+ "sMortar",
+ "sMouse",
+ "sMouseFlip",
+ "sMouseWheelDownShort",
+ "sMouseWheelUpShort",
+ "sMove",
+ "sMoveDownXbox",
+ "sMoveUpXbox",
+ "sMusic",
+ "sName",
+ "sNameTitle",
+ "sNear",
+ "sNeedOneSkill",
+ "sNeedTwoSkills",
+ "sNewGame",
+ "sNext",
+ "sNextRank",
+ "sNextSpell",
+ "sNextSpellXbox",
+ "sNextWeapon",
+ "sNextWeaponXbox",
+ "sNightblade",
+ "sNo",
+ "sNoName",
+ "sNone",
+ "sNotifyMessage1",
+ "sNotifyMessage10",
+ "sNotifyMessage11",
+ "sNotifyMessage12",
+ "sNotifyMessage13",
+ "sNotifyMessage14",
+ "sNotifyMessage15",
+ "sNotifyMessage16",
+ "sNotifyMessage16_a",
+ "sNotifyMessage17",
+ "sNotifyMessage18",
+ "sNotifyMessage19",
+ "sNotifyMessage2",
+ "sNotifyMessage20",
+ "sNotifyMessage21",
+ "sNotifyMessage22",
+ "sNotifyMessage23",
+ "sNotifyMessage24",
+ "sNotifyMessage25",
+ "sNotifyMessage26",
+ "sNotifyMessage27",
+ "sNotifyMessage28",
+ "sNotifyMessage29",
+ "sNotifyMessage3",
+ "sNotifyMessage30",
+ "sNotifyMessage31",
+ "sNotifyMessage32",
+ "sNotifyMessage33",
+ "sNotifyMessage34",
+ "sNotifyMessage35",
+ "sNotifyMessage36",
+ "sNotifyMessage37",
+ "sNotifyMessage38",
+ "sNotifyMessage39",
+ "sNotifyMessage4",
+ "sNotifyMessage40",
+ "sNotifyMessage41",
+ "sNotifyMessage42",
+ "sNotifyMessage43",
+ "sNotifyMessage44",
+ "sNotifyMessage45",
+ "sNotifyMessage46",
+ "sNotifyMessage47",
+ "sNotifyMessage48",
+ "sNotifyMessage49",
+ "sNotifyMessage4XBOX",
+ "sNotifyMessage5",
+ "sNotifyMessage50",
+ "sNotifyMessage51",
+ "sNotifyMessage52",
+ "sNotifyMessage53",
+ "sNotifyMessage54",
+ "sNotifyMessage55",
+ "sNotifyMessage56",
+ "sNotifyMessage57",
+ "sNotifyMessage58",
+ "sNotifyMessage59",
+ "sNotifyMessage6",
+ "sNotifyMessage60",
+ "sNotifyMessage61",
+ "sNotifyMessage62",
+ "sNotifyMessage63",
+ "sNotifyMessage64",
+ "sNotifyMessage65",
+ "sNotifyMessage66",
+ "sNotifyMessage67",
+ "sNotifyMessage6a",
+ "sNotifyMessage7",
+ "sNotifyMessage8",
+ "sNotifyMessage9",
+ "sOff",
+ "sOffer",
+ "sOfferMenuTitle",
+ "sOK",
+ "sOn",
+ "sOnce",
+ "sOneHanded",
+ "sOnetypeEffectMessage",
+ "sonword",
+ "sOptions",
+ "sOptionsMenuXbox",
+ "spercent",
+ "sPerDesc",
+ "sPersuasion",
+ "sPersuasionMenuTitle",
+ "sPickUp",
+ "sPilgrim",
+ "spoint",
+ "spoints",
+ "sPotionSuccess",
+ "sPowerAlreadyUsed",
+ "sPowers",
+ "sPreferences",
+ "sPrefs",
+ "sPrev",
+ "sPrevSpell",
+ "sPrevSpellXbox",
+ "sPrevWeapon",
+ "sPrevWeaponXbox",
+ "sProfitValue",
+ "sQuality",
+ "sQuanityMenuMessage01",
+ "sQuanityMenuMessage02",
+ "sQuestionDeleteSpell",
+ "sQuestionMark",
+ "sQuick0Xbox",
+ "sQuick10Cmd",
+ "sQuick1Cmd",
+ "sQuick2Cmd",
+ "sQuick3Cmd",
+ "sQuick4Cmd",
+ "sQuick4Xbox",
+ "sQuick5Cmd",
+ "sQuick5Xbox",
+ "sQuick6Cmd",
+ "sQuick6Xbox",
+ "sQuick7Cmd",
+ "sQuick7Xbox",
+ "sQuick8Cmd",
+ "sQuick8Xbox",
+ "sQuick9Cmd",
+ "sQuick9Xbox",
+ "sQuick_Save",
+ "sQuickLoadCmd",
+ "sQuickLoadXbox",
+ "sQuickMenu",
+ "sQuickMenu1",
+ "sQuickMenu2",
+ "sQuickMenu3",
+ "sQuickMenu4",
+ "sQuickMenu5",
+ "sQuickMenu6",
+ "sQuickMenuInstruc",
+ "sQuickMenuTitle",
+ "sQuickSaveCmd",
+ "sQuickSaveXbox",
+ "sRace",
+ "sRaceMenu1",
+ "sRaceMenu2",
+ "sRaceMenu3",
+ "sRaceMenu4",
+ "sRaceMenu5",
+ "sRaceMenu6",
+ "sRaceMenu7",
+ "sRacialTraits",
+ "sRange",
+ "sRangeDes",
+ "sRangeSelf",
+ "sRangeTarget",
+ "sRangeTouch",
+ "sReady_Magic",
+ "sReady_Weapon",
+ "sReadyItemXbox",
+ "sReadyMagicXbox",
+ "sRechargeEnchantment",
+ "sRender_Distance",
+ "sRepair",
+ "sRepairFailed",
+ "sRepairServiceTitle",
+ "sRepairSuccess",
+ "sReputation",
+ "sResChangeWarning",
+ "sRest",
+ "sRestIllegal",
+ "sRestKey",
+ "sRestMenu1",
+ "sRestMenu2",
+ "sRestMenu3",
+ "sRestMenu4",
+ "sRestMenuXbox",
+ "sRestore",
+ "sRetort",
+ "sReturnToGame",
+ "sRight",
+ "sRogue",
+ "sRun",
+ "sRunXbox",
+ "sSave",
+ "sSaveGame",
+ "sSaveGameDenied",
+ "sSaveGameFailed",
+ "sSaveGameNoMemory",
+ "sSaveGameTooBig",
+ "sSaveMenu1",
+ "sSaveMenuHelp01",
+ "sSaveMenuHelp02",
+ "sSaveMenuHelp03",
+ "sSaveMenuHelp04",
+ "sSaveMenuHelp05",
+ "sSaveMenuHelp06",
+ "sSchool",
+ "sSchoolAlteration",
+ "sSchoolConjuration",
+ "sSchoolDestruction",
+ "sSchoolIllusion",
+ "sSchoolMysticism",
+ "sSchoolRestoration",
+ "sScout",
+ "sScrolldown",
+ "sScrollup",
+ "ssecond",
+ "sseconds",
+ "sSeldom",
+ "sSelect",
+ "sSell",
+ "sSellerGold",
+ "sService",
+ "sServiceRefusal",
+ "sServiceRepairTitle",
+ "sServiceSpellsTitle",
+ "sServiceTrainingTitle",
+ "sServiceTrainingWords",
+ "sServiceTravelTitle",
+ "sSetValueMessage01",
+ "sSex",
+ "sShadows",
+ "sShadowText",
+ "sShift",
+ "sSkill",
+ "sSkillAcrobatics",
+ "sSkillAlchemy",
+ "sSkillAlteration",
+ "sSkillArmorer",
+ "sSkillAthletics",
+ "sSkillAxe",
+ "sSkillBlock",
+ "sSkillBluntweapon",
+ "sSkillClassMajor",
+ "sSkillClassMinor",
+ "sSkillClassMisc",
+ "sSkillConjuration",
+ "sSkillDestruction",
+ "sSkillEnchant",
+ "sSkillHandtohand",
+ "sSkillHeavyarmor",
+ "sSkillIllusion",
+ "sSkillLightarmor",
+ "sSkillLongblade",
+ "sSkillMarksman",
+ "sSkillMaxReached",
+ "sSkillMediumarmor",
+ "sSkillMercantile",
+ "sSkillMysticism",
+ "sSkillProgress",
+ "sSkillRestoration",
+ "sSkillSecurity",
+ "sSkillShortblade",
+ "sSkillsMenu1",
+ "sSkillsMenuReputationHelp",
+ "sSkillSneak",
+ "sSkillSpear",
+ "sSkillSpeechcraft",
+ "sSkillUnarmored",
+ "sSlash",
+ "sSleepInterrupt",
+ "sSlideLeftXbox",
+ "sSlideRightXbox",
+ "sSlow",
+ "sSorceror",
+ "sSoulGem",
+ "sSoulGemsWithSouls",
+ "sSoultrapSuccess",
+ "sSpace",
+ "sSpdDesc",
+ "sSpecialization",
+ "sSpecializationCombat",
+ "sSpecializationMagic",
+ "sSpecializationMenu1",
+ "sSpecializationStealth",
+ "sSpellmaking",
+ "sSpellmakingHelp1",
+ "sSpellmakingHelp2",
+ "sSpellmakingHelp3",
+ "sSpellmakingHelp4",
+ "sSpellmakingHelp5",
+ "sSpellmakingHelp6",
+ "sSpellmakingMenu1",
+ "sSpellmakingMenuTitle",
+ "sSpells",
+ "sSpellServiceTitle",
+ "sSpellsword",
+ "sStartCell",
+ "sStartCellError",
+ "sStartError",
+ "sStats",
+ "sStrafe",
+ "sStrDesc",
+ "sStrip",
+ "sSubtitles",
+ "sSystemMenuXbox",
+ "sTake",
+ "sTakeAll",
+ "sTargetCriticalStrike",
+ "sTaunt",
+ "sTauntFail",
+ "sTauntSuccess",
+ "sTeleportDisabled",
+ "sThief",
+ "sThrust",
+ "sTo",
+ "sTogglePOVCmd",
+ "sTogglePOVXbox",
+ "sToggleRunXbox",
+ "sTopics",
+ "sTotalCost",
+ "sTotalSold",
+ "sTraining",
+ "sTrainingServiceTitle",
+ "sTraits",
+ "sTransparency_Menu",
+ "sTrapFail",
+ "sTrapImpossible",
+ "sTrapped",
+ "sTrapSuccess",
+ "sTravel",
+ "sTravelServiceTitle",
+ "sTurn",
+ "sTurnLeftXbox",
+ "sTurnRightXbox",
+ "sTwoHanded",
+ "sType",
+ "sTypeAbility",
+ "sTypeBlightDisease",
+ "sTypeCurse",
+ "sTypeDisease",
+ "sTypePower",
+ "sTypeSpell",
+ "sUnequip",
+ "sUnlocked",
+ "sUntilHealed",
+ "sUse",
+ "sUserDefinedClass",
+ "sUses",
+ "sUseXbox",
+ "sValue",
+ "sVideo",
+ "sVideoWarning",
+ "sVoice",
+ "sWait",
+ "sWarrior",
+ "sWaterReflectUpdate",
+ "sWaterTerrainReflect",
+ "sWeaponTab",
+ "sWeight",
+ "sWerewolfAlarmMessage",
+ "sWerewolfPopup",
+ "sWerewolfRefusal",
+ "sWerewolfRestMessage",
+ "sWilDesc",
+ "sWitchhunter",
+ "sWorld",
+ "sWornTab",
+ "sXStrafe",
+ "sXTimes",
+ "sXTimesINT",
+ "sYes",
+ "sYourGold",
+ 0
+ };
+
+ for (int i=0; gmstFloats[i]; i++)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = gmstFloats[i];
+ gmst.mValue.setType (ESM::VT_Float);
+ gmst.mValue.setFloat (gmstFloatsValues[i]);
+ getData().getGmsts().add (gmst);
+ }
+
+ for (int i=0; gmstIntegers[i]; i++)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = gmstIntegers[i];
+ gmst.mValue.setType (ESM::VT_Int);
+ gmst.mValue.setInteger (gmstIntegersValues[i]);
+ getData().getGmsts().add (gmst);
+ }
+
+ for (int i=0; gmstStrings[i]; i++)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = gmstStrings[i];
+ gmst.mValue.setType (ESM::VT_String);
+ gmst.mValue.setString ("");
+ getData().getGmsts().add (gmst);
+ }
+}
+
+void CSMDoc::Document::addOptionalGmsts()
+{
+ static const char *sFloats[] =
+ {
+ "fCombatDistanceWerewolfMod",
+ "fFleeDistance",
+ "fWereWolfAcrobatics",
+ "fWereWolfAgility",
+ "fWereWolfAlchemy",
+ "fWereWolfAlteration",
+ "fWereWolfArmorer",
+ "fWereWolfAthletics",
+ "fWereWolfAxe",
+ "fWereWolfBlock",
+ "fWereWolfBluntWeapon",
+ "fWereWolfConjuration",
+ "fWereWolfDestruction",
+ "fWereWolfEnchant",
+ "fWereWolfEndurance",
+ "fWereWolfFatigue",
+ "fWereWolfHandtoHand",
+ "fWereWolfHealth",
+ "fWereWolfHeavyArmor",
+ "fWereWolfIllusion",
+ "fWereWolfIntellegence",
+ "fWereWolfLightArmor",
+ "fWereWolfLongBlade",
+ "fWereWolfLuck",
+ "fWereWolfMagicka",
+ "fWereWolfMarksman",
+ "fWereWolfMediumArmor",
+ "fWereWolfMerchantile",
+ "fWereWolfMysticism",
+ "fWereWolfPersonality",
+ "fWereWolfRestoration",
+ "fWereWolfRunMult",
+ "fWereWolfSecurity",
+ "fWereWolfShortBlade",
+ "fWereWolfSilverWeaponDamageMult",
+ "fWereWolfSneak",
+ "fWereWolfSpear",
+ "fWereWolfSpeechcraft",
+ "fWereWolfSpeed",
+ "fWereWolfStrength",
+ "fWereWolfUnarmored",
+ "fWereWolfWillPower",
+ 0
+ };
+
+ static const char *sIntegers[] =
+ {
+ "iWereWolfBounty",
+ "iWereWolfFightMod",
+ "iWereWolfFleeMod",
+ "iWereWolfLevelToAttack",
+ 0
+ };
+
+ static const char *sStrings[] =
+ {
+ "sCompanionShare",
+ "sCompanionWarningButtonOne",
+ "sCompanionWarningButtonTwo",
+ "sCompanionWarningMessage",
+ "sDeleteNote",
+ "sEditNote",
+ "sEffectSummonCreature01",
+ "sEffectSummonCreature02",
+ "sEffectSummonCreature03",
+ "sEffectSummonCreature04",
+ "sEffectSummonCreature05",
+ "sEffectSummonFabricant",
+ "sLevitateDisabled",
+ "sMagicCreature01ID",
+ "sMagicCreature02ID",
+ "sMagicCreature03ID",
+ "sMagicCreature04ID",
+ "sMagicCreature05ID",
+ "sMagicFabricantID",
+ "sMaxSale",
+ "sProfitValue",
+ "sTeleportDisabled",
+ "sWerewolfAlarmMessage",
+ "sWerewolfPopup",
+ "sWerewolfRefusal",
+ "sWerewolfRestMessage",
+ 0
+ };
+
+ for (int i=0; sFloats[i]; ++i)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = sFloats[i];
+ gmst.mValue.setType (ESM::VT_Float);
+ addOptionalGmst (gmst);
+ }
+
+ for (int i=0; sIntegers[i]; ++i)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = sIntegers[i];
+ gmst.mValue.setType (ESM::VT_Int);
+ addOptionalGmst (gmst);
+ }
+
+ for (int i=0; sStrings[i]; ++i)
+ {
+ ESM::GameSetting gmst;
+ gmst.mId = sStrings[i];
+ gmst.mValue.setType (ESM::VT_String);
+ gmst.mValue.setString ("<no text>");
+ addOptionalGmst (gmst);
+ }
+}
+
+void CSMDoc::Document::addOptionalGlobals()
+{
+ static const char *sGlobals[] =
+ {
+ "dayspassed",
+ "pcwerewolf",
+ "pcyear",
+ 0
+ };
+
+ for (int i=0; sGlobals[i]; ++i)
+ {
+ ESM::Global global;
+ global.mId = sGlobals[i];
+ global.mValue.setType (ESM::VT_Long);
+
+ if (i==0)
+ global.mValue.setInteger (1); // dayspassed starts counting at 1
+
+ addOptionalGlobal (global);
+ }
+}
+
+void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst)
+{
+ if (getData().getGmsts().searchId (gmst.mId)==-1)
+ {
+ CSMWorld::Record<ESM::GameSetting> record;
+ record.mBase = gmst;
+ record.mState = CSMWorld::RecordBase::State_BaseOnly;
+ getData().getGmsts().appendRecord (record);
+ }
+}
+
+void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global)
+{
+ if (getData().getGlobals().searchId (global.mId)==-1)
+ {
+ CSMWorld::Record<ESM::Global> record;
+ record.mBase = global;
+ record.mState = CSMWorld::RecordBase::State_BaseOnly;
+ getData().getGlobals().appendRecord (record);
+ }
+}
+
+void CSMDoc::Document::createBase()
+{
+ static const char *sGlobals[] =
+ {
+ "Day",
+ "DaysPassed",
+ "GameHour",
+ "Month",
+ "PCRace",
+ "PCVampire",
+ "PCWerewolf",
+ "PCYear",
+ 0
+ };
+
+ for (int i=0; sGlobals[i]; ++i)
+ {
+ ESM::Global record;
+ record.mId = sGlobals[i];
+ record.mValue.setType (i==2 ? ESM::VT_Float : ESM::VT_Long);
+
+ if (i==0 || i==1)
+ record.mValue.setInteger (1);
+
+ getData().getGlobals().add (record);
+ }
+
+ addGmsts();
+
+ for (int i=0; i<27; ++i)
+ {
+ ESM::Skill record;
+ record.mIndex = i;
+ record.mId = ESM::Skill::indexToId (record.mIndex);
+ record.blank();
+
+ getData().getSkills().add (record);
+ }
+}
+
+CSMDoc::Document::Document (const std::vector<boost::filesystem::path>& files, bool new_)
+: mTools (mData)
+{
+ if (files.empty())
+ throw std::runtime_error ("Empty content file sequence");
+
+ /// \todo adjust last file name:
+ /// \li make sure it is located in the data-local directory (adjust path if necessary)
+ /// \li make sure the extension matches the new scheme (change it if necesarry)
+
+ mName = files.back().filename().string();
+
+ if (new_ && files.size()==1)
+ createBase();
+ else
+ {
+ std::vector<boost::filesystem::path>::const_iterator end = files.end();
+
+ if (new_)
+ --end;
+
+ load (files.begin(), end, !new_);
+ }
+
+ addOptionalGmsts();
+ addOptionalGlobals();
+
+ connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool)));
+
+ connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
+ connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int)));
+
+ // dummy implementation -> remove when proper save is implemented.
+ mSaveCount = 0;
+ connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving()));
+}
+
+CSMDoc::Document::~Document()
+{
+}
+
+QUndoStack& CSMDoc::Document::getUndoStack()
+{
+ return mUndoStack;
+}
+
+int CSMDoc::Document::getState() const
+{
+ int state = 0;
+
+ if (!mUndoStack.isClean())
+ state |= State_Modified;
+
+ if (mSaveCount)
+ state |= State_Locked | State_Saving | State_Operation;
+
+ if (int operations = mTools.getRunningOperations())
+ state |= State_Locked | State_Operation | operations;
+
+ return state;
+}
+
+const std::string& CSMDoc::Document::getName() const
+{
+ return mName;
+}
+
+void CSMDoc::Document::save()
+{
+ mSaveCount = 1;
+ mSaveTimer.start (500);
+ emit stateChanged (getState(), this);
+ emit progress (1, 16, State_Saving, 1, this);
+}
+
+CSMWorld::UniversalId CSMDoc::Document::verify()
+{
+ CSMWorld::UniversalId id = mTools.runVerifier();
+ emit stateChanged (getState(), this);
+ return id;
+}
+
+void CSMDoc::Document::abortOperation (int type)
+{
+ mTools.abortOperation (type);
+
+ if (type==State_Saving)
+ {
+ mSaveCount=0;
+ mSaveTimer.stop();
+ emit stateChanged (getState(), this);
+ }
+}
+
+
+void CSMDoc::Document::modificationStateChanged (bool clean)
+{
+ emit stateChanged (getState(), this);
+}
+
+
+void CSMDoc::Document::operationDone (int type)
+{
+ emit stateChanged (getState(), this);
+}
+
+void CSMDoc::Document::saving()
+{
+ ++mSaveCount;
+
+ emit progress (mSaveCount, 16, State_Saving, 1, this);
+
+ if (mSaveCount>15)
+ {
+ //clear the stack before resetting the save state
+ //to avoid emitting incorrect states
+ mUndoStack.setClean();
+
+ mSaveCount = 0;
+ mSaveTimer.stop();
+ emit stateChanged (getState(), this);
+ }
+}
+
+const CSMWorld::Data& CSMDoc::Document::getData() const
+{
+ return mData;
+}
+
+CSMWorld::Data& CSMDoc::Document::getData()
+{
+ return mData;
+}
+
+CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& id)
+{
+ return mTools.getReport (id);
+}
+
+void CSMDoc::Document::progress (int current, int max, int type)
+{
+ emit progress (current, max, type, 1, this);
+}
diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp
new file mode 100644
index 0000000000..1c6d9db2a9
--- /dev/null
+++ b/apps/opencs/model/doc/document.hpp
@@ -0,0 +1,111 @@
+#ifndef CSM_DOC_DOCUMENT_H
+#define CSM_DOC_DOCUMENT_H
+
+#include <string>
+
+#include <boost/filesystem/path.hpp>
+
+#include <QUndoStack>
+#include <QObject>
+#include <QTimer>
+
+#include "../world/data.hpp"
+
+#include "../tools/tools.hpp"
+
+#include "state.hpp"
+
+class QAbstractItemModel;
+
+namespace ESM
+{
+ struct GameSetting;
+ struct Global;
+}
+
+namespace CSMDoc
+{
+ class Document : public QObject
+ {
+ Q_OBJECT
+
+ private:
+
+ std::string mName; ///< \todo replace name with ESX list
+ CSMWorld::Data mData;
+ CSMTools::Tools mTools;
+
+ // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is
+ // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late.
+ QUndoStack mUndoStack;
+
+ int mSaveCount; ///< dummy implementation -> remove when proper save is implemented.
+ QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented.
+
+ // not implemented
+ Document (const Document&);
+ Document& operator= (const Document&);
+
+ void load (const std::vector<boost::filesystem::path>::const_iterator& begin,
+ const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified);
+ ///< \param lastAsModified Store the last file in Modified instead of merging it into Base.
+
+ void createBase();
+
+ void addGmsts();
+
+ void addOptionalGmsts();
+
+ void addOptionalGlobals();
+
+ void addOptionalGmst (const ESM::GameSetting& gmst);
+
+ void addOptionalGlobal (const ESM::Global& global);
+
+ public:
+
+ Document (const std::vector<boost::filesystem::path>& files, bool new_);
+ ~Document();
+
+ QUndoStack& getUndoStack();
+
+ int getState() const;
+
+ const std::string& getName() const;
+ ///< \todo replace with ESX list
+
+ void save();
+
+ CSMWorld::UniversalId verify();
+
+ void abortOperation (int type);
+
+ const CSMWorld::Data& getData() const;
+
+ CSMWorld::Data& getData();
+
+ CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id);
+ ///< The ownership of the returned report is not transferred.
+
+ signals:
+
+ void stateChanged (int state, CSMDoc::Document *document);
+
+ void progress (int current, int max, int type, int threads, CSMDoc::Document *document);
+
+ private slots:
+
+ void modificationStateChanged (bool clean);
+
+ void operationDone (int type);
+
+ void saving();
+ ///< dummy implementation -> remove when proper save is implemented.
+
+ public slots:
+
+ void progress (int current, int max, int type);
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp
new file mode 100644
index 0000000000..740c0b5827
--- /dev/null
+++ b/apps/opencs/model/doc/documentmanager.cpp
@@ -0,0 +1,38 @@
+
+#include "documentmanager.hpp"
+
+#include <algorithm>
+#include <stdexcept>
+
+#include "document.hpp"
+
+CSMDoc::DocumentManager::DocumentManager() {}
+
+CSMDoc::DocumentManager::~DocumentManager()
+{
+ for (std::vector<Document *>::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter)
+ delete *iter;
+}
+
+CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files,
+ bool new_)
+{
+ Document *document = new Document (files, new_);
+
+ mDocuments.push_back (document);
+
+ return document;
+}
+
+bool CSMDoc::DocumentManager::removeDocument (Document *document)
+{
+ std::vector<Document *>::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document);
+
+ if (iter==mDocuments.end())
+ throw std::runtime_error ("removing invalid document");
+
+ mDocuments.erase (iter);
+ delete document;
+
+ return mDocuments.empty();
+} \ No newline at end of file
diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp
new file mode 100644
index 0000000000..a307b76a57
--- /dev/null
+++ b/apps/opencs/model/doc/documentmanager.hpp
@@ -0,0 +1,37 @@
+#ifndef CSM_DOC_DOCUMENTMGR_H
+#define CSM_DOC_DOCUMENTMGR_H
+
+#include <vector>
+#include <string>
+
+#include <boost/filesystem/path.hpp>
+
+namespace CSMDoc
+{
+ class Document;
+
+ class DocumentManager
+ {
+ std::vector<Document *> mDocuments;
+
+ DocumentManager (const DocumentManager&);
+ DocumentManager& operator= (const DocumentManager&);
+
+ public:
+
+ DocumentManager();
+
+ ~DocumentManager();
+
+ Document *addDocument (const std::vector<boost::filesystem::path>& files, bool new_);
+ ///< The ownership of the returned document is not transferred to the caller.
+ ///
+ /// \param new_ Do not load the last content file in \a files and instead create in an
+ /// appropriate way.
+
+ bool removeDocument (Document *document);
+ ///< \return last document removed?
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp
new file mode 100644
index 0000000000..04e6fae899
--- /dev/null
+++ b/apps/opencs/model/doc/state.hpp
@@ -0,0 +1,19 @@
+#ifndef CSM_DOC_STATE_H
+#define CSM_DOC_STATE_H
+
+namespace CSMDoc
+{
+ enum State
+ {
+ State_Modified = 1,
+ State_Locked = 2,
+ State_Operation = 4,
+
+ State_Saving = 8,
+ State_Verifying = 16,
+ State_Compiling = 32, // not implemented yet
+ State_Searching = 64 // not implemented yet
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp
new file mode 100644
index 0000000000..dfaa56e782
--- /dev/null
+++ b/apps/opencs/model/filter/andnode.cpp
@@ -0,0 +1,20 @@
+
+#include "andnode.hpp"
+
+#include <sstream>
+
+CSMFilter::AndNode::AndNode (const std::vector<boost::shared_ptr<Node> >& nodes)
+: NAryNode (nodes, "and")
+{}
+
+bool CSMFilter::AndNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ int size = getSize();
+
+ for (int i=0; i<size; ++i)
+ if (!(*this)[i].test (table, row, columns))
+ return false;
+
+ return true;
+}
diff --git a/apps/opencs/model/filter/andnode.hpp b/apps/opencs/model/filter/andnode.hpp
new file mode 100644
index 0000000000..980ea554c6
--- /dev/null
+++ b/apps/opencs/model/filter/andnode.hpp
@@ -0,0 +1,23 @@
+#ifndef CSM_FILTER_ANDNODE_H
+#define CSM_FILTER_ANDNODE_H
+
+#include "narynode.hpp"
+
+namespace CSMFilter
+{
+ class AndNode : public NAryNode
+ {
+ bool mAnd;
+
+ public:
+
+ AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp
new file mode 100644
index 0000000000..267e06a64a
--- /dev/null
+++ b/apps/opencs/model/filter/booleannode.cpp
@@ -0,0 +1,15 @@
+
+#include "booleannode.hpp"
+
+CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {}
+
+bool CSMFilter::BooleanNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ return mTrue;
+}
+
+std::string CSMFilter::BooleanNode::toString (bool numericColumns) const
+{
+ return mTrue ? "true" : "false";
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/booleannode.hpp b/apps/opencs/model/filter/booleannode.hpp
new file mode 100644
index 0000000000..d19219e352
--- /dev/null
+++ b/apps/opencs/model/filter/booleannode.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_FILTER_BOOLEANNODE_H
+#define CSM_FILTER_BOOLEANNODE_H
+
+#include "leafnode.hpp"
+
+namespace CSMFilter
+{
+ class BooleanNode : public LeafNode
+ {
+ bool mTrue;
+
+ public:
+
+ BooleanNode (bool true_);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+
+ virtual std::string toString (bool numericColumns) const;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/filter/filter.hpp b/apps/opencs/model/filter/filter.hpp
new file mode 100644
index 0000000000..62170ca802
--- /dev/null
+++ b/apps/opencs/model/filter/filter.hpp
@@ -0,0 +1,25 @@
+#ifndef CSM_FILTER_FILTER_H
+#define CSM_FILTER_FILTER_H
+
+#include <vector>
+#include <string>
+
+#include <components/esm/filter.hpp>
+
+namespace CSMFilter
+{
+ /// \brief Wrapper for Filter record
+ struct Filter : public ESM::Filter
+ {
+ enum Scope
+ {
+ Scope_Project = 0, // per project
+ Scope_Session = 1, // exists only for one editing session; not saved
+ Scope_Content = 2 // embedded in the edited content file
+ };
+
+ Scope mScope;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp
new file mode 100644
index 0000000000..055a1747cc
--- /dev/null
+++ b/apps/opencs/model/filter/leafnode.cpp
@@ -0,0 +1,8 @@
+
+#include "leafnode.hpp"
+
+std::vector<int> CSMFilter::LeafNode::getReferencedColumns() const
+{
+ return std::vector<int>();
+}
+
diff --git a/apps/opencs/model/filter/leafnode.hpp b/apps/opencs/model/filter/leafnode.hpp
new file mode 100644
index 0000000000..2f3d910708
--- /dev/null
+++ b/apps/opencs/model/filter/leafnode.hpp
@@ -0,0 +1,20 @@
+#ifndef CSM_FILTER_LEAFNODE_H
+#define CSM_FILTER_LEAFNODE_H
+
+#include <memory>
+
+#include "node.hpp"
+
+namespace CSMFilter
+{
+ class LeafNode : public Node
+ {
+ public:
+
+ virtual std::vector<int> getReferencedColumns() const;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp
new file mode 100644
index 0000000000..98f706c875
--- /dev/null
+++ b/apps/opencs/model/filter/narynode.cpp
@@ -0,0 +1,60 @@
+
+#include "narynode.hpp"
+
+#include <sstream>
+
+CSMFilter::NAryNode::NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes,
+ const std::string& name)
+: mNodes (nodes), mName (name)
+{}
+
+int CSMFilter::NAryNode::getSize() const
+{
+ return mNodes.size();
+}
+
+const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const
+{
+ return *mNodes.at (index);
+}
+
+std::vector<int> CSMFilter::NAryNode::getReferencedColumns() const
+{
+ std::vector<int> columns;
+
+ for (std::vector<boost::shared_ptr<Node> >::const_iterator iter (mNodes.begin());
+ iter!=mNodes.end(); ++iter)
+ {
+ std::vector<int> columns2 = (*iter)->getReferencedColumns();
+
+ columns.insert (columns.end(), columns2.begin(), columns2.end());
+ }
+
+ return columns;
+}
+
+std::string CSMFilter::NAryNode::toString (bool numericColumns) const
+{
+ std::ostringstream stream;
+
+ stream << mName << " (";
+
+ bool first = true;
+ int size = getSize();
+
+ for (int i=0; i<size; ++i)
+ {
+ if (first)
+ first = false;
+ else
+ stream << ", ";
+
+ stream << (*this)[i].toString (numericColumns);
+ }
+
+ stream << ")";
+
+ return stream.str();
+}
+
+
diff --git a/apps/opencs/model/filter/narynode.hpp b/apps/opencs/model/filter/narynode.hpp
new file mode 100644
index 0000000000..aa501d0095
--- /dev/null
+++ b/apps/opencs/model/filter/narynode.hpp
@@ -0,0 +1,37 @@
+#ifndef CSM_FILTER_NARYNODE_H
+#define CSM_FILTER_NARYNODE_H
+
+#include <vector>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include "node.hpp"
+
+namespace CSMFilter
+{
+ class NAryNode : public Node
+ {
+ std::vector<boost::shared_ptr<Node> > mNodes;
+ std::string mName;
+
+ public:
+
+ NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes, const std::string& name);
+
+ int getSize() const;
+
+ const Node& operator[] (int index) const;
+
+ virtual std::vector<int> getReferencedColumns() const;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+
+ virtual std::string toString (bool numericColumns) const;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp
new file mode 100644
index 0000000000..276861cdc4
--- /dev/null
+++ b/apps/opencs/model/filter/node.cpp
@@ -0,0 +1,6 @@
+
+#include "node.hpp"
+
+CSMFilter::Node::Node() {}
+
+CSMFilter::Node::~Node() {} \ No newline at end of file
diff --git a/apps/opencs/model/filter/node.hpp b/apps/opencs/model/filter/node.hpp
new file mode 100644
index 0000000000..ef18353a48
--- /dev/null
+++ b/apps/opencs/model/filter/node.hpp
@@ -0,0 +1,53 @@
+#ifndef CSM_FILTER_NODE_H
+#define CSM_FILTER_NODE_H
+
+#include <string>
+#include <map>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QMetaType>
+
+namespace CSMWorld
+{
+ class IdTable;
+}
+
+namespace CSMFilter
+{
+ /// \brief Root class for the filter node hierarchy
+ ///
+ /// \note When the function documentation for this class mentions "this node", this should be
+ /// interpreted as "the node and all its children".
+ class Node
+ {
+ // not implemented
+ Node (const Node&);
+ Node& operator= (const Node&);
+
+ public:
+
+ Node();
+
+ virtual ~Node();
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const = 0;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+
+ virtual std::vector<int> getReferencedColumns() const = 0;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+
+ virtual std::string toString (bool numericColumns) const = 0;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+ };
+}
+
+Q_DECLARE_METATYPE (boost::shared_ptr<CSMFilter::Node>)
+
+#endif
diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp
new file mode 100644
index 0000000000..1b22ea7a6a
--- /dev/null
+++ b/apps/opencs/model/filter/notnode.cpp
@@ -0,0 +1,10 @@
+
+#include "notnode.hpp"
+
+CSMFilter::NotNode::NotNode (boost::shared_ptr<Node> child) : UnaryNode (child, "not") {}
+
+bool CSMFilter::NotNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ return !getChild().test (table, row, columns);
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/notnode.hpp b/apps/opencs/model/filter/notnode.hpp
new file mode 100644
index 0000000000..b9e80b8c6d
--- /dev/null
+++ b/apps/opencs/model/filter/notnode.hpp
@@ -0,0 +1,21 @@
+#ifndef CSM_FILTER_NOTNODE_H
+#define CSM_FILTER_NOTNODE_H
+
+#include "unarynode.hpp"
+
+namespace CSMFilter
+{
+ class NotNode : public UnaryNode
+ {
+ public:
+
+ NotNode (boost::shared_ptr<Node> child);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp
new file mode 100644
index 0000000000..4fc34e1d5a
--- /dev/null
+++ b/apps/opencs/model/filter/ornode.cpp
@@ -0,0 +1,20 @@
+
+#include "ornode.hpp"
+
+#include <sstream>
+
+CSMFilter::OrNode::OrNode (const std::vector<boost::shared_ptr<Node> >& nodes)
+: NAryNode (nodes, "or")
+{}
+
+bool CSMFilter::OrNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ int size = getSize();
+
+ for (int i=0; i<size; ++i)
+ if ((*this)[i].test (table, row, columns))
+ return true;
+
+ return false;
+}
diff --git a/apps/opencs/model/filter/ornode.hpp b/apps/opencs/model/filter/ornode.hpp
new file mode 100644
index 0000000000..63ed2f10ce
--- /dev/null
+++ b/apps/opencs/model/filter/ornode.hpp
@@ -0,0 +1,23 @@
+#ifndef CSM_FILTER_ORNODE_H
+#define CSM_FILTER_ORNODE_H
+
+#include "narynode.hpp"
+
+namespace CSMFilter
+{
+ class OrNode : public NAryNode
+ {
+ bool mAnd;
+
+ public:
+
+ OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp
new file mode 100644
index 0000000000..d334a7f634
--- /dev/null
+++ b/apps/opencs/model/filter/parser.cpp
@@ -0,0 +1,595 @@
+
+#include "parser.hpp"
+
+#include <cctype>
+#include <stdexcept>
+#include <sstream>
+
+#include <components/misc/stringops.hpp>
+
+#include "../world/columns.hpp"
+#include "../world/data.hpp"
+#include "../world/idcollection.hpp"
+
+#include "booleannode.hpp"
+#include "ornode.hpp"
+#include "andnode.hpp"
+#include "notnode.hpp"
+#include "textnode.hpp"
+#include "valuenode.hpp"
+
+namespace CSMFilter
+{
+ struct Token
+ {
+ enum Type
+ {
+ Type_EOS,
+ Type_None,
+ Type_String,
+ Type_Number,
+ Type_Open,
+ Type_Close,
+ Type_OpenSquare,
+ Type_CloseSquare,
+ Type_Comma,
+ Type_OneShot,
+ Type_Keyword_True, ///< \attention Keyword enums must be arranged continuously.
+ Type_Keyword_False,
+ Type_Keyword_And,
+ Type_Keyword_Or,
+ Type_Keyword_Not,
+ Type_Keyword_Text,
+ Type_Keyword_Value
+ };
+
+ Type mType;
+ std::string mString;
+ double mNumber;
+
+ Token (Type type = Type_None);
+
+ Token (const std::string& string);
+
+ Token (double number);
+
+ operator bool() const;
+ };
+
+ Token::Token (Type type) : mType (type) {}
+
+ Token::Token (const std::string& string) : mType (Type_String), mString (string) {}
+
+ Token::Token (double number) : mType (Type_Number), mNumber (number) {}
+
+ Token::operator bool() const
+ {
+ return mType!=Type_None;
+ }
+
+ bool operator== (const Token& left, const Token& right)
+ {
+ if (left.mType!=right.mType)
+ return false;
+
+ switch (left.mType)
+ {
+ case Token::Type_String: return left.mString==right.mString;
+ case Token::Type_Number: return left.mNumber==right.mNumber;
+
+ default: return true;
+ }
+ }
+}
+
+CSMFilter::Token CSMFilter::Parser::getStringToken()
+{
+ std::string string;
+
+ int size = static_cast<int> (mInput.size());
+
+ for (; mIndex<size; ++mIndex)
+ {
+ char c = mInput[mIndex];
+
+ if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' ||
+ (!string.empty() && string[0]=='"'))
+ string += c;
+ else
+ break;
+
+ if (c=='"' && string.size()>1)
+ {
+ ++mIndex;
+ break;
+ }
+ };
+
+ if (!string.empty())
+ {
+ if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) )
+ {
+ error();
+ return Token (Token::Type_None);
+ }
+
+ if (string[0]!='"' && string[string.size()-1]=='"')
+ {
+ error();
+ return Token (Token::Type_None);
+ }
+
+ if (string[0]=='"')
+ string = string.substr (1, string.size()-2);
+ }
+
+ return checkKeywords (string);
+}
+
+CSMFilter::Token CSMFilter::Parser::getNumberToken()
+{
+ std::string string;
+
+ int size = static_cast<int> (mInput.size());
+
+ bool hasDecimalPoint = false;
+ bool hasDigit = false;
+
+ for (; mIndex<size; ++mIndex)
+ {
+ char c = mInput[mIndex];
+
+ if (std::isdigit (c))
+ {
+ string += c;
+ hasDigit = true;
+ }
+ else if (c=='.' && !hasDecimalPoint)
+ {
+ string += c;
+ hasDecimalPoint = true;
+ }
+ else if (string.empty() && c=='-')
+ string += c;
+ else
+ break;
+ }
+
+ if (!hasDigit)
+ {
+ error();
+ return Token (Token::Type_None);
+ }
+
+ float value;
+ std::istringstream stream (string.c_str());
+ stream >> value;
+
+ return value;
+}
+
+CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token)
+{
+ static const char *sKeywords[] =
+ {
+ "true", "false",
+ "and", "or", "not",
+ "string", "value",
+ 0
+ };
+
+ std::string string = Misc::StringUtils::lowerCase (token.mString);
+
+ for (int i=0; sKeywords[i]; ++i)
+ if (sKeywords[i]==string || (string.size()==1 && sKeywords[i][0]==string[0]))
+ return Token (static_cast<Token::Type> (i+Token::Type_Keyword_True));
+
+ return token;
+}
+
+CSMFilter::Token CSMFilter::Parser::getNextToken()
+{
+ int size = static_cast<int> (mInput.size());
+
+ char c = 0;
+
+ for (; mIndex<size; ++mIndex)
+ {
+ c = mInput[mIndex];
+
+ if (c!=' ')
+ break;
+ }
+
+ if (mIndex>=size)
+ return Token (Token::Type_EOS);
+
+ switch (c)
+ {
+ case '(': ++mIndex; return Token (Token::Type_Open);
+ case ')': ++mIndex; return Token (Token::Type_Close);
+ case '[': ++mIndex; return Token (Token::Type_OpenSquare);
+ case ']': ++mIndex; return Token (Token::Type_CloseSquare);
+ case ',': ++mIndex; return Token (Token::Type_Comma);
+ case '!': ++mIndex; return Token (Token::Type_OneShot);
+ }
+
+ if (c=='"' || c=='_' || std::isalpha (c) || c==':')
+ return getStringToken();
+
+ if (c=='-' || c=='.' || std::isdigit (c))
+ return getNumberToken();
+
+ error();
+ return Token (Token::Type_None);
+}
+
+boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseImp (bool allowEmpty, bool ignoreOneShot)
+{
+ if (Token token = getNextToken())
+ {
+ if (token==Token (Token::Type_OneShot))
+ token = getNextToken();
+
+ if (token)
+ switch (token.mType)
+ {
+ case Token::Type_Keyword_True:
+
+ return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (true));
+
+ case Token::Type_Keyword_False:
+
+ return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (false));
+
+ case Token::Type_Keyword_And:
+ case Token::Type_Keyword_Or:
+
+ return parseNAry (token);
+
+ case Token::Type_Keyword_Not:
+ {
+ boost::shared_ptr<CSMFilter::Node> node = parseImp();
+
+ if (mError)
+ return boost::shared_ptr<Node>();
+
+ return boost::shared_ptr<CSMFilter::Node> (new NotNode (node));
+ }
+
+ case Token::Type_Keyword_Text:
+
+ return parseText();
+
+ case Token::Type_Keyword_Value:
+
+ return parseValue();
+
+ case Token::Type_EOS:
+
+ if (!allowEmpty)
+ error();
+
+ return boost::shared_ptr<Node>();
+
+ default:
+
+ error();
+ }
+ }
+
+ return boost::shared_ptr<Node>();
+}
+
+boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseNAry (const Token& keyword)
+{
+ std::vector<boost::shared_ptr<Node> > nodes;
+
+ Token token = getNextToken();
+
+ if (token.mType!=Token::Type_Open)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ for (;;)
+ {
+ boost::shared_ptr<Node> node = parseImp();
+
+ if (mError)
+ return boost::shared_ptr<Node>();
+
+ nodes.push_back (node);
+
+ Token token = getNextToken();
+
+ if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma))
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ if (token.mType==Token::Type_Close)
+ break;
+ }
+
+ if (nodes.empty())
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ switch (keyword.mType)
+ {
+ case Token::Type_Keyword_And: return boost::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
+ case Token::Type_Keyword_Or: return boost::shared_ptr<CSMFilter::Node> (new OrNode (nodes));
+ default: error(); return boost::shared_ptr<Node>();
+ }
+}
+
+boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseText()
+{
+ Token token = getNextToken();
+
+ if (token.mType!=Token::Type_Open)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (!token)
+ return boost::shared_ptr<Node>();
+
+ // parse column ID
+ int columnId = -1;
+
+ if (token.mType==Token::Type_Number)
+ {
+ if (static_cast<int> (token.mNumber)==token.mNumber)
+ columnId = static_cast<int> (token.mNumber);
+ }
+ else if (token.mType==Token::Type_String)
+ {
+ columnId = CSMWorld::Columns::getId (token.mString);
+ }
+
+ if (columnId<0)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Comma)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ // parse text pattern
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_String)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ std::string text = token.mString;
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Close)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ return boost::shared_ptr<Node> (new TextNode (columnId, text));
+}
+
+boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseValue()
+{
+ Token token = getNextToken();
+
+ if (token.mType!=Token::Type_Open)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (!token)
+ return boost::shared_ptr<Node>();
+
+ // parse column ID
+ int columnId = -1;
+
+ if (token.mType==Token::Type_Number)
+ {
+ if (static_cast<int> (token.mNumber)==token.mNumber)
+ columnId = static_cast<int> (token.mNumber);
+ }
+ else if (token.mType==Token::Type_String)
+ {
+ columnId = CSMWorld::Columns::getId (token.mString);
+ }
+
+ if (columnId<0)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Comma)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ // parse value
+ double lower = 0;
+ double upper = 0;
+ bool min = false;
+ bool max = false;
+
+ token = getNextToken();
+
+ if (token.mType==Token::Type_Number)
+ {
+ // single value
+ min = max = true;
+ lower = upper = token.mNumber;
+ }
+ else
+ {
+ // interval
+ if (token.mType==Token::Type_OpenSquare)
+ min = true;
+ else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Number)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ lower = token.mNumber;
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Comma)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Number)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ upper = token.mNumber;
+
+ token = getNextToken();
+
+ if (token.mType==Token::Type_CloseSquare)
+ max = true;
+ else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+ }
+
+ token = getNextToken();
+
+ if (token.mType!=Token::Type_Close)
+ {
+ error();
+ return boost::shared_ptr<Node>();
+ }
+
+ return boost::shared_ptr<Node> (new ValueNode (columnId, lower, upper, min, max));
+}
+
+void CSMFilter::Parser::error()
+{
+ mError = true;
+}
+
+CSMFilter::Parser::Parser (const CSMWorld::Data& data)
+: mIndex (0), mError (false), mData (data) {}
+
+bool CSMFilter::Parser::parse (const std::string& filter, bool allowPredefined)
+{
+ // reset
+ mFilter.reset();
+ mError = false;
+ mInput = filter;
+ mIndex = 0;
+
+ Token token;
+
+ if (allowPredefined)
+ token = getNextToken();
+
+ if (!allowPredefined || token==Token (Token::Type_OneShot))
+ {
+ boost::shared_ptr<Node> node = parseImp (true, token!=Token (Token::Type_OneShot));
+
+ if (mError)
+ return false;
+
+ if (getNextToken()!=Token (Token::Type_EOS))
+ {
+ error();
+ return false;
+ }
+
+ if (node)
+ mFilter = node;
+ else
+ {
+ // Empty filter string equals to filter "true".
+ mFilter.reset (new BooleanNode (true));
+ }
+
+ return true;
+ }
+ else if (token.mType==Token::Type_String && allowPredefined)
+ {
+ if (getNextToken()!=Token (Token::Type_EOS))
+ {
+ error();
+ return false;
+ }
+
+ int index = mData.getFilters().searchId (token.mString);
+
+ if (index==-1)
+ {
+ error();
+ return false;
+ }
+
+ const CSMWorld::Record<CSMFilter::Filter>& record = mData.getFilters().getRecord (index);
+
+ if (record.isDeleted())
+ {
+ error();
+ return false;
+ }
+
+ return parse (record.get().mFilter, false);
+ }
+ else
+ {
+ error();
+ return false;
+ }
+}
+
+boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
+{
+ if (mError)
+ throw std::logic_error ("No filter available");
+
+ return mFilter;
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/parser.hpp b/apps/opencs/model/filter/parser.hpp
new file mode 100644
index 0000000000..5700102cf1
--- /dev/null
+++ b/apps/opencs/model/filter/parser.hpp
@@ -0,0 +1,59 @@
+#ifndef CSM_FILTER_PARSER_H
+#define CSM_FILTER_PARSER_H
+
+#include <boost/shared_ptr.hpp>
+
+#include "node.hpp"
+
+namespace CSMWorld
+{
+ class Data;
+}
+
+namespace CSMFilter
+{
+ struct Token;
+
+ class Parser
+ {
+ boost::shared_ptr<Node> mFilter;
+ std::string mInput;
+ int mIndex;
+ bool mError;
+ const CSMWorld::Data& mData;
+
+ Token getStringToken();
+
+ Token getNumberToken();
+
+ Token getNextToken();
+
+ Token checkKeywords (const Token& token);
+ ///< Turn string token into keyword token, if possible.
+
+ boost::shared_ptr<Node> parseImp (bool allowEmpty = false, bool ignoreOneShot = false);
+ ///< Will return a null-pointer, if there is nothing more to parse.
+
+ boost::shared_ptr<Node> parseNAry (const Token& keyword);
+
+ boost::shared_ptr<Node> parseText();
+
+ boost::shared_ptr<Node> parseValue();
+
+ void error();
+
+ public:
+
+ Parser (const CSMWorld::Data& data);
+
+ bool parse (const std::string& filter, bool allowPredefined = true);
+ ///< Discards any previous calls to parse
+ ///
+ /// \return Success?
+
+ boost::shared_ptr<Node> getFilter() const;
+ ///< Throws an exception if the last call to parse did not return true.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp
new file mode 100644
index 0000000000..9987c66d22
--- /dev/null
+++ b/apps/opencs/model/filter/textnode.cpp
@@ -0,0 +1,62 @@
+
+#include "textnode.hpp"
+
+#include <sstream>
+#include <stdexcept>
+
+#include <QRegExp>
+
+#include "../world/columns.hpp"
+#include "../world/idtable.hpp"
+
+CSMFilter::TextNode::TextNode (int columnId, const std::string& text)
+: mColumnId (columnId), mText (text)
+{}
+
+bool CSMFilter::TextNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
+
+ if (iter==columns.end())
+ throw std::logic_error ("invalid column in text node test");
+
+ if (iter->second==-1)
+ return true;
+
+ QModelIndex index = table.index (row, iter->second);
+
+ QVariant data = table.data (index);
+
+ if (data.type()!=QVariant::String)
+ return false;
+
+ /// \todo make pattern syntax configurable
+ QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive);
+
+ return regExp.exactMatch (data.toString());
+}
+
+std::vector<int> CSMFilter::TextNode::getReferencedColumns() const
+{
+ return std::vector<int> (1, mColumnId);
+}
+
+std::string CSMFilter::TextNode::toString (bool numericColumns) const
+{
+ std::ostringstream stream;
+
+ stream << "text (";
+
+ if (numericColumns)
+ stream << mColumnId;
+ else
+ stream
+ << "\""
+ << CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
+ << "\"";
+
+ stream << ", \"" << mText << "\")";
+
+ return stream.str();
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp
new file mode 100644
index 0000000000..663fa7382a
--- /dev/null
+++ b/apps/opencs/model/filter/textnode.hpp
@@ -0,0 +1,33 @@
+#ifndef CSM_FILTER_TEXTNODE_H
+#define CSM_FILTER_TEXTNODE_H
+
+#include "leafnode.hpp"
+
+namespace CSMFilter
+{
+ class TextNode : public LeafNode
+ {
+ int mColumnId;
+ std::string mText;
+
+ public:
+
+ TextNode (int columnId, const std::string& text);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+
+ virtual std::vector<int> getReferencedColumns() const;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+
+ virtual std::string toString (bool numericColumns) const;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp
new file mode 100644
index 0000000000..43a24b76a1
--- /dev/null
+++ b/apps/opencs/model/filter/unarynode.cpp
@@ -0,0 +1,26 @@
+
+#include "unarynode.hpp"
+
+CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr<Node> child, const std::string& name)
+: mChild (child), mName (name)
+{}
+
+const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const
+{
+ return *mChild;
+}
+
+CSMFilter::Node& CSMFilter::UnaryNode::getChild()
+{
+ return *mChild;
+}
+
+std::vector<int> CSMFilter::UnaryNode::getReferencedColumns() const
+{
+ return mChild->getReferencedColumns();
+}
+
+std::string CSMFilter::UnaryNode::toString (bool numericColumns) const
+{
+ return mName + " " + mChild->toString (numericColumns);
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/unarynode.hpp b/apps/opencs/model/filter/unarynode.hpp
new file mode 100644
index 0000000000..6bbc960920
--- /dev/null
+++ b/apps/opencs/model/filter/unarynode.hpp
@@ -0,0 +1,34 @@
+#ifndef CSM_FILTER_UNARYNODE_H
+#define CSM_FILTER_UNARYNODE_H
+
+#include <boost/shared_ptr.hpp>
+
+#include "node.hpp"
+
+namespace CSMFilter
+{
+ class UnaryNode : public Node
+ {
+ boost::shared_ptr<Node> mChild;
+ std::string mName;
+
+ public:
+
+ UnaryNode (boost::shared_ptr<Node> child, const std::string& name);
+
+ const Node& getChild() const;
+
+ Node& getChild();
+
+ virtual std::vector<int> getReferencedColumns() const;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+
+ virtual std::string toString (bool numericColumns) const;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp
new file mode 100644
index 0000000000..f6cb20e4cb
--- /dev/null
+++ b/apps/opencs/model/filter/valuenode.cpp
@@ -0,0 +1,71 @@
+
+#include "valuenode.hpp"
+
+#include <sstream>
+#include <stdexcept>
+
+#include "../world/columns.hpp"
+#include "../world/idtable.hpp"
+
+CSMFilter::ValueNode::ValueNode (int columnId,
+ double lower, double upper, bool min, bool max)
+: mColumnId (columnId), mLower (lower), mUpper (upper), mMin (min), mMax (max)
+{}
+
+bool CSMFilter::ValueNode::test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const
+{
+ const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
+
+ if (iter==columns.end())
+ throw std::logic_error ("invalid column in test value test");
+
+ if (iter->second==-1)
+ return true;
+
+ QModelIndex index = table.index (row, iter->second);
+
+ QVariant data = table.data (index);
+
+ if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int &&
+ data.type()!=QVariant::UInt)
+ return false;
+
+ double value = data.toDouble();
+
+ if (mLower==mUpper && mMin && mMax)
+ return value==mLower;
+
+ return (mMin ? value>=mLower : value>mLower) && (mMax ? value<=mUpper : value<mUpper);
+}
+
+std::vector<int> CSMFilter::ValueNode::getReferencedColumns() const
+{
+ return std::vector<int> (1, mColumnId);
+}
+
+std::string CSMFilter::ValueNode::toString (bool numericColumns) const
+{
+ std::ostringstream stream;
+
+ stream << "value (";
+
+ if (numericColumns)
+ stream << mColumnId;
+ else
+ stream
+ << "\""
+ << CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
+ << "\"";
+
+ stream << ", \"";
+
+ if (mLower==mUpper && mMin && mMax)
+ stream << mLower;
+ else
+ stream << (mMin ? "[" : "(") << mLower << ", " << mUpper << (mMax ? "]" : ")");
+
+ stream << ")";
+
+ return stream.str();
+} \ No newline at end of file
diff --git a/apps/opencs/model/filter/valuenode.hpp b/apps/opencs/model/filter/valuenode.hpp
new file mode 100644
index 0000000000..faaa1e2ff6
--- /dev/null
+++ b/apps/opencs/model/filter/valuenode.hpp
@@ -0,0 +1,37 @@
+#ifndef CSM_FILTER_VALUENODE_H
+#define CSM_FILTER_VALUENODE_H
+
+#include "leafnode.hpp"
+
+namespace CSMFilter
+{
+ class ValueNode : public LeafNode
+ {
+ int mColumnId;
+ std::string mText;
+ double mLower;
+ double mUpper;
+ bool mMin;
+ bool mMax;
+
+ public:
+
+ ValueNode (int columnId, double lower, double upper, bool min, bool max);
+
+ virtual bool test (const CSMWorld::IdTable& table, int row,
+ const std::map<int, int>& columns) const;
+ ///< \return Can the specified table row pass through to filter?
+ /// \param columns column ID to column index mapping
+
+ virtual std::vector<int> getReferencedColumns() const;
+ ///< Return a list of the IDs of the columns referenced by this node. The column mapping
+ /// passed into test as columns must contain all columns listed here.
+
+ virtual std::string toString (bool numericColumns) const;
+ ///< Return a string that represents this node.
+ ///
+ /// \param numericColumns Use numeric IDs instead of string to represent columns.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/settings/settingcontainer.cpp b/apps/opencs/model/settings/settingcontainer.cpp
new file mode 100644
index 0000000000..a75a84ec55
--- /dev/null
+++ b/apps/opencs/model/settings/settingcontainer.cpp
@@ -0,0 +1,82 @@
+#include "settingcontainer.hpp"
+
+#include <QStringList>
+
+CSMSettings::SettingContainer::SettingContainer(QObject *parent) :
+ QObject(parent), mValue (0), mValues (0)
+{
+}
+
+CSMSettings::SettingContainer::SettingContainer(const QString &value, QObject *parent) :
+ QObject(parent), mValue (new QString (value)), mValues (0)
+{
+}
+
+void CSMSettings::SettingContainer::insert (const QString &value)
+{
+ if (mValue)
+ {
+ mValues = new QStringList;
+ mValues->push_back (*mValue);
+ mValues->push_back (value);
+
+ delete mValue;
+ mValue = 0;
+ }
+ else
+ {
+ delete mValue;
+ mValue = new QString (value);
+ }
+
+}
+
+void CSMSettings::SettingContainer::update (const QString &value, int index)
+{
+ if (isEmpty())
+ mValue = new QString(value);
+
+ else if (mValue)
+ *mValue = value;
+
+ else if (mValues)
+ mValues->replace(index, value);
+}
+
+QString CSMSettings::SettingContainer::getValue (int index) const
+{
+ QString retVal("");
+
+ //if mValue is valid, it's a single-value property.
+ //ignore the index and return the value
+ if (mValue)
+ retVal = *mValue;
+
+ //otherwise, if it's a multivalued property
+ //return the appropriate value at the index
+ else if (mValues)
+ {
+ if (index == -1)
+ retVal = mValues->at(0);
+
+ else if (index < mValues->size())
+ retVal = mValues->at(index);
+ }
+
+ return retVal;
+}
+
+int CSMSettings::SettingContainer::count () const
+{
+ int retVal = 0;
+
+ if (!isEmpty())
+ {
+ if (mValues)
+ retVal = mValues->size();
+ else
+ retVal = 1;
+ }
+
+ return retVal;
+}
diff --git a/apps/opencs/model/settings/settingcontainer.hpp b/apps/opencs/model/settings/settingcontainer.hpp
new file mode 100644
index 0000000000..5af298a57a
--- /dev/null
+++ b/apps/opencs/model/settings/settingcontainer.hpp
@@ -0,0 +1,47 @@
+#ifndef SETTINGCONTAINER_HPP
+#define SETTINGCONTAINER_HPP
+
+#include <QObject>
+
+class QStringList;
+
+namespace CSMSettings
+{
+ class SettingContainer : public QObject
+ {
+ Q_OBJECT
+
+ QString *mValue;
+ QStringList *mValues;
+
+ public:
+
+ explicit SettingContainer (QObject *parent = 0);
+ explicit SettingContainer (const QString &value, QObject *parent = 0);
+
+ /// add a value to the container
+ /// multiple values supported
+ void insert (const QString &value);
+
+ /// update an existing value
+ /// index specifies multiple values
+ void update (const QString &value, int index = 0);
+
+ /// return value at specified index
+ QString getValue (int index = -1) const;
+
+ /// retrieve list of all values
+ inline QStringList *getValues() const { return mValues; }
+
+ /// return size of list
+ int count() const;
+
+ /// test for empty container
+ /// useful for default-constructed containers returned by QMap when invalid key is passed
+ inline bool isEmpty() const { return (!mValue && !mValues); }
+
+ inline bool isMultiValue() const { return (mValues); }
+ };
+}
+
+#endif // SETTINGCONTAINER_HPP
diff --git a/apps/opencs/model/settings/settingsitem.cpp b/apps/opencs/model/settings/settingsitem.cpp
new file mode 100644
index 0000000000..5d897448ac
--- /dev/null
+++ b/apps/opencs/model/settings/settingsitem.cpp
@@ -0,0 +1,104 @@
+#include "settingsitem.hpp"
+
+#include <QStringList>
+
+bool CSMSettings::SettingsItem::updateItem (const QStringList *values)
+{
+ QStringList::ConstIterator it = values->begin();
+
+ //if the item is not multivalued,
+ //save the last value passed in the container
+ if (!mIsMultiValue)
+ {
+ it = values->end();
+ it--;
+ }
+
+ bool isValid = true;
+ QString value ("");
+
+ for (; it != values->end(); ++it)
+ {
+ value = *it;
+ isValid = validate(value);
+
+ //skip only the invalid values
+ if (!isValid)
+ continue;
+
+ insert(value);
+ }
+
+ return isValid;
+}
+
+bool CSMSettings::SettingsItem::updateItem (const QString &value)
+{
+ //takes a value or a SettingsContainer and updates itself accordingly
+ //after validating the data against it's own definition
+
+ QString newValue = value;
+
+ if (!validate (newValue))
+ newValue = mDefaultValue;
+
+ bool success = (getValue() != newValue);
+
+ if (success)
+ {
+ if (mIsMultiValue)
+ insert (newValue);
+ else
+ update (newValue);
+ }
+ return success;
+}
+
+bool CSMSettings::SettingsItem::updateItem(int valueListIndex)
+{
+ bool success = false;
+
+ if (mValueList)
+ {
+ if (mValueList->size() > valueListIndex)
+ success = updateItem (mValueList->at(valueListIndex));
+ }
+ return success;
+}
+
+bool CSMSettings::SettingsItem::validate (const QString &value)
+{
+ //if there is no value list or value pair, there is no validation to do
+ bool isValid = !(!mValueList->isEmpty() || mValuePair);
+
+ if (!isValid && !mValueList->isEmpty())
+ {
+ for (QStringList::Iterator it = mValueList->begin(); it != mValueList->end(); ++it)
+ // foreach (QString listItem, *mValueList)
+ {
+ isValid = (value == *it);
+
+ if (isValid)
+ break;
+ }
+ }
+ else if (!isValid && mValuePair)
+ {
+ int numVal = value.toInt();
+
+ isValid = (numVal > mValuePair->left.toInt() && numVal < mValuePair->right.toInt());
+ }
+
+ return isValid;
+}
+
+void CSMSettings::SettingsItem::setDefaultValue (const QString &value)
+{
+ mDefaultValue = value;
+ update (value);
+}
+
+QString CSMSettings::SettingsItem::getDefaultValue() const
+{
+ return mDefaultValue;
+}
diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp
new file mode 100644
index 0000000000..a1daee4ac9
--- /dev/null
+++ b/apps/opencs/model/settings/settingsitem.hpp
@@ -0,0 +1,63 @@
+#ifndef SETTINGSITEM_HPP
+#define SETTINGSITEM_HPP
+
+#include <QObject>
+#include "support.hpp"
+#include "settingcontainer.hpp"
+
+namespace CSMSettings
+{
+ /// Represents a setting including metadata
+ /// (valid values, ranges, defaults, and multivalue status
+ class SettingsItem : public SettingContainer
+ {
+ QStringPair *mValuePair;
+ QStringList *mValueList;
+ bool mIsMultiValue;
+ QString mDefaultValue;
+
+ public:
+ explicit SettingsItem(QString name, bool isMultiValue,
+ const QString& defaultValue, QObject *parent = 0)
+ : SettingContainer(defaultValue, parent),
+ mIsMultiValue (isMultiValue), mValueList (0),
+ mValuePair (0), mDefaultValue (defaultValue)
+ {
+ QObject::setObjectName(name);
+ }
+
+ /// updateItem overloads for updating setting value
+ /// provided a list of values (multi-valued),
+ /// a specific value
+ /// or an index value corresponding to the mValueList
+ bool updateItem (const QStringList *values);
+ bool updateItem (const QString &value);
+ bool updateItem (int valueListIndex);
+
+ /// retrieve list of valid values for setting
+ inline QStringList *getValueList() { return mValueList; }
+
+ /// write list of valid values for setting
+ inline void setValueList (QStringList *valueList) { mValueList = valueList; }
+
+ /// valuePair used for spin boxes (max / min)
+ inline QStringPair *getValuePair() { return mValuePair; }
+
+ /// set value range (spinbox / integer use)
+ inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); }
+
+ inline bool isMultivalue () { return mIsMultiValue; }
+
+ void setDefaultValue (const QString &value);
+ QString getDefaultValue () const;
+
+ private:
+
+ /// Verifies that the supplied value is one of the following:
+ /// 1. Within the limits of the value pair (min / max)
+ /// 2. One of the values indicated in the value list
+ bool validate (const QString &value);
+ };
+}
+#endif // SETTINGSITEM_HPP
+
diff --git a/apps/opencs/model/settings/support.cpp b/apps/opencs/model/settings/support.cpp
new file mode 100644
index 0000000000..d79edfdb33
--- /dev/null
+++ b/apps/opencs/model/settings/support.cpp
@@ -0,0 +1 @@
+#include "support.hpp"
diff --git a/apps/opencs/model/settings/support.hpp b/apps/opencs/model/settings/support.hpp
new file mode 100644
index 0000000000..4ffd01b73c
--- /dev/null
+++ b/apps/opencs/model/settings/support.hpp
@@ -0,0 +1,39 @@
+#ifndef MODEL_SUPPORT_HPP
+#define MODEL_SUPPORT_HPP
+
+#include <QObject>
+#include <QStringList>
+
+class QLayout;
+class QWidget;
+class QListWidgetItem;
+
+namespace CSMSettings
+{
+ class SettingContainer;
+
+ typedef QList<SettingContainer *> SettingList;
+ typedef QMap<QString, SettingContainer *> SettingMap;
+ typedef QMap<QString, SettingMap *> SectionMap;
+
+ struct QStringPair
+ {
+ QStringPair(): left (""), right ("")
+ {}
+
+ QStringPair (const QString &leftValue, const QString &rightValue)
+ : left (leftValue), right(rightValue)
+ {}
+
+ QStringPair (const QStringPair &pair)
+ : left (pair.left), right (pair.right)
+ {}
+
+ QString left;
+ QString right;
+
+ bool isEmpty() const
+ { return (left.isEmpty() && right.isEmpty()); }
+ };
+}
+#endif // MODEL_SUPPORT_HPP
diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp
new file mode 100644
index 0000000000..1ce28ed75c
--- /dev/null
+++ b/apps/opencs/model/settings/usersettings.cpp
@@ -0,0 +1,355 @@
+#include "usersettings.hpp"
+
+#include <QTextStream>
+#include <QDir>
+#include <QString>
+#include <QRegExp>
+#include <QMap>
+#include <QMessageBox>
+#include <QTextCodec>
+
+#include <QFile>
+
+#include <components/files/configurationmanager.hpp>
+#include "settingcontainer.hpp"
+#include <boost/version.hpp>
+
+/**
+ * Workaround for problems with whitespaces in paths in older versions of Boost library
+ */
+#if (BOOST_VERSION <= 104600)
+namespace boost
+{
+
+ template<>
+ inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
+ {
+ return boost::filesystem::path(arg);
+ }
+
+} /* namespace boost */
+#endif /* (BOOST_VERSION <= 104600) */
+
+CSMSettings::UserSettings *CSMSettings::UserSettings::mUserSettingsInstance = 0;
+
+CSMSettings::UserSettings::UserSettings()
+{
+ assert(!mUserSettingsInstance);
+ mUserSettingsInstance = this;
+
+ mReadWriteMessage = QObject::tr("<br><b>Could not open or create file for writing</b><br><br> \
+ Please make sure you have the right permissions and try again.<br>");
+
+ mReadOnlyMessage = QObject::tr("<br><b>Could not open file for reading</b><br><br> \
+ Please make sure you have the right permissions and try again.<br>");
+
+ buildEditorSettingDefaults();
+}
+
+void CSMSettings::UserSettings::buildEditorSettingDefaults()
+{
+ SettingContainer *windowHeight = new SettingContainer("768", this);
+ SettingContainer *windowWidth = new SettingContainer("1024", this);
+ SettingContainer *rsDelegate = new SettingContainer("Icon and Text", this);
+ SettingContainer *refIdTypeDelegate = new SettingContainer("Icon and Text", this);
+
+ windowHeight->setObjectName ("Height");
+ windowWidth->setObjectName ("Width");
+ rsDelegate->setObjectName ("Record Status Display");
+ refIdTypeDelegate->setObjectName ("Referenceable ID Type Display");
+
+ SettingMap *displayFormatMap = new SettingMap;
+ SettingMap *windowSizeMap = new SettingMap;
+
+ displayFormatMap->insert (rsDelegate->objectName(), rsDelegate );
+ displayFormatMap->insert (refIdTypeDelegate->objectName(), refIdTypeDelegate);
+
+ windowSizeMap->insert (windowWidth->objectName(), windowWidth );
+ windowSizeMap->insert (windowHeight->objectName(), windowHeight );
+
+ mEditorSettingDefaults.insert ("Display Format", displayFormatMap);
+ mEditorSettingDefaults.insert ("Window Size", windowSizeMap);
+}
+
+CSMSettings::UserSettings::~UserSettings()
+{
+ mUserSettingsInstance = 0;
+}
+
+QTextStream *CSMSettings::UserSettings::openFileStream (const QString &filePath, bool isReadOnly) const
+{
+ QIODevice::OpenMode openFlags = QIODevice::Text;
+
+ if (isReadOnly)
+ openFlags = QIODevice::ReadOnly | openFlags;
+ else
+ openFlags = QIODevice::ReadWrite | QIODevice::Truncate | openFlags;
+
+ QFile *file = new QFile(filePath);
+ QTextStream *stream = 0;
+
+ if (file->open(openFlags))
+ {
+ stream = new QTextStream(file);
+ stream->setCodec(QTextCodec::codecForName("UTF-8"));
+ }
+
+ return stream;
+
+}
+
+bool CSMSettings::UserSettings::writeSettings(QMap<QString, CSMSettings::SettingList *> &settings)
+{
+ QTextStream *stream = openFileStream(mUserFilePath);
+
+ bool success = (stream);
+
+ if (success)
+ {
+ QList<QString> keyList = settings.keys();
+
+ foreach (QString key, keyList)
+ {
+ SettingList *sectionSettings = settings[key];
+
+ *stream << "[" << key << "]" << '\n';
+
+ foreach (SettingContainer *item, *sectionSettings)
+ *stream << item->objectName() << " = " << item->getValue() << '\n';
+ }
+
+ stream->device()->close();
+ delete stream;
+ stream = 0;
+ }
+ else
+ {
+ displayFileErrorMessage(mReadWriteMessage, false);
+ }
+
+ return (success);
+}
+
+
+const CSMSettings::SectionMap &CSMSettings::UserSettings::getSectionMap() const
+{
+ return mSectionSettings;
+}
+
+const CSMSettings::SettingMap *CSMSettings::UserSettings::getSettings(const QString &sectionName) const
+{
+ return getValidSettings(sectionName);
+}
+
+bool CSMSettings::UserSettings::loadFromFile(const QString &filePath)
+{
+ if (filePath.isEmpty())
+ return false;
+
+ SectionMap loadedSettings;
+
+ QTextStream *stream = openFileStream (filePath, true);
+
+ bool success = (stream);
+
+ if (success)
+ {
+ //looks for a square bracket, "'\\["
+ //that has one or more "not nothing" in it, "([^]]+)"
+ //and is closed with a square bracket, "\\]"
+
+ QRegExp sectionRe("^\\[([^]]+)\\]");
+
+ //Find any character(s) that is/are not equal sign(s), "[^=]+"
+ //followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*"
+ //and one or more periods, "(.+)"
+
+ QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
+
+ CSMSettings::SettingMap *settings = 0;
+ QString section = "none";
+
+ while (!stream->atEnd())
+ {
+ QString line = stream->readLine().simplified();
+
+ if (line.isEmpty() || line.startsWith("#"))
+ continue;
+
+ //if a section is found, push it onto a new QStringList
+ //and push the QStringList onto
+ if (sectionRe.exactMatch(line))
+ {
+ //add the previous section's settings to the member map
+ if (settings)
+ loadedSettings.insert(section, settings);
+
+ //save new section and create a new list
+ section = sectionRe.cap(1);
+ settings = new SettingMap;
+ continue;
+ }
+
+ if (keyRe.indexIn(line) != -1)
+ {
+ SettingContainer *sc = new SettingContainer (keyRe.cap(2).simplified());
+ sc->setObjectName(keyRe.cap(1).simplified());
+ (*settings)[keyRe.cap(1).simplified()] = sc;
+ }
+
+ }
+
+ loadedSettings.insert(section, settings);
+
+ stream->device()->close();
+ delete stream;
+ stream = 0;
+ }
+
+ mergeMap (loadedSettings);
+
+ return success;
+}
+
+void CSMSettings::UserSettings::mergeMap (const CSMSettings::SectionMap &sectionSettings)
+{
+ foreach (QString key, sectionSettings.uniqueKeys())
+ {
+ // insert entire section if it does not already exist in the loaded files
+ if (mSectionSettings.find(key) == mSectionSettings.end())
+ mSectionSettings.insert(key, sectionSettings.value(key));
+ else
+ {
+ SettingMap *passedSettings = sectionSettings.value(key);
+ SettingMap *settings = mSectionSettings.value(key);
+
+ foreach (QString key2, passedSettings->uniqueKeys())
+ {
+ //insert section settings individially if they do not already exist
+ if (settings->find(key2) == settings->end())
+ settings->insert(key2, passedSettings->value(key2));
+ else
+ {
+ settings->value(key2)->update(passedSettings->value(key2)->getValue());
+ }
+ }
+ }
+ }
+}
+
+void CSMSettings::UserSettings::loadSettings (const QString &fileName)
+{
+ mSectionSettings.clear();
+
+ //global
+ QString globalFilePath = QString::fromStdString(mCfgMgr.getGlobalPath().string()) + fileName;
+ bool globalOk = loadFromFile(globalFilePath);
+
+
+ //local
+ QString localFilePath = QString::fromStdString(mCfgMgr.getLocalPath().string()) + fileName;
+ bool localOk = loadFromFile(localFilePath);
+
+ //user
+ mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName;
+ loadFromFile(mUserFilePath);
+
+ if (!(localOk || globalOk))
+ {
+ QString message = QObject::tr("<br><b>Could not open user settings files for reading</b><br><br> \
+ Global and local settings files could not be read.\
+ You may have incorrect file permissions or the OpenCS installation may be corrupted.<br>");
+
+ message += QObject::tr("<br>Global filepath: ") + globalFilePath;
+ message += QObject::tr("<br>Local filepath: ") + localFilePath;
+
+ displayFileErrorMessage ( message, true);
+ }
+}
+
+void CSMSettings::UserSettings::updateSettings (const QString &sectionName, const QString &settingName)
+{
+
+ SettingMap *settings = getValidSettings(sectionName);
+
+ if (!settings)
+ return;
+
+ if (settingName.isEmpty())
+ {
+ foreach (const SettingContainer *setting, *settings)
+ emit signalUpdateEditorSetting (setting->objectName(), setting->getValue());
+ }
+ else
+ {
+ if (settings->find(settingName) != settings->end())
+ {
+ const SettingContainer *setting = settings->value(settingName);
+ emit signalUpdateEditorSetting (setting->objectName(), setting->getValue());
+ }
+ }
+}
+
+QString CSMSettings::UserSettings::getSetting (const QString &section, const QString &setting) const
+{
+ SettingMap *settings = getValidSettings(section);
+
+ QString retVal = "";
+
+ if (settings->find(setting) != settings->end())
+ retVal = settings->value(setting)->getValue();
+
+ return retVal;
+}
+
+CSMSettings::UserSettings& CSMSettings::UserSettings::instance()
+{
+ assert(mUserSettingsInstance);
+ return *mUserSettingsInstance;
+}
+
+void CSMSettings::UserSettings::displayFileErrorMessage(const QString &message, bool isReadOnly)
+{
+ // File cannot be opened or created
+ QMessageBox msgBox;
+ msgBox.setWindowTitle(QObject::tr("OpenCS configuration file I/O error"));
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Ok);
+
+ if (!isReadOnly)
+ msgBox.setText (mReadWriteMessage + message);
+ else
+ msgBox.setText (message);
+
+ msgBox.exec();
+}
+
+CSMSettings::SettingMap *
+CSMSettings::UserSettings::getValidSettings (const QString &sectionName) const
+{
+ SettingMap *settings = 0;
+
+ //copy the default values for the entire section if it's not found
+ if (mSectionSettings.find(sectionName) == mSectionSettings.end())
+ {
+ if (mEditorSettingDefaults.find(sectionName) != mEditorSettingDefaults.end())
+ settings = mEditorSettingDefaults.value (sectionName);
+ }
+ //otherwise, iterate the section's settings, looking for missing values and replacing them with defaults.
+ else
+ {
+ SettingMap *loadedSettings = mSectionSettings[sectionName];
+ SettingMap *defaultSettings = mEditorSettingDefaults[sectionName];
+
+ foreach (QString key, defaultSettings->uniqueKeys())
+ {
+ //write the default value to the loaded settings
+ if (loadedSettings->find((key))==loadedSettings->end())
+ loadedSettings->insert(key, defaultSettings->value(key));
+ }
+
+ settings = mSectionSettings.value (sectionName);
+ }
+
+ return settings;
+}
diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp
new file mode 100644
index 0000000000..63e78bd612
--- /dev/null
+++ b/apps/opencs/model/settings/usersettings.hpp
@@ -0,0 +1,94 @@
+#ifndef USERSETTINGS_HPP
+#define USERSETTINGS_HPP
+
+#include <QTextStream>
+#include <QStringList>
+#include <QString>
+#include <QMap>
+
+#include <boost/filesystem/path.hpp>
+
+#include "support.hpp"
+
+#ifndef Q_MOC_RUN
+#include <components/files/configurationmanager.hpp>
+#endif
+
+namespace Files { typedef std::vector<boost::filesystem::path> PathContainer;
+ struct ConfigurationManager;}
+
+class QFile;
+
+namespace CSMSettings {
+
+ struct UserSettings: public QObject
+ {
+
+ Q_OBJECT
+
+ SectionMap mSectionSettings;
+ SectionMap mEditorSettingDefaults;
+
+ static UserSettings *mUserSettingsInstance;
+ QString mUserFilePath;
+ Files::ConfigurationManager mCfgMgr;
+ QString mReadOnlyMessage;
+ QString mReadWriteMessage;
+
+ public:
+
+ /// Singleton implementation
+ static UserSettings& instance();
+
+ UserSettings();
+ ~UserSettings();
+
+ UserSettings (UserSettings const &); //not implemented
+ void operator= (UserSettings const &); //not implemented
+
+ /// Writes settings to the last loaded settings file
+ bool writeSettings(QMap<QString, SettingList *> &sections);
+
+ /// Called from editor to trigger signal to update the specified setting.
+ /// If no setting name is specified, all settings found in the specified section are updated.
+ void updateSettings (const QString &sectionName, const QString &settingName = "");
+
+ /// Retrieves the settings file at all three levels (global, local and user).
+
+ /// \todo Multi-valued settings are not fully implemented. Setting values
+ /// \todo loaded in later files will always overwrite previously loaded values.
+ void loadSettings (const QString &fileName);
+
+ /// Returns the entire map of settings across all sections
+ const SectionMap &getSectionMap () const;
+
+ const SettingMap *getSettings (const QString &sectionName) const;
+
+ /// Retrieves the value as a QString of the specified setting in the specified section
+ QString getSetting(const QString &section, const QString &setting) const;
+
+ private:
+
+
+ /// Opens a QTextStream from the provided path as read-only or read-write.
+ QTextStream *openFileStream (const QString &filePath, bool isReadOnly = false) const;
+
+ /// Parses a setting file specified in filePath from the provided text stream.
+ bool loadFromFile (const QString &filePath = "");
+
+ /// merge the passed map into mSectionSettings
+ void mergeMap (const SectionMap &);
+
+ void displayFileErrorMessage(const QString &message, bool isReadOnly);
+
+ void buildEditorSettingDefaults();
+
+ SettingMap *getValidSettings (const QString &sectionName) const;
+
+ signals:
+
+ void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ };
+}
+#endif // USERSETTINGS_HPP
diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp
new file mode 100644
index 0000000000..b673c93ded
--- /dev/null
+++ b/apps/opencs/model/tools/birthsigncheck.cpp
@@ -0,0 +1,39 @@
+
+#include "birthsigncheck.hpp"
+
+#include <sstream>
+#include <map>
+
+#include <components/esm/loadbsgn.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns)
+: mBirthsigns (birthsigns)
+{}
+
+int CSMTools::BirthsignCheckStage::setup()
+{
+ return mBirthsigns.getSize();
+}
+
+void CSMTools::BirthsignCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::BirthSign& birthsign = mBirthsigns.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId);
+
+ // test for empty name, description and texture
+ if (birthsign.mName.empty())
+ messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty name");
+
+ if (birthsign.mDescription.empty())
+ messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty description");
+
+ if (birthsign.mTexture.empty())
+ messages.push_back (id.toString() + "|" + birthsign.mId + " is missing a texture");
+
+ /// \todo test if the texture exists
+
+ /// \todo check data members that can't be edited in the table view
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp
new file mode 100644
index 0000000000..42b5a6b244
--- /dev/null
+++ b/apps/opencs/model/tools/birthsigncheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_BIRTHSIGNCHECK_H
+#define CSM_TOOLS_BIRTHSIGNCHECK_H
+
+#include <components/esm/loadbsgn.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that birthsign records are internally consistent
+ class BirthsignCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns;
+
+ public:
+
+ BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp
new file mode 100644
index 0000000000..da2e9f19a6
--- /dev/null
+++ b/apps/opencs/model/tools/classcheck.cpp
@@ -0,0 +1,72 @@
+
+#include "classcheck.hpp"
+
+#include <sstream>
+#include <map>
+
+#include <components/esm/loadclas.hpp>
+#include <components/esm/loadskil.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes)
+: mClasses (classes)
+{}
+
+int CSMTools::ClassCheckStage::setup()
+{
+ return mClasses.getSize();
+}
+
+void CSMTools::ClassCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Class& class_= mClasses.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId);
+
+ // test for empty name and description
+ if (class_.mName.empty())
+ messages.push_back (id.toString() + "|" + class_.mId + " has an empty name");
+
+ if (class_.mDescription.empty())
+ messages.push_back (id.toString() + "|" + class_.mId + " has an empty description");
+
+ // test for invalid attributes
+ for (int i=0; i<2; ++i)
+ if (class_.mData.mAttribute[i]==-1)
+ {
+ std::ostringstream stream;
+
+ stream << id.toString() << "|Attribute #" << i << " of " << class_.mId << " is not set";
+
+ messages.push_back (stream.str());
+ }
+
+ if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1)
+ {
+ std::ostringstream stream;
+
+ stream << id.toString() << "|Class lists same attribute twice";
+
+ messages.push_back (stream.str());
+ }
+
+ // test for non-unique skill
+ std::map<int, int> skills; // ID, number of occurrences
+
+ for (int i=0; i<5; ++i)
+ for (int i2=0; i2<2; ++i2)
+ ++skills[class_.mData.mSkills[i][i2]];
+
+ for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter)
+ if (iter->second>1)
+ {
+ std::ostringstream stream;
+
+ stream
+ << id.toString() << "|"
+ << ESM::Skill::indexToId (iter->first) << " is listed more than once";
+
+ messages.push_back (stream.str());
+ }
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp
new file mode 100644
index 0000000000..a29d7c8b78
--- /dev/null
+++ b/apps/opencs/model/tools/classcheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_CLASSCHECK_H
+#define CSM_TOOLS_CLASSCHECK_H
+
+#include <components/esm/loadclas.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that class records are internally consistent
+ class ClassCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Class>& mClasses;
+
+ public:
+
+ ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp
new file mode 100644
index 0000000000..af26904efa
--- /dev/null
+++ b/apps/opencs/model/tools/factioncheck.cpp
@@ -0,0 +1,61 @@
+
+#include "factioncheck.hpp"
+
+#include <sstream>
+#include <map>
+
+#include <components/esm/loadfact.hpp>
+#include <components/esm/loadskil.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions)
+: mFactions (factions)
+{}
+
+int CSMTools::FactionCheckStage::setup()
+{
+ return mFactions.getSize();
+}
+
+void CSMTools::FactionCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Faction& faction = mFactions.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Faction, faction.mId);
+
+ // test for empty name
+ if (faction.mName.empty())
+ messages.push_back (id.toString() + "|" + faction.mId + " has an empty name");
+
+ // test for invalid attributes
+ if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1)
+ {
+ std::ostringstream stream;
+
+ stream << id.toString() << "|Faction lists same attribute twice";
+
+ messages.push_back (stream.str());
+ }
+
+ // test for non-unique skill
+ std::map<int, int> skills; // ID, number of occurrences
+
+ for (int i=0; i<6; ++i)
+ if (faction.mData.mSkills[i]!=-1)
+ ++skills[faction.mData.mSkills[i]];
+
+ for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter)
+ if (iter->second>1)
+ {
+ std::ostringstream stream;
+
+ stream
+ << id.toString() << "|"
+ << ESM::Skill::indexToId (iter->first) << " is listed more than once";
+
+ messages.push_back (stream.str());
+ }
+
+ /// \todo check data members that can't be edited in the table view
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp
new file mode 100644
index 0000000000..8686505727
--- /dev/null
+++ b/apps/opencs/model/tools/factioncheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_FACTIONCHECK_H
+#define CSM_TOOLS_FACTIONCHECK_H
+
+#include <components/esm/loadfact.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that faction records are internally consistent
+ class FactionCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Faction>& mFactions;
+
+ public:
+
+ FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp
new file mode 100644
index 0000000000..b99abec6d2
--- /dev/null
+++ b/apps/opencs/model/tools/mandatoryid.cpp
@@ -0,0 +1,23 @@
+
+#include "mandatoryid.hpp"
+
+#include "../world/collectionbase.hpp"
+
+#include "../world/record.hpp"
+
+CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::CollectionBase& idCollection,
+ const CSMWorld::UniversalId& collectionId, const std::vector<std::string>& ids)
+: mIdCollection (idCollection), mCollectionId (collectionId), mIds (ids)
+{}
+
+int CSMTools::MandatoryIdStage::setup()
+{
+ return mIds.size();
+}
+
+void CSMTools::MandatoryIdStage::perform (int stage, std::vector<std::string>& messages)
+{
+ if (mIdCollection.searchId (mIds.at (stage))==-1 ||
+ mIdCollection.getRecord (mIds.at (stage)).isDeleted())
+ messages.push_back (mCollectionId.toString() + "|Missing mandatory record: " + mIds.at (stage));
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp
new file mode 100644
index 0000000000..342e2d7540
--- /dev/null
+++ b/apps/opencs/model/tools/mandatoryid.hpp
@@ -0,0 +1,38 @@
+#ifndef CSM_TOOLS_MANDATORYID_H
+#define CSM_TOOLS_MANDATORYID_H
+
+#include <string>
+#include <vector>
+
+#include "../world/universalid.hpp"
+
+#include "stage.hpp"
+
+namespace CSMWorld
+{
+ class CollectionBase;
+}
+
+namespace CSMTools
+{
+ /// \brief Verify stage: make sure that records with specific IDs exist.
+ class MandatoryIdStage : public Stage
+ {
+ const CSMWorld::CollectionBase& mIdCollection;
+ CSMWorld::UniversalId mCollectionId;
+ std::vector<std::string> mIds;
+
+ public:
+
+ MandatoryIdStage (const CSMWorld::CollectionBase& idCollection, const CSMWorld::UniversalId& collectionId,
+ const std::vector<std::string>& ids);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/operation.cpp b/apps/opencs/model/tools/operation.cpp
new file mode 100644
index 0000000000..71761cdaea
--- /dev/null
+++ b/apps/opencs/model/tools/operation.cpp
@@ -0,0 +1,84 @@
+
+#include "operation.hpp"
+
+#include <string>
+#include <vector>
+
+#include <QTimer>
+
+#include "../doc/state.hpp"
+
+#include "stage.hpp"
+
+void CSMTools::Operation::prepareStages()
+{
+ mCurrentStage = mStages.begin();
+ mCurrentStep = 0;
+ mCurrentStepTotal = 0;
+ mTotalSteps = 0;
+
+ for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
+ {
+ iter->second = iter->first->setup();
+ mTotalSteps += iter->second;
+ }
+}
+
+CSMTools::Operation::Operation (int type) : mType (type) {}
+
+CSMTools::Operation::~Operation()
+{
+ for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
+ delete iter->first;
+}
+
+void CSMTools::Operation::run()
+{
+ prepareStages();
+
+ QTimer timer;
+
+ timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify()));
+
+ timer.start (0);
+
+ exec();
+}
+
+void CSMTools::Operation::appendStage (Stage *stage)
+{
+ mStages.push_back (std::make_pair (stage, 0));
+}
+
+void CSMTools::Operation::abort()
+{
+ exit();
+}
+
+void CSMTools::Operation::verify()
+{
+ std::vector<std::string> messages;
+
+ while (mCurrentStage!=mStages.end())
+ {
+ if (mCurrentStep>=mCurrentStage->second)
+ {
+ mCurrentStep = 0;
+ ++mCurrentStage;
+ }
+ else
+ {
+ mCurrentStage->first->perform (mCurrentStep++, messages);
+ ++mCurrentStepTotal;
+ break;
+ }
+ }
+
+ emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType);
+
+ for (std::vector<std::string>::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter)
+ emit reportMessage (iter->c_str(), mType);
+
+ if (mCurrentStage==mStages.end())
+ exit();
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/operation.hpp b/apps/opencs/model/tools/operation.hpp
new file mode 100644
index 0000000000..4731c58fa8
--- /dev/null
+++ b/apps/opencs/model/tools/operation.hpp
@@ -0,0 +1,54 @@
+#ifndef CSM_TOOLS_OPERATION_H
+#define CSM_TOOLS_OPERATION_H
+
+#include <vector>
+
+#include <QThread>
+
+namespace CSMTools
+{
+ class Stage;
+
+ class Operation : public QThread
+ {
+ Q_OBJECT
+
+ int mType;
+ std::vector<std::pair<Stage *, int> > mStages; // stage, number of steps
+ std::vector<std::pair<Stage *, int> >::iterator mCurrentStage;
+ int mCurrentStep;
+ int mCurrentStepTotal;
+ int mTotalSteps;
+
+ void prepareStages();
+
+ public:
+
+ Operation (int type);
+
+ virtual ~Operation();
+
+ virtual void run();
+
+ void appendStage (Stage *stage);
+ ///< The ownership of \a stage is transferred to *this.
+ ///
+ /// \attention Do no call this function while this Operation is running.
+
+ signals:
+
+ void progress (int current, int max, int type);
+
+ void reportMessage (const QString& message, int type);
+
+ public slots:
+
+ void abort();
+
+ private slots:
+
+ void verify();
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp
new file mode 100644
index 0000000000..1e7a4cab45
--- /dev/null
+++ b/apps/opencs/model/tools/racecheck.cpp
@@ -0,0 +1,68 @@
+
+#include "racecheck.hpp"
+
+#include <sstream>
+
+#include <components/esm/loadrace.hpp>
+
+#include "../world/universalid.hpp"
+
+void CSMTools::RaceCheckStage::performPerRecord (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Race& race = mRaces.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId);
+
+ // test for empty name and description
+ if (race.mName.empty())
+ messages.push_back (id.toString() + "|" + race.mId + " has an empty name");
+
+ if (race.mDescription.empty())
+ messages.push_back (id.toString() + "|" + race.mId + " has an empty description");
+
+ // test for positive height
+ if (race.mData.mHeight.mMale<=0)
+ messages.push_back (id.toString() + "|male " + race.mId + " has non-positive height");
+
+ if (race.mData.mHeight.mFemale<=0)
+ messages.push_back (id.toString() + "|female " + race.mId + " has non-positive height");
+
+ // test for non-negative weight
+ if (race.mData.mWeight.mMale<0)
+ messages.push_back (id.toString() + "|male " + race.mId + " has negative weight");
+
+ if (race.mData.mWeight.mFemale<0)
+ messages.push_back (id.toString() + "|female " + race.mId + " has negative weight");
+
+ // remember playable flag
+ if (race.mData.mFlags & 0x1)
+ mPlayable = true;
+
+ /// \todo check data members that can't be edited in the table view
+}
+
+void CSMTools::RaceCheckStage::performFinal (std::vector<std::string>& messages)
+{
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races);
+
+ if (!mPlayable)
+ messages.push_back (id.toString() + "|No playable race");
+}
+
+CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races)
+: mRaces (races), mPlayable (false)
+{}
+
+int CSMTools::RaceCheckStage::setup()
+{
+ mPlayable = false;
+ return mRaces.getSize()+1;
+}
+
+void CSMTools::RaceCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ if (stage==mRaces.getSize())
+ performFinal (messages);
+ else
+ performPerRecord (stage, messages);
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp
new file mode 100644
index 0000000000..155f799021
--- /dev/null
+++ b/apps/opencs/model/tools/racecheck.hpp
@@ -0,0 +1,34 @@
+#ifndef CSM_TOOLS_RACECHECK_H
+#define CSM_TOOLS_RACECHECK_H
+
+#include <components/esm/loadrace.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that race records are internally consistent
+ class RaceCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Race>& mRaces;
+ bool mPlayable;
+
+ void performPerRecord (int stage, std::vector<std::string>& messages);
+
+ void performFinal (std::vector<std::string>& messages);
+
+ public:
+
+ RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp
new file mode 100644
index 0000000000..ac64ac0271
--- /dev/null
+++ b/apps/opencs/model/tools/regioncheck.cpp
@@ -0,0 +1,33 @@
+
+#include "regioncheck.hpp"
+
+#include <sstream>
+#include <map>
+
+#include <components/esm/loadregn.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions)
+: mRegions (regions)
+{}
+
+int CSMTools::RegionCheckStage::setup()
+{
+ return mRegions.getSize();
+}
+
+void CSMTools::RegionCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Region& region = mRegions.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Region, region.mId);
+
+ // test for empty name
+ if (region.mName.empty())
+ messages.push_back (id.toString() + "|" + region.mId + " has an empty name");
+
+ /// \todo test that the ID in mSleeplist exists
+
+ /// \todo check data members that can't be edited in the table view
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp
new file mode 100644
index 0000000000..b421356514
--- /dev/null
+++ b/apps/opencs/model/tools/regioncheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_REGIONCHECK_H
+#define CSM_TOOLS_REGIONCHECK_H
+
+#include <components/esm/loadregn.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that region records are internally consistent
+ class RegionCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Region>& mRegions;
+
+ public:
+
+ RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp
new file mode 100644
index 0000000000..b125318755
--- /dev/null
+++ b/apps/opencs/model/tools/reportmodel.cpp
@@ -0,0 +1,71 @@
+
+#include "reportmodel.hpp"
+
+#include <stdexcept>
+
+int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mRows.size();
+}
+
+int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return 2;
+}
+
+QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
+{
+ if (role!=Qt::DisplayRole)
+ return QVariant();
+
+ if (index.column()==0)
+ return static_cast<int> (mRows.at (index.row()).first.getType());
+ else
+ return mRows.at (index.row()).second.c_str();
+}
+
+QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const
+{
+ if (role!=Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation==Qt::Vertical)
+ return QVariant();
+
+ return tr (section==0 ? "Type" : "Description");
+}
+
+bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return false;
+
+ mRows.erase (mRows.begin()+row, mRows.begin()+row+count);
+
+ return true;
+}
+
+void CSMTools::ReportModel::add (const std::string& row)
+{
+ std::string::size_type index = row.find ('|');
+
+ if (index==std::string::npos)
+ throw std::logic_error ("invalid report message");
+
+ beginInsertRows (QModelIndex(), mRows.size(), mRows.size());
+
+ mRows.push_back (std::make_pair (row.substr (0, index), row.substr (index+1)));
+
+ endInsertRows();
+}
+
+const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const
+{
+ return mRows.at (row).first;
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp
new file mode 100644
index 0000000000..55c25d9078
--- /dev/null
+++ b/apps/opencs/model/tools/reportmodel.hpp
@@ -0,0 +1,37 @@
+#ifndef CSM_TOOLS_REPORTMODEL_H
+#define CSM_TOOLS_REPORTMODEL_H
+
+#include <vector>
+#include <string>
+
+#include <QAbstractTableModel>
+
+#include "../world/universalid.hpp"
+
+namespace CSMTools
+{
+ class ReportModel : public QAbstractTableModel
+ {
+ Q_OBJECT
+
+ std::vector<std::pair<CSMWorld::UniversalId, std::string> > mRows;
+
+ public:
+
+ virtual int rowCount (const QModelIndex & parent = QModelIndex()) const;
+
+ virtual int columnCount (const QModelIndex & parent = QModelIndex()) const;
+
+ virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+ virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex());
+
+ void add (const std::string& row);
+
+ const CSMWorld::UniversalId& getUniversalId (int row) const;
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp
new file mode 100644
index 0000000000..897aeab473
--- /dev/null
+++ b/apps/opencs/model/tools/skillcheck.cpp
@@ -0,0 +1,37 @@
+
+#include "skillcheck.hpp"
+
+#include <sstream>
+
+#include <components/esm/loadskil.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills)
+: mSkills (skills)
+{}
+
+int CSMTools::SkillCheckStage::setup()
+{
+ return mSkills.getSize();
+}
+
+void CSMTools::SkillCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Skill& skill = mSkills.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Skill, skill.mId);
+
+ for (int i=0; i<4; ++i)
+ if (skill.mData.mUseValue[i]<0)
+ {
+ std::ostringstream stream;
+
+ stream << id.toString() << "|Use value #" << i << " of " << skill.mId << " is negative";
+
+ messages.push_back (stream.str());
+ }
+
+ if (skill.mDescription.empty())
+ messages.push_back (id.toString() + "|" + skill.mId + " has an empty description");
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp
new file mode 100644
index 0000000000..30a3f01cad
--- /dev/null
+++ b/apps/opencs/model/tools/skillcheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_SKILLCHECK_H
+#define CSM_TOOLS_SKILLCHECK_H
+
+#include <components/esm/loadskil.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that skill records are internally consistent
+ class SkillCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Skill>& mSkills;
+
+ public:
+
+ SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp
new file mode 100644
index 0000000000..52834e6594
--- /dev/null
+++ b/apps/opencs/model/tools/soundcheck.cpp
@@ -0,0 +1,29 @@
+
+#include "soundcheck.hpp"
+
+#include <sstream>
+
+#include <components/esm/loadskil.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds)
+: mSounds (sounds)
+{}
+
+int CSMTools::SoundCheckStage::setup()
+{
+ return mSounds.getSize();
+}
+
+void CSMTools::SoundCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Sound& sound = mSounds.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId);
+
+ if (sound.mData.mMinRange>sound.mData.mMaxRange)
+ messages.push_back (id.toString() + "|Maximum range larger than minimum range");
+
+ /// \todo check, if the sound file exists
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp
new file mode 100644
index 0000000000..a309763a12
--- /dev/null
+++ b/apps/opencs/model/tools/soundcheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_SOUNDCHECK_H
+#define CSM_TOOLS_SOUNDCHECK_H
+
+#include <components/esm/loadsoun.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that sound records are internally consistent
+ class SoundCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Sound>& mSounds;
+
+ public:
+
+ SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp
new file mode 100644
index 0000000000..3adee0a4ef
--- /dev/null
+++ b/apps/opencs/model/tools/spellcheck.cpp
@@ -0,0 +1,35 @@
+
+#include "spellcheck.hpp"
+
+#include <sstream>
+#include <map>
+
+#include <components/esm/loadspel.hpp>
+
+#include "../world/universalid.hpp"
+
+CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells)
+: mSpells (spells)
+{}
+
+int CSMTools::SpellCheckStage::setup()
+{
+ return mSpells.getSize();
+}
+
+void CSMTools::SpellCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ const ESM::Spell& spell = mSpells.getRecord (stage).get();
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId);
+
+ // test for empty name and description
+ if (spell.mName.empty())
+ messages.push_back (id.toString() + "|" + spell.mId + " has an empty name");
+
+ // test for invalid cost values
+ if (spell.mData.mCost<0)
+ messages.push_back (id.toString() + "|" + spell.mId + " has a negative spell costs");
+
+ /// \todo check data members that can't be edited in the table view
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp
new file mode 100644
index 0000000000..0566392193
--- /dev/null
+++ b/apps/opencs/model/tools/spellcheck.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_TOOLS_SPELLCHECK_H
+#define CSM_TOOLS_SPELLCHECK_H
+
+#include <components/esm/loadspel.hpp>
+
+#include "../world/idcollection.hpp"
+
+#include "stage.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that spell records are internally consistent
+ class SpellCheckStage : public Stage
+ {
+ const CSMWorld::IdCollection<ESM::Spell>& mSpells;
+
+ public:
+
+ SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells);
+
+ virtual int setup();
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages);
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/stage.cpp b/apps/opencs/model/tools/stage.cpp
new file mode 100644
index 0000000000..6f4567e579
--- /dev/null
+++ b/apps/opencs/model/tools/stage.cpp
@@ -0,0 +1,4 @@
+
+#include "stage.hpp"
+
+CSMTools::Stage::~Stage() {} \ No newline at end of file
diff --git a/apps/opencs/model/tools/stage.hpp b/apps/opencs/model/tools/stage.hpp
new file mode 100644
index 0000000000..3020936f32
--- /dev/null
+++ b/apps/opencs/model/tools/stage.hpp
@@ -0,0 +1,24 @@
+#ifndef CSM_TOOLS_STAGE_H
+#define CSM_TOOLS_STAGE_H
+
+#include <vector>
+#include <string>
+
+namespace CSMTools
+{
+ class Stage
+ {
+ public:
+
+ virtual ~Stage();
+
+ virtual int setup() = 0;
+ ///< \return number of steps
+
+ virtual void perform (int stage, std::vector<std::string>& messages) = 0;
+ ///< Messages resulting from this tage will be appended to \a messages.
+ };
+}
+
+#endif
+
diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp
new file mode 100644
index 0000000000..803861203c
--- /dev/null
+++ b/apps/opencs/model/tools/tools.cpp
@@ -0,0 +1,147 @@
+
+#include "tools.hpp"
+
+#include <QThreadPool>
+
+#include "verifier.hpp"
+
+#include "../doc/state.hpp"
+
+#include "../world/data.hpp"
+#include "../world/universalid.hpp"
+
+#include "reportmodel.hpp"
+#include "mandatoryid.hpp"
+#include "skillcheck.hpp"
+#include "classcheck.hpp"
+#include "factioncheck.hpp"
+#include "racecheck.hpp"
+#include "soundcheck.hpp"
+#include "regioncheck.hpp"
+#include "birthsigncheck.hpp"
+#include "spellcheck.hpp"
+
+CSMTools::Operation *CSMTools::Tools::get (int type)
+{
+ switch (type)
+ {
+ case CSMDoc::State_Verifying: return mVerifier;
+ }
+
+ return 0;
+}
+
+const CSMTools::Operation *CSMTools::Tools::get (int type) const
+{
+ return const_cast<Tools *> (this)->get (type);
+}
+
+CSMTools::Verifier *CSMTools::Tools::getVerifier()
+{
+ if (!mVerifier)
+ {
+ mVerifier = new Verifier;
+
+ connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int)));
+ connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone()));
+ connect (mVerifier, SIGNAL (reportMessage (const QString&, int)),
+ this, SLOT (verifierMessage (const QString&, int)));
+
+ std::vector<std::string> mandatoryIds; // I want C++11, damn it!
+ mandatoryIds.push_back ("Day");
+ mandatoryIds.push_back ("DaysPassed");
+ mandatoryIds.push_back ("GameHour");
+ mandatoryIds.push_back ("Month");
+ mandatoryIds.push_back ("PCRace");
+ mandatoryIds.push_back ("PCVampire");
+ mandatoryIds.push_back ("PCWerewolf");
+ mandatoryIds.push_back ("PCYear");
+
+ mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(),
+ CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds));
+
+ mVerifier->appendStage (new SkillCheckStage (mData.getSkills()));
+
+ mVerifier->appendStage (new ClassCheckStage (mData.getClasses()));
+
+ mVerifier->appendStage (new FactionCheckStage (mData.getFactions()));
+
+ mVerifier->appendStage (new RaceCheckStage (mData.getRaces()));
+
+ mVerifier->appendStage (new SoundCheckStage (mData.getSounds()));
+
+ mVerifier->appendStage (new RegionCheckStage (mData.getRegions()));
+
+ mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns()));
+
+ mVerifier->appendStage (new SpellCheckStage (mData.getSpells()));
+ }
+
+ return mVerifier;
+}
+
+CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0)
+{
+ for (std::map<int, ReportModel *>::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter)
+ delete iter->second;
+}
+
+CSMTools::Tools::~Tools()
+{
+ delete mVerifier;
+}
+
+CSMWorld::UniversalId CSMTools::Tools::runVerifier()
+{
+ mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel));
+ mActiveReports[CSMDoc::State_Verifying] = mNextReportNumber-1;
+
+ getVerifier()->start();
+
+ return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1);
+}
+
+void CSMTools::Tools::abortOperation (int type)
+{
+ if (Operation *operation = get (type))
+ operation->abort();
+}
+
+int CSMTools::Tools::getRunningOperations() const
+{
+ static const int sOperations[] =
+ {
+ CSMDoc::State_Verifying,
+ -1
+ };
+
+ int result = 0;
+
+ for (int i=0; sOperations[i]!=-1; ++i)
+ if (const Operation *operation = get (sOperations[i]))
+ if (operation->isRunning())
+ result |= sOperations[i];
+
+ return result;
+}
+
+CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id)
+{
+ if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults)
+ throw std::logic_error ("invalid request for report model: " + id.toString());
+
+ return mReports.at (id.getIndex());
+}
+
+void CSMTools::Tools::verifierDone()
+{
+ emit done (CSMDoc::State_Verifying);
+}
+
+void CSMTools::Tools::verifierMessage (const QString& message, int type)
+{
+ std::map<int, int>::iterator iter = mActiveReports.find (type);
+
+ if (iter!=mActiveReports.end())
+ mReports[iter->second]->add (message.toStdString());
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp
new file mode 100644
index 0000000000..652345c6da
--- /dev/null
+++ b/apps/opencs/model/tools/tools.hpp
@@ -0,0 +1,73 @@
+#ifndef CSM_TOOLS_TOOLS_H
+#define CSM_TOOLS_TOOLS_H
+
+#include <QObject>
+
+#include <map>
+
+namespace CSMWorld
+{
+ class Data;
+ class UniversalId;
+}
+
+namespace CSMTools
+{
+ class Verifier;
+ class Operation;
+ class ReportModel;
+
+ class Tools : public QObject
+ {
+ Q_OBJECT
+
+ CSMWorld::Data& mData;
+ Verifier *mVerifier;
+ std::map<int, ReportModel *> mReports;
+ int mNextReportNumber;
+ std::map<int, int> mActiveReports; // type, report number
+
+ // not implemented
+ Tools (const Tools&);
+ Tools& operator= (const Tools&);
+
+ Verifier *getVerifier();
+
+ Operation *get (int type);
+ ///< Returns a 0-pointer, if operation hasn't been used yet.
+
+ const Operation *get (int type) const;
+ ///< Returns a 0-pointer, if operation hasn't been used yet.
+
+ public:
+
+ Tools (CSMWorld::Data& data);
+
+ virtual ~Tools();
+
+ CSMWorld::UniversalId runVerifier();
+ ///< \return ID of the report for this verification run
+
+ void abortOperation (int type);
+ ///< \attention The operation is not aborted immediately.
+
+ int getRunningOperations() const;
+
+ ReportModel *getReport (const CSMWorld::UniversalId& id);
+ ///< The ownership of the returned report is not transferred.
+
+ private slots:
+
+ void verifierDone();
+
+ void verifierMessage (const QString& message, int type);
+
+ signals:
+
+ void progress (int current, int max, int type);
+
+ void done (int type);
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp
new file mode 100644
index 0000000000..9c00d4ea7e
--- /dev/null
+++ b/apps/opencs/model/tools/verifier.cpp
@@ -0,0 +1,7 @@
+
+#include "verifier.hpp"
+
+#include "../doc/state.hpp"
+
+CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying)
+{}
diff --git a/apps/opencs/model/tools/verifier.hpp b/apps/opencs/model/tools/verifier.hpp
new file mode 100644
index 0000000000..054f87169c
--- /dev/null
+++ b/apps/opencs/model/tools/verifier.hpp
@@ -0,0 +1,17 @@
+#ifndef CSM_TOOLS_VERIFIER_H
+#define CSM_TOOLS_VERIFIER_H
+
+#include "operation.hpp"
+
+namespace CSMTools
+{
+ class Verifier : public Operation
+ {
+ public:
+
+ Verifier();
+
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp
new file mode 100644
index 0000000000..cd58fca1e4
--- /dev/null
+++ b/apps/opencs/model/world/cell.cpp
@@ -0,0 +1,25 @@
+
+#include "cell.hpp"
+
+#include <sstream>
+
+void CSMWorld::Cell::load (ESM::ESMReader &esm)
+{
+ mName = mId;
+
+ ESM::Cell::load (esm, false);
+
+ if (!(mData.mFlags & Interior))
+ {
+ std::ostringstream stream;
+
+ stream << "#" << mData.mX << " " << mData.mY;
+
+ mId = stream.str();
+ }
+}
+
+void CSMWorld::Cell::addRef (const std::string& id)
+{
+ mRefs.push_back (std::make_pair (id, false));
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp
new file mode 100644
index 0000000000..89854312ae
--- /dev/null
+++ b/apps/opencs/model/world/cell.hpp
@@ -0,0 +1,24 @@
+#ifndef CSM_WOLRD_CELL_H
+#define CSM_WOLRD_CELL_H
+
+#include <vector>
+#include <string>
+
+#include <components/esm/loadcell.hpp>
+
+namespace CSMWorld
+{
+ /// \brief Wrapper for Cell record
+ struct Cell : public ESM::Cell
+ {
+ std::string mId;
+ std::vector<std::pair<std::string, bool> > mRefs; // ID, modified
+ std::vector<std::string> mDeletedRefs;
+
+ void load (ESM::ESMReader &esm);
+
+ void addRef (const std::string& id);
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp
new file mode 100644
index 0000000000..6cf31d0a4f
--- /dev/null
+++ b/apps/opencs/model/world/collection.hpp
@@ -0,0 +1,319 @@
+#ifndef CSM_WOLRD_COLLECTION_H
+#define CSM_WOLRD_COLLECTION_H
+
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <cctype>
+#include <stdexcept>
+#include <functional>
+
+#include <QVariant>
+
+#include <components/misc/stringops.hpp>
+
+#include "columnbase.hpp"
+
+#include "collectionbase.hpp"
+
+namespace CSMWorld
+{
+ /// \brief Access to ID field in records
+ template<typename ESXRecordT>
+ struct IdAccessor
+ {
+ std::string& getId (ESXRecordT& record);
+
+ const std::string getId (const ESXRecordT& record) const;
+ };
+
+ template<typename ESXRecordT>
+ std::string& IdAccessor<ESXRecordT>::getId (ESXRecordT& record)
+ {
+ return record.mId;
+ }
+
+ template<typename ESXRecordT>
+ const std::string IdAccessor<ESXRecordT>::getId (const ESXRecordT& record) const
+ {
+ return record.mId;
+ }
+
+ /// \brief Single-type record collection
+ template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
+ class Collection : public CollectionBase
+ {
+ std::vector<Record<ESXRecordT> > mRecords;
+ std::map<std::string, int> mIndex;
+ std::vector<Column<ESXRecordT> *> mColumns;
+
+ // not implemented
+ Collection (const Collection&);
+ Collection& operator= (const Collection&);
+
+ public:
+
+ Collection();
+
+ virtual ~Collection();
+
+ void add (const ESXRecordT& record);
+ ///< Add a new record (modified)
+
+ virtual int getSize() const;
+
+ virtual std::string getId (int index) const;
+
+ virtual int getIndex (const std::string& id) const;
+
+ virtual int getColumns() const;
+
+ virtual QVariant getData (int index, int column) const;
+
+ virtual void setData (int index, int column, const QVariant& data);
+
+ virtual const ColumnBase& getColumn (int column) const;
+
+ virtual void merge();
+ ///< Merge modified into base.
+
+ virtual void purge();
+ ///< Remove records that are flagged as erased.
+
+ virtual void removeRows (int index, int count) ;
+
+ virtual void appendBlankRecord (const std::string& id,
+ UniversalId::Type type = UniversalId::Type_None);
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual int searchId (const std::string& id) const;
+ ////< Search record with \a id.
+ /// \return index of record (if found) or -1 (not found)
+
+ virtual void replace (int index, const RecordBase& record);
+ ///< If the record type does not match, an exception is thrown.
+ ///
+ /// \attention \a record must not change the ID.
+
+ virtual void appendRecord (const RecordBase& record,
+ UniversalId::Type type = UniversalId::Type_None);
+ ///< If the record type does not match, an exception is thrown.
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual const Record<ESXRecordT>& getRecord (const std::string& id) const;
+
+ virtual const Record<ESXRecordT>& getRecord (int index) const;
+
+ virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const;
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ void addColumn (Column<ESXRecordT> *column);
+
+ void setRecord (int index, const Record<ESXRecordT>& record);
+ ///< \attention This function must not change the ID.
+ };
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ Collection<ESXRecordT, IdAccessorT>::Collection()
+ {}
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ Collection<ESXRecordT, IdAccessorT>::~Collection()
+ {
+ for (typename std::vector<Column<ESXRecordT> *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter)
+ delete *iter;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::add (const ESXRecordT& record)
+ {
+ std::string id = Misc::StringUtils::lowerCase (IdAccessorT().getId (record));
+
+ std::map<std::string, int>::iterator iter = mIndex.find (id);
+
+ if (iter==mIndex.end())
+ {
+ Record<ESXRecordT> record2;
+ 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));
+ }
+ else
+ {
+ mRecords[iter->second].setModified (record);
+ }
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ int Collection<ESXRecordT, IdAccessorT>::getSize() const
+ {
+ return mRecords.size();
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ std::string Collection<ESXRecordT, IdAccessorT>::getId (int index) const
+ {
+ return IdAccessorT().getId (mRecords.at (index).get());
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ int Collection<ESXRecordT, IdAccessorT>::getIndex (const std::string& id) const
+ {
+ int index = searchId (id);
+
+ if (index==-1)
+ throw std::runtime_error ("invalid ID: " + id);
+
+ return index;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ int Collection<ESXRecordT, IdAccessorT>::getColumns() const
+ {
+ return mColumns.size();
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ QVariant Collection<ESXRecordT, IdAccessorT>::getData (int index, int column) const
+ {
+ return mColumns.at (column)->get (mRecords.at (index));
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::setData (int index, int column, const QVariant& data)
+ {
+ return mColumns.at (column)->set (mRecords.at (index), data);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ const ColumnBase& Collection<ESXRecordT, IdAccessorT>::getColumn (int column) const
+ {
+ return *mColumns.at (column);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::addColumn (Column<ESXRecordT> *column)
+ {
+ mColumns.push_back (column);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::merge()
+ {
+ for (typename std::vector<Record<ESXRecordT> >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter)
+ iter->merge();
+
+ purge();
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::purge()
+ {
+ int i = 0;
+
+ while (i<static_cast<int> (mRecords.size()))
+ {
+ if (mRecords[i].isErased())
+ removeRows (i, 1);
+ else
+ ++i;
+ }
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::removeRows (int index, int count)
+ {
+ mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count);
+
+ typename std::map<std::string, int>::iterator iter = mIndex.begin();
+
+ while (iter!=mIndex.end())
+ {
+ if (iter->second>=index)
+ {
+ if (iter->second>=index+count)
+ {
+ iter->second -= count;
+ ++iter;
+ }
+ else
+ {
+ mIndex.erase (iter++);
+ }
+ }
+ else
+ ++iter;
+ }
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::appendBlankRecord (const std::string& id,
+ UniversalId::Type type)
+ {
+ ESXRecordT record;
+ IdAccessorT().getId (record) = id;
+ record.blank();
+ add (record);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ int Collection<ESXRecordT, IdAccessorT>::searchId (const std::string& id) const
+ {
+ std::string id2 = Misc::StringUtils::lowerCase(id);
+
+ std::map<std::string, int>::const_iterator iter = mIndex.find (id2);
+
+ if (iter==mIndex.end())
+ return -1;
+
+ return iter->second;
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::replace (int index, const RecordBase& record)
+ {
+ mRecords.at (index) = dynamic_cast<const Record<ESXRecordT>&> (record);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ 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));
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ int Collection<ESXRecordT, IdAccessorT>::getAppendIndex (UniversalId::Type type) const
+ {
+ return static_cast<int> (mRecords.size());
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (const std::string& id) const
+ {
+ int index = getIndex (id);
+ return mRecords.at (index);
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (int index) const
+ {
+ return mRecords.at (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()))
+ throw std::runtime_error ("attempt to change the ID of a record");
+
+ mRecords.at (index) = record;
+ }
+}
+
+#endif
diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp
new file mode 100644
index 0000000000..932ea27b58
--- /dev/null
+++ b/apps/opencs/model/world/collectionbase.cpp
@@ -0,0 +1,6 @@
+
+#include "collectionbase.hpp"
+
+CSMWorld::CollectionBase::CollectionBase() {}
+
+CSMWorld::CollectionBase::~CollectionBase() {}
diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp
new file mode 100644
index 0000000000..1700a68ecd
--- /dev/null
+++ b/apps/opencs/model/world/collectionbase.hpp
@@ -0,0 +1,85 @@
+#ifndef CSM_WOLRD_COLLECTIONBASE_H
+#define CSM_WOLRD_COLLECTIONBASE_H
+
+#include <string>
+
+#include "universalid.hpp"
+
+class QVariant;
+
+namespace CSMWorld
+{
+ struct ColumnBase;
+ struct RecordBase;
+
+ /// \brief Base class for record collections
+ ///
+ /// \attention Modifying records through the interface does not update connected views.
+ /// Such modifications should be done through the table model interface instead unless no views
+ /// are connected to the model or special precautions have been taken to send update signals
+ /// manually.
+ class CollectionBase
+ {
+ // not implemented
+ CollectionBase (const CollectionBase&);
+ CollectionBase& operator= (const CollectionBase&);
+
+ public:
+
+ CollectionBase();
+
+ virtual ~CollectionBase();
+
+ virtual int getSize() const = 0;
+
+ virtual std::string getId (int index) const = 0;
+
+ virtual int getIndex (const std::string& id) const = 0;
+
+ virtual int getColumns() const = 0;
+
+ virtual const ColumnBase& getColumn (int column) const = 0;
+
+ virtual QVariant getData (int index, int column) const = 0;
+
+ virtual void setData (int index, int column, const QVariant& data) = 0;
+
+// Not in use. Temporarily removed so that the implementation of RefIdCollection can continue without
+// these functions for now.
+// virtual void merge() = 0;
+ ///< Merge modified into base.
+
+// virtual void purge() = 0;
+ ///< Remove records that are flagged as erased.
+
+ virtual void removeRows (int index, int count) = 0;
+
+ virtual void appendBlankRecord (const std::string& id,
+ UniversalId::Type type = UniversalId::Type_None) = 0;
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual int searchId (const std::string& id) const = 0;
+ ////< Search record with \a id.
+ /// \return index of record (if found) or -1 (not found)
+
+ virtual void replace (int index, const RecordBase& record) = 0;
+ ///< If the record type does not match, an exception is thrown.
+ ///
+ /// \attention \a record must not change the ID.
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual void appendRecord (const RecordBase& record,
+ UniversalId::Type type = UniversalId::Type_None) = 0;
+ ///< If the record type does not match, an exception is thrown.
+
+ virtual const RecordBase& getRecord (const std::string& id) const = 0;
+
+ virtual const RecordBase& getRecord (int index) const = 0;
+
+ virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const = 0;
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+ };
+
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp
new file mode 100644
index 0000000000..34bad20cc5
--- /dev/null
+++ b/apps/opencs/model/world/columnbase.cpp
@@ -0,0 +1,20 @@
+
+#include "columnbase.hpp"
+
+#include "columns.hpp"
+
+CSMWorld::ColumnBase::ColumnBase (int columnId, Display displayType, int flags)
+: mColumnId (columnId), mDisplayType (displayType), mFlags (flags)
+{}
+
+CSMWorld::ColumnBase::~ColumnBase() {}
+
+bool CSMWorld::ColumnBase::isUserEditable() const
+{
+ return isEditable();
+}
+
+std::string CSMWorld::ColumnBase::getTitle() const
+{
+ return Columns::getName (static_cast<Columns::ColumnId> (mColumnId));
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp
new file mode 100644
index 0000000000..c1b423c94c
--- /dev/null
+++ b/apps/opencs/model/world/columnbase.hpp
@@ -0,0 +1,82 @@
+#ifndef CSM_WOLRD_COLUMNBASE_H
+#define CSM_WOLRD_COLUMNBASE_H
+
+#include <string>
+
+#include <Qt>
+#include <QVariant>
+
+#include "record.hpp"
+
+namespace CSMWorld
+{
+ struct ColumnBase
+ {
+ enum Roles
+ {
+ Role_Flags = Qt::UserRole,
+ Role_Display = Qt::UserRole+1
+ };
+
+ enum Flags
+ {
+ Flag_Table = 1, // column should be displayed in table view
+ Flag_Dialogue = 2 // column should be displayed in dialogue view
+ };
+
+ enum Display
+ {
+ Display_String,
+ Display_Integer,
+ Display_Float,
+ Display_Var,
+ Display_GmstVarType,
+ Display_GlobalVarType,
+ Display_Specialisation,
+ Display_Attribute,
+ Display_Boolean,
+ Display_SpellType,
+ Display_Script,
+ Display_ApparatusType,
+ Display_ArmorType,
+ Display_ClothingType,
+ Display_CreatureType,
+ Display_WeaponType,
+ Display_RecordState,
+ Display_RefRecordType
+ };
+
+ int mColumnId;
+ int mFlags;
+ Display mDisplayType;
+
+ ColumnBase (int columnId, Display displayType, int flag);
+
+ virtual ~ColumnBase();
+
+ virtual bool isEditable() const = 0;
+
+ virtual bool isUserEditable() const;
+ ///< Can this column be edited directly by the user?
+
+ virtual std::string getTitle() const;
+ };
+
+ template<typename ESXRecordT>
+ struct Column : public ColumnBase
+ {
+ int mFlags;
+
+ Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue)
+ : ColumnBase (columnId, displayType, flags) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const = 0;
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ throw std::logic_error ("Column " + getTitle() + " is not editable");
+ }
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp
new file mode 100644
index 0000000000..1a2bf9df13
--- /dev/null
+++ b/apps/opencs/model/world/columnimp.hpp
@@ -0,0 +1,1221 @@
+#ifndef CSM_WOLRD_COLUMNIMP_H
+#define CSM_WOLRD_COLUMNIMP_H
+
+#include <sstream>
+
+#include <boost/lexical_cast.hpp>
+
+#include <QColor>
+
+#include "columnbase.hpp"
+#include "columns.hpp"
+
+namespace CSMWorld
+{
+ /// \note Shares ID with VarValueColumn. A table can not have both.
+ template<typename ESXRecordT>
+ struct FloatValueColumn : public Column<ESXRecordT>
+ {
+ FloatValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Float) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mValue.getFloat();
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mValue.setFloat (data.toFloat());
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ /// \note Shares ID with IdColumn. A table can not have both.
+ template<typename ESXRecordT>
+ struct StringIdColumn : public Column<ESXRecordT>
+ {
+ StringIdColumn (bool hidden = false)
+ : Column<ESXRecordT> (Columns::ColumnId_Id, ColumnBase::Display_String,
+ hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mId.c_str());
+ }
+
+ virtual bool isEditable() const
+ {
+ return false;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct RecordStateColumn : public Column<ESXRecordT>
+ {
+ RecordStateColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Modification, ColumnBase::Display_RecordState)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ if (record.mState==Record<ESXRecordT>::State_Erased)
+ return static_cast<int> (Record<ESXRecordT>::State_Deleted);
+
+ return static_cast<int> (record.mState);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ record.mState = static_cast<RecordBase::State> (data.toInt());
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+
+ virtual bool isUserEditable() const
+ {
+ return false;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct FixedRecordTypeColumn : public Column<ESXRecordT>
+ {
+ int mType;
+
+ FixedRecordTypeColumn (int type)
+ : Column<ESXRecordT> (Columns::ColumnId_RecordType, ColumnBase::Display_Integer, 0),
+ mType (type)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return mType;
+ }
+
+ virtual bool isEditable() const
+ {
+ return false;
+ }
+ };
+
+ /// \attention A var type column must be immediately followed by a suitable value column.
+ template<typename ESXRecordT>
+ struct VarTypeColumn : public Column<ESXRecordT>
+ {
+ VarTypeColumn (ColumnBase::Display display)
+ : Column<ESXRecordT> (Columns::ColumnId_ValueType, display)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return static_cast<int> (record.get().mValue.getType());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mValue.setType (static_cast<ESM::VarType> (data.toInt()));
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ /// \note Shares ID with FloatValueColumn. A table can not have both.
+ template<typename ESXRecordT>
+ struct VarValueColumn : public Column<ESXRecordT>
+ {
+ VarValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Var) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ switch (record.get().mValue.getType())
+ {
+ case ESM::VT_String:
+
+ return QString::fromUtf8 (record.get().mValue.getString().c_str());
+
+ case ESM::VT_Int:
+ case ESM::VT_Short:
+ case ESM::VT_Long:
+
+ return record.get().mValue.getInteger();
+
+ case ESM::VT_Float:
+
+ return record.get().mValue.getFloat();
+
+ default: return QVariant();
+ }
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ switch (record2.mValue.getType())
+ {
+ case ESM::VT_String:
+
+ record2.mValue.setString (data.toString().toUtf8().constData());
+ break;
+
+ case ESM::VT_Int:
+ case ESM::VT_Short:
+ case ESM::VT_Long:
+
+ record2.mValue.setInteger (data.toInt());
+ break;
+
+ case ESM::VT_Float:
+
+ record2.mValue.setFloat (data.toFloat());
+ break;
+
+ default: break;
+ }
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct DescriptionColumn : public Column<ESXRecordT>
+ {
+ DescriptionColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Description, ColumnBase::Display_String)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mDescription.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mDescription = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SpecialisationColumn : public Column<ESXRecordT>
+ {
+ SpecialisationColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Specialisation, ColumnBase::Display_Specialisation)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mSpecialization;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mSpecialization = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct UseValueColumn : public Column<ESXRecordT>
+ {
+ int mIndex;
+
+ UseValueColumn (int index)
+ : Column<ESXRecordT> (Columns::ColumnId_UseValue1 + index, ColumnBase::Display_Float),
+ mIndex (index)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mUseValue[mIndex];
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mUseValue[mIndex] = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct AttributeColumn : public Column<ESXRecordT>
+ {
+ AttributeColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mAttribute;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mAttribute = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct NameColumn : public Column<ESXRecordT>
+ {
+ NameColumn() : Column<ESXRecordT> (Columns::ColumnId_Name, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mName.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mName = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct AttributesColumn : public Column<ESXRecordT>
+ {
+ int mIndex;
+
+ AttributesColumn (int index)
+ : Column<ESXRecordT> (Columns::ColumnId_Attribute1 + index, ColumnBase::Display_Attribute),
+ mIndex (index)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mAttribute[mIndex];
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mAttribute[mIndex] = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SkillsColumn : public Column<ESXRecordT>
+ {
+ int mIndex;
+ bool mMajor;
+
+ SkillsColumn (int index, bool typePrefix = false, bool major = false)
+ : Column<ESXRecordT> ((typePrefix ? (
+ major ? Columns::ColumnId_MajorSkill1 : Columns::ColumnId_MinorSkill1) :
+ Columns::ColumnId_Skill1) + index, ColumnBase::Display_String),
+ mIndex (index), mMajor (major)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ int skill = record.get().mData.getSkill (mIndex, mMajor);
+
+ return QString::fromUtf8 (ESM::Skill::indexToId (skill).c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ std::istringstream stream (data.toString().toUtf8().constData());
+
+ int index = -1;
+ char c;
+
+ stream >> c >> index;
+
+ if (index!=-1)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.getSkill (mIndex, mMajor) = index;
+
+ record.setModified (record2);
+ }
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct PlayableColumn : public Column<ESXRecordT>
+ {
+ PlayableColumn() : Column<ESXRecordT> (Columns::ColumnId_Playable, ColumnBase::Display_Boolean)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mIsPlayable!=0;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mIsPlayable = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct HiddenColumn : public Column<ESXRecordT>
+ {
+ HiddenColumn() : Column<ESXRecordT> (Columns::ColumnId_Hidden, ColumnBase::Display_Boolean) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mIsHidden!=0;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mIsHidden = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct FlagColumn : public Column<ESXRecordT>
+ {
+ int mMask;
+
+ FlagColumn (int columnId, int mask)
+ : Column<ESXRecordT> (columnId, ColumnBase::Display_Boolean), mMask (mask)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return (record.get().mData.mFlags & mMask)!=0;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ int flags = record2.mData.mFlags & ~mMask;
+
+ if (data.toInt())
+ flags |= mMask;
+
+ record2.mData.mFlags = flags;
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct WeightHeightColumn : public Column<ESXRecordT>
+ {
+ bool mMale;
+ bool mWeight;
+
+ WeightHeightColumn (bool male, bool weight)
+ : Column<ESXRecordT> (male ?
+ (weight ? Columns::ColumnId_MaleWeight : Columns::ColumnId_MaleHeight) :
+ (weight ? Columns::ColumnId_FemaleWeight : Columns::ColumnId_FemaleHeight),
+ ColumnBase::Display_Float),
+ mMale (male), mWeight (weight)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ const ESM::Race::MaleFemaleF& value =
+ mWeight ? record.get().mData.mWeight : record.get().mData.mHeight;
+
+ return mMale ? value.mMale : value.mFemale;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ ESM::Race::MaleFemaleF& value =
+ mWeight ? record2.mData.mWeight : record2.mData.mHeight;
+
+ (mMale ? value.mMale : value.mFemale) = data.toFloat();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SoundParamColumn : public Column<ESXRecordT>
+ {
+ enum Type
+ {
+ Type_Volume,
+ Type_MinRange,
+ Type_MaxRange
+ };
+
+ Type mType;
+
+ SoundParamColumn (Type type)
+ : Column<ESXRecordT> (type==Type_Volume ? Columns::ColumnId_Volume :
+ (type==Type_MinRange ? Columns::ColumnId_MinRange : Columns::ColumnId_MaxRange),
+ ColumnBase::Display_Integer),
+ mType (type)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ int value = 0;
+
+ switch (mType)
+ {
+ case Type_Volume: value = record.get().mData.mVolume; break;
+ case Type_MinRange: value = record.get().mData.mMinRange; break;
+ case Type_MaxRange: value = record.get().mData.mMaxRange; break;
+ }
+
+ return value;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ int value = data.toInt();
+
+ if (value<0)
+ value = 0;
+ else if (value>255)
+ value = 255;
+
+ ESXRecordT record2 = record.get();
+
+ switch (mType)
+ {
+ case Type_Volume: record2.mData.mVolume = static_cast<unsigned char> (value); break;
+ case Type_MinRange: record2.mData.mMinRange = static_cast<unsigned char> (value); break;
+ case Type_MaxRange: record2.mData.mMaxRange = static_cast<unsigned char> (value); break;
+ }
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SoundFileColumn : public Column<ESXRecordT>
+ {
+ SoundFileColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_SoundFile, ColumnBase::Display_String)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mSound.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mSound = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ /// \todo QColor is a GUI class and should not be in model. Need to think of an alternative
+ /// solution.
+ template<typename ESXRecordT>
+ struct MapColourColumn : public Column<ESXRecordT>
+ {
+ /// \todo Replace Display_Integer with something that displays the colour value more directly.
+ MapColourColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_MapColour, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ int colour = record.get().mMapColor;
+
+ return QColor (colour & 0xff, (colour>>8) & 0xff, (colour>>16) & 0xff);
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ QColor colour = data.value<QColor>();
+
+ record2.mMapColor = colour.rgb() & 0xffffff;
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SleepListColumn : public Column<ESXRecordT>
+ {
+ SleepListColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_SleepEncounter, ColumnBase::Display_String)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mSleepList.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mSleepList = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct TextureColumn : public Column<ESXRecordT>
+ {
+ TextureColumn() : Column<ESXRecordT> (Columns::ColumnId_Texture, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mTexture.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mTexture = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SpellTypeColumn : public Column<ESXRecordT>
+ {
+ SpellTypeColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_SpellType, ColumnBase::Display_SpellType)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mType;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mData.mType = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct CostColumn : public Column<ESXRecordT>
+ {
+ CostColumn() : Column<ESXRecordT> (Columns::ColumnId_Cost, ColumnBase::Display_Integer) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mData.mCost;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mData.mCost = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ScriptColumn : public Column<ESXRecordT>
+ {
+ ScriptColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_ScriptText, ColumnBase::Display_Script, 0) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mScriptText.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mScriptText = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct RegionColumn : public Column<ESXRecordT>
+ {
+ RegionColumn() : Column<ESXRecordT> (Columns::ColumnId_Region, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mRegion.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mRegion = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct CellColumn : public Column<ESXRecordT>
+ {
+ CellColumn() : Column<ESXRecordT> (Columns::ColumnId_Cell, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mCellId.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mCellId = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+
+ virtual bool isUserEditable() const
+ {
+ return false;
+ }
+ };
+
+ /// \note Shares ID with StringIdColumn. A table can not have both.
+ template<typename ESXRecordT>
+ struct IdColumn : public Column<ESXRecordT>
+ {
+ IdColumn() : Column<ESXRecordT> (Columns::ColumnId_Id, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mRefID.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mRefID = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ScaleColumn : public Column<ESXRecordT>
+ {
+ ScaleColumn() : Column<ESXRecordT> (Columns::ColumnId_Scale, ColumnBase::Display_Float) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mScale;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mScale = data.toFloat();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct OwnerColumn : public Column<ESXRecordT>
+ {
+ OwnerColumn() : Column<ESXRecordT> (Columns::ColumnId_Owner, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mOwner.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mOwner = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct SoulColumn : public Column<ESXRecordT>
+ {
+ SoulColumn() : Column<ESXRecordT> (Columns::ColumnId_Soul, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mSoul.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mSoul = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct FactionColumn : public Column<ESXRecordT>
+ {
+ FactionColumn() : Column<ESXRecordT> (Columns::ColumnId_Faction, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mFaction.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mFaction = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct FactionIndexColumn : public Column<ESXRecordT>
+ {
+ FactionIndexColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_FactionIndex, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mFactIndex;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mFactIndex = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct ChargesColumn : public Column<ESXRecordT>
+ {
+ ChargesColumn() : Column<ESXRecordT> (Columns::ColumnId_Charges, ColumnBase::Display_Integer) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mCharge;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mCharge = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct EnchantmentChargesColumn : public Column<ESXRecordT>
+ {
+ EnchantmentChargesColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Enchantment, ColumnBase::Display_Float)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mEnchantmentCharge;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mEnchantmentCharge = data.toFloat();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct GoldValueColumn : public Column<ESXRecordT>
+ {
+ GoldValueColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mGoldValue;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mGoldValue = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct TeleportColumn : public Column<ESXRecordT>
+ {
+ TeleportColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_Teleport, ColumnBase::Display_Boolean)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mTeleport;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mTeleport = data.toInt();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct TeleportCellColumn : public Column<ESXRecordT>
+ {
+ TeleportCellColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_TeleportCell, ColumnBase::Display_String)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mDestCell.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mDestCell = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+
+ virtual bool isUserEditable() const
+ {
+ return false;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct LockLevelColumn : public Column<ESXRecordT>
+ {
+ LockLevelColumn()
+ : Column<ESXRecordT> (Columns::ColumnId_LockLevel, ColumnBase::Display_Integer)
+ {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return record.get().mLockLevel;
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+ record2.mLockLevel = data.toInt();
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct KeyColumn : public Column<ESXRecordT>
+ {
+ KeyColumn() : Column<ESXRecordT> (Columns::ColumnId_Key, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mKey.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mKey = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct TrapColumn : public Column<ESXRecordT>
+ {
+ TrapColumn() : Column<ESXRecordT> (Columns::ColumnId_Trap, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mTrap.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mTrap = data.toString().toUtf8().constData();
+
+ record.setModified (record2);
+ }
+
+ virtual bool isEditable() const
+ {
+ return true;
+ }
+ };
+
+ template<typename ESXRecordT>
+ struct FilterColumn : public Column<ESXRecordT>
+ {
+ FilterColumn() : Column<ESXRecordT> (Columns::ColumnId_Filter, ColumnBase::Display_String) {}
+
+ virtual QVariant get (const Record<ESXRecordT>& record) const
+ {
+ return QString::fromUtf8 (record.get().mFilter.c_str());
+ }
+
+ virtual void set (Record<ESXRecordT>& record, const QVariant& data)
+ {
+ ESXRecordT record2 = record.get();
+
+ record2.mFilter = data.toString().toUtf8().constData();
+
+ 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
new file mode 100644
index 0000000000..b206322582
--- /dev/null
+++ b/apps/opencs/model/world/columns.cpp
@@ -0,0 +1,199 @@
+
+#include "columns.hpp"
+
+#include <components/misc/stringops.hpp>
+
+namespace CSMWorld
+{
+ namespace Columns
+ {
+ struct ColumnDesc
+ {
+ int mId;
+ const char *mName;
+ };
+
+ const ColumnDesc sNames[] =
+ {
+ { ColumnId_Value, "Value" },
+ { ColumnId_Id, "ID" },
+ { ColumnId_Modification, "Modified" },
+ { ColumnId_RecordType, "Record Type" },
+ { ColumnId_ValueType, "Value Type" },
+ { ColumnId_Description, "Description" },
+ { ColumnId_Specialisation, "Specialisation" },
+ { ColumnId_Attribute, "Attribute" },
+ { ColumnId_Name, "Name" },
+ { ColumnId_Playable, "Playable" },
+ { ColumnId_Hidden, "Hidden" },
+ { ColumnId_MaleWeight, "Male Weight" },
+ { ColumnId_FemaleWeight, "Female Weight" },
+ { ColumnId_MaleHeight, "Male Height" },
+ { ColumnId_FemaleHeight, "Female Height" },
+ { ColumnId_Volume, "Volume" },
+ { ColumnId_MinRange, "Min Range" },
+ { ColumnId_MaxRange, "Max Range" },
+ { ColumnId_SoundFile, "Sound File" },
+ { ColumnId_MapColour, "Map Colour" },
+ { ColumnId_SleepEncounter, "Sleep Encounter" },
+ { ColumnId_Texture, "Texture" },
+ { ColumnId_SpellType, "Spell Type" },
+ { ColumnId_Cost, "Cost" },
+ { ColumnId_ScriptText, "Script Text" },
+ { ColumnId_Region, "Region" },
+ { ColumnId_Cell, "Cell" },
+ { ColumnId_Scale, "Scale" },
+ { ColumnId_Owner, "Owner" },
+ { ColumnId_Soul, "Soul" },
+ { ColumnId_Faction, "Faction" },
+ { ColumnId_FactionIndex, "Faction Index" },
+ { ColumnId_Charges, "Charges" },
+ { ColumnId_Enchantment, "Enchantment" },
+ { ColumnId_Value, "Coin Value" },
+ { ColumnId_Teleport, "Teleport" },
+ { ColumnId_TeleportCell, "Teleport Cell" },
+ { ColumnId_LockLevel, "Lock Level" },
+ { ColumnId_Key, "Key" },
+ { ColumnId_Trap, "Trap" },
+ { ColumnId_BeastRace, "Beast Race" },
+ { ColumnId_AutoCalc, "Auto Calc" },
+ { ColumnId_StarterSpell, "Starter Spell" },
+ { ColumnId_AlwaysSucceeds, "Always Succeeds" },
+ { ColumnId_SleepForbidden, "Sleep Forbidden" },
+ { ColumnId_InteriorWater, "Interior Water" },
+ { ColumnId_InteriorSky, "Interior Sky" },
+ { ColumnId_Model, "Model" },
+ { ColumnId_Script, "Script" },
+ { ColumnId_Icon, "Icon" },
+ { ColumnId_Weight, "Weight" },
+ { ColumnId_EnchantmentPoints, "Enchantment Points" },
+ { ColumnId_Quality, "Quality" },
+ { ColumnId_Ai, "AI" },
+ { ColumnId_AiHello, "AI Hello" },
+ { ColumnId_AiFlee, "AI Flee" },
+ { ColumnId_AiFight, "AI Fight" },
+ { ColumnId_AiAlarm, "AI Alarm" },
+ { ColumnId_BuysWeapons, "Buys Weapons" },
+ { ColumnId_BuysArmor, "Buys Armor" },
+ { ColumnId_BuysClothing, "Buys Clothing" },
+ { ColumnId_BuysBooks, "Buys Books" },
+ { ColumnId_BuysIngredients, "Buys Ingredients" },
+ { ColumnId_BuysLockpicks, "Buys Lockpicks" },
+ { ColumnId_BuysProbes, "Buys Probes" },
+ { ColumnId_BuysLights, "Buys Lights" },
+ { ColumnId_BuysApparati, "Buys Apparati" },
+ { ColumnId_BuysRepairItems, "Buys Repair Items" },
+ { ColumnId_BuysMiscItems, "Buys Misc Items" },
+ { ColumnId_BuysPotions, "Buys Potions" },
+ { ColumnId_BuysMagicItems, "Buys Magic Items" },
+ { ColumnId_SellsSpells, "Sells Spells" },
+ { ColumnId_Trainer, "Trainer" },
+ { ColumnId_Spellmaking, "Spellmaking" },
+ { ColumnId_EnchantingService, "Enchanting Service" },
+ { ColumnId_RepairService, "Repair Serivce" },
+ { ColumnId_ApparatusType, "Apparatus Type" },
+ { ColumnId_ArmorType, "Armor Type" },
+ { ColumnId_Health, "Health" },
+ { ColumnId_ArmorValue, "Armor Value" },
+ { ColumnId_Scroll, "Scroll" },
+ { ColumnId_ClothingType, "Clothing Type" },
+ { ColumnId_WeightCapacity, "Weight Capacity" },
+ { ColumnId_OrganicContainer, "Organic Container" },
+ { ColumnId_Respawn, "Respawn" },
+ { ColumnId_CreatureType, "Creature Type" },
+ { ColumnId_SoulPoints, "Soul Points" },
+ { ColumnId_OriginalCreature, "Original Creature" },
+ { ColumnId_Biped, "Biped" },
+ { ColumnId_HasWeapon, "Has Weapon" },
+ { ColumnId_NoMovement, "No Movement" },
+ { ColumnId_Swims, "Swims" },
+ { ColumnId_Flies, "Flies" },
+ { ColumnId_Walks, "Walks" },
+ { ColumnId_Essential, "Essential" },
+ { ColumnId_SkeletonBlood, "Skeleton Blood" },
+ { ColumnId_MetalBlood, "Metal Blood" },
+ { ColumnId_OpenSound, "Open Sound" },
+ { ColumnId_CloseSound, "Close Sound" },
+ { ColumnId_Duration, "Duration" },
+ { ColumnId_Radius, "Radius" },
+ { ColumnId_Colour, "Colour" },
+ { ColumnId_Sound, "Sound" },
+ { ColumnId_Dynamic, "Dynamic" },
+ { ColumnId_Portable, "Portable" },
+ { ColumnId_NegativeLight, "Negative Light" },
+ { ColumnId_Flickering, "Flickering" },
+ { ColumnId_SlowFlickering, "Slow Flickering" },
+ { ColumnId_Pulsing, "Pulsing" },
+ { ColumnId_SlowPulsing, "Slow Pulsing" },
+ { ColumnId_Fire, "Fire" },
+ { ColumnId_OffByDefault, "Off by default" },
+ { ColumnId_IsKey, "Is Key" },
+ { ColumnId_Race, "Race" },
+ { ColumnId_Class, "Class" },
+ { Columnid_Hair, "Hair" },
+ { ColumnId_Head, "Head" },
+ { ColumnId_Female, "Female" },
+ { ColumnId_WeaponType, "Weapon Type" },
+ { ColumnId_WeaponSpeed, "Weapon Speed" },
+ { ColumnId_WeaponReach, "Weapon Reach" },
+ { ColumnId_MinChop, "Min Chop" },
+ { ColumnId_MaxChip, "Max Chop" },
+ { Columnid_MinSlash, "Min Slash" },
+ { ColumnId_MaxSlash, "Max Slash" },
+ { ColumnId_MinThrust, "Min Thrust" },
+ { ColumnId_MaxThrust, "Max Thrust" },
+ { ColumnId_Magical, "Magical" },
+ { ColumnId_Silver, "Silver" },
+ { ColumnId_Filter, "Filter" },
+
+ { ColumnId_UseValue1, "Use value 1" },
+ { ColumnId_UseValue2, "Use value 2" },
+ { ColumnId_UseValue3, "Use value 3" },
+ { ColumnId_UseValue4, "Use value 4" },
+
+ { ColumnId_Attribute1, "Attribute 1" },
+ { ColumnId_Attribute2, "Attribute 2" },
+
+ { ColumnId_MajorSkill1, "Major Skill 1" },
+ { ColumnId_MajorSkill2, "Major Skill 2" },
+ { ColumnId_MajorSkill3, "Major Skill 3" },
+ { ColumnId_MajorSkill4, "Major Skill 4" },
+ { ColumnId_MajorSkill5, "Major Skill 5" },
+
+ { ColumnId_MinorSkill1, "Minor Skill 1" },
+ { ColumnId_MinorSkill2, "Minor Skill 2" },
+ { ColumnId_MinorSkill3, "Minor Skill 3" },
+ { ColumnId_MinorSkill4, "Minor Skill 4" },
+ { ColumnId_MinorSkill5, "Minor Skill 5" },
+
+ { ColumnId_Skill1, "Skill 1" },
+ { ColumnId_Skill2, "Skill 2" },
+ { ColumnId_Skill3, "Skill 3" },
+ { ColumnId_Skill4, "Skill 4" },
+ { ColumnId_Skill5, "Skill 5" },
+ { ColumnId_Skill6, "Skill 6" },
+
+ { -1, 0 } // end marker
+ };
+ }
+}
+
+std::string CSMWorld::Columns::getName (ColumnId column)
+{
+ for (int i=0; sNames[i].mName; ++i)
+ if (column==sNames[i].mId)
+ return sNames[i].mName;
+
+ return "";
+}
+
+int CSMWorld::Columns::getId (const std::string& name)
+{
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+
+ for (int i=0; sNames[i].mName; ++i)
+ if (name2==Misc::StringUtils::lowerCase (sNames[i].mName))
+ return sNames[i].mId;
+
+ return -1;
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp
new file mode 100644
index 0000000000..9a39e16780
--- /dev/null
+++ b/apps/opencs/model/world/columns.hpp
@@ -0,0 +1,186 @@
+#ifndef CSM_WOLRD_COLUMNS_H
+#define CSM_WOLRD_COLUMNS_H
+
+#include <string>
+
+namespace CSMWorld
+{
+ namespace Columns
+ {
+ enum ColumnId
+ {
+ ColumnId_Value = 0,
+ ColumnId_Id = 1,
+ ColumnId_Modification = 2,
+ ColumnId_RecordType = 3,
+ ColumnId_ValueType = 4,
+ ColumnId_Description = 5,
+ ColumnId_Specialisation = 6,
+ ColumnId_Attribute = 7,
+ ColumnId_Name = 8,
+ ColumnId_Playable = 9,
+ ColumnId_Hidden = 10,
+ ColumnId_MaleWeight = 11,
+ ColumnId_FemaleWeight = 12,
+ ColumnId_MaleHeight = 13,
+ ColumnId_FemaleHeight = 14,
+ ColumnId_Volume = 15,
+ ColumnId_MinRange = 16,
+ ColumnId_MaxRange = 17,
+ ColumnId_SoundFile = 18,
+ ColumnId_MapColour = 19,
+ ColumnId_SleepEncounter = 20,
+ ColumnId_Texture = 21,
+ ColumnId_SpellType = 22,
+ ColumnId_Cost = 23,
+ ColumnId_ScriptText = 24,
+ ColumnId_Region = 25,
+ ColumnId_Cell = 26,
+ ColumnId_Scale = 27,
+ ColumnId_Owner = 28,
+ ColumnId_Soul = 29,
+ ColumnId_Faction = 30,
+ ColumnId_FactionIndex = 31,
+ ColumnId_Charges = 32,
+ ColumnId_Enchantment = 33,
+ ColumnId_CoinValue = 34,
+ ColumnId_Teleport = 25,
+ ColumnId_TeleportCell = 26,
+ ColumnId_LockLevel = 27,
+ ColumnId_Key = 28,
+ ColumnId_Trap = 29,
+ ColumnId_BeastRace = 30,
+ ColumnId_AutoCalc = 31,
+ ColumnId_StarterSpell = 32,
+ ColumnId_AlwaysSucceeds = 33,
+ ColumnId_SleepForbidden = 34,
+ ColumnId_InteriorWater = 35,
+ ColumnId_InteriorSky = 36,
+ ColumnId_Model = 37,
+ ColumnId_Script = 38,
+ ColumnId_Icon = 39,
+ ColumnId_Weight = 40,
+ ColumnId_EnchantmentPoints = 31,
+ ColumnId_Quality = 32,
+ ColumnId_Ai = 33,
+ ColumnId_AiHello = 34,
+ ColumnId_AiFlee = 35,
+ ColumnId_AiFight = 36,
+ ColumnId_AiAlarm = 37,
+ ColumnId_BuysWeapons = 38,
+ ColumnId_BuysArmor = 39,
+ ColumnId_BuysClothing = 40,
+ ColumnId_BuysBooks = 41,
+ ColumnId_BuysIngredients = 42,
+ ColumnId_BuysLockpicks = 43,
+ ColumnId_BuysProbes = 44,
+ ColumnId_BuysLights = 45,
+ ColumnId_BuysApparati = 46,
+ ColumnId_BuysRepairItems = 47,
+ ColumnId_BuysMiscItems = 48,
+ ColumnId_BuysPotions = 49,
+ ColumnId_BuysMagicItems = 50,
+ ColumnId_SellsSpells = 51,
+ ColumnId_Trainer = 52,
+ ColumnId_Spellmaking = 53,
+ ColumnId_EnchantingService = 54,
+ ColumnId_RepairService = 55,
+ ColumnId_ApparatusType = 56,
+ ColumnId_ArmorType = 57,
+ ColumnId_Health = 58,
+ ColumnId_ArmorValue = 59,
+ ColumnId_Scroll = 60,
+ ColumnId_ClothingType = 61,
+ ColumnId_WeightCapacity = 62,
+ ColumnId_OrganicContainer = 63,
+ ColumnId_Respawn = 64,
+ ColumnId_CreatureType = 65,
+ ColumnId_SoulPoints = 66,
+ ColumnId_OriginalCreature = 67,
+ ColumnId_Biped = 68,
+ ColumnId_HasWeapon = 69,
+ ColumnId_NoMovement = 70,
+ ColumnId_Swims = 71,
+ ColumnId_Flies = 72,
+ ColumnId_Walks = 73,
+ ColumnId_Essential = 74,
+ ColumnId_SkeletonBlood = 75,
+ ColumnId_MetalBlood = 76,
+ ColumnId_OpenSound = 77,
+ ColumnId_CloseSound = 78,
+ ColumnId_Duration = 79,
+ ColumnId_Radius = 80,
+ ColumnId_Colour = 81,
+ ColumnId_Sound = 82,
+ ColumnId_Dynamic = 83,
+ ColumnId_Portable = 84,
+ ColumnId_NegativeLight = 85,
+ ColumnId_Flickering = 86,
+ ColumnId_SlowFlickering = 87,
+ ColumnId_Pulsing = 88,
+ ColumnId_SlowPulsing = 89,
+ ColumnId_Fire = 90,
+ ColumnId_OffByDefault = 91,
+ ColumnId_IsKey = 92,
+ ColumnId_Race = 93,
+ ColumnId_Class = 94,
+ Columnid_Hair = 95,
+ ColumnId_Head = 96,
+ ColumnId_Female = 97,
+ ColumnId_WeaponType = 98,
+ ColumnId_WeaponSpeed = 99,
+ ColumnId_WeaponReach = 100,
+ ColumnId_MinChop = 101,
+ ColumnId_MaxChip = 102,
+ Columnid_MinSlash = 103,
+ ColumnId_MaxSlash = 104,
+ ColumnId_MinThrust = 105,
+ ColumnId_MaxThrust = 106,
+ ColumnId_Magical = 107,
+ ColumnId_Silver = 108,
+ ColumnId_Filter = 109,
+
+ // Allocated to a separate value range, so we don't get a collision should we ever need
+ // to extend the number of use values.
+ ColumnId_UseValue1 = 0x10000,
+ ColumnId_UseValue2 = 0x10001,
+ ColumnId_UseValue3 = 0x10002,
+ ColumnId_UseValue4 = 0x10003,
+
+ // Allocated to a separate value range, so we don't get a collision should we ever need
+ // to extend the number of attributes. Note that this is not the number of different
+ // attributes, but the number of attributes that can be references from a record.
+ ColumnId_Attribute1 = 0x20000,
+ ColumnId_Attribute2 = 0x20001,
+
+ // Allocated to a separate value range, so we don't get a collision should we ever need
+ // to extend the number of skills. Note that this is not the number of different
+ // skills, but the number of skills that can be references from a record.
+ ColumnId_MajorSkill1 = 0x30000,
+ ColumnId_MajorSkill2 = 0x30001,
+ ColumnId_MajorSkill3 = 0x30002,
+ ColumnId_MajorSkill4 = 0x30003,
+ ColumnId_MajorSkill5 = 0x30004,
+
+ ColumnId_MinorSkill1 = 0x40000,
+ ColumnId_MinorSkill2 = 0x40001,
+ ColumnId_MinorSkill3 = 0x40002,
+ ColumnId_MinorSkill4 = 0x40003,
+ ColumnId_MinorSkill5 = 0x40004,
+
+ ColumnId_Skill1 = 0x50000,
+ ColumnId_Skill2 = 0x50001,
+ ColumnId_Skill3 = 0x50002,
+ ColumnId_Skill4 = 0x50003,
+ ColumnId_Skill5 = 0x50004,
+ ColumnId_Skill6 = 0x50005
+ };
+
+ std::string getName (ColumnId column);
+
+ int getId (const std::string& name);
+ ///< Will return -1 for an invalid name.
+ }
+}
+
+#endif
diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp
new file mode 100644
index 0000000000..f6f421c6ae
--- /dev/null
+++ b/apps/opencs/model/world/commands.cpp
@@ -0,0 +1,125 @@
+
+#include "commands.hpp"
+
+#include <QAbstractItemModel>
+
+#include "idtable.hpp"
+#include "idtable.hpp"
+
+CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index,
+ const QVariant& new_, QUndoCommand *parent)
+: QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_)
+{
+ mOld = mModel.data (mIndex, Qt::EditRole);
+
+ setText ("Modify " + mModel.headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString());
+}
+
+void CSMWorld::ModifyCommand::redo()
+{
+ mModel.setData (mIndex, mNew);
+}
+
+void CSMWorld::ModifyCommand::undo()
+{
+ mModel.setData (mIndex, mOld);
+}
+
+CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+: QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None)
+{
+ setText (("Create record " + id).c_str());
+}
+
+void CSMWorld::CreateCommand::addValue (int column, const QVariant& value)
+{
+ mValues[column] = value;
+}
+
+void CSMWorld::CreateCommand::setType (UniversalId::Type type)
+{
+ mType = type;
+}
+
+void CSMWorld::CreateCommand::redo()
+{
+ mModel.addRecord (mId, mType);
+
+ for (std::map<int, QVariant>::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter)
+ mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second);
+}
+
+void CSMWorld::CreateCommand::undo()
+{
+ mModel.removeRow (mModel.getModelIndex (mId, 0).row());
+}
+
+CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+: QUndoCommand (parent), mModel (model), mId (id), mOld (0)
+{
+ setText (("Revert record " + id).c_str());
+
+ mOld = model.getRecord (id).clone();
+}
+
+CSMWorld::RevertCommand::~RevertCommand()
+{
+ delete mOld;
+}
+
+void CSMWorld::RevertCommand::redo()
+{
+ int column = mModel.findColumnIndex (Columns::ColumnId_Modification);
+
+ QModelIndex index = mModel.getModelIndex (mId, column);
+ RecordBase::State state = static_cast<RecordBase::State> (mModel.data (index).toInt());
+
+ if (state==RecordBase::State_ModifiedOnly)
+ {
+ mModel.removeRows (index.row(), 1);
+ }
+ else
+ {
+ mModel.setData (index, static_cast<int> (RecordBase::State_BaseOnly));
+ }
+}
+
+void CSMWorld::RevertCommand::undo()
+{
+ mModel.setRecord (mId, *mOld);
+}
+
+CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+: QUndoCommand (parent), mModel (model), mId (id), mOld (0)
+{
+ setText (("Delete record " + id).c_str());
+
+ mOld = model.getRecord (id).clone();
+}
+
+CSMWorld::DeleteCommand::~DeleteCommand()
+{
+ delete mOld;
+}
+
+void CSMWorld::DeleteCommand::redo()
+{
+ int column = mModel.findColumnIndex (Columns::ColumnId_Modification);
+
+ QModelIndex index = mModel.getModelIndex (mId, column);
+ RecordBase::State state = static_cast<RecordBase::State> (mModel.data (index).toInt());
+
+ if (state==RecordBase::State_ModifiedOnly)
+ {
+ mModel.removeRows (index.row(), 1);
+ }
+ else
+ {
+ mModel.setData (index, static_cast<int> (RecordBase::State_Deleted));
+ }
+}
+
+void CSMWorld::DeleteCommand::undo()
+{
+ mModel.setRecord (mId, *mOld);
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp
new file mode 100644
index 0000000000..3fc2522ea5
--- /dev/null
+++ b/apps/opencs/model/world/commands.hpp
@@ -0,0 +1,104 @@
+#ifndef CSM_WOLRD_COMMANDS_H
+#define CSM_WOLRD_COMMANDS_H
+
+#include "record.hpp"
+
+#include <string>
+#include <map>
+
+#include <QVariant>
+#include <QUndoCommand>
+#include <QModelIndex>
+
+#include "universalid.hpp"
+
+class QModelIndex;
+class QAbstractItemModel;
+
+namespace CSMWorld
+{
+ class IdTable;
+ class IdTable;
+ class RecordBase;
+
+ class ModifyCommand : public QUndoCommand
+ {
+ QAbstractItemModel& mModel;
+ QModelIndex mIndex;
+ QVariant mNew;
+ QVariant mOld;
+
+ public:
+
+ ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_,
+ QUndoCommand *parent = 0);
+
+ virtual void redo();
+
+ virtual void undo();
+ };
+
+ class CreateCommand : public QUndoCommand
+ {
+ IdTable& mModel;
+ std::string mId;
+ UniversalId::Type mType;
+ std::map<int, QVariant> mValues;
+
+ public:
+
+ CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0);
+
+ void setType (UniversalId::Type type);
+
+ void addValue (int column, const QVariant& value);
+
+ virtual void redo();
+
+ virtual void undo();
+ };
+
+ class RevertCommand : public QUndoCommand
+ {
+ IdTable& mModel;
+ std::string mId;
+ RecordBase *mOld;
+
+ // not implemented
+ RevertCommand (const RevertCommand&);
+ RevertCommand& operator= (const RevertCommand&);
+
+ public:
+
+ RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0);
+
+ virtual ~RevertCommand();
+
+ virtual void redo();
+
+ virtual void undo();
+ };
+
+ class DeleteCommand : public QUndoCommand
+ {
+ IdTable& mModel;
+ std::string mId;
+ RecordBase *mOld;
+
+ // not implemented
+ DeleteCommand (const DeleteCommand&);
+ DeleteCommand& operator= (const DeleteCommand&);
+
+ public:
+
+ DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0);
+
+ virtual ~DeleteCommand();
+
+ 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
new file mode 100644
index 0000000000..fbdbb44136
--- /dev/null
+++ b/apps/opencs/model/world/data.cpp
@@ -0,0 +1,442 @@
+
+#include "data.hpp"
+
+#include <stdexcept>
+
+#include <QAbstractItemModel>
+
+#include <components/esm/esmreader.hpp>
+#include <components/esm/defs.hpp>
+#include <components/esm/loadglob.hpp>
+
+#include "idtable.hpp"
+#include "columnimp.hpp"
+#include "regionmap.hpp"
+#include "columns.hpp"
+
+void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type1,
+ UniversalId::Type type2)
+{
+ mModels.push_back (model);
+ mModelIndex.insert (std::make_pair (type1, model));
+
+ if (type2!=UniversalId::Type_None)
+ mModelIndex.insert (std::make_pair (type2, model));
+}
+
+CSMWorld::Data::Data() : mRefs (mCells)
+{
+ mGlobals.addColumn (new StringIdColumn<ESM::Global>);
+ mGlobals.addColumn (new RecordStateColumn<ESM::Global>);
+ mGlobals.addColumn (new FixedRecordTypeColumn<ESM::Global> (UniversalId::Type_Global));
+ mGlobals.addColumn (new VarTypeColumn<ESM::Global> (ColumnBase::Display_GlobalVarType));
+ mGlobals.addColumn (new VarValueColumn<ESM::Global>);
+
+ mGmsts.addColumn (new StringIdColumn<ESM::GameSetting>);
+ mGmsts.addColumn (new RecordStateColumn<ESM::GameSetting>);
+ mGmsts.addColumn (new FixedRecordTypeColumn<ESM::GameSetting> (UniversalId::Type_Gmst));
+ mGmsts.addColumn (new FixedRecordTypeColumn<ESM::GameSetting> (UniversalId::Type_Gmst));
+ mGmsts.addColumn (new VarTypeColumn<ESM::GameSetting> (ColumnBase::Display_GmstVarType));
+ mGmsts.addColumn (new VarValueColumn<ESM::GameSetting>);
+
+ mSkills.addColumn (new StringIdColumn<ESM::Skill>);
+ mSkills.addColumn (new RecordStateColumn<ESM::Skill>);
+ mSkills.addColumn (new FixedRecordTypeColumn<ESM::Skill> (UniversalId::Type_Skill));
+ mSkills.addColumn (new AttributeColumn<ESM::Skill>);
+ mSkills.addColumn (new SpecialisationColumn<ESM::Skill>);
+ for (int i=0; i<4; ++i)
+ mSkills.addColumn (new UseValueColumn<ESM::Skill> (i));
+ mSkills.addColumn (new DescriptionColumn<ESM::Skill>);
+
+ mClasses.addColumn (new StringIdColumn<ESM::Class>);
+ mClasses.addColumn (new RecordStateColumn<ESM::Class>);
+ mClasses.addColumn (new FixedRecordTypeColumn<ESM::Class> (UniversalId::Type_Class));
+ mClasses.addColumn (new NameColumn<ESM::Class>);
+ mClasses.addColumn (new AttributesColumn<ESM::Class> (0));
+ mClasses.addColumn (new AttributesColumn<ESM::Class> (1));
+ mClasses.addColumn (new SpecialisationColumn<ESM::Class>);
+ for (int i=0; i<5; ++i)
+ mClasses.addColumn (new SkillsColumn<ESM::Class> (i, true, true));
+ for (int i=0; i<5; ++i)
+ mClasses.addColumn (new SkillsColumn<ESM::Class> (i, true, false));
+ mClasses.addColumn (new PlayableColumn<ESM::Class>);
+ mClasses.addColumn (new DescriptionColumn<ESM::Class>);
+
+ mFactions.addColumn (new StringIdColumn<ESM::Faction>);
+ mFactions.addColumn (new RecordStateColumn<ESM::Faction>);
+ mFactions.addColumn (new FixedRecordTypeColumn<ESM::Faction> (UniversalId::Type_Faction));
+ mFactions.addColumn (new NameColumn<ESM::Faction>);
+ mFactions.addColumn (new AttributesColumn<ESM::Faction> (0));
+ mFactions.addColumn (new AttributesColumn<ESM::Faction> (1));
+ mFactions.addColumn (new HiddenColumn<ESM::Faction>);
+ for (int i=0; i<6; ++i)
+ mFactions.addColumn (new SkillsColumn<ESM::Faction> (i));
+
+ mRaces.addColumn (new StringIdColumn<ESM::Race>);
+ mRaces.addColumn (new RecordStateColumn<ESM::Race>);
+ mRaces.addColumn (new FixedRecordTypeColumn<ESM::Race> (UniversalId::Type_Race));
+ mRaces.addColumn (new NameColumn<ESM::Race>);
+ mRaces.addColumn (new DescriptionColumn<ESM::Race>);
+ mRaces.addColumn (new FlagColumn<ESM::Race> (Columns::ColumnId_Playable, 0x1));
+ mRaces.addColumn (new FlagColumn<ESM::Race> (Columns::ColumnId_BeastRace, 0x2));
+ mRaces.addColumn (new WeightHeightColumn<ESM::Race> (true, true));
+ mRaces.addColumn (new WeightHeightColumn<ESM::Race> (true, false));
+ mRaces.addColumn (new WeightHeightColumn<ESM::Race> (false, true));
+ mRaces.addColumn (new WeightHeightColumn<ESM::Race> (false, false));
+
+ mSounds.addColumn (new StringIdColumn<ESM::Sound>);
+ mSounds.addColumn (new RecordStateColumn<ESM::Sound>);
+ mSounds.addColumn (new FixedRecordTypeColumn<ESM::Sound> (UniversalId::Type_Sound));
+ mSounds.addColumn (new SoundParamColumn<ESM::Sound> (SoundParamColumn<ESM::Sound>::Type_Volume));
+ mSounds.addColumn (new SoundParamColumn<ESM::Sound> (SoundParamColumn<ESM::Sound>::Type_MinRange));
+ mSounds.addColumn (new SoundParamColumn<ESM::Sound> (SoundParamColumn<ESM::Sound>::Type_MaxRange));
+ mSounds.addColumn (new SoundFileColumn<ESM::Sound>);
+
+ mScripts.addColumn (new StringIdColumn<ESM::Script>);
+ mScripts.addColumn (new RecordStateColumn<ESM::Script>);
+ mScripts.addColumn (new FixedRecordTypeColumn<ESM::Script> (UniversalId::Type_Script));
+ mScripts.addColumn (new ScriptColumn<ESM::Script>);
+
+ mRegions.addColumn (new StringIdColumn<ESM::Region>);
+ mRegions.addColumn (new RecordStateColumn<ESM::Region>);
+ mRegions.addColumn (new FixedRecordTypeColumn<ESM::Region> (UniversalId::Type_Region));
+ mRegions.addColumn (new NameColumn<ESM::Region>);
+ mRegions.addColumn (new MapColourColumn<ESM::Region>);
+ mRegions.addColumn (new SleepListColumn<ESM::Region>);
+
+ mBirthsigns.addColumn (new StringIdColumn<ESM::BirthSign>);
+ mBirthsigns.addColumn (new RecordStateColumn<ESM::BirthSign>);
+ mBirthsigns.addColumn (new FixedRecordTypeColumn<ESM::BirthSign> (UniversalId::Type_Birthsign));
+ mBirthsigns.addColumn (new NameColumn<ESM::BirthSign>);
+ mBirthsigns.addColumn (new TextureColumn<ESM::BirthSign>);
+ mBirthsigns.addColumn (new DescriptionColumn<ESM::BirthSign>);
+
+ mSpells.addColumn (new StringIdColumn<ESM::Spell>);
+ mSpells.addColumn (new RecordStateColumn<ESM::Spell>);
+ mSpells.addColumn (new FixedRecordTypeColumn<ESM::Spell> (UniversalId::Type_Spell));
+ mSpells.addColumn (new NameColumn<ESM::Spell>);
+ mSpells.addColumn (new SpellTypeColumn<ESM::Spell>);
+ mSpells.addColumn (new CostColumn<ESM::Spell>);
+ mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AutoCalc, 0x1));
+ mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2));
+ mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AlwaysSucceeds, 0x4));
+
+ mCells.addColumn (new StringIdColumn<Cell>);
+ mCells.addColumn (new RecordStateColumn<Cell>);
+ mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell));
+ mCells.addColumn (new NameColumn<Cell>);
+ mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep));
+ mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater));
+ mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_InteriorSky, ESM::Cell::QuasiEx));
+ mCells.addColumn (new RegionColumn<Cell>);
+
+ mRefs.addColumn (new StringIdColumn<CellRef> (true));
+ mRefs.addColumn (new RecordStateColumn<CellRef>);
+ mRefs.addColumn (new CellColumn<CellRef>);
+ mRefs.addColumn (new IdColumn<CellRef>);
+ mRefs.addColumn (new ScaleColumn<CellRef>);
+ mRefs.addColumn (new OwnerColumn<CellRef>);
+ mRefs.addColumn (new SoulColumn<CellRef>);
+ mRefs.addColumn (new FactionColumn<CellRef>);
+ mRefs.addColumn (new FactionIndexColumn<CellRef>);
+ mRefs.addColumn (new ChargesColumn<CellRef>);
+ mRefs.addColumn (new EnchantmentChargesColumn<CellRef>);
+ mRefs.addColumn (new GoldValueColumn<CellRef>);
+ mRefs.addColumn (new TeleportColumn<CellRef>);
+ mRefs.addColumn (new TeleportCellColumn<CellRef>);
+ mRefs.addColumn (new LockLevelColumn<CellRef>);
+ mRefs.addColumn (new KeyColumn<CellRef>);
+ mRefs.addColumn (new TrapColumn<CellRef>);
+
+ mFilters.addColumn (new StringIdColumn<CSMFilter::Filter>);
+ mFilters.addColumn (new RecordStateColumn<CSMFilter::Filter>);
+ mFilters.addColumn (new FilterColumn<CSMFilter::Filter>);
+ mFilters.addColumn (new DescriptionColumn<CSMFilter::Filter>);
+
+ addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global);
+ addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst);
+ addModel (new IdTable (&mSkills), UniversalId::Type_Skills, UniversalId::Type_Skill);
+ addModel (new IdTable (&mClasses), UniversalId::Type_Classes, UniversalId::Type_Class);
+ addModel (new IdTable (&mFactions), UniversalId::Type_Factions, UniversalId::Type_Faction);
+ addModel (new IdTable (&mRaces), UniversalId::Type_Races, UniversalId::Type_Race);
+ addModel (new IdTable (&mSounds), UniversalId::Type_Sounds, UniversalId::Type_Sound);
+ addModel (new IdTable (&mScripts), UniversalId::Type_Scripts, UniversalId::Type_Script);
+ addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region);
+ addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign);
+ addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell);
+ addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell);
+ addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables,
+ UniversalId::Type_Referenceable);
+ addModel (new IdTable (&mRefs), UniversalId::Type_References, UniversalId::Type_Reference);
+ addModel (new IdTable (&mFilters), UniversalId::Type_Filters, UniversalId::Type_Filter);
+}
+
+CSMWorld::Data::~Data()
+{
+ for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
+ delete *iter;
+}
+
+const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
+{
+ return mGlobals;
+}
+
+CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals()
+{
+ return mGlobals;
+}
+
+const CSMWorld::IdCollection<ESM::GameSetting>& CSMWorld::Data::getGmsts() const
+{
+ return mGmsts;
+}
+
+CSMWorld::IdCollection<ESM::GameSetting>& CSMWorld::Data::getGmsts()
+{
+ return mGmsts;
+}
+
+const CSMWorld::IdCollection<ESM::Skill>& CSMWorld::Data::getSkills() const
+{
+ return mSkills;
+}
+
+CSMWorld::IdCollection<ESM::Skill>& CSMWorld::Data::getSkills()
+{
+ return mSkills;
+}
+
+const CSMWorld::IdCollection<ESM::Class>& CSMWorld::Data::getClasses() const
+{
+ return mClasses;
+}
+
+CSMWorld::IdCollection<ESM::Class>& CSMWorld::Data::getClasses()
+{
+ return mClasses;
+}
+
+const CSMWorld::IdCollection<ESM::Faction>& CSMWorld::Data::getFactions() const
+{
+ return mFactions;
+}
+
+CSMWorld::IdCollection<ESM::Faction>& CSMWorld::Data::getFactions()
+{
+ return mFactions;
+}
+
+const CSMWorld::IdCollection<ESM::Race>& CSMWorld::Data::getRaces() const
+{
+ return mRaces;
+}
+
+CSMWorld::IdCollection<ESM::Race>& CSMWorld::Data::getRaces()
+{
+ return mRaces;
+}
+
+const CSMWorld::IdCollection<ESM::Sound>& CSMWorld::Data::getSounds() const
+{
+ return mSounds;
+}
+
+CSMWorld::IdCollection<ESM::Sound>& CSMWorld::Data::getSounds()
+{
+ return mSounds;
+}
+
+const CSMWorld::IdCollection<ESM::Script>& CSMWorld::Data::getScripts() const
+{
+ return mScripts;
+}
+
+CSMWorld::IdCollection<ESM::Script>& CSMWorld::Data::getScripts()
+{
+ return mScripts;
+}
+
+const CSMWorld::IdCollection<ESM::Region>& CSMWorld::Data::getRegions() const
+{
+ return mRegions;
+}
+
+CSMWorld::IdCollection<ESM::Region>& CSMWorld::Data::getRegions()
+{
+ return mRegions;
+}
+
+const CSMWorld::IdCollection<ESM::BirthSign>& CSMWorld::Data::getBirthsigns() const
+{
+ return mBirthsigns;
+}
+
+CSMWorld::IdCollection<ESM::BirthSign>& CSMWorld::Data::getBirthsigns()
+{
+ return mBirthsigns;
+}
+
+const CSMWorld::IdCollection<ESM::Spell>& CSMWorld::Data::getSpells() const
+{
+ return mSpells;
+}
+
+CSMWorld::IdCollection<ESM::Spell>& CSMWorld::Data::getSpells()
+{
+ return mSpells;
+}
+
+const CSMWorld::IdCollection<CSMWorld::Cell>& CSMWorld::Data::getCells() const
+{
+ return mCells;
+}
+
+CSMWorld::IdCollection<CSMWorld::Cell>& CSMWorld::Data::getCells()
+{
+ return mCells;
+}
+
+const CSMWorld::RefIdCollection& CSMWorld::Data::getReferenceables() const
+{
+ return mReferenceables;
+}
+
+CSMWorld::RefIdCollection& CSMWorld::Data::getReferenceables()
+{
+ return mReferenceables;
+}
+
+const CSMWorld::RefCollection& CSMWorld::Data::getReferences() const
+{
+ return mRefs;
+}
+
+CSMWorld::RefCollection& CSMWorld::Data::getReferences()
+{
+ return mRefs;
+}
+
+const CSMWorld::IdCollection<CSMFilter::Filter>& CSMWorld::Data::getFilters() const
+{
+ return mFilters;
+}
+
+CSMWorld::IdCollection<CSMFilter::Filter>& CSMWorld::Data::getFilters()
+{
+ return mFilters;
+}
+
+QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id)
+{
+ std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
+
+ if (iter==mModelIndex.end())
+ {
+ // try creating missing (secondary) tables on the fly
+ //
+ // Note: We create these tables here so we don't have to deal with them during load/initial
+ // construction of the ESX data where no update signals are available.
+ if (id.getType()==UniversalId::Type_RegionMap)
+ {
+ RegionMap *table = 0;
+ addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap,
+ UniversalId::Type_None);
+ return table;
+ }
+ throw std::logic_error ("No table model available for " + id.toString());
+ }
+
+ return iter->second;
+}
+
+void CSMWorld::Data::merge()
+{
+ mGlobals.merge();
+}
+
+void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base)
+{
+ ESM::ESMReader reader;
+
+ /// \todo set encoding properly, once config implementation has been fixed.
+ ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding ("win1252"));
+ reader.setEncoder (&encoder);
+
+ reader.open (path.string());
+
+ // Note: We do not need to send update signals here, because at this point the model is not connected
+ // to any view.
+ while (reader.hasMoreRecs())
+ {
+ ESM::NAME n = reader.getRecName();
+ reader.getRecHeader();
+
+ switch (n.val)
+ {
+ case ESM::REC_GLOB: mGlobals.load (reader, base); break;
+ case ESM::REC_GMST: mGmsts.load (reader, base); break;
+ case ESM::REC_SKIL: mSkills.load (reader, base); break;
+ case ESM::REC_CLAS: mClasses.load (reader, base); break;
+ case ESM::REC_FACT: mFactions.load (reader, base); break;
+ case ESM::REC_RACE: mRaces.load (reader, base); break;
+ case ESM::REC_SOUN: mSounds.load (reader, base); break;
+ case ESM::REC_SCPT: mScripts.load (reader, base); break;
+ case ESM::REC_REGN: mRegions.load (reader, base); break;
+ case ESM::REC_BSGN: mBirthsigns.load (reader, base); break;
+ case ESM::REC_SPEL: mSpells.load (reader, base); break;
+
+ case ESM::REC_CELL:
+ mCells.load (reader, base);
+ mRefs.load (reader, mCells.getSize()-1, base);
+ break;
+
+ case ESM::REC_ACTI: mReferenceables.load (reader, base, UniversalId::Type_Activator); break;
+ case ESM::REC_ALCH: mReferenceables.load (reader, base, UniversalId::Type_Potion); break;
+ case ESM::REC_APPA: mReferenceables.load (reader, base, UniversalId::Type_Apparatus); break;
+ case ESM::REC_ARMO: mReferenceables.load (reader, base, UniversalId::Type_Armor); break;
+ case ESM::REC_BOOK: mReferenceables.load (reader, base, UniversalId::Type_Book); break;
+ case ESM::REC_CLOT: mReferenceables.load (reader, base, UniversalId::Type_Clothing); break;
+ case ESM::REC_CONT: mReferenceables.load (reader, base, UniversalId::Type_Container); break;
+ case ESM::REC_CREA: mReferenceables.load (reader, base, UniversalId::Type_Creature); break;
+ case ESM::REC_DOOR: mReferenceables.load (reader, base, UniversalId::Type_Door); break;
+ case ESM::REC_INGR: mReferenceables.load (reader, base, UniversalId::Type_Ingredient); break;
+ case ESM::REC_LEVC:
+ mReferenceables.load (reader, base, UniversalId::Type_CreatureLevelledList); break;
+ case ESM::REC_LEVI:
+ mReferenceables.load (reader, base, UniversalId::Type_ItemLevelledList); break;
+ case ESM::REC_LIGH: mReferenceables.load (reader, base, UniversalId::Type_Light); break;
+ case ESM::REC_LOCK: mReferenceables.load (reader, base, UniversalId::Type_Lockpick); break;
+ case ESM::REC_MISC:
+ mReferenceables.load (reader, base, UniversalId::Type_Miscellaneous); break;
+ case ESM::REC_NPC_: mReferenceables.load (reader, base, UniversalId::Type_Npc); break;
+ case ESM::REC_PROB: mReferenceables.load (reader, base, UniversalId::Type_Probe); break;
+ case ESM::REC_REPA: mReferenceables.load (reader, base, UniversalId::Type_Repair); break;
+ case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break;
+ case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break;
+
+ default:
+
+ /// \todo throw an exception instead, once all records are implemented
+ reader.skipRecord();
+ }
+ }
+}
+
+bool CSMWorld::Data::hasId (const std::string& id) const
+{
+ return
+ getGlobals().searchId (id)!=-1 ||
+ getGmsts().searchId (id)!=-1 ||
+ getSkills().searchId (id)!=-1 ||
+ getClasses().searchId (id)!=-1 ||
+ getFactions().searchId (id)!=-1 ||
+ getRaces().searchId (id)!=-1 ||
+ getSounds().searchId (id)!=-1 ||
+ getScripts().searchId (id)!=-1 ||
+ getRegions().searchId (id)!=-1 ||
+ getBirthsigns().searchId (id)!=-1 ||
+ getSpells().searchId (id)!=-1 ||
+ getCells().searchId (id)!=-1 ||
+ getReferenceables().searchId (id)!=-1;
+}
diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp
new file mode 100644
index 0000000000..2f8a2117e0
--- /dev/null
+++ b/apps/opencs/model/world/data.hpp
@@ -0,0 +1,142 @@
+#ifndef CSM_WOLRD_DATA_H
+#define CSM_WOLRD_DATA_H
+
+#include <map>
+#include <vector>
+
+#include <boost/filesystem/path.hpp>
+
+#include <components/esm/loadglob.hpp>
+#include <components/esm/loadgmst.hpp>
+#include <components/esm/loadskil.hpp>
+#include <components/esm/loadclas.hpp>
+#include <components/esm/loadfact.hpp>
+#include <components/esm/loadrace.hpp>
+#include <components/esm/loadsoun.hpp>
+#include <components/esm/loadscpt.hpp>
+#include <components/esm/loadregn.hpp>
+#include <components/esm/loadbsgn.hpp>
+#include <components/esm/loadspel.hpp>
+
+#include "../filter/filter.hpp"
+
+#include "idcollection.hpp"
+#include "universalid.hpp"
+#include "cell.hpp"
+#include "refidcollection.hpp"
+#include "refcollection.hpp"
+
+class QAbstractItemModel;
+
+namespace CSMWorld
+{
+ class Data
+ {
+ IdCollection<ESM::Global> mGlobals;
+ IdCollection<ESM::GameSetting> mGmsts;
+ IdCollection<ESM::Skill> mSkills;
+ IdCollection<ESM::Class> mClasses;
+ IdCollection<ESM::Faction> mFactions;
+ IdCollection<ESM::Race> mRaces;
+ IdCollection<ESM::Sound> mSounds;
+ IdCollection<ESM::Script> mScripts;
+ IdCollection<ESM::Region> mRegions;
+ IdCollection<ESM::BirthSign> mBirthsigns;
+ IdCollection<ESM::Spell> mSpells;
+ IdCollection<Cell> mCells;
+ RefIdCollection mReferenceables;
+ RefCollection mRefs;
+ IdCollection<CSMFilter::Filter> mFilters;
+ std::vector<QAbstractItemModel *> mModels;
+ std::map<UniversalId::Type, QAbstractItemModel *> mModelIndex;
+
+ // not implemented
+ Data (const Data&);
+ Data& operator= (const Data&);
+
+ void addModel (QAbstractItemModel *model, UniversalId::Type type1,
+ UniversalId::Type type2 = UniversalId::Type_None);
+
+ public:
+
+ Data();
+
+ ~Data();
+
+ const IdCollection<ESM::Global>& getGlobals() const;
+
+ IdCollection<ESM::Global>& getGlobals();
+
+ const IdCollection<ESM::GameSetting>& getGmsts() const;
+
+ IdCollection<ESM::GameSetting>& getGmsts();
+
+ const IdCollection<ESM::Skill>& getSkills() const;
+
+ IdCollection<ESM::Skill>& getSkills();
+
+ const IdCollection<ESM::Class>& getClasses() const;
+
+ IdCollection<ESM::Class>& getClasses();
+
+ const IdCollection<ESM::Faction>& getFactions() const;
+
+ IdCollection<ESM::Faction>& getFactions();
+
+ const IdCollection<ESM::Race>& getRaces() const;
+
+ IdCollection<ESM::Race>& getRaces();
+
+ const IdCollection<ESM::Sound>& getSounds() const;
+
+ IdCollection<ESM::Sound>& getSounds();
+
+ const IdCollection<ESM::Script>& getScripts() const;
+
+ IdCollection<ESM::Script>& getScripts();
+
+ const IdCollection<ESM::Region>& getRegions() const;
+
+ IdCollection<ESM::Region>& getRegions();
+
+ const IdCollection<ESM::BirthSign>& getBirthsigns() const;
+
+ IdCollection<ESM::BirthSign>& getBirthsigns();
+
+ const IdCollection<ESM::Spell>& getSpells() const;
+
+ IdCollection<ESM::Spell>& getSpells();
+
+ const IdCollection<Cell>& getCells() const;
+
+ IdCollection<Cell>& getCells();
+
+ const RefIdCollection& getReferenceables() const;
+
+ RefIdCollection& getReferenceables();
+
+ const RefCollection& getReferences() const;
+
+ RefCollection& getReferences();
+
+ const IdCollection<CSMFilter::Filter>& getFilters() const;
+
+ IdCollection<CSMFilter::Filter>& getFilters();
+
+ QAbstractItemModel *getTableModel (const UniversalId& id);
+ ///< If no table model is available for \a id, an exception is thrown.
+ ///
+ /// \note The returned table may either be the model for the ID itself or the model that
+ /// contains the record specified by the ID.
+
+ void merge();
+ ///< Merge modified into base.
+
+ void loadFile (const boost::filesystem::path& path, bool base);
+ ///< Merging content of a file into base or modified.
+
+ bool hasId (const std::string& id) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp
new file mode 100644
index 0000000000..04e65eea7b
--- /dev/null
+++ b/apps/opencs/model/world/idcollection.hpp
@@ -0,0 +1,86 @@
+#ifndef CSM_WOLRD_IDCOLLECTION_H
+#define CSM_WOLRD_IDCOLLECTION_H
+
+#include <components/esm/esmreader.hpp>
+
+#include "collection.hpp"
+
+namespace CSMWorld
+{
+
+ /// \brief Single type collection of top level records
+ template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
+ class IdCollection : public Collection<ESXRecordT, IdAccessorT>
+ {
+ public:
+
+ void load (ESM::ESMReader& reader, bool base,
+ UniversalId::Type type = UniversalId::Type_None);
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+ };
+
+ template<typename ESXRecordT, typename IdAccessorT>
+ void IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base,
+ UniversalId::Type type)
+ {
+ std::string id = reader.getHNOString ("NAME");
+
+ if (reader.isNextSub ("DELE"))
+ {
+ int index = Collection<ESXRecordT, IdAccessorT>::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)
+ {
+ Collection<ESXRecordT, IdAccessorT>::removeRows (index, 1);
+ }
+ else
+ {
+ Record<ESXRecordT> record = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
+ record.mState = RecordBase::State_Deleted;
+ this->setRecord (index, record);
+ }
+ }
+ else
+ {
+ ESXRecordT record;
+ IdAccessorT().getId (record) = id;
+ record.load (reader);
+
+ int index = this->searchId (IdAccessorT().getId (record));
+
+ if (index==-1)
+ {
+ // new record
+ Record<ESXRecordT> record2;
+ record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
+ (base ? record2.mBase : record2.mModified) = record;
+
+ this->appendRecord (record2);
+ }
+ else
+ {
+ // old record
+ Record<ESXRecordT> record2 = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
+
+ if (base)
+ record2.mBase = record;
+ else
+ record2.setModified (record);
+
+ this->setRecord (index, record2);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp
new file mode 100644
index 0000000000..baaf75289c
--- /dev/null
+++ b/apps/opencs/model/world/idtable.cpp
@@ -0,0 +1,181 @@
+
+#include "idtable.hpp"
+
+#include "collectionbase.hpp"
+#include "columnbase.hpp"
+
+CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection)
+{
+
+}
+
+CSMWorld::IdTable::~IdTable()
+{
+
+}
+
+int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mIdCollection->getSize();
+}
+
+int CSMWorld::IdTable::columnCount (const QModelIndex & parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mIdCollection->getColumns();
+}
+
+QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const
+{
+ if (role!=Qt::DisplayRole && role!=Qt::EditRole)
+ return QVariant();
+
+ if (role==Qt::EditRole && !mIdCollection->getColumn (index.column()).isEditable())
+ return QVariant();
+
+ return mIdCollection->getData (index.row(), index.column());
+}
+
+QVariant CSMWorld::IdTable::headerData (int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation==Qt::Vertical)
+ return QVariant();
+
+ if (role==Qt::DisplayRole)
+ return tr (mIdCollection->getColumn (section).getTitle().c_str());
+
+ if (role==ColumnBase::Role_Flags)
+ return mIdCollection->getColumn (section).mFlags;
+
+ if (role==ColumnBase::Role_Display)
+ return mIdCollection->getColumn (section).mDisplayType;
+
+ return QVariant();
+}
+
+bool CSMWorld::IdTable::setData ( const QModelIndex &index, const QVariant &value, int role)
+{
+ if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole)
+ {
+ mIdCollection->setData (index.row(), index.column(), value);
+
+ emit dataChanged (CSMWorld::IdTable::index (index.row(), 0),
+ CSMWorld::IdTable::index (index.row(), mIdCollection->getColumns()-1));
+
+ return true;
+ }
+
+ return false;
+}
+
+Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const
+{
+ Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+
+ if (mIdCollection->getColumn (index.column()).isUserEditable())
+ flags |= Qt::ItemIsEditable;
+
+ return flags;
+}
+
+bool CSMWorld::IdTable::removeRows (int row, int count, const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return false;
+
+ beginRemoveRows (parent, row, row+count-1);
+
+ mIdCollection->removeRows (row, count);
+
+ endRemoveRows();
+
+ return true;
+}
+
+QModelIndex CSMWorld::IdTable::index (int row, int column, const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return QModelIndex();
+
+ if (row<0 || row>=mIdCollection->getSize())
+ return QModelIndex();
+
+ if (column<0 || column>=mIdCollection->getColumns())
+ return QModelIndex();
+
+ return createIndex (row, column);
+}
+
+QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const
+{
+ return QModelIndex();
+}
+
+void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type)
+{
+ int index = mIdCollection->getAppendIndex();
+
+ beginInsertRows (QModelIndex(), index, index);
+
+ mIdCollection->appendBlankRecord (id, type);
+
+ endInsertRows();
+}
+
+QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const
+{
+ return index (mIdCollection->getIndex (id), column);
+}
+
+void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record)
+{
+ int index = mIdCollection->searchId (id);
+
+ if (index==-1)
+ {
+ int index = mIdCollection->getAppendIndex();
+
+ beginInsertRows (QModelIndex(), index, index);
+
+ mIdCollection->appendRecord (record);
+
+ endInsertRows();
+ }
+ else
+ {
+ mIdCollection->replace (index, record);
+ emit dataChanged (CSMWorld::IdTable::index (index, 0),
+ CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1));
+ }
+}
+
+const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const
+{
+ return mIdCollection->getRecord (id);
+}
+
+int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const
+{
+ int columns = mIdCollection->getColumns();
+
+ for (int i=0; i<columns; ++i)
+ if (mIdCollection->getColumn (i).mColumnId==id)
+ return i;
+
+ return -1;
+}
+
+int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const
+{
+ int index = searchColumnIndex (id);
+
+ if (index==-1)
+ throw std::logic_error ("invalid column index");
+
+ return index;
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp
new file mode 100644
index 0000000000..00c5772366
--- /dev/null
+++ b/apps/opencs/model/world/idtable.hpp
@@ -0,0 +1,69 @@
+#ifndef CSM_WOLRD_IDTABLE_H
+#define CSM_WOLRD_IDTABLE_H
+
+#include <QAbstractItemModel>
+
+#include "universalid.hpp"
+#include "columns.hpp"
+
+namespace CSMWorld
+{
+ class CollectionBase;
+ class RecordBase;
+
+ class IdTable : public QAbstractItemModel
+ {
+ Q_OBJECT
+
+ CollectionBase *mIdCollection;
+
+ // not implemented
+ IdTable (const IdTable&);
+ IdTable& operator= (const IdTable&);
+
+ public:
+
+ IdTable (CollectionBase *idCollection);
+ ///< The ownership of \a idCollection is not transferred.
+
+ virtual ~IdTable();
+
+ virtual int rowCount (const QModelIndex & parent = QModelIndex()) const;
+
+ virtual int columnCount (const QModelIndex & parent = QModelIndex()) const;
+
+ virtual QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+ virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ virtual bool setData ( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ virtual Qt::ItemFlags flags (const QModelIndex & index) const;
+
+ virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex());
+
+ virtual QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex())
+ const;
+
+ virtual QModelIndex parent (const QModelIndex& index) const;
+
+ void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None);
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ QModelIndex getModelIndex (const std::string& id, int column) const;
+
+ void setRecord (const std::string& id, const RecordBase& record);
+ ///< Add record or overwrite existing recrod.
+
+ const RecordBase& getRecord (const std::string& id) const;
+
+ int searchColumnIndex (Columns::ColumnId id) const;
+ ///< Return index of column with the given \a id. If no such column exists, -1 is returned.
+
+ int findColumnIndex (Columns::ColumnId id) const;
+ ///< Return index of column with the given \a id. If no such column exists, an exception is
+ /// thrown.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp
new file mode 100644
index 0000000000..2b757adfe5
--- /dev/null
+++ b/apps/opencs/model/world/idtableproxymodel.cpp
@@ -0,0 +1,48 @@
+
+#include "idtableproxymodel.hpp"
+
+#include <vector>
+
+#include "idtable.hpp"
+
+void CSMWorld::IdTableProxyModel::updateColumnMap()
+{
+ mColumnMap.clear();
+
+ if (mFilter)
+ {
+ std::vector<int> columns = mFilter->getReferencedColumns();
+
+ const IdTable& table = dynamic_cast<const IdTable&> (*sourceModel());
+
+ for (std::vector<int>::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter)
+ mColumnMap.insert (std::make_pair (*iter,
+ table.searchColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (*iter))));
+ }
+}
+
+bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent)
+ const
+{
+ if (!mFilter)
+ return true;
+
+ return mFilter->test (
+ dynamic_cast<IdTable&> (*sourceModel()), sourceRow, mColumnMap);
+}
+
+CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
+: QSortFilterProxyModel (parent)
+{}
+
+QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
+{
+ return mapFromSource (dynamic_cast<IdTable&> (*sourceModel()).getModelIndex (id, column));
+}
+
+void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter)
+{
+ mFilter = filter;
+ updateColumnMap();
+ invalidateFilter();
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp
new file mode 100644
index 0000000000..b63dccd5e2
--- /dev/null
+++ b/apps/opencs/model/world/idtableproxymodel.hpp
@@ -0,0 +1,39 @@
+#ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H
+#define CSM_WOLRD_IDTABLEPROXYMODEL_H
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+
+#include <QSortFilterProxyModel>
+
+#include "../filter/node.hpp"
+
+namespace CSMWorld
+{
+ class IdTableProxyModel : public QSortFilterProxyModel
+ {
+ Q_OBJECT
+
+ boost::shared_ptr<CSMFilter::Node> mFilter;
+ std::map<int, int> mColumnMap; // column ID, column index in this model (or -1)
+
+ private:
+
+ void updateColumnMap();
+
+ bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const;
+
+ public:
+
+ IdTableProxyModel (QObject *parent = 0);
+
+ virtual QModelIndex getModelIndex (const std::string& id, int column) const;
+
+ void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter);
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp
new file mode 100644
index 0000000000..14f63c155d
--- /dev/null
+++ b/apps/opencs/model/world/record.cpp
@@ -0,0 +1,21 @@
+
+#include "record.hpp"
+
+CSMWorld::RecordBase::~RecordBase() {}
+
+bool CSMWorld::RecordBase::isDeleted() const
+{
+ return mState==State_Deleted || mState==State_Erased;
+}
+
+
+bool CSMWorld::RecordBase::isErased() const
+{
+ return mState==State_Erased;
+}
+
+
+bool CSMWorld::RecordBase::isModified() const
+{
+ return mState==State_Modified || mState==State_ModifiedOnly;
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp
new file mode 100644
index 0000000000..861fc47a3b
--- /dev/null
+++ b/apps/opencs/model/world/record.hpp
@@ -0,0 +1,127 @@
+#ifndef CSM_WOLRD_RECORD_H
+#define CSM_WOLRD_RECORD_H
+
+#include <stdexcept>
+
+namespace CSMWorld
+{
+ struct RecordBase
+ {
+ enum State
+ {
+ State_BaseOnly = 0, // defined in base only
+ State_Modified = 1, // exists in base, but has been modified
+ State_ModifiedOnly = 2, // newly created in modified
+ State_Deleted = 3, // exists in base, but has been deleted
+ State_Erased = 4 // does not exist at all (we mostly treat that the same way as deleted)
+ };
+
+ State mState;
+
+ virtual ~RecordBase();
+
+ virtual RecordBase *clone() const = 0;
+
+ virtual void assign (const RecordBase& record) = 0;
+ ///< Will throw an exception if the types don't match.
+
+ bool isDeleted() const;
+
+ bool isErased() const;
+
+ bool isModified() const;
+ };
+
+ template <typename ESXRecordT>
+ struct Record : public RecordBase
+ {
+ ESXRecordT mBase;
+ ESXRecordT mModified;
+
+ virtual RecordBase *clone() const;
+
+ virtual void assign (const RecordBase& record);
+
+ const ESXRecordT& get() const;
+ ///< Throws an exception, if the record is deleted.
+
+ ESXRecordT& get();
+ ///< Throws an exception, if the record is deleted.
+
+ const ESXRecordT& getBase() const;
+ ///< Throws an exception, if the record is deleted. Returns modified, if there is no base.
+
+ void setModified (const ESXRecordT& modified);
+ ///< Throws an exception, if the record is deleted.
+
+ void merge();
+ ///< Merge modified into base.
+ };
+
+ template <typename ESXRecordT>
+ RecordBase *Record<ESXRecordT>::clone() const
+ {
+ return new Record<ESXRecordT> (*this);
+ }
+
+ template <typename ESXRecordT>
+ void Record<ESXRecordT>::assign (const RecordBase& record)
+ {
+ *this = dynamic_cast<const Record<ESXRecordT>& > (record);
+ }
+
+ template <typename ESXRecordT>
+ const ESXRecordT& Record<ESXRecordT>::get() const
+ {
+ if (mState==State_Erased)
+ throw std::logic_error ("attempt to access a deleted record");
+
+ return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified;
+ }
+
+ template <typename ESXRecordT>
+ ESXRecordT& Record<ESXRecordT>::get()
+ {
+ if (mState==State_Erased)
+ throw std::logic_error ("attempt to access a deleted record");
+
+ return mState==State_BaseOnly || mState==State_Deleted ? mBase : mModified;
+ }
+
+ template <typename ESXRecordT>
+ const ESXRecordT& Record<ESXRecordT>::getBase() const
+ {
+ if (mState==State_Erased)
+ throw std::logic_error ("attempt to access a deleted record");
+
+ return mState==State_ModifiedOnly ? mModified : mBase;
+ }
+
+ template <typename ESXRecordT>
+ void Record<ESXRecordT>::setModified (const ESXRecordT& modified)
+ {
+ if (mState==State_Erased)
+ throw std::logic_error ("attempt to modify a deleted record");
+
+ mModified = modified;
+
+ if (mState!=State_ModifiedOnly)
+ mState = State_Modified;
+ }
+
+ template <typename ESXRecordT>
+ void Record<ESXRecordT>::merge()
+ {
+ if (isModified())
+ {
+ mBase = mModified;
+ mState = State_BaseOnly;
+ }
+ else if (mState==State_Deleted)
+ {
+ mState = State_Erased;
+ }
+ }
+}
+
+#endif
diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp
new file mode 100644
index 0000000000..af363bafb4
--- /dev/null
+++ b/apps/opencs/model/world/ref.cpp
@@ -0,0 +1,13 @@
+
+#include "ref.hpp"
+
+#include "cell.hpp"
+
+void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id)
+{
+ mId = id;
+ mCellId = cell.mId;
+
+ if (!mDeleted)
+ cell.addRef (mId);
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp
new file mode 100644
index 0000000000..3d107d6754
--- /dev/null
+++ b/apps/opencs/model/world/ref.hpp
@@ -0,0 +1,26 @@
+#ifndef CSM_WOLRD_REF_H
+#define CSM_WOLRD_REF_H
+
+#include <components/esm/cellref.hpp>
+
+namespace ESM
+{
+ class ESMReader;
+}
+
+namespace CSMWorld
+{
+ class Cell;
+
+ /// \brief Wrapper for CellRef sub record
+ struct CellRef : public ESM::CellRef
+ {
+ std::string mId;
+ std::string mCellId;
+
+ void load (ESM::ESMReader &esm, Cell& cell, const std::string& id);
+ ///< Load cell ref and register it with \a cell.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp
new file mode 100644
index 0000000000..085817753e
--- /dev/null
+++ b/apps/opencs/model/world/refcollection.cpp
@@ -0,0 +1,41 @@
+
+#include "refcollection.hpp"
+
+#include <sstream>
+
+#include "ref.hpp"
+#include "cell.hpp"
+
+CSMWorld::RefCollection::RefCollection (Collection<Cell>& cells)
+: mCells (cells), mNextId (0)
+{}
+
+void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base)
+{
+ Record<Cell> cell = mCells.getRecord (cellIndex);
+
+ Cell& cell2 = base ? cell.mBase : cell.mModified;
+
+ CellRef ref;
+
+ while (cell2.getNextRef (reader, ref))
+ {
+ /// \todo handle deleted and moved references
+ ref.load (reader, cell2, getNewId());
+
+ Record<CellRef> record2;
+ record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
+ (base ? record2.mBase : record2.mModified) = ref;
+
+ appendRecord (record2);
+ }
+
+ mCells.setRecord (cellIndex, cell);
+}
+
+std::string CSMWorld::RefCollection::getNewId()
+{
+ std::ostringstream stream;
+ stream << "ref#" << mNextId++;
+ return stream.str();
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp
new file mode 100644
index 0000000000..895315a179
--- /dev/null
+++ b/apps/opencs/model/world/refcollection.hpp
@@ -0,0 +1,29 @@
+#ifndef CSM_WOLRD_REFCOLLECTION_H
+#define CSM_WOLRD_REFCOLLECTION_H
+
+#include "collection.hpp"
+#include "ref.hpp"
+#include "record.hpp"
+
+namespace CSMWorld
+{
+ struct Cell;
+
+ /// \brief References in cells
+ class RefCollection : public Collection<CellRef>
+ {
+ Collection<Cell>& mCells;
+ int mNextId;
+
+ public:
+
+ RefCollection (Collection<Cell>& cells);
+
+ void load (ESM::ESMReader& reader, int cellIndex, bool base);
+ ///< Load a sequence of references.
+
+ std::string getNewId();
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/refidadapter.cpp b/apps/opencs/model/world/refidadapter.cpp
new file mode 100644
index 0000000000..94ae38c3c2
--- /dev/null
+++ b/apps/opencs/model/world/refidadapter.cpp
@@ -0,0 +1,6 @@
+
+#include "refidadapter.hpp"
+
+CSMWorld::RefIdAdapter::RefIdAdapter() {}
+
+CSMWorld::RefIdAdapter::~RefIdAdapter() {} \ No newline at end of file
diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp
new file mode 100644
index 0000000000..df0ceae706
--- /dev/null
+++ b/apps/opencs/model/world/refidadapter.hpp
@@ -0,0 +1,37 @@
+#ifndef CSM_WOLRD_REFIDADAPTER_H
+#define CSM_WOLRD_REFIDADAPTER_H
+
+#include <string>
+
+class QVariant;
+
+namespace CSMWorld
+{
+ class RefIdColumn;
+ class RefIdData;
+ class RecordBase;
+
+ class RefIdAdapter
+ {
+ // not implemented
+ RefIdAdapter (const RefIdAdapter&);
+ RefIdAdapter& operator= (const RefIdAdapter&);
+
+ public:
+
+ RefIdAdapter();
+
+ virtual ~RefIdAdapter();
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int idnex)
+ const = 0;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const = 0;
+ ///< If the data type does not match an exception is thrown.
+
+ virtual std::string getId (const RecordBase& record) const = 0;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp
new file mode 100644
index 0000000000..f00e9fc77b
--- /dev/null
+++ b/apps/opencs/model/world/refidadapterimp.cpp
@@ -0,0 +1,575 @@
+
+#include "refidadapterimp.hpp"
+
+CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const InventoryColumns& columns,
+ const RefIdColumn *autoCalc)
+: InventoryRefIdAdapter<ESM::Potion> (UniversalId::Type_Potion, columns),
+ mAutoCalc (autoCalc)
+{}
+
+QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Potion>& record = static_cast<const Record<ESM::Potion>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion)));
+
+ if (column==mAutoCalc)
+ return record.get().mData.mAutoCalc!=0;
+
+ return InventoryRefIdAdapter<ESM::Potion>::getData (column, data, index);
+}
+
+void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Potion>& record = static_cast<Record<ESM::Potion>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion)));
+
+ if (column==mAutoCalc)
+ record.get().mData.mAutoCalc = value.toInt();
+ else
+ InventoryRefIdAdapter<ESM::Potion>::setData (column, data, index, value);
+}
+
+
+CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns,
+ const RefIdColumn *type, const RefIdColumn *quality)
+: InventoryRefIdAdapter<ESM::Apparatus> (UniversalId::Type_Apparatus, columns),
+ mType (type), mQuality (quality)
+{}
+
+QVariant CSMWorld::ApparatusRefIdAdapter::getData (const RefIdColumn *column,
+ const RefIdData& data, int index) const
+{
+ const Record<ESM::Apparatus>& record = static_cast<const Record<ESM::Apparatus>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus)));
+
+ if (column==mType)
+ return record.get().mData.mType;
+
+ if (column==mQuality)
+ return record.get().mData.mQuality;
+
+ return InventoryRefIdAdapter<ESM::Apparatus>::getData (column, data, index);
+}
+
+void CSMWorld::ApparatusRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Apparatus>& record = static_cast<Record<ESM::Apparatus>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus)));
+
+ if (column==mType)
+ record.get().mData.mType = value.toInt();
+ else if (column==mQuality)
+ record.get().mData.mQuality = value.toFloat();
+ else
+ InventoryRefIdAdapter<ESM::Apparatus>::setData (column, data, index, value);
+}
+
+
+CSMWorld::ArmorRefIdAdapter::ArmorRefIdAdapter (const EnchantableColumns& columns,
+ const RefIdColumn *type, const RefIdColumn *health, const RefIdColumn *armor)
+: EnchantableRefIdAdapter<ESM::Armor> (UniversalId::Type_Armor, columns),
+ mType (type), mHealth (health), mArmor (armor)
+{}
+
+QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column,
+ const RefIdData& data, int index) const
+{
+ const Record<ESM::Armor>& record = static_cast<const Record<ESM::Armor>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor)));
+
+ if (column==mType)
+ return record.get().mData.mType;
+
+ if (column==mHealth)
+ return record.get().mData.mHealth;
+
+ if (column==mArmor)
+ return record.get().mData.mArmor;
+
+ return EnchantableRefIdAdapter<ESM::Armor>::getData (column, data, index);
+}
+
+void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Armor>& record = static_cast<Record<ESM::Armor>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor)));
+
+ if (column==mType)
+ record.get().mData.mType = value.toInt();
+ else if (column==mHealth)
+ record.get().mData.mHealth = value.toInt();
+ else if (column==mArmor)
+ record.get().mData.mArmor = value.toInt();
+ else
+ EnchantableRefIdAdapter<ESM::Armor>::setData (column, data, index, value);
+}
+
+CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns,
+ const RefIdColumn *scroll, const RefIdColumn *skill)
+: EnchantableRefIdAdapter<ESM::Book> (UniversalId::Type_Book, columns),
+ mScroll (scroll), mSkill (skill)
+{}
+
+QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column,
+ const RefIdData& data, int index) const
+{
+ const Record<ESM::Book>& record = static_cast<const Record<ESM::Book>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book)));
+
+ if (column==mScroll)
+ return record.get().mData.mIsScroll!=0;
+
+ if (column==mSkill)
+ return record.get().mData.mSkillID;
+
+ return EnchantableRefIdAdapter<ESM::Book>::getData (column, data, index);
+}
+
+void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Book>& record = static_cast<Record<ESM::Book>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book)));
+
+ if (column==mScroll)
+ record.get().mData.mIsScroll = value.toInt();
+ else if (column==mSkill)
+ record.get().mData.mSkillID = value.toInt();
+ else
+ EnchantableRefIdAdapter<ESM::Book>::setData (column, data, index, value);
+}
+
+CSMWorld::ClothingRefIdAdapter::ClothingRefIdAdapter (const EnchantableColumns& columns,
+ const RefIdColumn *type)
+: EnchantableRefIdAdapter<ESM::Clothing> (UniversalId::Type_Clothing, columns), mType (type)
+{}
+
+QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column,
+ const RefIdData& data, int index) const
+{
+ const Record<ESM::Clothing>& record = static_cast<const Record<ESM::Clothing>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing)));
+
+ if (column==mType)
+ return record.get().mData.mType;
+
+ return EnchantableRefIdAdapter<ESM::Clothing>::getData (column, data, index);
+}
+
+void CSMWorld::ClothingRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Clothing>& record = static_cast<Record<ESM::Clothing>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing)));
+
+ if (column==mType)
+ record.get().mData.mType = value.toInt();
+ else
+ EnchantableRefIdAdapter<ESM::Clothing>::setData (column, data, index, value);
+}
+
+CSMWorld::ContainerRefIdAdapter::ContainerRefIdAdapter (const NameColumns& columns,
+ const RefIdColumn *weight, const RefIdColumn *organic, const RefIdColumn *respawn)
+: NameRefIdAdapter<ESM::Container> (UniversalId::Type_Container, columns), mWeight (weight),
+ mOrganic (organic), mRespawn (respawn)
+{}
+
+QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Container>& record = static_cast<const Record<ESM::Container>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container)));
+
+ if (column==mWeight)
+ return record.get().mWeight;
+
+ if (column==mOrganic)
+ return (record.get().mFlags & ESM::Container::Organic)!=0;
+
+ if (column==mRespawn)
+ return (record.get().mFlags & ESM::Container::Respawn)!=0;
+
+ return NameRefIdAdapter<ESM::Container>::getData (column, data, index);
+}
+
+void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Container>& record = static_cast<Record<ESM::Container>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container)));
+
+ if (column==mWeight)
+ record.get().mWeight = value.toFloat();
+ else if (column==mOrganic)
+ {
+ if (value.toInt())
+ record.get().mFlags |= ESM::Container::Organic;
+ else
+ record.get().mFlags &= ~ESM::Container::Organic;
+ }
+ else if (column==mRespawn)
+ {
+ if (value.toInt())
+ record.get().mFlags |= ESM::Container::Respawn;
+ else
+ record.get().mFlags &= ~ESM::Container::Respawn;
+ }
+ else
+ NameRefIdAdapter<ESM::Container>::setData (column, data, index, value);
+}
+
+CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns)
+: ActorColumns (actorColumns)
+{}
+
+CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns)
+: ActorRefIdAdapter<ESM::Creature> (UniversalId::Type_Creature, columns), mColumns (columns)
+{}
+
+QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Creature>& record = static_cast<const Record<ESM::Creature>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature)));
+
+ if (column==mColumns.mType)
+ return record.get().mData.mType;
+
+ if (column==mColumns.mSoul)
+ return record.get().mData.mSoul;
+
+ if (column==mColumns.mScale)
+ return record.get().mScale;
+
+ if (column==mColumns.mOriginal)
+ return QString::fromUtf8 (record.get().mOriginal.c_str());
+
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ return (record.get().mFlags & iter->second)!=0;
+
+ return ActorRefIdAdapter<ESM::Creature>::getData (column, data, index);
+}
+
+void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Creature>& record = static_cast<Record<ESM::Creature>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature)));
+
+ if (column==mColumns.mType)
+ record.get().mData.mType = value.toInt();
+ else if (column==mColumns.mSoul)
+ record.get().mData.mSoul = value.toInt();
+ else if (column==mColumns.mScale)
+ record.get().mScale = value.toFloat();
+ else if (column==mColumns.mOriginal)
+ record.get().mOriginal = value.toString().toUtf8().constData();
+ else
+ {
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ {
+ if (value.toInt()!=0)
+ record.get().mFlags |= iter->second;
+ else
+ record.get().mFlags &= ~iter->second;
+ }
+ else
+ ActorRefIdAdapter<ESM::Creature>::setData (column, data, index, value);
+ }
+}
+
+CSMWorld::DoorRefIdAdapter::DoorRefIdAdapter (const NameColumns& columns,
+ const RefIdColumn *openSound, const RefIdColumn *closeSound)
+: NameRefIdAdapter<ESM::Door> (UniversalId::Type_Door, columns), mOpenSound (openSound),
+ mCloseSound (closeSound)
+{}
+
+QVariant CSMWorld::DoorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Door>& record = static_cast<const Record<ESM::Door>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door)));
+
+ if (column==mOpenSound)
+ return QString::fromUtf8 (record.get().mOpenSound.c_str());
+
+ if (column==mCloseSound)
+ return QString::fromUtf8 (record.get().mCloseSound.c_str());
+
+ return NameRefIdAdapter<ESM::Door>::getData (column, data, index);
+}
+
+void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Door>& record = static_cast<Record<ESM::Door>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door)));
+
+ if (column==mOpenSound)
+ record.get().mOpenSound = value.toString().toUtf8().constData();
+ else if (column==mCloseSound)
+ record.get().mCloseSound = value.toString().toUtf8().constData();
+ else
+ NameRefIdAdapter<ESM::Door>::setData (column, data, index, value);
+}
+
+CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns)
+: InventoryColumns (columns) {}
+
+CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
+: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns)
+{}
+
+QVariant CSMWorld::LightRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Light>& record = static_cast<const Record<ESM::Light>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light)));
+
+ if (column==mColumns.mTime)
+ return record.get().mData.mTime;
+
+ if (column==mColumns.mRadius)
+ return record.get().mData.mRadius;
+
+ if (column==mColumns.mColor)
+ return record.get().mData.mColor;
+
+ if (column==mColumns.mSound)
+ return QString::fromUtf8 (record.get().mSound.c_str());
+
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ return (record.get().mData.mFlags & iter->second)!=0;
+
+ return InventoryRefIdAdapter<ESM::Light>::getData (column, data, index);
+}
+
+void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Light>& record = static_cast<Record<ESM::Light>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light)));
+
+ if (column==mColumns.mTime)
+ record.get().mData.mTime = value.toInt();
+ else if (column==mColumns.mRadius)
+ record.get().mData.mRadius = value.toInt();
+ else if (column==mColumns.mColor)
+ record.get().mData.mColor = value.toInt();
+ else if (column==mColumns.mSound)
+ record.get().mSound = value.toString().toUtf8().constData();
+ else
+ {
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ {
+ if (value.toInt()!=0)
+ record.get().mData.mFlags |= iter->second;
+ else
+ record.get().mData.mFlags &= ~iter->second;
+ }
+ else
+ InventoryRefIdAdapter<ESM::Light>::setData (column, data, index, value);
+ }
+}
+
+CSMWorld::MiscRefIdAdapter::MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key)
+: InventoryRefIdAdapter<ESM::Miscellaneous> (UniversalId::Type_Miscellaneous, columns), mKey (key)
+{}
+
+QVariant CSMWorld::MiscRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Miscellaneous>& record = static_cast<const Record<ESM::Miscellaneous>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous)));
+
+ if (column==mKey)
+ return record.get().mData.mIsKey!=0;
+
+ return InventoryRefIdAdapter<ESM::Miscellaneous>::getData (column, data, index);
+}
+
+void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Miscellaneous>& record = static_cast<Record<ESM::Miscellaneous>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous)));
+
+ if (column==mKey)
+ record.get().mData.mIsKey = value.toInt();
+ else
+ InventoryRefIdAdapter<ESM::Miscellaneous>::setData (column, data, index, value);
+}
+
+CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns) {}
+
+CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns)
+: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns)
+{}
+
+QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const
+{
+ const Record<ESM::NPC>& record = static_cast<const Record<ESM::NPC>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
+
+ if (column==mColumns.mRace)
+ return QString::fromUtf8 (record.get().mRace.c_str());
+
+ if (column==mColumns.mClass)
+ return QString::fromUtf8 (record.get().mClass.c_str());
+
+ if (column==mColumns.mFaction)
+ return QString::fromUtf8 (record.get().mFaction.c_str());
+
+ if (column==mColumns.mHair)
+ return QString::fromUtf8 (record.get().mHair.c_str());
+
+ if (column==mColumns.mHead)
+ return QString::fromUtf8 (record.get().mHead.c_str());
+
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ return (record.get().mFlags & iter->second)!=0;
+
+ return ActorRefIdAdapter<ESM::NPC>::getData (column, data, index);
+}
+
+void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::NPC>& record = static_cast<Record<ESM::NPC>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
+
+ if (column==mColumns.mRace)
+ record.get().mRace = value.toString().toUtf8().constData();
+ else if (column==mColumns.mClass)
+ record.get().mClass = value.toString().toUtf8().constData();
+ else if (column==mColumns.mFaction)
+ record.get().mFaction = value.toString().toUtf8().constData();
+ else if (column==mColumns.mHair)
+ record.get().mHair = value.toString().toUtf8().constData();
+ else if (column==mColumns.mHead)
+ record.get().mHead = value.toString().toUtf8().constData();
+ else
+ {
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ {
+ if (value.toInt()!=0)
+ record.get().mFlags |= iter->second;
+ else
+ record.get().mFlags &= ~iter->second;
+ }
+ else
+ ActorRefIdAdapter<ESM::NPC>::setData (column, data, index, value);
+ }
+}
+
+CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns)
+: EnchantableColumns (columns) {}
+
+CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
+: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)
+{}
+
+QVariant CSMWorld::WeaponRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+{
+ const Record<ESM::Weapon>& record = static_cast<const Record<ESM::Weapon>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon)));
+
+ if (column==mColumns.mType)
+ return record.get().mData.mType;
+
+ if (column==mColumns.mHealth)
+ return record.get().mData.mHealth;
+
+ if (column==mColumns.mSpeed)
+ return record.get().mData.mSpeed;
+
+ if (column==mColumns.mReach)
+ return record.get().mData.mReach;
+
+ for (int i=0; i<2; ++i)
+ {
+ if (column==mColumns.mChop[i])
+ return record.get().mData.mChop[i];
+
+ if (column==mColumns.mSlash[i])
+ return record.get().mData.mSlash[i];
+
+ if (column==mColumns.mThrust[i])
+ return record.get().mData.mThrust[i];
+ }
+
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ return (record.get().mData.mFlags & iter->second)!=0;
+
+ return EnchantableRefIdAdapter<ESM::Weapon>::getData (column, data, index);
+}
+
+void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+{
+ Record<ESM::Weapon>& record = static_cast<Record<ESM::Weapon>&> (
+ data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon)));
+
+ if (column==mColumns.mType)
+ record.get().mData.mType = value.toInt();
+ else if (column==mColumns.mHealth)
+ record.get().mData.mHealth = value.toInt();
+ else if (column==mColumns.mSpeed)
+ record.get().mData.mSpeed = value.toFloat();
+ else if (column==mColumns.mReach)
+ record.get().mData.mReach = value.toFloat();
+ else if (column==mColumns.mChop[0])
+ record.get().mData.mChop[0] = value.toInt();
+ else if (column==mColumns.mChop[1])
+ record.get().mData.mChop[1] = value.toInt();
+ else if (column==mColumns.mSlash[0])
+ record.get().mData.mSlash[0] = value.toInt();
+ else if (column==mColumns.mSlash[1])
+ record.get().mData.mSlash[1] = value.toInt();
+ else if (column==mColumns.mThrust[0])
+ record.get().mData.mThrust[0] = value.toInt();
+ else if (column==mColumns.mThrust[1])
+ record.get().mData.mThrust[1] = value.toInt();
+ else
+ {
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mColumns.mFlags.find (column);
+
+ if (iter!=mColumns.mFlags.end())
+ {
+ if (value.toInt()!=0)
+ record.get().mData.mFlags |= iter->second;
+ else
+ record.get().mData.mFlags &= ~iter->second;
+ }
+ else
+ EnchantableRefIdAdapter<ESM::Weapon>::setData (column, data, index, value);
+ }
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp
new file mode 100644
index 0000000000..d5d84a8f7e
--- /dev/null
+++ b/apps/opencs/model/world/refidadapterimp.hpp
@@ -0,0 +1,766 @@
+#ifndef CSM_WOLRD_REFIDADAPTERIMP_H
+#define CSM_WOLRD_REFIDADAPTERIMP_H
+
+#include <map>
+
+#include <QVariant>
+
+#include <components/esm/loadalch.hpp>
+#include <components/esm/loadappa.hpp>
+
+#include "record.hpp"
+#include "refiddata.hpp"
+#include "universalid.hpp"
+#include "refidadapter.hpp"
+
+namespace CSMWorld
+{
+ struct BaseColumns
+ {
+ const RefIdColumn *mId;
+ const RefIdColumn *mModified;
+ const RefIdColumn *mType;
+ };
+
+ /// \brief Base adapter for all refereceable record types
+ template<typename RecordT>
+ class BaseRefIdAdapter : public RefIdAdapter
+ {
+ UniversalId::Type mType;
+ BaseColumns mBase;
+
+ public:
+
+ BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base);
+
+ virtual std::string getId (const RecordBase& record) const;
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+
+ UniversalId::Type getType() const;
+ };
+
+ template<typename RecordT>
+ BaseRefIdAdapter<RecordT>::BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base)
+ : mType (type), mBase (base)
+ {}
+
+ template<typename RecordT>
+ std::string BaseRefIdAdapter<RecordT>::getId (const RecordBase& record) const
+ {
+ return dynamic_cast<const Record<RecordT>&> (record).get().mId;
+ }
+
+ template<typename RecordT>
+ QVariant BaseRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, mType)));
+
+ if (column==mBase.mId)
+ return QString::fromUtf8 (record.get().mId.c_str());
+
+ if (column==mBase.mModified)
+ {
+ if (record.mState==Record<RecordT>::State_Erased)
+ return static_cast<int> (Record<RecordT>::State_Deleted);
+
+ return static_cast<int> (record.mState);
+ }
+
+ if (column==mBase.mType)
+ return static_cast<int> (mType);
+
+ return QVariant();
+ }
+
+ template<typename RecordT>
+ void BaseRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, mType)));
+
+ if (column==mBase.mModified)
+ record.mState = static_cast<RecordBase::State> (value.toInt());
+ }
+
+ template<typename RecordT>
+ UniversalId::Type BaseRefIdAdapter<RecordT>::getType() const
+ {
+ return mType;
+ }
+
+
+ struct ModelColumns : public BaseColumns
+ {
+ const RefIdColumn *mModel;
+
+ ModelColumns (const BaseColumns& base) : BaseColumns (base) {}
+ };
+
+ /// \brief Adapter for IDs with models (all but levelled lists)
+ template<typename RecordT>
+ class ModelRefIdAdapter : public BaseRefIdAdapter<RecordT>
+ {
+ ModelColumns mModel;
+
+ public:
+
+ ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ ModelRefIdAdapter<RecordT>::ModelRefIdAdapter (UniversalId::Type type, const ModelColumns& columns)
+ : BaseRefIdAdapter<RecordT> (type, columns), mModel (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant ModelRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mModel.mModel)
+ return QString::fromUtf8 (record.get().mModel.c_str());
+
+ return BaseRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void ModelRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mModel.mModel)
+ record.get().mModel = value.toString().toUtf8().constData();
+ else
+ BaseRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+
+ struct NameColumns : public ModelColumns
+ {
+ const RefIdColumn *mName;
+ const RefIdColumn *mScript;
+
+ NameColumns (const ModelColumns& base) : ModelColumns (base) {}
+ };
+
+ /// \brief Adapter for IDs with names (all but levelled lists and statics)
+ template<typename RecordT>
+ class NameRefIdAdapter : public ModelRefIdAdapter<RecordT>
+ {
+ NameColumns mName;
+
+ public:
+
+ NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ NameRefIdAdapter<RecordT>::NameRefIdAdapter (UniversalId::Type type, const NameColumns& columns)
+ : ModelRefIdAdapter<RecordT> (type, columns), mName (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant NameRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mName.mName)
+ return QString::fromUtf8 (record.get().mName.c_str());
+
+ if (column==mName.mScript)
+ return QString::fromUtf8 (record.get().mScript.c_str());
+
+ return ModelRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void NameRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mName.mName)
+ record.get().mName = value.toString().toUtf8().constData();
+ else if (column==mName.mScript)
+ record.get().mScript = value.toString().toUtf8().constData();
+ else
+ ModelRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+
+ struct InventoryColumns : public NameColumns
+ {
+ const RefIdColumn *mIcon;
+ const RefIdColumn *mWeight;
+ const RefIdColumn *mValue;
+
+ InventoryColumns (const NameColumns& base) : NameColumns (base) {}
+ };
+
+ /// \brief Adapter for IDs that can go into an inventory
+ template<typename RecordT>
+ class InventoryRefIdAdapter : public NameRefIdAdapter<RecordT>
+ {
+ InventoryColumns mInventory;
+
+ public:
+
+ InventoryRefIdAdapter (UniversalId::Type type, const InventoryColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ InventoryRefIdAdapter<RecordT>::InventoryRefIdAdapter (UniversalId::Type type,
+ const InventoryColumns& columns)
+ : NameRefIdAdapter<RecordT> (type, columns), mInventory (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant InventoryRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mInventory.mIcon)
+ return QString::fromUtf8 (record.get().mIcon.c_str());
+
+ if (column==mInventory.mWeight)
+ return record.get().mData.mWeight;
+
+ if (column==mInventory.mValue)
+ return record.get().mData.mValue;
+
+ return NameRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void InventoryRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mInventory.mIcon)
+ record.get().mIcon = value.toString().toUtf8().constData();
+ else if (column==mInventory.mWeight)
+ record.get().mData.mWeight = value.toFloat();
+ else if (column==mInventory.mValue)
+ record.get().mData.mValue = value.toInt();
+ else
+ NameRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+
+ class PotionRefIdAdapter : public InventoryRefIdAdapter<ESM::Potion>
+ {
+ const RefIdColumn *mAutoCalc;
+
+ public:
+
+ PotionRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *autoCalc);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ struct EnchantableColumns : public InventoryColumns
+ {
+ const RefIdColumn *mEnchantment;
+ const RefIdColumn *mEnchantmentPoints;
+
+ EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {}
+ };
+
+ /// \brief Adapter for enchantable IDs
+ template<typename RecordT>
+ class EnchantableRefIdAdapter : public InventoryRefIdAdapter<RecordT>
+ {
+ EnchantableColumns mEnchantable;
+
+ public:
+
+ EnchantableRefIdAdapter (UniversalId::Type type, const EnchantableColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ EnchantableRefIdAdapter<RecordT>::EnchantableRefIdAdapter (UniversalId::Type type,
+ const EnchantableColumns& columns)
+ : InventoryRefIdAdapter<RecordT> (type, columns), mEnchantable (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant EnchantableRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mEnchantable.mEnchantment)
+ return QString::fromUtf8 (record.get().mEnchant.c_str());
+
+ if (column==mEnchantable.mEnchantmentPoints)
+ return static_cast<int> (record.get().mData.mEnchant);
+
+ return InventoryRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void EnchantableRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data,
+ int index, const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mEnchantable.mEnchantment)
+ record.get().mEnchant = value.toString().toUtf8().constData();
+ else if (column==mEnchantable.mEnchantmentPoints)
+ record.get().mData.mEnchant = value.toInt();
+ else
+ InventoryRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+
+ struct ToolColumns : public InventoryColumns
+ {
+ const RefIdColumn *mQuality;
+ const RefIdColumn *mUses;
+
+ ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {}
+ };
+
+ /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
+ template<typename RecordT>
+ class ToolRefIdAdapter : public InventoryRefIdAdapter<RecordT>
+ {
+ ToolColumns mTools;
+
+ public:
+
+ ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ ToolRefIdAdapter<RecordT>::ToolRefIdAdapter (UniversalId::Type type, const ToolColumns& columns)
+ : InventoryRefIdAdapter<RecordT> (type, columns), mTools (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant ToolRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mTools.mQuality)
+ return record.get().mData.mQuality;
+
+ if (column==mTools.mUses)
+ return record.get().mData.mUses;
+
+ return InventoryRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void ToolRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data,
+ int index, const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mTools.mQuality)
+ record.get().mData.mQuality = value.toFloat();
+ else if (column==mTools.mUses)
+ record.get().mData.mUses = value.toInt();
+ else
+ InventoryRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+
+ struct ActorColumns : public NameColumns
+ {
+ const RefIdColumn *mHasAi;
+ const RefIdColumn *mHello;
+ const RefIdColumn *mFlee;
+ const RefIdColumn *mFight;
+ const RefIdColumn *mAlarm;
+ std::map<const RefIdColumn *, unsigned int> mServices;
+
+ ActorColumns (const NameColumns& base) : NameColumns (base) {}
+ };
+
+ /// \brief Adapter for actor IDs (handles common AI functionality)
+ template<typename RecordT>
+ class ActorRefIdAdapter : public NameRefIdAdapter<RecordT>
+ {
+ ActorColumns mActors;
+
+ public:
+
+ ActorRefIdAdapter (UniversalId::Type type, const ActorColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ template<typename RecordT>
+ ActorRefIdAdapter<RecordT>::ActorRefIdAdapter (UniversalId::Type type,
+ const ActorColumns& columns)
+ : NameRefIdAdapter<RecordT> (type, columns), mActors (columns)
+ {}
+
+ template<typename RecordT>
+ QVariant ActorRefIdAdapter<RecordT>::getData (const RefIdColumn *column, const RefIdData& data,
+ int index) const
+ {
+ const Record<RecordT>& record = static_cast<const Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mActors.mHasAi)
+ return record.get().mHasAI!=0;
+
+ if (column==mActors.mHello)
+ return record.get().mAiData.mHello;
+
+ if (column==mActors.mFlee)
+ return record.get().mAiData.mFlee;
+
+ if (column==mActors.mFight)
+ return record.get().mAiData.mFight;
+
+ if (column==mActors.mAlarm)
+ return record.get().mAiData.mAlarm;
+
+ std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mActors.mServices.find (column);
+
+ if (iter!=mActors.mServices.end())
+ return (record.get().mAiData.mServices & iter->second)!=0;
+
+ return NameRefIdAdapter<RecordT>::getData (column, data, index);
+ }
+
+ template<typename RecordT>
+ void ActorRefIdAdapter<RecordT>::setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const
+ {
+ Record<RecordT>& record = static_cast<Record<RecordT>&> (
+ data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter<RecordT>::getType())));
+
+ if (column==mActors.mHasAi)
+ record.get().mHasAI = value.toInt();
+ else if (column==mActors.mHello)
+ record.get().mAiData.mHello = value.toInt();
+ else if (column==mActors.mFlee)
+ record.get().mAiData.mFlee = value.toInt();
+ else if (column==mActors.mFight)
+ record.get().mAiData.mFight = value.toInt();
+ else if (column==mActors.mAlarm)
+ record.get().mAiData.mAlarm = value.toInt();
+ else
+ {
+ typename std::map<const RefIdColumn *, unsigned int>::const_iterator iter =
+ mActors.mServices.find (column);
+ if (iter!=mActors.mServices.end())
+ {
+ if (value.toInt()!=0)
+ record.get().mAiData.mServices |= iter->second;
+ else
+ record.get().mAiData.mServices &= ~iter->second;
+ }
+ else
+ NameRefIdAdapter<RecordT>::setData (column, data, index, value);
+ }
+ }
+
+ class ApparatusRefIdAdapter : public InventoryRefIdAdapter<ESM::Apparatus>
+ {
+ const RefIdColumn *mType;
+ const RefIdColumn *mQuality;
+
+ public:
+
+ ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type,
+ const RefIdColumn *quality);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class ArmorRefIdAdapter : public EnchantableRefIdAdapter<ESM::Armor>
+ {
+ const RefIdColumn *mType;
+ const RefIdColumn *mHealth;
+ const RefIdColumn *mArmor;
+
+ public:
+
+ ArmorRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type,
+ const RefIdColumn *health, const RefIdColumn *armor);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class BookRefIdAdapter : public EnchantableRefIdAdapter<ESM::Book>
+ {
+ const RefIdColumn *mScroll;
+ const RefIdColumn *mSkill;
+
+ public:
+
+ BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *scroll,
+ const RefIdColumn *skill);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class ClothingRefIdAdapter : public EnchantableRefIdAdapter<ESM::Clothing>
+ {
+ const RefIdColumn *mType;
+
+ public:
+
+ ClothingRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *type);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class ContainerRefIdAdapter : public NameRefIdAdapter<ESM::Container>
+ {
+ const RefIdColumn *mWeight;
+ const RefIdColumn *mOrganic;
+ const RefIdColumn *mRespawn;
+
+ public:
+
+ ContainerRefIdAdapter (const NameColumns& columns, const RefIdColumn *weight,
+ const RefIdColumn *organic, const RefIdColumn *respawn);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ struct CreatureColumns : public ActorColumns
+ {
+ std::map<const RefIdColumn *, unsigned int> mFlags;
+ const RefIdColumn *mType;
+ const RefIdColumn *mSoul;
+ const RefIdColumn *mScale;
+ const RefIdColumn *mOriginal;
+
+ CreatureColumns (const ActorColumns& actorColumns);
+ };
+
+ class CreatureRefIdAdapter : public ActorRefIdAdapter<ESM::Creature>
+ {
+ CreatureColumns mColumns;
+
+ public:
+
+ CreatureRefIdAdapter (const CreatureColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class DoorRefIdAdapter : public NameRefIdAdapter<ESM::Door>
+ {
+ const RefIdColumn *mOpenSound;
+ const RefIdColumn *mCloseSound;
+
+ public:
+
+ DoorRefIdAdapter (const NameColumns& columns, const RefIdColumn *openSound,
+ const RefIdColumn *closeSound);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ struct LightColumns : public InventoryColumns
+ {
+ const RefIdColumn *mTime;
+ const RefIdColumn *mRadius;
+ const RefIdColumn *mColor;
+ const RefIdColumn *mSound;
+ std::map<const RefIdColumn *, unsigned int> mFlags;
+
+ LightColumns (const InventoryColumns& columns);
+ };
+
+ class LightRefIdAdapter : public InventoryRefIdAdapter<ESM::Light>
+ {
+ LightColumns mColumns;
+
+ public:
+
+ LightRefIdAdapter (const LightColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ class MiscRefIdAdapter : public InventoryRefIdAdapter<ESM::Miscellaneous>
+ {
+ const RefIdColumn *mKey;
+
+ public:
+
+ MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ struct NpcColumns : public ActorColumns
+ {
+ std::map<const RefIdColumn *, unsigned int> mFlags;
+ const RefIdColumn *mRace;
+ const RefIdColumn *mClass;
+ const RefIdColumn *mFaction;
+ const RefIdColumn *mHair;
+ const RefIdColumn *mHead;
+
+ NpcColumns (const ActorColumns& actorColumns);
+ };
+
+ class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC>
+ {
+ NpcColumns mColumns;
+
+ public:
+
+ NpcRefIdAdapter (const NpcColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+
+ struct WeaponColumns : public EnchantableColumns
+ {
+ const RefIdColumn *mType;
+ const RefIdColumn *mHealth;
+ const RefIdColumn *mSpeed;
+ const RefIdColumn *mReach;
+ const RefIdColumn *mChop[2];
+ const RefIdColumn *mSlash[2];
+ const RefIdColumn *mThrust[2];
+ std::map<const RefIdColumn *, unsigned int> mFlags;
+
+ WeaponColumns (const EnchantableColumns& columns);
+ };
+
+ class WeaponRefIdAdapter : public EnchantableRefIdAdapter<ESM::Weapon>
+ {
+ WeaponColumns mColumns;
+
+ public:
+
+ WeaponRefIdAdapter (const WeaponColumns& columns);
+
+ virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
+ const;
+
+ virtual void setData (const RefIdColumn *column, RefIdData& data, int index,
+ const QVariant& value) const;
+ ///< If the data type does not match an exception is thrown.
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp
new file mode 100644
index 0000000000..343cbe302a
--- /dev/null
+++ b/apps/opencs/model/world/refidcollection.cpp
@@ -0,0 +1,536 @@
+
+#include "refidcollection.hpp"
+
+#include <stdexcept>
+
+#include <components/esm/esmreader.hpp>
+
+#include "refidadapter.hpp"
+#include "refidadapterimp.hpp"
+#include "columns.hpp"
+
+CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag,
+ bool editable, bool userEditable)
+: ColumnBase (columnId, displayType, flag), mEditable (editable), mUserEditable (userEditable)
+{}
+
+bool CSMWorld::RefIdColumn::isEditable() const
+{
+ return mEditable;
+}
+
+bool CSMWorld::RefIdColumn::isUserEditable() const
+{
+ return mUserEditable;
+}
+
+
+const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdaptor (UniversalId::Type type) const
+{
+ std::map<UniversalId::Type, RefIdAdapter *>::const_iterator iter = mAdapters.find (type);
+
+ if (iter==mAdapters.end())
+ throw std::logic_error ("unsupported type in RefIdCollection");
+
+ return *iter->second;
+}
+
+CSMWorld::RefIdCollection::RefIdCollection()
+{
+ BaseColumns baseColumns;
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Id, ColumnBase::Display_String,
+ ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false));
+ baseColumns.mId = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Modification, ColumnBase::Display_RecordState,
+ ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false));
+ baseColumns.mModified = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType,
+ ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false));
+ baseColumns.mType = &mColumns.back();
+
+ ModelColumns modelColumns (baseColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Model, ColumnBase::Display_String));
+ modelColumns.mModel = &mColumns.back();
+
+ NameColumns nameColumns (modelColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Name, ColumnBase::Display_String));
+ nameColumns.mName = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Script, ColumnBase::Display_String));
+ nameColumns.mScript = &mColumns.back();
+
+ InventoryColumns inventoryColumns (nameColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Icon, ColumnBase::Display_String));
+ inventoryColumns.mIcon = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Weight, ColumnBase::Display_Float));
+ inventoryColumns.mWeight = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer));
+ inventoryColumns.mValue = &mColumns.back();
+
+ EnchantableColumns enchantableColumns (inventoryColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Enchantment, ColumnBase::Display_String));
+ enchantableColumns.mEnchantment = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_EnchantmentPoints, ColumnBase::Display_Integer));
+ enchantableColumns.mEnchantmentPoints = &mColumns.back();
+
+ ToolColumns toolsColumns (inventoryColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Quality, ColumnBase::Display_Float));
+ toolsColumns.mQuality = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Charges, ColumnBase::Display_Integer));
+ toolsColumns.mUses = &mColumns.back();
+
+ ActorColumns actorsColumns (nameColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Ai, ColumnBase::Display_Boolean));
+ actorsColumns.mHasAi = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer));
+ actorsColumns.mHello = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer));
+ actorsColumns.mFlee = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFight, ColumnBase::Display_Integer));
+ actorsColumns.mFight = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_AiAlarm, ColumnBase::Display_Integer));
+ actorsColumns.mAlarm = &mColumns.back();
+
+ static const struct
+ {
+ int mName;
+ unsigned int mFlag;
+ } sServiceTable[] =
+ {
+ { Columns::ColumnId_BuysWeapons, ESM::NPC::Weapon},
+ { Columns::ColumnId_BuysArmor, ESM::NPC::Armor},
+ { Columns::ColumnId_BuysClothing, ESM::NPC::Clothing},
+ { Columns::ColumnId_BuysBooks, ESM::NPC::Books},
+ { Columns::ColumnId_BuysIngredients, ESM::NPC::Ingredients},
+ { Columns::ColumnId_BuysLockpicks, ESM::NPC::Picks},
+ { Columns::ColumnId_BuysProbes, ESM::NPC::Probes},
+ { Columns::ColumnId_BuysLights, ESM::NPC::Lights},
+ { Columns::ColumnId_BuysApparati, ESM::NPC::Apparatus},
+ { Columns::ColumnId_BuysRepairItems, ESM::NPC::RepairItem},
+ { Columns::ColumnId_BuysMiscItems, ESM::NPC::Misc},
+ { Columns::ColumnId_BuysPotions, ESM::NPC::Potions},
+ { Columns::ColumnId_BuysMagicItems, ESM::NPC::MagicItems},
+ { Columns::ColumnId_SellsSpells, ESM::NPC::Spells},
+ { Columns::ColumnId_Trainer, ESM::NPC::Training},
+ { Columns::ColumnId_Spellmaking, ESM::NPC::Spellmaking},
+ { Columns::ColumnId_EnchantingService, ESM::NPC::Enchanting},
+ { Columns::ColumnId_RepairService, ESM::NPC::Repair},
+ { -1, 0 }
+ };
+
+ for (int i=0; sServiceTable[i].mName!=-1; ++i)
+ {
+ mColumns.push_back (RefIdColumn (sServiceTable[i].mName, ColumnBase::Display_Boolean));
+ actorsColumns.mServices.insert (std::make_pair (&mColumns.back(), sServiceTable[i].mFlag));
+ }
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_AutoCalc, ColumnBase::Display_Boolean));
+ const RefIdColumn *autoCalc = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_ApparatusType,
+ ColumnBase::Display_ApparatusType));
+ const RefIdColumn *apparatusType = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_ArmorType, ColumnBase::Display_ArmorType));
+ const RefIdColumn *armorType = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Health, ColumnBase::Display_Integer));
+ const RefIdColumn *health = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_ArmorValue, ColumnBase::Display_Integer));
+ const RefIdColumn *armor = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Scroll, ColumnBase::Display_Boolean));
+ const RefIdColumn *scroll = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute));
+ const RefIdColumn *attribute = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType));
+ const RefIdColumn *clothingType = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_WeightCapacity, ColumnBase::Display_Float));
+ const RefIdColumn *weightCapacity = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_OrganicContainer, ColumnBase::Display_Boolean));
+ const RefIdColumn *organic = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Respawn, ColumnBase::Display_Boolean));
+ const RefIdColumn *respawn = &mColumns.back();
+
+ CreatureColumns creatureColumns (actorsColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType));
+ creatureColumns.mType = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_SoulPoints, ColumnBase::Display_Integer));
+ creatureColumns.mSoul = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float));
+ creatureColumns.mScale = &mColumns.back();
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_String));
+ creatureColumns.mOriginal = &mColumns.back();
+
+ static const struct
+ {
+ int mName;
+ unsigned int mFlag;
+ } sCreatureFlagTable[] =
+ {
+ { Columns::ColumnId_Biped, ESM::Creature::Biped },
+ { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon },
+ { Columns::ColumnId_NoMovement, ESM::Creature::None },
+ { Columns::ColumnId_Swims, ESM::Creature::Swims },
+ { Columns::ColumnId_Flies, ESM::Creature::Flies },
+ { Columns::ColumnId_Walks, ESM::Creature::Walks },
+ { Columns::ColumnId_Essential, ESM::Creature::Essential },
+ { Columns::ColumnId_SkeletonBlood, ESM::Creature::Skeleton },
+ { Columns::ColumnId_MetalBlood, ESM::Creature::Metal },
+ { -1, 0 }
+ };
+
+ // for re-use in NPC records
+ const RefIdColumn *essential = 0;
+ const RefIdColumn *skeletonBlood = 0;
+ const RefIdColumn *metalBlood = 0;
+
+ for (int i=0; sCreatureFlagTable[i].mName!=-1; ++i)
+ {
+ mColumns.push_back (RefIdColumn (sCreatureFlagTable[i].mName, ColumnBase::Display_Boolean));
+ creatureColumns.mFlags.insert (std::make_pair (&mColumns.back(), sCreatureFlagTable[i].mFlag));
+
+ switch (sCreatureFlagTable[i].mFlag)
+ {
+ case ESM::Creature::Essential: essential = &mColumns.back(); break;
+ case ESM::Creature::Skeleton: skeletonBlood = &mColumns.back(); break;
+ case ESM::Creature::Metal: metalBlood = &mColumns.back(); break;
+ }
+ }
+
+ creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn));
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_String));
+ const RefIdColumn *openSound = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_CloseSound, ColumnBase::Display_String));
+ const RefIdColumn *closeSound = &mColumns.back();
+
+ LightColumns lightColumns (inventoryColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer));
+ lightColumns.mTime = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Radius, ColumnBase::Display_Integer));
+ lightColumns.mRadius = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Colour, ColumnBase::Display_Integer));
+ lightColumns.mColor = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Sound, ColumnBase::Display_String));
+ lightColumns.mSound = &mColumns.back();
+
+ static const struct
+ {
+ int mName;
+ unsigned int mFlag;
+ } sLightFlagTable[] =
+ {
+ { Columns::ColumnId_Dynamic, ESM::Light::Dynamic },
+ { Columns::ColumnId_Portable, ESM::Light::Carry },
+ { Columns::ColumnId_NegativeLight, ESM::Light::Negative },
+ { Columns::ColumnId_Flickering, ESM::Light::Flicker },
+ { Columns::ColumnId_SlowFlickering, ESM::Light::Flicker },
+ { Columns::ColumnId_Pulsing, ESM::Light::Pulse },
+ { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow },
+ { Columns::ColumnId_Fire, ESM::Light::Fire },
+ { Columns::ColumnId_OffByDefault, ESM::Light::OffDefault },
+ { -1, 0 }
+ };
+
+ for (int i=0; sLightFlagTable[i].mName!=-1; ++i)
+ {
+ mColumns.push_back (RefIdColumn (sLightFlagTable[i].mName, ColumnBase::Display_Boolean));
+ lightColumns.mFlags.insert (std::make_pair (&mColumns.back(), sLightFlagTable[i].mFlag));
+ }
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_IsKey, ColumnBase::Display_Boolean));
+ const RefIdColumn *key = &mColumns.back();
+
+ NpcColumns npcColumns (actorsColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Race, ColumnBase::Display_String));
+ npcColumns.mRace = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Class, ColumnBase::Display_String));
+ npcColumns.mClass = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Faction, ColumnBase::Display_String));
+ npcColumns.mFaction = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::Columnid_Hair, ColumnBase::Display_String));
+ npcColumns.mHair = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_String));
+ npcColumns.mHead = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Female, ColumnBase::Display_Boolean));
+ npcColumns.mFlags.insert (std::make_pair (&mColumns.back(), ESM::NPC::Female));
+
+ npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential));
+
+ npcColumns.mFlags.insert (std::make_pair (respawn, ESM::NPC::Respawn));
+
+ npcColumns.mFlags.insert (std::make_pair (autoCalc, ESM::NPC::Autocalc));
+
+ npcColumns.mFlags.insert (std::make_pair (skeletonBlood, ESM::NPC::Skeleton));
+
+ npcColumns.mFlags.insert (std::make_pair (metalBlood, ESM::NPC::Metal));
+
+ WeaponColumns weaponColumns (enchantableColumns);
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_WeaponType, ColumnBase::Display_WeaponType));
+ weaponColumns.mType = &mColumns.back();
+
+ weaponColumns.mHealth = health;
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_WeaponSpeed, ColumnBase::Display_Float));
+ weaponColumns.mSpeed = &mColumns.back();
+
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_WeaponReach, ColumnBase::Display_Float));
+ weaponColumns.mReach = &mColumns.back();
+
+ for (int i=0; i<6; ++i)
+ {
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_MinChop + i, ColumnBase::Display_Integer));
+ weaponColumns.mChop[i] = &mColumns.back();
+ }
+
+ static const struct
+ {
+ int mName;
+ unsigned int mFlag;
+ } sWeaponFlagTable[] =
+ {
+ { Columns::ColumnId_Magical, ESM::Weapon::Magical },
+ { Columns::ColumnId_Silver, ESM::Weapon::Silver },
+ { -1, 0 }
+ };
+
+ for (int i=0; sWeaponFlagTable[i].mName!=-1; ++i)
+ {
+ mColumns.push_back (RefIdColumn (sWeaponFlagTable[i].mName, ColumnBase::Display_Boolean));
+ weaponColumns.mFlags.insert (std::make_pair (&mColumns.back(), sWeaponFlagTable[i].mFlag));
+ }
+
+ mAdapters.insert (std::make_pair (UniversalId::Type_Activator,
+ new NameRefIdAdapter<ESM::Activator> (UniversalId::Type_Activator, nameColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Potion,
+ new PotionRefIdAdapter (inventoryColumns, autoCalc)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Apparatus,
+ new ApparatusRefIdAdapter (inventoryColumns, apparatusType, toolsColumns.mQuality)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Armor,
+ new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Book,
+ new BookRefIdAdapter (enchantableColumns, scroll, attribute)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Clothing,
+ new ClothingRefIdAdapter (enchantableColumns, clothingType)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Container,
+ new ContainerRefIdAdapter (nameColumns, weightCapacity, organic, respawn)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Creature,
+ new CreatureRefIdAdapter (creatureColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Door,
+ new DoorRefIdAdapter (nameColumns, openSound, closeSound)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient,
+ new InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, inventoryColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList,
+ new BaseRefIdAdapter<ESM::CreatureLevList> (
+ UniversalId::Type_CreatureLevelledList, baseColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_ItemLevelledList,
+ new BaseRefIdAdapter<ESM::ItemLevList> (UniversalId::Type_ItemLevelledList, baseColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Light,
+ new LightRefIdAdapter (lightColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Lockpick,
+ new ToolRefIdAdapter<ESM::Lockpick> (UniversalId::Type_Lockpick, toolsColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous,
+ new MiscRefIdAdapter (inventoryColumns, key)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Npc,
+ new NpcRefIdAdapter (npcColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Probe,
+ new ToolRefIdAdapter<ESM::Probe> (UniversalId::Type_Probe, toolsColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Repair,
+ new ToolRefIdAdapter<ESM::Repair> (UniversalId::Type_Repair, toolsColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Static,
+ new ModelRefIdAdapter<ESM::Static> (UniversalId::Type_Static, modelColumns)));
+ mAdapters.insert (std::make_pair (UniversalId::Type_Weapon,
+ new WeaponRefIdAdapter (weaponColumns)));
+}
+
+CSMWorld::RefIdCollection::~RefIdCollection()
+{
+ for (std::map<UniversalId::Type, RefIdAdapter *>::iterator iter (mAdapters.begin());
+ iter!=mAdapters.end(); ++iter)
+ delete iter->second;
+}
+
+int CSMWorld::RefIdCollection::getSize() const
+{
+ return mData.getSize();
+}
+
+std::string CSMWorld::RefIdCollection::getId (int index) const
+{
+ return getData (index, 0).toString().toUtf8().constData();
+}
+
+int CSMWorld::RefIdCollection::getIndex (const std::string& id) const
+{
+ int index = searchId (id);
+
+ if (index==-1)
+ throw std::runtime_error ("invalid ID: " + id);
+
+ return index;
+}
+
+int CSMWorld::RefIdCollection::getColumns() const
+{
+ return mColumns.size();
+}
+
+const CSMWorld::ColumnBase& CSMWorld::RefIdCollection::getColumn (int column) const
+{
+ return mColumns.at (column);
+}
+
+QVariant CSMWorld::RefIdCollection::getData (int index, int column) const
+{
+ RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
+
+ const RefIdAdapter& adaptor = findAdaptor (localIndex.second);
+
+ return adaptor.getData (&mColumns.at (column), mData, localIndex.first);
+}
+
+void CSMWorld::RefIdCollection::setData (int index, int column, const QVariant& data)
+{
+ RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
+
+ const RefIdAdapter& adaptor = findAdaptor (localIndex.second);
+
+ adaptor.setData (&mColumns.at (column), mData, localIndex.first, data);
+}
+
+void CSMWorld::RefIdCollection::removeRows (int index, int count)
+{
+ mData.erase (index, count);
+}
+
+void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type)
+{
+ mData.appendRecord (type, id);
+}
+
+int CSMWorld::RefIdCollection::searchId (const std::string& id) const
+{
+ RefIdData::LocalIndex localIndex = mData.searchId (id);
+
+ if (localIndex.first==-1)
+ return -1;
+
+ return mData.localToGlobalIndex (localIndex);
+}
+
+void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record)
+{
+ mData.getRecord (mData.globalToLocalIndex (index)).assign (record);
+}
+
+void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record,
+ UniversalId::Type type)
+{
+ std::string id = findAdaptor (type).getId (record);
+
+ int index = mData.getAppendIndex (type);
+
+ mData.appendRecord (type, id);
+
+ mData.getRecord (mData.globalToLocalIndex (index)).assign (record);
+}
+
+const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const
+{
+ return mData.getRecord (mData.searchId (id));
+}
+
+const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) const
+{
+ return mData.getRecord (mData.globalToLocalIndex (index));
+}
+
+void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type)
+{
+ std::string id = reader.getHNOString ("NAME");
+
+ int index = searchId (id);
+
+ if (reader.isNextSub ("DELE"))
+ {
+ 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)
+ {
+ mData.erase (index, 1);
+ }
+ else
+ {
+ mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted;
+ }
+ }
+ else
+ {
+ if (index==-1)
+ {
+ // new record
+ int index = mData.getAppendIndex (type);
+ mData.appendRecord (type, id);
+
+ RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
+
+ mData.load (localIndex, reader, base);
+
+ mData.getRecord (localIndex).mState =
+ base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
+ }
+ else
+ {
+ // old record
+ RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
+
+ if (!base)
+ if (mData.getRecord (localIndex).mState==RecordBase::State_Erased)
+ throw std::logic_error ("attempt to access a deleted record");
+
+ mData.load (localIndex, reader, base);
+
+ if (!base)
+ mData.getRecord (localIndex).mState = RecordBase::State_Modified;
+ }
+ }
+}
+
+int CSMWorld::RefIdCollection::getAppendIndex (UniversalId::Type type) const
+{
+ return mData.getAppendIndex (type);
+}
diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp
new file mode 100644
index 0000000000..c10d1d2d0f
--- /dev/null
+++ b/apps/opencs/model/world/refidcollection.hpp
@@ -0,0 +1,95 @@
+#ifndef CSM_WOLRD_REFIDCOLLECTION_H
+#define CSM_WOLRD_REFIDCOLLECTION_H
+
+#include <vector>
+#include <map>
+#include <deque>
+
+#include "columnbase.hpp"
+#include "collectionbase.hpp"
+#include "refiddata.hpp"
+
+namespace CSMWorld
+{
+ class RefIdAdapter;
+
+ class RefIdColumn : public ColumnBase
+ {
+ bool mEditable;
+ bool mUserEditable;
+
+ public:
+
+ RefIdColumn (int columnId, Display displayType,
+ int flag = Flag_Table | Flag_Dialogue, bool editable = true,
+ bool userEditable = true);
+
+ virtual bool isEditable() const;
+
+ virtual bool isUserEditable() const;
+ };
+
+ class RefIdCollection : public CollectionBase
+ {
+ private:
+
+ RefIdData mData;
+ std::deque<RefIdColumn> mColumns;
+ std::map<UniversalId::Type, RefIdAdapter *> mAdapters;
+
+ private:
+
+ const RefIdAdapter& findAdaptor (UniversalId::Type) const;
+ ///< Throws an exception if no adaptor for \a Type can be found.
+
+ public:
+
+ RefIdCollection();
+
+ virtual ~RefIdCollection();
+
+ virtual int getSize() const;
+
+ virtual std::string getId (int index) const;
+
+ virtual int getIndex (const std::string& id) const;
+
+ virtual int getColumns() const;
+
+ virtual const ColumnBase& getColumn (int column) const;
+
+ virtual QVariant getData (int index, int column) const;
+
+ virtual void setData (int index, int column, const QVariant& data);
+
+ virtual void removeRows (int index, int count);
+
+ virtual void appendBlankRecord (const std::string& id, UniversalId::Type type);
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual int searchId (const std::string& id) const;
+ ////< Search record with \a id.
+ /// \return index of record (if found) or -1 (not found)
+
+ virtual void replace (int index, const RecordBase& record);
+ ///< If the record type does not match, an exception is thrown.
+ ///
+ /// \attention \a record must not change the ID.
+
+ virtual void appendRecord (const RecordBase& record, UniversalId::Type type);
+ ///< If the record type does not match, an exception is thrown.
+ ///
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+
+ virtual const RecordBase& getRecord (const std::string& id) const;
+
+ virtual const RecordBase& getRecord (int index) const;
+
+ void load (ESM::ESMReader& reader, bool base, UniversalId::Type type);
+
+ virtual int getAppendIndex (UniversalId::Type type) const;
+ ///< \param type Will be ignored, unless the collection supports multiple record types
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp
new file mode 100644
index 0000000000..c95db045f5
--- /dev/null
+++ b/apps/opencs/model/world/refiddata.cpp
@@ -0,0 +1,198 @@
+
+#include "refiddata.hpp"
+
+#include <cassert>
+
+#include <components/misc/stringops.hpp>
+
+CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {}
+
+CSMWorld::RefIdData::RefIdData()
+{
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Potion, &mPotions));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Apparatus, &mApparati));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Armor, &mArmors));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Book, &mBooks));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Clothing, &mClothing));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Container, &mContainers));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Creature, &mCreatures));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Door, &mDoors));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Ingredient, &mIngredients));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_CreatureLevelledList,
+ &mCreatureLevelledLists));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_ItemLevelledList, &mItemLevelledLists));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Light, &mLights));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Lockpick, &mLockpicks));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Miscellaneous, &mMiscellaneous));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Npc, &mNpcs));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Probe, &mProbes));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Repair, &mRepairs));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Static, &mStatics));
+ mRecordContainers.insert (std::make_pair (UniversalId::Type_Weapon, &mWeapons));
+}
+
+CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::globalToLocalIndex (int index) const
+{
+ for (std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator iter (
+ mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter)
+ {
+ if (index<iter->second->getSize())
+ return LocalIndex (index, iter->first);
+
+ index -= iter->second->getSize();
+ }
+
+ throw std::runtime_error ("RefIdData index out of range");
+}
+
+int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index)
+ const
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator end =
+ mRecordContainers.find (index.second);
+
+ if (end==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ int globalIndex = index.first;
+
+ for (std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator iter (
+ mRecordContainers.begin()); iter!=end; ++iter)
+ globalIndex += iter->second->getSize();
+
+ return globalIndex;
+}
+
+CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId (
+ const std::string& id) const
+{
+ std::string id2 = Misc::StringUtils::lowerCase (id);
+
+ std::map<std::string, std::pair<int, UniversalId::Type> >::const_iterator iter = mIndex.find (id2);
+
+ if (iter==mIndex.end())
+ return std::make_pair (-1, CSMWorld::UniversalId::Type_None);
+
+ return iter->second;
+}
+
+void CSMWorld::RefIdData::erase (int index, int count)
+{
+ LocalIndex localIndex = globalToLocalIndex (index);
+
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator iter =
+ mRecordContainers.find (localIndex.second);
+
+ while (count>0 && iter!=mRecordContainers.end())
+ {
+ int size = iter->second->getSize();
+
+ if (localIndex.first+count>size)
+ {
+ erase (localIndex, size-localIndex.first);
+ count -= size-localIndex.first;
+
+ ++iter;
+
+ if (iter==mRecordContainers.end())
+ throw std::runtime_error ("invalid count value for erase operation");
+
+ localIndex.first = 0;
+ localIndex.second = iter->first;
+ }
+ else
+ {
+ erase (localIndex, count);
+ count = 0;
+ }
+ }
+}
+
+const CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index) const
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator iter =
+ mRecordContainers.find (index.second);
+
+ if (iter==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ return iter->second->getRecord (index.first);
+}
+
+CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index)
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
+ mRecordContainers.find (index.second);
+
+ if (iter==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ return iter->second->getRecord (index.first);
+}
+
+void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::string& id)
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
+ mRecordContainers.find (type);
+
+ if (iter==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ iter->second->appendRecord (id);
+
+ mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id),
+ LocalIndex (iter->second->getSize()-1, type)));
+}
+
+int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const
+{
+ int index = 0;
+
+ for (std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator iter (
+ mRecordContainers.begin()); iter!=mRecordContainers.end(); ++iter)
+ {
+ index += iter->second->getSize();
+
+ if (type==iter->first)
+ break;
+ }
+
+ return index;
+}
+
+void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base)
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
+ mRecordContainers.find (index.second);
+
+ if (iter==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ iter->second->load (index.first, reader, base);
+}
+
+void CSMWorld::RefIdData::erase (const LocalIndex& index, int count)
+{
+ std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
+ mRecordContainers.find (index.second);
+
+ if (iter==mRecordContainers.end())
+ throw std::logic_error ("invalid local index type");
+
+ for (int i=index.first; i<index.first+count; ++i)
+ {
+ std::map<std::string, LocalIndex>::iterator result =
+ mIndex.find (Misc::StringUtils::lowerCase (iter->second->getId (i)));
+
+ if (result!=mIndex.end())
+ mIndex.erase (result);
+ }
+
+ iter->second->erase (index.first, count);
+}
+
+int CSMWorld::RefIdData::getSize() const
+{
+ return mIndex.size();
+}
diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp
new file mode 100644
index 0000000000..475566fb54
--- /dev/null
+++ b/apps/opencs/model/world/refiddata.hpp
@@ -0,0 +1,188 @@
+#ifndef CSM_WOLRD_REFIDDATA_H
+#define CSM_WOLRD_REFIDDATA_H
+
+#include <vector>
+#include <map>
+
+#include <components/esm/loadacti.hpp>
+#include <components/esm/loadalch.hpp>
+#include <components/esm/loadappa.hpp>
+#include <components/esm/loadarmo.hpp>
+#include <components/esm/loadbook.hpp>
+#include <components/esm/loadclot.hpp>
+#include <components/esm/loadcont.hpp>
+#include <components/esm/loadcrea.hpp>
+#include <components/esm/loaddoor.hpp>
+#include <components/esm/loadingr.hpp>
+#include <components/esm/loadlevlist.hpp>
+#include <components/esm/loadligh.hpp>
+#include <components/esm/loadlock.hpp>
+#include <components/esm/loadprob.hpp>
+#include <components/esm/loadrepa.hpp>
+#include <components/esm/loadstat.hpp>
+#include <components/esm/loadweap.hpp>
+#include <components/esm/loadnpc.hpp>
+#include <components/esm/loadmisc.hpp>
+
+#include "record.hpp"
+#include "universalid.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+}
+
+namespace CSMWorld
+{
+ struct RefIdDataContainerBase
+ {
+ virtual ~RefIdDataContainerBase();
+
+ virtual int getSize() const = 0;
+
+ virtual const RecordBase& getRecord (int index) const = 0;
+
+ virtual RecordBase& getRecord (int index)= 0;
+
+ virtual void appendRecord (const std::string& id) = 0;
+
+ virtual void load (int index, ESM::ESMReader& reader, bool base) = 0;
+
+ virtual void erase (int index, int count) = 0;
+
+ virtual std::string getId (int index) const = 0;
+ };
+
+ template<typename RecordT>
+ struct RefIdDataContainer : public RefIdDataContainerBase
+ {
+ std::vector<Record<RecordT> > mContainer;
+
+ virtual int getSize() const;
+
+ virtual const RecordBase& getRecord (int index) const;
+
+ virtual RecordBase& getRecord (int index);
+
+ virtual void appendRecord (const std::string& id);
+
+ virtual void load (int index, ESM::ESMReader& reader, bool base);
+
+ virtual void erase (int index, int count);
+
+ virtual std::string getId (int index) const;
+ };
+
+ template<typename RecordT>
+ int RefIdDataContainer<RecordT>::getSize() const
+ {
+ return static_cast<int> (mContainer.size());
+ }
+
+ template<typename RecordT>
+ const RecordBase& RefIdDataContainer<RecordT>::getRecord (int index) const
+ {
+ return mContainer.at (index);
+ }
+
+ template<typename RecordT>
+ RecordBase& RefIdDataContainer<RecordT>::getRecord (int index)
+ {
+ return mContainer.at (index);
+ }
+
+ template<typename RecordT>
+ void RefIdDataContainer<RecordT>::appendRecord (const std::string& id)
+ {
+ Record<RecordT> record;
+ record.mModified.mId = id;
+ record.mModified.blank();
+ record.mState = RecordBase::State_ModifiedOnly;
+
+ mContainer.push_back (record);
+ }
+
+ template<typename RecordT>
+ void RefIdDataContainer<RecordT>::load (int index, ESM::ESMReader& reader, bool base)
+ {
+ (base ? mContainer.at (index).mBase : mContainer.at (index).mModified).load (reader);
+ }
+
+ template<typename RecordT>
+ void RefIdDataContainer<RecordT>::erase (int index, int count)
+ {
+ if (index<0 || index+count>=getSize())
+ throw std::runtime_error ("invalid RefIdDataContainer index");
+
+ mContainer.erase (mContainer.begin()+index, mContainer.begin()+index+count);
+ }
+
+ template<typename RecordT>
+ std::string RefIdDataContainer<RecordT>::getId (int index) const
+ {
+ return mContainer.at (index).get().mId;
+ }
+
+ class RefIdData
+ {
+ public:
+
+ typedef std::pair<int, UniversalId::Type> LocalIndex;
+
+ private:
+
+ RefIdDataContainer<ESM::Activator> mActivators;
+ RefIdDataContainer<ESM::Potion> mPotions;
+ RefIdDataContainer<ESM::Apparatus> mApparati;
+ RefIdDataContainer<ESM::Armor> mArmors;
+ RefIdDataContainer<ESM::Book> mBooks;
+ RefIdDataContainer<ESM::Clothing> mClothing;
+ RefIdDataContainer<ESM::Container> mContainers;
+ RefIdDataContainer<ESM::Creature> mCreatures;
+ RefIdDataContainer<ESM::Door> mDoors;
+ RefIdDataContainer<ESM::Ingredient> mIngredients;
+ RefIdDataContainer<ESM::CreatureLevList> mCreatureLevelledLists;
+ RefIdDataContainer<ESM::ItemLevList> mItemLevelledLists;
+ RefIdDataContainer<ESM::Light> mLights;
+ RefIdDataContainer<ESM::Lockpick> mLockpicks;
+ RefIdDataContainer<ESM::Miscellaneous> mMiscellaneous;
+ RefIdDataContainer<ESM::NPC> mNpcs;
+ RefIdDataContainer<ESM::Probe> mProbes;
+ RefIdDataContainer<ESM::Repair> mRepairs;
+ RefIdDataContainer<ESM::Static> mStatics;
+ RefIdDataContainer<ESM::Weapon> mWeapons;
+
+ std::map<std::string, LocalIndex> mIndex;
+
+ std::map<UniversalId::Type, RefIdDataContainerBase *> mRecordContainers;
+
+ void erase (const LocalIndex& index, int count);
+ ///< Must not spill over into another type.
+
+ public:
+
+ RefIdData();
+
+ LocalIndex globalToLocalIndex (int index) const;
+
+ int localToGlobalIndex (const LocalIndex& index) const;
+
+ LocalIndex searchId (const std::string& id) const;
+
+ void erase (int index, int count);
+
+ const RecordBase& getRecord (const LocalIndex& index) const;
+
+ RecordBase& getRecord (const LocalIndex& index);
+
+ void appendRecord (UniversalId::Type type, const std::string& id);
+
+ int getAppendIndex (UniversalId::Type type) const;
+
+ void load (const LocalIndex& index, ESM::ESMReader& reader, bool base);
+
+ int getSize() const;
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp
new file mode 100644
index 0000000000..697c5f4500
--- /dev/null
+++ b/apps/opencs/model/world/regionmap.cpp
@@ -0,0 +1,513 @@
+
+#include "regionmap.hpp"
+
+#include <algorithm>
+
+#include <QBrush>
+
+#include <components/misc/stringops.hpp>
+
+#include "data.hpp"
+#include "universalid.hpp"
+
+CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted (false) {}
+
+CSMWorld::RegionMap::CellDescription::CellDescription (const Record<Cell>& cell)
+{
+ const Cell& cell2 = cell.get();
+
+ if (!cell2.isExterior())
+ throw std::logic_error ("Interior cell in region map");
+
+ mDeleted = cell.isDeleted();
+
+ mRegion = cell2.mRegion;
+ mName = cell2.mName;
+}
+
+CSMWorld::RegionMap::CellIndex CSMWorld::RegionMap::getIndex (const QModelIndex& index) const
+{
+ return CellIndex (index.column()+mMin.first,
+ (mMax.second-mMin.second - index.row()-1)+mMin.second);
+}
+
+QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const
+{
+ // I hate you, Qt API naming scheme!
+ return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1,
+ index.first-mMin.first);
+}
+
+void CSMWorld::RegionMap::buildRegions()
+{
+ const IdCollection<ESM::Region>& regions = mData.getRegions();
+
+ int size = regions.getSize();
+
+ for (int i=0; i<size; ++i)
+ {
+ const Record<ESM::Region>& region = regions.getRecord (i);
+
+ if (!region.isDeleted())
+ mColours.insert (std::make_pair (Misc::StringUtils::lowerCase (region.get().mId),
+ region.get().mMapColor));
+ }
+}
+
+void CSMWorld::RegionMap::buildMap()
+{
+ const IdCollection<Cell>& cells = mData.getCells();
+
+ int size = cells.getSize();
+
+ for (int i=0; i<size; ++i)
+ {
+ const Record<Cell>& cell = cells.getRecord (i);
+
+ const Cell& cell2 = cell.get();
+
+ if (cell2.isExterior())
+ {
+ CellDescription description (cell);
+
+ CellIndex index (cell2.mData.mX, cell2.mData.mY);
+
+ mMap.insert (std::make_pair (index, description));
+ }
+ }
+
+ std::pair<CellIndex, CellIndex> mapSize = getSize();
+
+ mMin = mapSize.first;
+ mMax = mapSize.second;
+}
+
+void CSMWorld::RegionMap::addCell (const CellIndex& index, const CellDescription& description)
+{
+ std::map<CellIndex, CellDescription>::iterator cell = mMap.find (index);
+
+ if (cell!=mMap.end())
+ {
+ cell->second = description;
+ }
+ else
+ {
+ updateSize();
+
+ mMap.insert (std::make_pair (index, description));
+ }
+
+ QModelIndex index2 = getIndex (index);
+
+ dataChanged (index2, index2);
+}
+
+void CSMWorld::RegionMap::addCells (int start, int end)
+{
+ const IdCollection<Cell>& cells = mData.getCells();
+
+ for (int i=start; i<=end; ++i)
+ {
+ const Record<Cell>& cell = cells.getRecord (i);
+
+ const Cell& cell2 = cell.get();
+
+ if (cell2.isExterior())
+ {
+ CellIndex index (cell2.mData.mX, cell2.mData.mY);
+
+ CellDescription description (cell);
+
+ addCell (index, description);
+ }
+ }
+}
+
+void CSMWorld::RegionMap::removeCell (const CellIndex& index)
+{
+ std::map<CellIndex, CellDescription>::iterator cell = mMap.find (index);
+
+ if (cell!=mMap.end())
+ {
+ mMap.erase (cell);
+
+ QModelIndex index2 = getIndex (index);
+
+ dataChanged (index2, index2);
+
+ updateSize();
+ }
+}
+
+void CSMWorld::RegionMap::addRegion (const std::string& region, unsigned int colour)
+{
+ mColours[Misc::StringUtils::lowerCase (region)] = colour;
+}
+
+void CSMWorld::RegionMap::removeRegion (const std::string& region)
+{
+ std::map<std::string, unsigned int>::iterator iter (
+ mColours.find (Misc::StringUtils::lowerCase (region)));
+
+ if (iter!=mColours.end())
+ mColours.erase (iter);
+}
+
+void CSMWorld::RegionMap::updateRegions (const std::vector<std::string>& regions)
+{
+ std::vector<std::string> regions2 (regions);
+
+ std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase);
+ std::sort (regions2.begin(), regions2.end());
+
+ for (std::map<CellIndex, CellDescription>::const_iterator iter (mMap.begin());
+ iter!=mMap.end(); ++iter)
+ {
+ if (!iter->second.mRegion.empty() &&
+ std::find (regions2.begin(), regions2.end(),
+ Misc::StringUtils::lowerCase (iter->second.mRegion))!=regions2.end())
+ {
+ QModelIndex index = getIndex (iter->first);
+
+ dataChanged (index, index);
+ }
+ }
+}
+
+void CSMWorld::RegionMap::updateSize()
+{
+ std::pair<CellIndex, CellIndex> size = getSize();
+
+ {
+ int diff = size.first.first - mMin.first;
+
+ if (diff<0)
+ {
+ beginInsertColumns (QModelIndex(), 0, -diff-1);
+ mMin.first = size.first.first;
+ endInsertColumns();
+ }
+ else if (diff>0)
+ {
+ beginRemoveColumns (QModelIndex(), 0, diff-1);
+ mMin.first = size.first.first;
+ endRemoveColumns();
+ }
+ }
+
+ {
+ int diff = size.first.second - mMin.second;
+
+ if (diff<0)
+ {
+ beginInsertRows (QModelIndex(), 0, -diff-1);
+ mMin.second = size.first.second;
+ endInsertRows();
+ }
+ else if (diff>0)
+ {
+ beginRemoveRows (QModelIndex(), 0, diff-1);
+ mMin.second = size.first.second;
+ endRemoveRows();
+ }
+ }
+
+ {
+ int diff = size.second.first - mMax.first;
+
+ if (diff>0)
+ {
+ int columns = columnCount();
+ beginInsertColumns (QModelIndex(), columns, columns+diff-1);
+ mMax.first = size.second.first;
+ endInsertColumns();
+ }
+ else if (diff<0)
+ {
+ int columns = columnCount();
+ beginRemoveColumns (QModelIndex(), columns+diff, columns-1);
+ mMax.first = size.second.first;
+ endRemoveColumns();
+ }
+ }
+
+ {
+ int diff = size.second.second - mMax.second;
+
+ if (diff>0)
+ {
+ int rows = rowCount();
+ beginInsertRows (QModelIndex(), rows, rows+diff-1);
+ mMax.second = size.second.second;
+ endInsertRows();
+ }
+ else if (diff<0)
+ {
+ int rows = rowCount();
+ beginRemoveRows (QModelIndex(), rows+diff, rows-1);
+ mMax.second = size.second.second;
+ endRemoveRows();
+ }
+ }
+}
+
+std::pair<CSMWorld::RegionMap::CellIndex, CSMWorld::RegionMap::CellIndex> CSMWorld::RegionMap::getSize()
+ const
+{
+ const IdCollection<Cell>& cells = mData.getCells();
+
+ int size = cells.getSize();
+
+ CellIndex min (0, 0);
+ CellIndex max (0, 0);
+
+ for (int i=0; i<size; ++i)
+ {
+ const Record<Cell>& cell = cells.getRecord (i);
+
+ const Cell& cell2 = cell.get();
+
+ if (cell2.isExterior())
+ {
+ CellIndex index (cell2.mData.mX, cell2.mData.mY);
+
+ if (min==max)
+ {
+ min = index;
+ max = std::make_pair (min.first+1, min.second+1);
+ }
+ else
+ {
+ if (index.first<min.first)
+ min.first = index.first;
+ else if (index.first>=max.first)
+ max.first = index.first + 1;
+
+ if (index.second<min.second)
+ min.second = index.second;
+ else if (index.second>=max.second)
+ max.second = index.second + 1;
+ }
+ }
+ }
+
+ return std::make_pair (min, max);
+}
+
+CSMWorld::RegionMap::RegionMap (Data& data) : mData (data)
+{
+ buildRegions();
+ buildMap();
+
+ QAbstractItemModel *regions = data.getTableModel (UniversalId (UniversalId::Type_Regions));
+
+ connect (regions, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
+ this, SLOT (regionsAboutToBeRemoved (const QModelIndex&, int, int)));
+ connect (regions, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
+ this, SLOT (regionsInserted (const QModelIndex&, int, int)));
+ connect (regions, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
+ this, SLOT (regionsChanged (const QModelIndex&, const QModelIndex&)));
+
+ QAbstractItemModel *cells = data.getTableModel (UniversalId (UniversalId::Type_Cells));
+
+ connect (cells, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
+ this, SLOT (cellsAboutToBeRemoved (const QModelIndex&, int, int)));
+ connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
+ this, SLOT (cellsInserted (const QModelIndex&, int, int)));
+ connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
+ this, SLOT (cellsChanged (const QModelIndex&, const QModelIndex&)));
+}
+
+int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mMax.second-mMin.second;
+}
+
+int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return mMax.first-mMin.first;
+}
+
+QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const
+{
+ if (role==Qt::SizeHintRole)
+ return QSize (16, 16);
+
+ if (role==Qt::BackgroundRole)
+ {
+ /// \todo GUI class in non-GUI code. Needs to be addressed eventually.
+
+ std::map<CellIndex, CellDescription>::const_iterator cell =
+ mMap.find (getIndex (index));
+
+ if (cell!=mMap.end())
+ {
+ if (cell->second.mDeleted)
+ return QBrush (Qt::red, Qt::DiagCrossPattern);
+
+ std::map<std::string, unsigned int>::const_iterator iter =
+ mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion));
+
+ if (iter!=mColours.end())
+ return QBrush (
+ QColor (iter->second>>24, (iter->second>>16) & 255, (iter->second>>8) & 255,
+ iter->second & 255));
+
+ if (cell->second.mRegion.empty())
+ return QBrush (Qt::Dense6Pattern); // no region
+
+ return QBrush (Qt::red, Qt::Dense6Pattern); // invalid region
+ }
+
+ return QBrush (Qt::DiagCrossPattern);
+ }
+
+ if (role==Qt::ToolTipRole)
+ {
+ CellIndex cellIndex = getIndex (index);
+
+ std::ostringstream stream;
+
+ stream << cellIndex.first << ", " << cellIndex.second;
+
+ std::map<CellIndex, CellDescription>::const_iterator cell =
+ mMap.find (cellIndex);
+
+ if (cell!=mMap.end())
+ {
+ if (!cell->second.mName.empty())
+ stream << " " << cell->second.mName;
+
+ if (cell->second.mDeleted)
+ stream << " (deleted)";
+
+ if (!cell->second.mRegion.empty())
+ {
+ stream << "<br>";
+
+ std::map<std::string, unsigned int>::const_iterator iter =
+ mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion));
+
+ if (iter!=mColours.end())
+ stream << cell->second.mRegion;
+ else
+ stream << "<font color=red>" << cell->second.mRegion << "</font>";
+ }
+ }
+ else
+ stream << " (no cell)";
+
+ return QString::fromUtf8 (stream.str().c_str());
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const
+{
+ if (mMap.find (getIndex (index))!=mMap.end())
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+
+ return 0;
+}
+
+void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end)
+{
+ std::vector<std::string> update;
+
+ const IdCollection<ESM::Region>& regions = mData.getRegions();
+
+ for (int i=start; i<=end; ++i)
+ {
+ const Record<ESM::Region>& region = regions.getRecord (i);
+
+ update.push_back (region.get().mId);
+
+ removeRegion (region.get().mId);
+ }
+
+ updateRegions (update);
+}
+
+void CSMWorld::RegionMap::regionsInserted (const QModelIndex& parent, int start, int end)
+{
+ std::vector<std::string> update;
+
+ const IdCollection<ESM::Region>& regions = mData.getRegions();
+
+ for (int i=start; i<=end; ++i)
+ {
+ const Record<ESM::Region>& region = regions.getRecord (i);
+
+ if (!region.isDeleted())
+ {
+ update.push_back (region.get().mId);
+
+ addRegion (region.get().mId, region.get().mMapColor);
+ }
+ }
+
+ updateRegions (update);
+}
+
+void CSMWorld::RegionMap::regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
+{
+ // Note: At this point an additional check could be inserted to see if there is any change to the
+ // columns we are interested in. If not we can exit the function here and avoid all updating.
+
+ std::vector<std::string> update;
+
+ const IdCollection<ESM::Region>& regions = mData.getRegions();
+
+ for (int i=topLeft.row(); i<=bottomRight.column(); ++i)
+ {
+ const Record<ESM::Region>& region = regions.getRecord (i);
+
+ update.push_back (region.get().mId);
+
+ if (!region.isDeleted())
+ addRegion (region.get().mId, region.get().mMapColor);
+ else
+ removeRegion (region.get().mId);
+ }
+
+ updateRegions (update);
+}
+
+void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end)
+{
+ const IdCollection<Cell>& cells = mData.getCells();
+
+ for (int i=start; i<=end; ++i)
+ {
+ const Record<Cell>& cell = cells.getRecord (i);
+
+ const Cell& cell2 = cell.get();
+
+ if (cell2.isExterior())
+ {
+ CellIndex index (cell2.mData.mX, cell2.mData.mY);
+
+ removeCell (index);
+ }
+ }
+}
+
+void CSMWorld::RegionMap::cellsInserted (const QModelIndex& parent, int start, int end)
+{
+ addCells (start, end);
+}
+
+void CSMWorld::RegionMap::cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
+{
+ // Note: At this point an additional check could be inserted to see if there is any change to the
+ // columns we are interested in. If not we can exit the function here and avoid all updating.
+
+ addCells (topLeft.row(), bottomRight.row());
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp
new file mode 100644
index 0000000000..7fb89f20ac
--- /dev/null
+++ b/apps/opencs/model/world/regionmap.hpp
@@ -0,0 +1,111 @@
+#ifndef CSM_WOLRD_REGIONMAP_H
+#define CSM_WOLRD_REGIONMAP_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <QAbstractTableModel>
+
+#include "record.hpp"
+#include "cell.hpp"
+
+namespace CSMWorld
+{
+ class Data;
+
+ /// \brief Model for the region map
+ ///
+ /// This class does not holds any record data (other than for the purpose of buffering).
+ class RegionMap : public QAbstractTableModel
+ {
+ Q_OBJECT
+
+ public:
+
+ typedef std::pair<int, int> CellIndex;
+
+ private:
+
+ struct CellDescription
+ {
+ bool mDeleted;
+ std::string mRegion;
+ std::string mName;
+
+ CellDescription();
+
+ CellDescription (const Record<Cell>& cell);
+ };
+
+ Data& mData;
+ std::map<CellIndex, CellDescription> mMap;
+ CellIndex mMin; ///< inclusive
+ CellIndex mMax; ///< exclusive
+ std::map<std::string, unsigned int> mColours; ///< region ID, colour (RGBA)
+
+ CellIndex getIndex (const QModelIndex& index) const;
+ ///< Translates a Qt model index into a cell index (which can contain negative components)
+
+ QModelIndex getIndex (const CellIndex& index) const;
+
+ void buildRegions();
+
+ void buildMap();
+
+ void addCell (const CellIndex& index, const CellDescription& description);
+ ///< May be called on a cell that is already in the map (in which case an update is
+ // performed)
+
+ void addCells (int start, int end);
+
+ void removeCell (const CellIndex& index);
+ ///< May be called on a cell that is not in the map (in which case the call is ignored)
+
+ void addRegion (const std::string& region, unsigned int colour);
+ ///< May be called on a region that is already listed (in which case an update is
+ /// performed)
+ ///
+ /// \note This function does not update the region map.
+
+ void removeRegion (const std::string& region);
+ ///< May be called on a region that is not listed (in which case the call is ignored)
+ ///
+ /// \note This function does not update the region map.
+
+ void updateRegions (const std::vector<std::string>& regions);
+ ///< Update cells affected by the listed regions
+
+ void updateSize();
+
+ std::pair<CellIndex, CellIndex> getSize() const;
+
+ public:
+
+ RegionMap (Data& data);
+
+ virtual int rowCount (const QModelIndex& parent = QModelIndex()) const;
+
+ virtual int columnCount (const QModelIndex& parent = QModelIndex()) const;
+
+ virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const;
+
+ virtual Qt::ItemFlags flags (const QModelIndex& index) const;
+
+ private slots:
+
+ void regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end);
+
+ void regionsInserted (const QModelIndex& parent, int start, int end);
+
+ void regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
+
+ void cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end);
+
+ void cellsInserted (const QModelIndex& parent, int start, int end);
+
+ void cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp
new file mode 100644
index 0000000000..69b72abf26
--- /dev/null
+++ b/apps/opencs/model/world/scriptcontext.cpp
@@ -0,0 +1,22 @@
+
+#include "scriptcontext.hpp"
+
+bool CSMWorld::ScriptContext::canDeclareLocals() const
+{
+ return false;
+}
+
+char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const
+{
+ return ' ';
+}
+
+char CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const
+{
+ return ' ';
+}
+
+bool CSMWorld::ScriptContext::isId (const std::string& name) const
+{
+ return false;
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp
new file mode 100644
index 0000000000..1231aea649
--- /dev/null
+++ b/apps/opencs/model/world/scriptcontext.hpp
@@ -0,0 +1,26 @@
+#ifndef CSM_WORLD_SCRIPTCONTEXT_H
+#define CSM_WORLD_SCRIPTCONTEXT_H
+
+#include <components/compiler/context.hpp>
+
+namespace CSMWorld
+{
+ class ScriptContext : public Compiler::Context
+ {
+ public:
+
+ virtual bool canDeclareLocals() const;
+ ///< Is the compiler allowed to declare local variables?
+
+ virtual char getGlobalType (const std::string& name) const;
+ ///< 'l: long, 's': short, 'f': float, ' ': does not exist.
+
+ virtual char getMemberType (const std::string& name, const std::string& id) const;
+ ///< 'l: long, 's': short, 'f': float, ' ': does not exist.
+
+ virtual bool isId (const std::string& name) const;
+ ///< Does \a name match an ID, that can be referenced?
+ };
+}
+
+#endif
diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp
new file mode 100644
index 0000000000..42ebd1f807
--- /dev/null
+++ b/apps/opencs/model/world/universalid.cpp
@@ -0,0 +1,333 @@
+
+#include "universalid.hpp"
+
+#include <ostream>
+#include <stdexcept>
+#include <sstream>
+
+namespace
+{
+ struct TypeData
+ {
+ CSMWorld::UniversalId::Class mClass;
+ CSMWorld::UniversalId::Type mType;
+ const char *mName;
+ const char *mIcon;
+ };
+
+ static const TypeData sNoArg[] =
+ {
+ { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables,
+ "Referenceables", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References,
+ "References", 0 },
+ { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap,
+ "Region Map", 0 },
+ { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 },
+
+ { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker
+ };
+
+ static const TypeData sIdArg[] =
+ {
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", 0 },
+ { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":./apparatus.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":./container.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList,
+ "Creature Levelled List", ":./creature.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList,
+ "Item Levelled List", ":./leveled-item.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous,
+ "Miscellaneous", ":./miscellaneous.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" },
+ { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" },
+ { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 },
+
+ { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker
+ };
+
+ static const TypeData sIndexArg[] =
+ {
+ { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 },
+
+ { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker
+ };
+
+ static const unsigned int IDARG_SIZE = sizeof (sIdArg) / sizeof (TypeData);
+}
+
+CSMWorld::UniversalId::UniversalId (const std::string& universalId)
+{
+ std::string::size_type index = universalId.find (':');
+
+ if (index!=std::string::npos)
+ {
+ std::string type = universalId.substr (0, index);
+
+ for (int i=0; sIdArg[i].mName; ++i)
+ if (type==sIdArg[i].mName)
+ {
+ mArgumentType = ArgumentType_Id;
+ mType = sIdArg[i].mType;
+ mClass = sIdArg[i].mClass;
+ mId = universalId.substr (index+2);
+ return;
+ }
+
+ for (int i=0; sIndexArg[i].mName; ++i)
+ if (type==sIndexArg[i].mName)
+ {
+ mArgumentType = ArgumentType_Index;
+ mType = sIndexArg[i].mType;
+ mClass = sIndexArg[i].mClass;
+
+ std::istringstream stream (universalId.substr (index+2));
+
+ if (stream >> mIndex)
+ return;
+
+ break;
+ }
+ }
+ else
+ {
+ for (int i=0; sNoArg[i].mName; ++i)
+ if (universalId==sNoArg[i].mName)
+ {
+ mArgumentType = ArgumentType_None;
+ mType = sNoArg[i].mType;
+ mClass = sNoArg[i].mClass;
+ return;
+ }
+ }
+
+ throw std::runtime_error ("invalid UniversalId: " + universalId);
+}
+
+CSMWorld::UniversalId::UniversalId (Type type) : mArgumentType (ArgumentType_None), mType (type), mIndex (0)
+{
+ for (int i=0; sNoArg[i].mName; ++i)
+ if (type==sNoArg[i].mType)
+ {
+ mClass = sNoArg[i].mClass;
+ return;
+ }
+
+ throw std::logic_error ("invalid argument-less UniversalId type");
+}
+
+CSMWorld::UniversalId::UniversalId (Type type, const std::string& id)
+: mArgumentType (ArgumentType_Id), mType (type), mId (id), mIndex (0)
+{
+ for (int i=0; sIdArg[i].mName; ++i)
+ if (type==sIdArg[i].mType)
+ {
+ mClass = sIdArg[i].mClass;
+ return;
+ }
+
+ throw std::logic_error ("invalid ID argument UniversalId type");
+}
+
+CSMWorld::UniversalId::UniversalId (Type type, int index)
+: mArgumentType (ArgumentType_Index), mType (type), mIndex (index)
+{
+ for (int i=0; sIndexArg[i].mName; ++i)
+ if (type==sIndexArg[i].mType)
+ {
+ mClass = sIndexArg[i].mClass;
+ return;
+ }
+
+ throw std::logic_error ("invalid index argument UniversalId type");
+}
+
+CSMWorld::UniversalId::Class CSMWorld::UniversalId::getClass() const
+{
+ return mClass;
+}
+
+CSMWorld::UniversalId::ArgumentType CSMWorld::UniversalId::getArgumentType() const
+{
+ return mArgumentType;
+}
+
+CSMWorld::UniversalId::Type CSMWorld::UniversalId::getType() const
+{
+ return mType;
+}
+
+const std::string& CSMWorld::UniversalId::getId() const
+{
+ if (mArgumentType!=ArgumentType_Id)
+ throw std::logic_error ("invalid access to ID of non-ID UniversalId");
+
+ return mId;
+}
+
+int CSMWorld::UniversalId::getIndex() const
+{
+ if (mArgumentType!=ArgumentType_Index)
+ throw std::logic_error ("invalid access to index of non-index UniversalId");
+
+ return mIndex;
+}
+
+bool CSMWorld::UniversalId::isEqual (const UniversalId& universalId) const
+{
+ if (mClass!=universalId.mClass || mArgumentType!=universalId.mArgumentType || mType!=universalId.mType)
+ return false;
+
+ switch (mArgumentType)
+ {
+ case ArgumentType_Id: return mId==universalId.mId;
+ case ArgumentType_Index: return mIndex==universalId.mIndex;
+
+ default: return true;
+ }
+}
+
+bool CSMWorld::UniversalId::isLess (const UniversalId& universalId) const
+{
+ if (mType<universalId.mType)
+ return true;
+
+ if (mType>universalId.mType)
+ return false;
+
+ switch (mArgumentType)
+ {
+ case ArgumentType_Id: return mId<universalId.mId;
+ case ArgumentType_Index: return mIndex<universalId.mIndex;
+
+ default: return false;
+ }
+}
+
+std::string CSMWorld::UniversalId::getTypeName() const
+{
+ const TypeData *typeData = mArgumentType==ArgumentType_None ? sNoArg :
+ (mArgumentType==ArgumentType_Id ? sIdArg : sIndexArg);
+
+ for (int i=0; typeData[i].mName; ++i)
+ if (typeData[i].mType==mType)
+ return typeData[i].mName;
+
+ throw std::logic_error ("failed to retrieve UniversalId type name");
+}
+
+std::string CSMWorld::UniversalId::toString() const
+{
+ std::ostringstream stream;
+
+ stream << getTypeName();
+
+ switch (mArgumentType)
+ {
+ case ArgumentType_None: break;
+ case ArgumentType_Id: stream << ": " << mId; break;
+ case ArgumentType_Index: stream << ": " << mIndex; break;
+ }
+
+ return stream.str();
+}
+
+std::string CSMWorld::UniversalId::getIcon() const
+{
+ const TypeData *typeData = mArgumentType==ArgumentType_None ? sNoArg :
+ (mArgumentType==ArgumentType_Id ? sIdArg : sIndexArg);
+
+ for (int i=0; typeData[i].mName; ++i)
+ if (typeData[i].mType==mType)
+ return typeData[i].mIcon ? typeData[i].mIcon : "";
+
+ throw std::logic_error ("failed to retrieve UniversalId type icon");
+}
+
+std::vector<CSMWorld::UniversalId::Type> CSMWorld::UniversalId::listReferenceableTypes()
+{
+ std::vector<CSMWorld::UniversalId::Type> list;
+
+ for (int i=0; sIdArg[i].mName; ++i)
+ if (sIdArg[i].mClass==Class_RefRecord)
+ list.push_back (sIdArg[i].mType);
+
+ return list;
+}
+
+std::pair<int, const char *> CSMWorld::UniversalId::getIdArgPair (unsigned int index)
+{
+ std::pair<int, const char *> retPair;
+
+ if ( index < IDARG_SIZE )
+ {
+ retPair.first = sIdArg[index].mType;
+ retPair.second = sIdArg[index].mName;
+ }
+
+ return retPair;
+}
+
+unsigned int CSMWorld::UniversalId::getIdArgSize()
+{
+ return IDARG_SIZE;
+}
+
+
+bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right)
+{
+ return left.isEqual (right);
+}
+
+bool CSMWorld::operator!= (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right)
+{
+ return !left.isEqual (right);
+}
+
+bool CSMWorld::operator< (const UniversalId& left, const UniversalId& right)
+{
+ return left.isLess (right);
+}
+
+std::ostream& CSMWorld::operator< (std::ostream& stream, const CSMWorld::UniversalId& universalId)
+{
+ return stream << universalId.toString();
+}
diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp
new file mode 100644
index 0000000000..8042c3dfdf
--- /dev/null
+++ b/apps/opencs/model/world/universalid.hpp
@@ -0,0 +1,152 @@
+#ifndef CSM_WOLRD_UNIVERSALID_H
+#define CSM_WOLRD_UNIVERSALID_H
+
+#include <string>
+#include <iosfwd>
+#include <vector>
+
+#include <QMetaType>
+
+namespace CSMWorld
+{
+ class UniversalId
+ {
+ public:
+
+ enum Class
+ {
+ Class_None = 0,
+ Class_Record,
+ Class_RefRecord, // referenceable record
+ Class_SubRecord,
+ Class_RecordList,
+ Class_Collection, // multiple types of records combined
+ Class_Transient, // not part of the world data or the project data
+ Class_NonRecord // record like data that is not part of the world
+ };
+
+ enum ArgumentType
+ {
+ ArgumentType_None,
+ ArgumentType_Id,
+ ArgumentType_Index
+ };
+
+ enum Type
+ {
+ Type_None,
+ Type_Globals,
+ Type_Global,
+ Type_VerificationResults,
+ Type_Gmsts,
+ Type_Gmst,
+ Type_Skills,
+ Type_Skill,
+ Type_Classes,
+ Type_Class,
+ Type_Factions,
+ Type_Faction,
+ Type_Races,
+ Type_Race,
+ Type_Sounds,
+ Type_Sound,
+ Type_Scripts,
+ Type_Script,
+ Type_Regions,
+ Type_Region,
+ Type_Birthsigns,
+ Type_Birthsign,
+ Type_Spells,
+ Type_Spell,
+ Type_Cells,
+ Type_Cell,
+ Type_Referenceables,
+ Type_Referenceable,
+ Type_Activator,
+ Type_Potion,
+ Type_Apparatus,
+ Type_Armor,
+ Type_Book,
+ Type_Clothing,
+ Type_Container,
+ Type_Creature,
+ Type_Door,
+ Type_Ingredient,
+ Type_CreatureLevelledList,
+ Type_ItemLevelledList,
+ Type_Light,
+ Type_Lockpick,
+ Type_Miscellaneous,
+ Type_Npc,
+ Type_Probe,
+ Type_Repair,
+ Type_Static,
+ Type_Weapon,
+ Type_References,
+ Type_Reference,
+ Type_RegionMap,
+ Type_Filter,
+ Type_Filters
+ };
+
+ private:
+
+ Class mClass;
+ ArgumentType mArgumentType;
+ Type mType;
+ std::string mId;
+ int mIndex;
+
+ public:
+
+ UniversalId (const std::string& universalId);
+
+ UniversalId (Type type = Type_None);
+ ///< Using a type for a non-argument-less UniversalId will throw an exception.
+
+ UniversalId (Type type, const std::string& id);
+ ///< Using a type for a non-ID-argument UniversalId will throw an exception.
+
+ UniversalId (Type type, int index);
+ ///< Using a type for a non-index-argument UniversalId will throw an exception.
+
+ Class getClass() const;
+
+ ArgumentType getArgumentType() const;
+
+ Type getType() const;
+
+ const std::string& getId() const;
+ ///< Calling this function for a non-ID type will throw an exception.
+
+ int getIndex() const;
+ ///< Calling this function for a non-index type will throw an exception.
+
+ bool isEqual (const UniversalId& universalId) const;
+
+ bool isLess (const UniversalId& universalId) const;
+
+ std::string getTypeName() const;
+
+ std::string toString() const;
+
+ std::string getIcon() const;
+ ///< Will return an empty string, if no icon is available.
+
+ static std::vector<Type> listReferenceableTypes();
+
+ static std::pair<int, const char *> getIdArgPair (unsigned int index);
+ static unsigned int getIdArgSize ();
+ };
+
+ bool operator== (const UniversalId& left, const UniversalId& right);
+ bool operator!= (const UniversalId& left, const UniversalId& right);
+
+ bool operator< (const UniversalId& left, const UniversalId& right);
+
+ std::ostream& operator< (std::ostream& stream, const UniversalId& universalId);
+}
+
+Q_DECLARE_METATYPE (CSMWorld::UniversalId)
+
+#endif
diff --git a/apps/opencs/ocspropertywidget.cpp b/apps/opencs/ocspropertywidget.cpp
new file mode 100644
index 0000000000..68315201ae
--- /dev/null
+++ b/apps/opencs/ocspropertywidget.cpp
@@ -0,0 +1,6 @@
+#include "ocspropertywidget.hpp"
+
+OcsPropertyWidget::OcsPropertyWidget(QObject *parent) :
+ QObject(parent)
+{
+}
diff --git a/apps/opencs/ocspropertywidget.hpp b/apps/opencs/ocspropertywidget.hpp
new file mode 100644
index 0000000000..fc64a0a692
--- /dev/null
+++ b/apps/opencs/ocspropertywidget.hpp
@@ -0,0 +1,18 @@
+#ifndef OCSPROPERTYWIDGET_HPP
+#define OCSPROPERTYWIDGET_HPP
+
+#include <QObject>
+
+class OcsPropertyWidget : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OcsPropertyWidget(QObject *parent = 0);
+
+signals:
+
+public slots:
+
+};
+
+#endif // OCSPROPERTYWIDGET_HPP
diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp
new file mode 100644
index 0000000000..f956317a71
--- /dev/null
+++ b/apps/opencs/view/doc/filedialog.cpp
@@ -0,0 +1,272 @@
+#include "filedialog.hpp"
+
+#include <QCheckBox>
+#include <QPushButton>
+#include <QDialogButtonBox>
+#include <QSortFilterProxyModel>
+#include <QRegExpValidator>
+#include <QRegExp>
+#include <QSpacerItem>
+#include <QPushButton>
+#include <QLabel>
+
+#include <components/fileorderlist/model/datafilesmodel.hpp>
+#include <components/fileorderlist/model/pluginsproxymodel.hpp>
+#include <components/fileorderlist/model/esm/esmfile.hpp>
+
+#include <components/fileorderlist/utils/lineedit.hpp>
+
+FileDialog::FileDialog(QWidget *parent) :
+ QDialog(parent)
+{
+ setupUi(this);
+
+ // Models
+ mDataFilesModel = new DataFilesModel(this);
+
+ mMastersProxyModel = new QSortFilterProxyModel();
+ mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm"));
+ mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ mMastersProxyModel->setSourceModel(mDataFilesModel);
+
+ mPluginsProxyModel = new PluginsProxyModel();
+ mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp"));
+ mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ mPluginsProxyModel->setSourceModel(mDataFilesModel);
+
+ mFilterProxyModel = new QSortFilterProxyModel();
+ mFilterProxyModel->setDynamicSortFilter(true);
+ mFilterProxyModel->setSourceModel(mPluginsProxyModel);
+
+ QCheckBox checkBox;
+ unsigned int height = checkBox.sizeHint().height() + 4;
+
+ mastersTable->setModel(mMastersProxyModel);
+ mastersTable->setObjectName("MastersTable");
+ mastersTable->setContextMenuPolicy(Qt::CustomContextMenu);
+ mastersTable->setSortingEnabled(false);
+ mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ mastersTable->setAlternatingRowColors(true);
+ mastersTable->horizontalHeader()->setStretchLastSection(true);
+
+ // Set the row height to the size of the checkboxes
+ mastersTable->verticalHeader()->setDefaultSectionSize(height);
+ mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
+ mastersTable->verticalHeader()->hide();
+
+ pluginsTable->setModel(mFilterProxyModel);
+ pluginsTable->setObjectName("PluginsTable");
+ pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
+ pluginsTable->setSortingEnabled(false);
+ pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ pluginsTable->setAlternatingRowColors(true);
+ pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
+ pluginsTable->horizontalHeader()->setStretchLastSection(true);
+
+ pluginsTable->verticalHeader()->setDefaultSectionSize(height);
+ pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
+
+ // Hide the profile elements
+ profileLabel->hide();
+ profilesComboBox->hide();
+ newProfileButton->hide();
+ deleteProfileButton->hide();
+
+ // Add some extra widgets
+ QHBoxLayout *nameLayout = new QHBoxLayout();
+ QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+
+ mNameLabel = new QLabel(tr("File Name:"), this);
+
+ QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$"));
+ mNameLineEdit = new LineEdit(this);
+ mNameLineEdit->setValidator(validator);
+
+ nameLayout->addSpacerItem(spacer);
+ nameLayout->addWidget(mNameLabel);
+ nameLayout->addWidget(mNameLineEdit);
+
+ mButtonBox = new QDialogButtonBox(this);
+
+ mCreateButton = new QPushButton(tr("Create"), this);
+ mCreateButton->setEnabled(false);
+
+ verticalLayout->addLayout(nameLayout);
+ verticalLayout->addWidget(mButtonBox);
+
+ // Set sizes
+ QList<int> sizeList;
+ sizeList << 175;
+ sizeList << 200;
+
+ splitter->setSizes(sizeList);
+
+ resize(600, 400);
+
+ connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews()));
+ connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList)));
+ connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString)));
+
+ connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
+
+ connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
+ connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
+
+ connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked()));
+
+ connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+void FileDialog::updateViews()
+{
+ // Ensure the columns are hidden because sort() re-enables them
+ mastersTable->setColumnHidden(1, true);
+ mastersTable->setColumnHidden(3, true);
+ mastersTable->setColumnHidden(4, true);
+ mastersTable->setColumnHidden(5, true);
+ mastersTable->setColumnHidden(6, true);
+ mastersTable->setColumnHidden(7, true);
+ mastersTable->setColumnHidden(8, true);
+ mastersTable->resizeColumnsToContents();
+
+ pluginsTable->setColumnHidden(1, true);
+ pluginsTable->setColumnHidden(3, true);
+ pluginsTable->setColumnHidden(4, true);
+ pluginsTable->setColumnHidden(5, true);
+ pluginsTable->setColumnHidden(6, true);
+ pluginsTable->setColumnHidden(7, true);
+ pluginsTable->setColumnHidden(8, true);
+ pluginsTable->resizeColumnsToContents();
+
+}
+
+void FileDialog::updateOpenButton(const QStringList &items)
+{
+ QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open);
+
+ if (!openButton)
+ return;
+
+ openButton->setEnabled(!items.isEmpty());
+}
+
+void FileDialog::updateCreateButton(const QString &name)
+{
+ if (!mCreateButton->isVisible())
+ return;
+
+ mCreateButton->setEnabled(!name.isEmpty());
+}
+
+void FileDialog::filterChanged(const QString &filter)
+{
+ QRegExp filterRe(filter, Qt::CaseInsensitive, QRegExp::FixedString);
+ mFilterProxyModel->setFilterRegExp(filterRe);
+}
+
+void FileDialog::addFiles(const QString &path)
+{
+ mDataFilesModel->addFiles(path);
+ mDataFilesModel->sort(3); // Sort by date accessed
+}
+
+void FileDialog::setEncoding(const QString &encoding)
+{
+ mDataFilesModel->setEncoding(encoding);
+}
+
+void FileDialog::setCheckState(QModelIndex index)
+{
+ if (!index.isValid())
+ return;
+
+ QObject *object = QObject::sender();
+
+ // Not a signal-slot call
+ if (!object)
+ return;
+
+
+ if (object->objectName() == QLatin1String("PluginsTable")) {
+ QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
+ mFilterProxyModel->mapToSource(index));
+
+ if (sourceIndex.isValid()) {
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
+ : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
+ }
+ }
+
+ if (object->objectName() == QLatin1String("MastersTable")) {
+ QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
+
+ if (sourceIndex.isValid()) {
+ (mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
+ ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
+ : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
+ }
+ }
+
+ return;
+}
+
+QStringList FileDialog::checkedItemsPaths()
+{
+ return mDataFilesModel->checkedItemsPaths();
+}
+
+QString FileDialog::fileName()
+{
+ return mNameLineEdit->text();
+}
+
+void FileDialog::openFile()
+{
+ setWindowTitle(tr("Open"));
+
+ mNameLabel->hide();
+ mNameLineEdit->hide();
+ mCreateButton->hide();
+
+ mButtonBox->removeButton(mCreateButton);
+ mButtonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Open);
+ QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open);
+ openButton->setEnabled(false);
+
+ show();
+ raise();
+ activateWindow();
+}
+
+void FileDialog::newFile()
+{
+ setWindowTitle(tr("New"));
+
+ mNameLabel->show();
+ mNameLineEdit->clear();
+ mNameLineEdit->show();
+ mCreateButton->show();
+
+ mButtonBox->setStandardButtons(QDialogButtonBox::Cancel);
+ mButtonBox->addButton(mCreateButton, QDialogButtonBox::ActionRole);
+
+ show();
+ raise();
+ activateWindow();
+}
+
+void FileDialog::accept()
+{
+ emit openFiles();
+}
+
+void FileDialog::createButtonClicked()
+{
+ emit createNewFile();
+}
diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp
new file mode 100644
index 0000000000..b21618d5de
--- /dev/null
+++ b/apps/opencs/view/doc/filedialog.hpp
@@ -0,0 +1,66 @@
+#ifndef FILEDIALOG_HPP
+#define FILEDIALOG_HPP
+
+#include <QDialog>
+#include <QModelIndex>
+
+#include "ui_datafilespage.h"
+
+class QDialogButtonBox;
+class QSortFilterProxyModel;
+class QAbstractItemModel;
+class QPushButton;
+class QStringList;
+class QString;
+class QMenu;
+
+class DataFilesModel;
+class PluginsProxyModel;
+
+class FileDialog : public QDialog, private Ui::DataFilesPage
+{
+ Q_OBJECT
+public:
+ explicit FileDialog(QWidget *parent = 0);
+ void addFiles(const QString &path);
+ void setEncoding(const QString &encoding);
+
+ void openFile();
+ void newFile();
+ void accepted();
+
+ QStringList checkedItemsPaths();
+ QString fileName();
+
+signals:
+ void openFiles();
+ void createNewFile();
+
+public slots:
+ void accept();
+
+private slots:
+ void updateViews();
+ void updateOpenButton(const QStringList &items);
+ void updateCreateButton(const QString &name);
+ void setCheckState(QModelIndex index);
+
+ void filterChanged(const QString &filter);
+
+ void createButtonClicked();
+
+private:
+ QLabel *mNameLabel;
+ LineEdit *mNameLineEdit;
+
+ QPushButton *mCreateButton;
+ QDialogButtonBox *mButtonBox;
+
+ DataFilesModel *mDataFilesModel;
+
+ PluginsProxyModel *mPluginsProxyModel;
+ QSortFilterProxyModel *mMastersProxyModel;
+ QSortFilterProxyModel *mFilterProxyModel;
+};
+
+#endif // FILEDIALOG_HPP
diff --git a/apps/opencs/view/doc/opendialog.cpp b/apps/opencs/view/doc/opendialog.cpp
new file mode 100644
index 0000000000..7b62aafa31
--- /dev/null
+++ b/apps/opencs/view/doc/opendialog.cpp
@@ -0,0 +1,62 @@
+#include <QVBoxLayout>
+#include <QDialogButtonBox>
+
+#include <components/fileorderlist/datafileslist.hpp>
+
+#include "opendialog.hpp"
+
+OpenDialog::OpenDialog(QWidget * parent) : QDialog(parent)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ mFileSelector = new DataFilesList(mCfgMgr, this);
+ layout->addWidget(mFileSelector);
+
+ /// \todo move config to Editor class and add command line options.
+ // We use the Configuration Manager to retrieve the configuration values
+ boost::program_options::variables_map variables;
+ boost::program_options::options_description desc;
+
+ desc.add_options()
+ ("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
+ ("data-local", boost::program_options::value<std::string>()->default_value(""))
+ ("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
+ ("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
+
+ boost::program_options::notify(variables);
+
+ mCfgMgr.readConfiguration(variables, desc);
+
+ Files::PathContainer mDataDirs, mDataLocal;
+ if (!variables["data"].empty()) {
+ mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
+ }
+
+ std::string local = variables["data-local"].as<std::string>();
+ if (!local.empty()) {
+ mDataLocal.push_back(Files::PathContainer::value_type(local));
+ }
+
+ mCfgMgr.processPaths(mDataDirs);
+ mCfgMgr.processPaths(mDataLocal);
+
+ // Set the charset for reading the esm/esp files
+ QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
+
+ Files::PathContainer dataDirs;
+ dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end());
+ dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end());
+ mFileSelector->setupDataFiles(dataDirs, encoding);
+
+ buttonBox = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel, Qt::Horizontal, this);
+ connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ layout->addWidget(buttonBox);
+
+ setLayout(layout);
+ setWindowTitle(tr("Open"));
+}
+
+void OpenDialog::getFileList(std::vector<boost::filesystem::path>& paths)
+{
+ mFileSelector->selectedFiles(paths);
+}
diff --git a/apps/opencs/view/doc/opendialog.hpp b/apps/opencs/view/doc/opendialog.hpp
new file mode 100644
index 0000000000..6355aea44f
--- /dev/null
+++ b/apps/opencs/view/doc/opendialog.hpp
@@ -0,0 +1,17 @@
+#include <qdialog.h>
+#include <components/files/configurationmanager.hpp>
+
+class DataFilesList;
+class QDialogButtonBox;
+
+class OpenDialog : public QDialog {
+ Q_OBJECT
+public:
+ OpenDialog(QWidget * parent = 0);
+
+ void getFileList(std::vector<boost::filesystem::path>& paths);
+private:
+ DataFilesList * mFileSelector;
+ QDialogButtonBox * buttonBox;
+ Files::ConfigurationManager mCfgMgr;
+}; \ No newline at end of file
diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp
new file mode 100644
index 0000000000..6977d79535
--- /dev/null
+++ b/apps/opencs/view/doc/operation.cpp
@@ -0,0 +1,151 @@
+#include "operation.hpp"
+
+#include <sstream>
+
+#include <QProgressBar>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#include "../../model/doc/document.hpp"
+
+void CSVDoc::Operation::updateLabel (int threads)
+{
+ if (threads==-1 || ((threads==0)!=mStalling))
+ {
+ std::string name ("unknown operation");
+
+ switch (mType)
+ {
+ case CSMDoc::State_Saving: name = "saving"; break;
+ case CSMDoc::State_Verifying: name = "verifying"; break;
+ }
+
+ std::ostringstream stream;
+
+ if ((mStalling = (threads<=0)))
+ {
+ stream << name << " (waiting for a free worker thread)";
+ }
+ else
+ {
+ stream << name << " (%p%)";
+ }
+
+ mProgressBar->setFormat (stream.str().c_str());
+ }
+}
+
+CSVDoc::Operation::Operation (int type, QWidget* parent) : mType (type), mStalling (false)
+{
+ /// \todo Add a cancel button or a pop up menu with a cancel item
+ initWidgets();
+ setBarColor( type);
+ updateLabel();
+
+ /// \todo assign different progress bar colours to allow the user to distinguish easily between operation types
+}
+
+CSVDoc::Operation::~Operation()
+{
+ delete mLayout;
+ delete mProgressBar;
+ delete mAbortButton;
+}
+
+void CSVDoc::Operation::initWidgets()
+{
+ mProgressBar = new QProgressBar ();
+ mAbortButton = new QPushButton("Abort");
+ mLayout = new QHBoxLayout();
+
+ mLayout->addWidget (mProgressBar);
+ mLayout->addWidget (mAbortButton);
+
+ connect (mAbortButton, SIGNAL (clicked()), this, SLOT (abortOperation()));
+}
+
+void CSVDoc::Operation::setProgress (int current, int max, int threads)
+{
+ updateLabel (threads);
+ mProgressBar->setRange (0, max);
+ mProgressBar->setValue (current);
+}
+
+int CSVDoc::Operation::getType() const
+{
+ return mType;
+}
+
+void CSVDoc::Operation::setBarColor (int type)
+{
+ QString style ="QProgressBar {"
+ "text-align: center;"
+ "}"
+ "QProgressBar::chunk {"
+ "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:.50 %2 stop: .51 %3 stop:1 %4);"
+ "text-align: center;"
+ "margin: 2px 1px 1p 2px;"
+ "}";
+
+ QString topColor = "#F2F6F8";
+ QString bottomColor = "#E0EFF9";
+ QString midTopColor = "#D8E1E7";
+ QString midBottomColor = "#B5C6D0"; // default gray gloss
+
+ // colors inspired by samples from:
+ // http://www.colorzilla.com/gradient-editor/
+
+ switch (type)
+ {
+ case CSMDoc::State_Saving:
+
+ topColor = "#FECCB1";
+ midTopColor = "#F17432";
+ midBottomColor = "#EA5507";
+ bottomColor = "#FB955E"; // red gloss #2
+ break;
+
+ case CSMDoc::State_Searching:
+
+ topColor = "#EBF1F6";
+ midTopColor = "#ABD3EE";
+ midBottomColor = "#89C3EB";
+ bottomColor = "#D5EBFB"; //blue gloss #4
+ break;
+
+ case CSMDoc::State_Verifying:
+
+ topColor = "#BFD255";
+ midTopColor = "#8EB92A";
+ midBottomColor = "#72AA00";
+ bottomColor = "#9ECB2D"; //green gloss
+ break;
+
+ case CSMDoc::State_Compiling:
+
+ topColor = "#F3E2C7";
+ midTopColor = "#C19E67";
+ midBottomColor = "#B68D4C";
+ bottomColor = "#E9D4B3"; //l Brown 3D
+ break;
+
+ default:
+
+ topColor = "#F2F6F8";
+ bottomColor = "#E0EFF9";
+ midTopColor = "#D8E1E7";
+ midBottomColor = "#B5C6D0"; // gray gloss for undefined ops
+ }
+
+ mProgressBar->setStyleSheet(style.arg(topColor).arg(midTopColor).arg(midBottomColor).arg(bottomColor));
+}
+
+QHBoxLayout *CSVDoc::Operation::getLayout() const
+{
+ return mLayout;
+}
+
+void CSVDoc::Operation::abortOperation()
+{
+ emit abortOperation (mType);
+}
diff --git a/apps/opencs/view/doc/operation.hpp b/apps/opencs/view/doc/operation.hpp
new file mode 100644
index 0000000000..48839fada4
--- /dev/null
+++ b/apps/opencs/view/doc/operation.hpp
@@ -0,0 +1,53 @@
+#ifndef CSV_DOC_OPERATION_H
+#define CSV_DOC_OPERATION_H
+
+#include <QObject>
+
+class QHBoxLayout;
+class QPushButton;
+class QProgressBar;
+
+namespace CSVDoc
+{
+ class Operation : public QObject
+ {
+ Q_OBJECT
+
+ int mType;
+ bool mStalling;
+ QProgressBar *mProgressBar;
+ QPushButton *mAbortButton;
+ QHBoxLayout *mLayout;
+
+ // not implemented
+ Operation (const Operation&);
+ Operation& operator= (const Operation&);
+
+ void updateLabel (int threads = -1);
+
+ public:
+
+ Operation (int type, QWidget *parent);
+ ~Operation();
+
+ void setProgress (int current, int max, int threads);
+
+ int getType() const;
+ QHBoxLayout *getLayout() const;
+
+ private:
+
+ void setBarColor (int type);
+ void initWidgets();
+
+ signals:
+
+ void abortOperation (int type);
+
+ private slots:
+
+ void abortOperation();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp
new file mode 100644
index 0000000000..7ee4b87260
--- /dev/null
+++ b/apps/opencs/view/doc/operations.cpp
@@ -0,0 +1,69 @@
+#include "operations.hpp"
+
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+
+#include "operation.hpp"
+
+CSVDoc::Operations::Operations()
+{
+ /// \todo make widget height fixed (exactly the height required to display all operations)
+
+ setFeatures (QDockWidget::NoDockWidgetFeatures);
+
+ QWidget *widgetContainer = new QWidget (this);
+ mLayout = new QVBoxLayout;
+
+ widgetContainer->setLayout (mLayout);
+ setWidget (widgetContainer);
+ setVisible (false);
+ setFixedHeight (widgetContainer->height());
+ setTitleBarWidget (new QWidget (this));
+}
+
+void CSVDoc::Operations::setProgress (int current, int max, int type, int threads)
+{
+ for (std::vector<Operation *>::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter)
+ if ((*iter)->getType()==type)
+ {
+ (*iter)->setProgress (current, max, threads);
+ return;
+ }
+
+ int oldCount = mOperations.size();
+ int newCount = oldCount + 1;
+
+ Operation *operation = new Operation (type, this);
+ connect (operation, SIGNAL (abortOperation (int)), this, SIGNAL (abortOperation (int)));
+
+ mLayout->addLayout (operation->getLayout());
+ mOperations.push_back (operation);
+ operation->setProgress (current, max, threads);
+
+ if ( oldCount > 0)
+ setFixedHeight (height()/oldCount * newCount);
+
+ setVisible (true);
+}
+
+void CSVDoc::Operations::quitOperation (int type)
+{
+ for (std::vector<Operation *>::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter)
+ if ((*iter)->getType()==type)
+ {
+ int oldCount = mOperations.size();
+ int newCount = oldCount - 1;
+
+ mLayout->removeItem ((*iter)->getLayout());
+
+ (*iter)->deleteLater();
+ mOperations.erase (iter);
+
+ if (oldCount > 1)
+ setFixedHeight (height() / oldCount * newCount);
+ else
+ setVisible (false);
+
+ break;
+ }
+}
diff --git a/apps/opencs/view/doc/operations.hpp b/apps/opencs/view/doc/operations.hpp
new file mode 100644
index 0000000000..71c595f66b
--- /dev/null
+++ b/apps/opencs/view/doc/operations.hpp
@@ -0,0 +1,41 @@
+#ifndef CSV_DOC_OPERATIONS_H
+#define CSV_DOC_OPERATIONS_H
+
+#include <vector>
+
+#include <QDockWidget>
+
+class QVBoxLayout;
+
+namespace CSVDoc
+{
+ class Operation;
+
+ class Operations : public QDockWidget
+ {
+ Q_OBJECT
+
+ QVBoxLayout *mLayout;
+ std::vector<Operation *> mOperations;
+
+ // not implemented
+ Operations (const Operations&);
+ Operations& operator= (const Operations&);
+
+ public:
+
+ Operations();
+
+ void setProgress (int current, int max, int type, int threads);
+ ///< Implicitly starts the operation, if it is not running already.
+
+ void quitOperation (int type);
+ ///< Calling this function for an operation that is not running is a no-op.
+
+ signals:
+
+ void abortOperation (int type);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp
new file mode 100644
index 0000000000..6c1e740589
--- /dev/null
+++ b/apps/opencs/view/doc/startup.cpp
@@ -0,0 +1,27 @@
+
+#include "startup.hpp"
+
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QPushButton>
+#include <QHBoxLayout>
+#include <QRect>
+
+CSVDoc::StartupDialogue::StartupDialogue()
+{
+ QHBoxLayout *layout = new QHBoxLayout (this);
+
+ QPushButton *createDocument = new QPushButton ("new", this);
+ connect (createDocument, SIGNAL (clicked()), this, SIGNAL (createDocument()));
+ layout->addWidget (createDocument);
+
+ QPushButton *loadDocument = new QPushButton ("load", this);
+ connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument()));
+ layout->addWidget (loadDocument);
+
+ setLayout (layout);
+
+ QRect scr = QApplication::desktop()->screenGeometry();
+ QRect rect = geometry();
+ move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y());
+}
diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp
new file mode 100644
index 0000000000..f24d2a64ba
--- /dev/null
+++ b/apps/opencs/view/doc/startup.hpp
@@ -0,0 +1,24 @@
+#ifndef CSV_DOC_STARTUP_H
+#define CSV_DOC_STARTUP_H
+
+#include <QWidget>
+
+namespace CSVDoc
+{
+ class StartupDialogue : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ StartupDialogue();
+
+ signals:
+
+ void createDocument();
+
+ void loadDocument();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp
new file mode 100644
index 0000000000..09361a1c03
--- /dev/null
+++ b/apps/opencs/view/doc/subview.cpp
@@ -0,0 +1,19 @@
+#include "subview.hpp"
+
+CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id)
+{
+ /// \todo add a button to the title bar that clones this sub view
+
+ setWindowTitle (mUniversalId.toString().c_str());
+}
+
+CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const
+{
+ return mUniversalId;
+}
+
+void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+}
+
+void CSVDoc::SubView::setStatusBar (bool show) {} \ No newline at end of file
diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp
new file mode 100644
index 0000000000..aa073f81dd
--- /dev/null
+++ b/apps/opencs/view/doc/subview.hpp
@@ -0,0 +1,49 @@
+#ifndef CSV_DOC_SUBVIEW_H
+#define CSV_DOC_SUBVIEW_H
+
+#include "../../model/doc/document.hpp"
+
+#include "../../model/world/universalid.hpp"
+
+#include "subviewfactory.hpp"
+
+#include <QDockWidget>
+
+class QUndoStack;
+
+namespace CSMWorld
+{
+ class Data;
+}
+
+namespace CSVDoc
+{
+ class SubView : public QDockWidget
+ {
+ Q_OBJECT
+
+ CSMWorld::UniversalId mUniversalId;
+
+ // not implemented
+ SubView (const SubView&);
+ SubView& operator= (SubView&);
+
+ public:
+
+ SubView (const CSMWorld::UniversalId& id);
+
+ CSMWorld::UniversalId getUniversalId() const;
+
+ virtual void setEditLock (bool locked) = 0;
+ virtual void updateEditorSetting (const QString &, const QString &);
+
+ virtual void setStatusBar (bool show);
+ ///< Default implementation: ignored
+
+ signals:
+
+ void focusId (const CSMWorld::UniversalId& universalId);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp
new file mode 100644
index 0000000000..8576f6b1d1
--- /dev/null
+++ b/apps/opencs/view/doc/subviewfactory.cpp
@@ -0,0 +1,38 @@
+
+#include "subviewfactory.hpp"
+
+#include <cassert>
+
+#include <stdexcept>
+
+CSVDoc::SubViewFactoryBase::SubViewFactoryBase() {}
+
+CSVDoc::SubViewFactoryBase::~SubViewFactoryBase() {}
+
+
+CSVDoc::SubViewFactoryManager::SubViewFactoryManager() {}
+
+CSVDoc::SubViewFactoryManager::~SubViewFactoryManager()
+{
+ for (std::map<CSMWorld::UniversalId::Type, SubViewFactoryBase *>::iterator iter (mSubViewFactories.begin());
+ iter!=mSubViewFactories.end(); ++iter)
+ delete iter->second;
+}
+
+void CSVDoc::SubViewFactoryManager::add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory)
+{
+ assert (mSubViewFactories.find (id)==mSubViewFactories.end());
+
+ mSubViewFactories.insert (std::make_pair (id, factory));
+}
+
+CSVDoc::SubView *CSVDoc::SubViewFactoryManager::makeSubView (const CSMWorld::UniversalId& id,
+ CSMDoc::Document& document)
+{
+ std::map<CSMWorld::UniversalId::Type, SubViewFactoryBase *>::iterator iter = mSubViewFactories.find (id.getType());
+
+ if (iter==mSubViewFactories.end())
+ throw std::runtime_error ("Failed to create a sub view for: " + id.toString());
+
+ return iter->second->makeSubView (id, document);
+} \ No newline at end of file
diff --git a/apps/opencs/view/doc/subviewfactory.hpp b/apps/opencs/view/doc/subviewfactory.hpp
new file mode 100644
index 0000000000..1f7c154806
--- /dev/null
+++ b/apps/opencs/view/doc/subviewfactory.hpp
@@ -0,0 +1,55 @@
+#ifndef CSV_DOC_SUBVIEWFACTORY_H
+#define CSV_DOC_SUBVIEWFACTORY_H
+
+#include <map>
+
+#include "../../model/world/universalid.hpp"
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSVDoc
+{
+ class SubView;
+
+ class SubViewFactoryBase
+ {
+ // not implemented
+ SubViewFactoryBase (const SubViewFactoryBase&);
+ SubViewFactoryBase& operator= (const SubViewFactoryBase&);
+
+ public:
+
+ SubViewFactoryBase();
+
+ virtual ~SubViewFactoryBase();
+
+ virtual SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) = 0;
+ ///< The ownership of the returned sub view is not transferred.
+ };
+
+ class SubViewFactoryManager
+ {
+ std::map<CSMWorld::UniversalId::Type, SubViewFactoryBase *> mSubViewFactories;
+
+ // not implemented
+ SubViewFactoryManager (const SubViewFactoryManager&);
+ SubViewFactoryManager& operator= (const SubViewFactoryManager&);
+
+ public:
+
+ SubViewFactoryManager();
+
+ ~SubViewFactoryManager();
+
+ void add (const CSMWorld::UniversalId::Type& id, SubViewFactoryBase *factory);
+ ///< The ownership of \a factory is transferred to this.
+
+ SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
+ ///< The ownership of the returned sub view is not transferred.
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp
new file mode 100644
index 0000000000..2c4158f854
--- /dev/null
+++ b/apps/opencs/view/doc/subviewfactoryimp.hpp
@@ -0,0 +1,42 @@
+#ifndef CSV_DOC_SUBVIEWFACTORYIMP_H
+#define CSV_DOC_SUBVIEWFACTORYIMP_H
+
+#include "../../model/doc/document.hpp"
+
+#include "subviewfactory.hpp"
+
+namespace CSVDoc
+{
+ template<class SubViewT>
+ class SubViewFactory : public SubViewFactoryBase
+ {
+ public:
+
+ virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
+ };
+
+ template<class SubViewT>
+ CSVDoc::SubView *SubViewFactory<SubViewT>::makeSubView (const CSMWorld::UniversalId& id,
+ CSMDoc::Document& document)
+ {
+ return new SubViewT (id, document);
+ }
+
+
+ template<class SubViewT, class CreatorFactoryT>
+ class SubViewFactoryWithCreator : public SubViewFactoryBase
+ {
+ public:
+
+ virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
+ };
+
+ template<class SubViewT, class CreatorFactoryT>
+ CSVDoc::SubView *SubViewFactoryWithCreator<SubViewT, CreatorFactoryT>::makeSubView (
+ const CSMWorld::UniversalId& id, CSMDoc::Document& document)
+ {
+ return new SubViewT (id, document, CreatorFactoryT());
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp
new file mode 100644
index 0000000000..6801ea20df
--- /dev/null
+++ b/apps/opencs/view/doc/view.cpp
@@ -0,0 +1,464 @@
+#include "view.hpp"
+
+#include <sstream>
+#include <stdexcept>
+
+#include <QCloseEvent>
+#include <QMenuBar>
+#include <QMdiArea>
+#include <QDockWidget>
+#include <QtGui/QApplication>
+
+#include "../../model/doc/document.hpp"
+#include "../world/subviews.hpp"
+#include "../tools/subviews.hpp"
+#include "../settings/usersettingsdialog.hpp"
+#include "viewmanager.hpp"
+#include "operations.hpp"
+#include "subview.hpp"
+
+void CSVDoc::View::closeEvent (QCloseEvent *event)
+{
+ if (!mViewManager.closeRequest (this))
+ event->ignore();
+}
+
+void CSVDoc::View::setupFileMenu()
+{
+ QMenu *file = menuBar()->addMenu (tr ("&File"));
+
+ QAction *new_ = new QAction (tr ("New"), this);
+ connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest()));
+ file->addAction (new_);
+
+ QAction *open = new QAction (tr ("&Open"), this);
+ connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest()));
+ file->addAction (open);
+
+ mSave = new QAction (tr ("&Save"), this);
+ connect (mSave, SIGNAL (triggered()), this, SLOT (save()));
+ file->addAction (mSave);
+
+ mVerify = new QAction (tr ("&Verify"), this);
+ connect (mVerify, SIGNAL (triggered()), this, SLOT (verify()));
+ file->addAction (mVerify);
+
+ QAction *close = new QAction (tr ("&Close"), this);
+ connect (close, SIGNAL (triggered()), this, SLOT (close()));
+ file->addAction(close);
+
+ QAction *exit = new QAction (tr ("&Exit"), this);
+ connect (exit, SIGNAL (triggered()), this, SLOT (exit()));
+ connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *)));
+
+ file->addAction(exit);
+}
+
+void CSVDoc::View::setupEditMenu()
+{
+ QMenu *edit = menuBar()->addMenu (tr ("&Edit"));
+
+ mUndo = mDocument->getUndoStack().createUndoAction (this, tr("&Undo"));
+ mUndo->setShortcuts (QKeySequence::Undo);
+ edit->addAction (mUndo);
+
+ mRedo= mDocument->getUndoStack().createRedoAction (this, tr("&Redo"));
+ mRedo->setShortcuts (QKeySequence::Redo);
+ edit->addAction (mRedo);
+
+ QAction *userSettings = new QAction (tr ("&Preferences"), this);
+ connect (userSettings, SIGNAL (triggered()), this, SLOT (showUserSettings()));
+ edit->addAction (userSettings);
+}
+
+void CSVDoc::View::setupViewMenu()
+{
+ QMenu *view = menuBar()->addMenu (tr ("&View"));
+
+ QAction *newWindow = new QAction (tr ("&New View"), this);
+ connect (newWindow, SIGNAL (triggered()), this, SLOT (newView()));
+ view->addAction (newWindow);
+
+ mShowStatusBar = new QAction (tr ("Show Status Bar"), this);
+ mShowStatusBar->setCheckable (true);
+ connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool)));
+ view->addAction (mShowStatusBar);
+
+ QAction *filters = new QAction (tr ("Filters"), this);
+ connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView()));
+ view->addAction (filters);
+}
+
+void CSVDoc::View::setupWorldMenu()
+{
+ QMenu *world = menuBar()->addMenu (tr ("&World"));
+
+ QAction *regions = new QAction (tr ("Regions"), this);
+ connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView()));
+ world->addAction (regions);
+
+ QAction *cells = new QAction (tr ("Cells"), this);
+ connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView()));
+ world->addAction (cells);
+
+ QAction *referenceables = new QAction (tr ("Referenceables"), this);
+ connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView()));
+ world->addAction (referenceables);
+
+ QAction *references = new QAction (tr ("References"), this);
+ connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView()));
+ world->addAction (references);
+
+ world->addSeparator(); // items that don't represent single record lists follow here
+
+ QAction *regionMap = new QAction (tr ("Region Map"), this);
+ connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView()));
+ world->addAction (regionMap);
+}
+
+void CSVDoc::View::setupMechanicsMenu()
+{
+ QMenu *mechanics = menuBar()->addMenu (tr ("&Mechanics"));
+
+ QAction *globals = new QAction (tr ("Globals"), this);
+ connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
+ mechanics->addAction (globals);
+
+ QAction *gmsts = new QAction (tr ("Game settings"), this);
+ connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
+ mechanics->addAction (gmsts);
+
+ QAction *skills = new QAction (tr ("Skills"), this);
+ connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView()));
+ mechanics->addAction (skills);
+
+ QAction *classes = new QAction (tr ("Classes"), this);
+ connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView()));
+ mechanics->addAction (classes);
+
+ QAction *factions = new QAction (tr ("Factions"), this);
+ connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView()));
+ mechanics->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);
+
+ 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);
+}
+
+void CSVDoc::View::setupAssetsMenu()
+{
+ QMenu *assets = menuBar()->addMenu (tr ("&Assets"));
+
+ QAction *sounds = new QAction (tr ("Sounds"), this);
+ connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView()));
+ assets->addAction (sounds);
+}
+
+void CSVDoc::View::setupUi()
+{
+ setupFileMenu();
+ setupEditMenu();
+ setupViewMenu();
+ setupWorldMenu();
+ setupMechanicsMenu();
+ setupAssetsMenu();
+}
+
+void CSVDoc::View::updateTitle()
+{
+ std::ostringstream stream;
+
+ stream << mDocument->getName();
+
+ if (mDocument->getState() & CSMDoc::State_Modified)
+ stream << " *";
+
+ if (mViewTotal>1)
+ stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]";
+
+ setWindowTitle (stream.str().c_str());
+}
+
+void CSVDoc::View::updateActions()
+{
+ bool editing = !(mDocument->getState() & CSMDoc::State_Locked);
+
+ for (std::vector<QAction *>::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter)
+ (*iter)->setEnabled (editing);
+
+ mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo());
+ mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo());
+
+ mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving));
+ mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying));
+}
+
+CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews)
+ : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1),
+ mViewTotal (totalViews)
+{
+ QString width = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Width"));
+ QString height = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Height"));
+
+ resize (width.toInt(), height.toInt());
+
+ mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks);
+
+ setCentralWidget (&mSubViewWindow);
+
+ mOperations = new Operations;
+ addDockWidget (Qt::BottomDockWidgetArea, mOperations);
+
+ updateTitle();
+
+ setupUi();
+
+ CSVWorld::addSubViewFactories (mSubViewFactory);
+ CSVTools::addSubViewFactories (mSubViewFactory);
+
+ connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int)));
+}
+
+CSVDoc::View::~View()
+{
+}
+
+const CSMDoc::Document *CSVDoc::View::getDocument() const
+{
+ return mDocument;
+}
+
+CSMDoc::Document *CSVDoc::View::getDocument()
+{
+ return mDocument;
+}
+
+void CSVDoc::View::setIndex (int viewIndex, int totalViews)
+{
+ mViewIndex = viewIndex;
+ mViewTotal = totalViews;
+ updateTitle();
+}
+
+void CSVDoc::View::updateDocumentState()
+{
+ updateTitle();
+ updateActions();
+
+ static const int operations[] =
+ {
+ CSMDoc::State_Saving, CSMDoc::State_Verifying,
+ -1 // end marker
+ };
+
+ int state = mDocument->getState() ;
+
+ for (int i=0; operations[i]!=-1; ++i)
+ if (!(state & operations[i]))
+ mOperations->quitOperation (operations[i]);
+
+ QList<CSVDoc::SubView *> subViews = findChildren<CSVDoc::SubView *>();
+
+ for (QList<CSVDoc::SubView *>::iterator iter (subViews.begin()); iter!=subViews.end(); ++iter)
+ (*iter)->setEditLock (state & CSMDoc::State_Locked);
+}
+
+void CSVDoc::View::updateProgress (int current, int max, int type, int threads)
+{
+ mOperations->setProgress (current, max, type, threads);
+}
+
+void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id)
+{
+ /// \todo add an user setting for limiting the number of sub views per top level view. Automatically open a new top level view if this
+ /// number is exceeded
+
+ /// \todo if the sub view limit setting is one, the sub view title bar should be hidden and the text in the main title bar adjusted
+ /// accordingly
+
+ /// \todo add an user setting to reuse sub views (on a per document basis or on a per top level view basis)
+
+ SubView *view = mSubViewFactory.makeSubView (id, *mDocument);
+
+ view->setStatusBar (mShowStatusBar->isChecked());
+
+ mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view);
+
+ connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this,
+ SLOT (addSubView (const CSMWorld::UniversalId&)));
+
+ CSMSettings::UserSettings::instance().updateSettings("Display Format");
+
+ view->show();
+}
+
+void CSVDoc::View::newView()
+{
+ mViewManager.addView (mDocument);
+}
+
+void CSVDoc::View::save()
+{
+ mDocument->save();
+}
+
+void CSVDoc::View::verify()
+{
+ addSubView (mDocument->verify());
+}
+
+void CSVDoc::View::addGlobalsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Globals);
+}
+
+void CSVDoc::View::addGmstsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Gmsts);
+}
+
+void CSVDoc::View::addSkillsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Skills);
+}
+
+void CSVDoc::View::addClassesSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Classes);
+}
+
+void CSVDoc::View::addFactionsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Factions);
+}
+
+void CSVDoc::View::addRacesSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Races);
+}
+
+void CSVDoc::View::addSoundsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Sounds);
+}
+
+void CSVDoc::View::addScriptsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Scripts);
+}
+
+void CSVDoc::View::addRegionsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Regions);
+}
+
+void CSVDoc::View::addBirthsignsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Birthsigns);
+}
+
+void CSVDoc::View::addSpellsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Spells);
+}
+
+void CSVDoc::View::addCellsSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Cells);
+}
+
+void CSVDoc::View::addReferenceablesSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Referenceables);
+}
+
+void CSVDoc::View::addReferencesSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_References);
+}
+
+void CSVDoc::View::addRegionMapSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_RegionMap);
+}
+
+void CSVDoc::View::addFiltersSubView()
+{
+ addSubView (CSMWorld::UniversalId::Type_Filters);
+}
+
+void CSVDoc::View::abortOperation (int type)
+{
+ mDocument->abortOperation (type);
+ updateActions();
+}
+
+CSVDoc::Operations *CSVDoc::View::getOperations() const
+{
+ return mOperations;
+}
+
+void CSVDoc::View::exit()
+{
+ emit exitApplicationRequest (this);
+}
+
+void CSVDoc::View::showUserSettings()
+{
+ CSVSettings::UserSettingsDialog *settingsDialog = new CSVSettings::UserSettingsDialog(this);
+
+ settingsDialog->show();
+}
+
+void CSVDoc::View::resizeViewWidth (int width)
+{
+ if (width >= 0)
+ resize (width, geometry().height());
+}
+
+void CSVDoc::View::resizeViewHeight (int height)
+{
+ if (height >= 0)
+ resize (geometry().width(), height);
+}
+
+void CSVDoc::View::updateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+ if ( (settingName == "Record Status Display") || (settingName == "Referenceable ID Type Display") )
+ {
+ foreach (QObject *view, mSubViewWindow.children())
+ {
+ // not all mSubviewWindow children are CSVDoc::Subview objects
+ CSVDoc::SubView *subview = dynamic_cast<CSVDoc::SubView *>(view);
+
+ if (subview)
+ subview->updateEditorSetting (settingName, settingValue);
+ }
+ }
+ else if (settingName == "Width")
+ resizeViewWidth (settingValue.toInt());
+
+ else if (settingName == "Height")
+ resizeViewHeight (settingValue.toInt());
+}
+
+void CSVDoc::View::toggleShowStatusBar (bool show)
+{
+ foreach (QObject *view, mSubViewWindow.children())
+ {
+ if (CSVDoc::SubView *subView = dynamic_cast<CSVDoc::SubView *> (view))
+ subView->setStatusBar (show);
+ }
+}
diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp
new file mode 100644
index 0000000000..56c0b3edd4
--- /dev/null
+++ b/apps/opencs/view/doc/view.hpp
@@ -0,0 +1,169 @@
+#ifndef CSV_DOC_VIEW_H
+#define CSV_DOC_VIEW_H
+
+#include <vector>
+#include <map>
+
+#include <QMainWindow>
+
+#include "subviewfactory.hpp"
+
+class QAction;
+class QDockWidget;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSMWorld
+{
+ class UniversalId;
+}
+
+namespace CSVDoc
+{
+ class ViewManager;
+ class Operations;
+
+ class View : public QMainWindow
+ {
+ Q_OBJECT
+
+ ViewManager& mViewManager;
+ CSMDoc::Document *mDocument;
+ int mViewIndex;
+ int mViewTotal;
+ QAction *mUndo;
+ QAction *mRedo;
+ QAction *mSave;
+ QAction *mVerify;
+ QAction *mShowStatusBar;
+ std::vector<QAction *> mEditingActions;
+ Operations *mOperations;
+ SubViewFactoryManager mSubViewFactory;
+ QMainWindow mSubViewWindow;
+
+
+ // not implemented
+ View (const View&);
+ View& operator= (const View&);
+
+ private:
+
+ void closeEvent (QCloseEvent *event);
+
+ void setupFileMenu();
+
+ void setupEditMenu();
+
+ void setupViewMenu();
+
+ void setupWorldMenu();
+
+ void setupMechanicsMenu();
+
+ void setupAssetsMenu();
+
+ void setupUi();
+
+ void updateTitle();
+
+ void updateActions();
+
+ void exitApplication();
+
+ void loadUserSettings();
+
+ /// User preference function
+ void resizeViewWidth (int width);
+
+ /// User preference function
+ void resizeViewHeight (int height);
+
+ public:
+
+ View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews);
+
+ ///< The ownership of \a document is not transferred to *this.
+
+ virtual ~View();
+
+ const CSMDoc::Document *getDocument() const;
+
+ CSMDoc::Document *getDocument();
+
+ void setIndex (int viewIndex, int totalViews);
+
+ void updateDocumentState();
+
+ void updateProgress (int current, int max, int type, int threads);
+
+ Operations *getOperations() const;
+
+ /// Function called by view manager when user preferences are updated
+ void updateEditorSetting (const QString &, const QString &);
+
+ signals:
+
+ void newDocumentRequest();
+
+ void loadDocumentRequest();
+
+ void exitApplicationRequest (CSVDoc::View *view);
+
+ public slots:
+
+ void addSubView (const CSMWorld::UniversalId& id);
+
+ void abortOperation (int type);
+
+ private slots:
+
+ void newView();
+
+ void save();
+
+ void exit();
+
+ void verify();
+
+ void addGlobalsSubView();
+
+ void addGmstsSubView();
+
+ void addSkillsSubView();
+
+ void addClassesSubView();
+
+ void addFactionsSubView();
+
+ void addRacesSubView();
+
+ void addSoundsSubView();
+
+ void addScriptsSubView();
+
+ void addRegionsSubView();
+
+ void addBirthsignsSubView();
+
+ void addSpellsSubView();
+
+ void addCellsSubView();
+
+ void addReferenceablesSubView();
+
+ void addReferencesSubView();
+
+ void addRegionMapSubView();
+
+ void addFiltersSubView();
+
+ void showUserSettings();
+
+ void toggleShowStatusBar (bool show);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp
new file mode 100644
index 0000000000..6d06e42488
--- /dev/null
+++ b/apps/opencs/view/doc/viewmanager.cpp
@@ -0,0 +1,362 @@
+
+#include "viewmanager.hpp"
+
+#include <map>
+
+#include <QApplication>
+#include <QDesktopWidget>
+
+#include "../../model/doc/documentmanager.hpp"
+#include "../../model/doc/document.hpp"
+
+#include "../world/util.hpp"
+#include "../world/enumdelegate.hpp"
+#include "../world/vartypedelegate.hpp"
+#include "../world/recordstatusdelegate.hpp"
+#include "../world/refidtypedelegate.hpp"
+#include "../settings/usersettingsdialog.hpp"
+
+#include "view.hpp"
+
+#include <QMessageBox>
+#include <QPushButton>
+#include <QtGui/QApplication>
+
+void CSVDoc::ViewManager::updateIndices()
+{
+ std::map<CSMDoc::Document *, std::pair<int, int> > documents;
+
+ for (std::vector<View *>::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter)
+ {
+ std::map<CSMDoc::Document *, std::pair<int, int> >::iterator document = documents.find ((*iter)->getDocument());
+
+ if (document==documents.end())
+ document =
+ documents.insert (
+ std::make_pair ((*iter)->getDocument(), std::make_pair (0, countViews ((*iter)->getDocument())))).
+ first;
+
+ (*iter)->setIndex (document->second.first++, document->second.second);
+ }
+}
+
+CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager)
+ : mDocumentManager (documentManager), mExitOnSaveStateChange(false), mUserWarned(false)
+{
+ static const char *sSpecialisations[] =
+ {
+ "Combat", "Magic", "Stealth", 0
+ };
+
+ static const char *sAttributes[] =
+ {
+ "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality",
+ "Luck", 0
+ };
+
+ static const char *sSpellTypes[] =
+ {
+ "Spell", "Ability", "Blight", "Disease", "Curse", "Power", 0
+ };
+
+ static const char *sApparatusTypes[] =
+ {
+ "Mortar & Pestle", "Albemic", "Calcinator", "Retort", 0
+ };
+
+ static const char *sArmorTypes[] =
+ {
+ "Helmet", "Cuirass", "Left Pauldron", "Right Pauldron", "Greaves", "Boots", "Left Gauntlet",
+ "Right Gauntlet", "Shield", "Left Bracer", "Right Bracer", 0
+ };
+
+ static const char *sClothingTypes[] =
+ {
+ "Pants", "Shoes", "Shirt", "Belt", "Robe", "Right Glove", "Left Glove", "Skirt", "Ring",
+ "Amulet", 0
+ };
+
+ static const char *sCreatureTypes[] =
+ {
+ "Creature", "Deadra", "Undead", "Humanoid", 0
+ };
+
+ static const char *sWeaponTypes[] =
+ {
+ "Short Blade 1H", "Long Blade 1H", "Long Blade 2H", "Blunt 1H", "Blunt 2H Close",
+ "Blunt 2H Wide", "Spear 2H", "Axe 1H", "Axe 2H", "Bow", "Crossbow", "Thrown", "Arrow",
+ "Bolt", 0
+ };
+
+ mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection;
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType,
+ new CSVWorld::VarTypeDelegateFactory (ESM::VT_None, ESM::VT_String, ESM::VT_Int, ESM::VT_Float));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType,
+ new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_Specialisation,
+ new CSVWorld::EnumDelegateFactory (sSpecialisations));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_Attribute,
+ new CSVWorld::EnumDelegateFactory (sAttributes, true));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_SpellType,
+ new CSVWorld::EnumDelegateFactory (sSpellTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_ApparatusType,
+ new CSVWorld::EnumDelegateFactory (sApparatusTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_ArmorType,
+ new CSVWorld::EnumDelegateFactory (sArmorTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_ClothingType,
+ new CSVWorld::EnumDelegateFactory (sClothingTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_CreatureType,
+ new CSVWorld::EnumDelegateFactory (sCreatureTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_WeaponType,
+ new CSVWorld::EnumDelegateFactory (sWeaponTypes));
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_RecordState,
+ new CSVWorld::RecordStatusDelegateFactory() );
+
+ mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType,
+ new CSVWorld::RefIdTypeDelegateFactory() );
+
+ connect (&CSMSettings::UserSettings::instance(), SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)),
+ this, SLOT (slotUpdateEditorSetting (const QString &, const QString &)));
+}
+
+CSVDoc::ViewManager::~ViewManager()
+{
+ delete mDelegateFactories;
+
+ for (std::vector<View *>::iterator iter (mViews.begin()); iter!=mViews.end(); ++iter)
+ delete *iter;
+}
+
+CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document)
+{
+ if (countViews (document)==0)
+ {
+ // new document
+ connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)),
+ this, SLOT (documentStateChanged (int, CSMDoc::Document *)));
+
+ connect (document, SIGNAL (progress (int, int, int, int, CSMDoc::Document *)),
+ this, SLOT (progress (int, int, int, int, CSMDoc::Document *)));
+ }
+
+ View *view = new View (*this, document, countViews (document)+1);
+
+
+ mViews.push_back (view);
+
+ view->show();
+
+ connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest()));
+ connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest()));
+
+ updateIndices();
+
+ return view;
+}
+
+int CSVDoc::ViewManager::countViews (const CSMDoc::Document *document) const
+{
+ int count = 0;
+
+ for (std::vector<View *>::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter)
+ if ((*iter)->getDocument()==document)
+ ++count;
+
+ return count;
+}
+
+bool CSVDoc::ViewManager::closeRequest (View *view)
+{
+ std::vector<View *>::iterator iter = std::find (mViews.begin(), mViews.end(), view);
+
+ bool continueWithClose = true;
+
+ if (iter!=mViews.end())
+ {
+ bool last = countViews (view->getDocument())<=1;
+
+ if (last)
+ continueWithClose = notifySaveOnClose (view);
+ else
+ {
+ (*iter)->deleteLater();
+ mViews.erase (iter);
+
+ updateIndices();
+ }
+ }
+
+ return continueWithClose;
+}
+
+bool CSVDoc::ViewManager::notifySaveOnClose (CSVDoc::View *view)
+{
+ bool result = true;
+ CSMDoc::Document *document = view->getDocument();
+
+ //notify user of saving in progress
+ if ( (document->getState() & CSMDoc::State_Saving) )
+ result = showSaveInProgressMessageBox (view);
+
+ //notify user of unsaved changes and process response
+ else if ( document->getState() & CSMDoc::State_Modified)
+ result = showModifiedDocumentMessageBox (view);
+
+ return result;
+}
+
+bool CSVDoc::ViewManager::showModifiedDocumentMessageBox (CSVDoc::View *view)
+{
+ QMessageBox messageBox;
+ CSMDoc::Document *document = view->getDocument();
+
+ messageBox.setText ("The document has been modified.");
+ messageBox.setInformativeText ("Do you want to save your changes?");
+ messageBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
+ messageBox.setDefaultButton (QMessageBox::Save);
+
+ bool retVal = true;
+
+ connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close()));
+
+ connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+
+ mUserWarned = true;
+ int response = messageBox.exec();
+ mUserWarned = false;
+
+ switch (response)
+ {
+ case QMessageBox::Save:
+
+ document->save();
+ mExitOnSaveStateChange = true;
+ retVal = false;
+ break;
+
+ case QMessageBox::Discard:
+
+ disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+ break;
+
+ case QMessageBox::Cancel:
+
+ //disconnect to prevent unintended view closures
+ disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+ retVal = false;
+ break;
+
+ default:
+ break;
+
+ }
+
+ return retVal;
+}
+
+bool CSVDoc::ViewManager::showSaveInProgressMessageBox (CSVDoc::View *view)
+{
+ QMessageBox messageBox;
+ CSMDoc::Document *document = view->getDocument();
+
+ messageBox.setText ("The document is currently being saved.");
+ messageBox.setInformativeText("Do you want to close now and abort saving, or wait until saving has completed?");
+
+ QPushButton* waitButton = messageBox.addButton (tr("Wait"), QMessageBox::YesRole);
+ QPushButton* closeButton = messageBox.addButton (tr("Close Now"), QMessageBox::RejectRole);
+ QPushButton* cancelButton = messageBox.addButton (tr("Cancel"), QMessageBox::NoRole);
+
+ messageBox.setDefaultButton (waitButton);
+
+ bool retVal = true;
+
+ //Connections shut down message box if operation ends before user makes a decision.
+ connect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+ connect (this, SIGNAL (closeMessageBox()), &messageBox, SLOT (close()));
+
+ //set / clear the user warned flag to indicate whether or not the message box is currently active.
+ mUserWarned = true;
+ messageBox.exec();
+ mUserWarned = false;
+
+ //if closed by the warning handler, defaults to the RejectRole button (closeButton)
+ if (messageBox.clickedButton() == waitButton)
+ {
+ //save the View iterator for shutdown after the save operation ends
+ mExitOnSaveStateChange = true;
+ retVal = false;
+ }
+
+ else if (messageBox.clickedButton() == closeButton)
+ {
+ //disconnect to avoid segmentation fault
+ disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+
+ view->abortOperation(CSMDoc::State_Saving);
+ mExitOnSaveStateChange = true;
+ }
+
+ else if (messageBox.clickedButton() == cancelButton)
+ {
+ //abort shutdown, allow save to complete
+ //disconnection to prevent unintended view closures
+ mExitOnSaveStateChange = false;
+ disconnect (document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (onExitWarningHandler(int, CSMDoc::Document *)));
+ retVal = false;
+ }
+
+ return retVal;
+}
+
+void CSVDoc::ViewManager::documentStateChanged (int state, CSMDoc::Document *document)
+{
+ for (std::vector<View *>::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter)
+ if ((*iter)->getDocument()==document)
+ (*iter)->updateDocumentState();
+}
+
+void CSVDoc::ViewManager::progress (int current, int max, int type, int threads, CSMDoc::Document *document)
+{
+ for (std::vector<View *>::const_iterator iter (mViews.begin()); iter!=mViews.end(); ++iter)
+ if ((*iter)->getDocument()==document)
+ (*iter)->updateProgress (current, max, type, threads);
+}
+
+void CSVDoc::ViewManager::onExitWarningHandler (int state, CSMDoc::Document *document)
+{
+ if ( !(state & CSMDoc::State_Saving) )
+ {
+ //if the user is being warned (message box is active), shut down the message box,
+ //as there is no save operation currently running
+ if ( mUserWarned )
+ emit closeMessageBox();
+
+ //otherwise, the user has closed the message box before the save operation ended.
+ //exit the application
+ else if (mExitOnSaveStateChange)
+ QApplication::instance()->exit();
+ }
+}
+
+void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view)
+{
+ if (notifySaveOnClose (view))
+ QApplication::instance()->exit();
+}
+
+void CSVDoc::ViewManager::slotUpdateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+ foreach (CSVDoc::View *view, mViews)
+ view->updateEditorSetting (settingName, settingValue);
+}
diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp
new file mode 100644
index 0000000000..1f4dcd51b1
--- /dev/null
+++ b/apps/opencs/view/doc/viewmanager.hpp
@@ -0,0 +1,82 @@
+#ifndef CSV_DOC_VIEWMANAGER_H
+#define CSV_DOC_VIEWMANAGER_H
+
+#include <vector>
+
+#include <QObject>
+
+namespace CSMDoc
+{
+ class Document;
+ class DocumentManager;
+}
+
+namespace CSVWorld
+{
+ class CommandDelegateFactoryCollection;
+}
+
+namespace CSVDoc
+{
+ class View;
+
+ class ViewManager : public QObject
+ {
+ Q_OBJECT
+
+ CSMDoc::DocumentManager& mDocumentManager;
+ std::vector<View *> mViews;
+ CSVWorld::CommandDelegateFactoryCollection *mDelegateFactories;
+ bool mExitOnSaveStateChange;
+ bool mUserWarned;
+
+ // not implemented
+ ViewManager (const ViewManager&);
+ ViewManager& operator= (const ViewManager&);
+
+ void updateIndices();
+ bool notifySaveOnClose (View *view = 0);
+ bool showModifiedDocumentMessageBox (View *view);
+ bool showSaveInProgressMessageBox (View *view);
+
+ public:
+
+ ViewManager (CSMDoc::DocumentManager& documentManager);
+
+ virtual ~ViewManager();
+
+ View *addView (CSMDoc::Document *document);
+ ///< The ownership of the returned view is not transferred.
+
+ int countViews (const CSMDoc::Document *document) const;
+ ///< Return number of views for \a document.
+
+ bool closeRequest (View *view);
+
+ signals:
+
+ void newDocumentRequest();
+
+ void loadDocumentRequest();
+
+ void closeMessageBox();
+
+ public slots:
+
+ void exitApplication (CSVDoc::View *view);
+
+ private slots:
+
+ void documentStateChanged (int state, CSMDoc::Document *document);
+
+ void progress (int current, int max, int type, int threads, CSMDoc::Document *document);
+
+ void onExitWarningHandler(int state, CSMDoc::Document* document);
+
+ /// connected to update signal in UserSettings
+ void slotUpdateEditorSetting (const QString &, const QString &);
+ };
+
+}
+
+#endif
diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp
new file mode 100644
index 0000000000..708d450325
--- /dev/null
+++ b/apps/opencs/view/filter/editwidget.cpp
@@ -0,0 +1,58 @@
+
+#include "editwidget.hpp"
+
+#include <QAbstractItemModel>
+
+#include "../../model/world/data.hpp"
+
+CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent)
+: QLineEdit (parent), mParser (data)
+{
+ mPalette = palette();
+ connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&)));
+
+ QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters);
+
+ connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)),
+ this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)),
+ Qt::QueuedConnection);
+ connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)),
+ this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)),
+ Qt::QueuedConnection);
+ connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
+ this, SLOT (filterRowsInserted (const QModelIndex&, int, int)),
+ Qt::QueuedConnection);
+}
+
+void CSVFilter::EditWidget::textChanged (const QString& text)
+{
+ if (mParser.parse (text.toUtf8().constData()))
+ {
+ setPalette (mPalette);
+ emit filterChanged (mParser.getFilter());
+ }
+ else
+ {
+ QPalette palette (mPalette);
+ palette.setColor (QPalette::Text, Qt::red);
+ setPalette (palette);
+
+ /// \todo improve error reporting; mark only the faulty part
+ }
+}
+
+void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft,
+ const QModelIndex& bottomRight)
+{
+ textChanged (text());
+}
+
+void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end)
+{
+ textChanged (text());
+}
+
+void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end)
+{
+ textChanged (text());
+}
diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp
new file mode 100644
index 0000000000..31904e624f
--- /dev/null
+++ b/apps/opencs/view/filter/editwidget.hpp
@@ -0,0 +1,48 @@
+#ifndef CSV_FILTER_EDITWIDGET_H
+#define CSV_FILTER_EDITWIDGET_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <QLineEdit>
+#include <QPalette>
+
+#include "../../model/filter/parser.hpp"
+#include "../../model/filter/node.hpp"
+
+class QModelIndex;
+
+namespace CSMWorld
+{
+ class Data;
+}
+
+namespace CSVFilter
+{
+ class EditWidget : public QLineEdit
+ {
+ Q_OBJECT
+
+ CSMFilter::Parser mParser;
+ QPalette mPalette;
+
+ public:
+
+ EditWidget (CSMWorld::Data& data, QWidget *parent = 0);
+
+ signals:
+
+ void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+
+ private slots:
+
+ void textChanged (const QString& text);
+
+ void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
+
+ void filterRowsRemoved (const QModelIndex& parent, int start, int end);
+
+ void filterRowsInserted (const QModelIndex& parent, int start, int end);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp
new file mode 100644
index 0000000000..2731708841
--- /dev/null
+++ b/apps/opencs/view/filter/filterbox.cpp
@@ -0,0 +1,24 @@
+
+#include "filterbox.hpp"
+
+#include <QHBoxLayout>
+
+#include "recordfilterbox.hpp"
+
+CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent)
+: QWidget (parent)
+{
+ QHBoxLayout *layout = new QHBoxLayout (this);
+
+ layout->setContentsMargins (0, 0, 0, 0);
+
+ RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this);
+
+ layout->addWidget (recordFilterBox);
+
+ setLayout (layout);
+
+ connect (recordFilterBox,
+ SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
+ this, SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
+} \ No newline at end of file
diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp
new file mode 100644
index 0000000000..2524fa0a38
--- /dev/null
+++ b/apps/opencs/view/filter/filterbox.hpp
@@ -0,0 +1,30 @@
+#ifndef CSV_FILTER_FILTERBOX_H
+#define CSV_FILTER_FILTERBOX_H
+
+#include <QWidget>
+
+#include "../../model/filter/node.hpp"
+
+namespace CSMWorld
+{
+ class Data;
+}
+
+namespace CSVFilter
+{
+ class FilterBox : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ FilterBox (CSMWorld::Data& data, QWidget *parent = 0);
+
+ signals:
+
+ void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ };
+
+}
+
+#endif
diff --git a/apps/opencs/view/filter/filtercreator.cpp b/apps/opencs/view/filter/filtercreator.cpp
new file mode 100644
index 0000000000..47925ea57a
--- /dev/null
+++ b/apps/opencs/view/filter/filtercreator.cpp
@@ -0,0 +1,63 @@
+
+#include "filtercreator.hpp"
+
+#include <QComboBox>
+#include <QLabel>
+
+#include "../../model/filter/filter.hpp"
+
+std::string CSVFilter::FilterCreator::getNamespace() const
+{
+ switch (mScope->currentIndex())
+ {
+ case CSMFilter::Filter::Scope_Project: return "project::";
+ case CSMFilter::Filter::Scope_Session: return "session::";
+ }
+
+ return "";
+}
+
+void CSVFilter::FilterCreator::update()
+{
+ mNamespace->setText (QString::fromUtf8 (getNamespace().c_str()));
+ GenericCreator::update();
+}
+
+std::string CSVFilter::FilterCreator::getId() const
+{
+ return getNamespace() + GenericCreator::getId();
+}
+
+CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: GenericCreator (data, undoStack, id)
+{
+ mNamespace = new QLabel ("::", this);
+ insertAtBeginning (mNamespace, false);
+
+ mScope = new QComboBox (this);
+
+ mScope->addItem ("Project");
+ mScope->addItem ("Session");
+ /// \ŧodo re-enable for OpenMW 1.1
+ // mScope->addItem ("Content");
+
+ connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int)));
+
+ insertAtBeginning (mScope, false);
+
+ QLabel *label = new QLabel ("Scope", this);
+ insertAtBeginning (label, false);
+
+ mScope->setCurrentIndex (1);
+}
+
+void CSVFilter::FilterCreator::reset()
+{
+ GenericCreator::reset();
+}
+
+void CSVFilter::FilterCreator::setScope (int index)
+{
+ update();
+}
diff --git a/apps/opencs/view/filter/filtercreator.hpp b/apps/opencs/view/filter/filtercreator.hpp
new file mode 100644
index 0000000000..82d38d22c7
--- /dev/null
+++ b/apps/opencs/view/filter/filtercreator.hpp
@@ -0,0 +1,41 @@
+#ifndef CSV_FILTER_FILTERCREATOR_H
+#define CSV_FILTER_FILTERCREATOR_H
+
+class QComboBox;
+class QLabel;
+
+#include "../world/genericcreator.hpp"
+
+namespace CSVFilter
+{
+ class FilterCreator : public CSVWorld::GenericCreator
+ {
+ Q_OBJECT
+
+ QComboBox *mScope;
+ QLabel *mNamespace;
+
+ private:
+
+ std::string getNamespace() const;
+
+ protected:
+
+ void update();
+
+ virtual std::string getId() const;
+
+ public:
+
+ FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id);
+
+ virtual void reset();
+
+ private slots:
+
+ void setScope (int index);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp
new file mode 100644
index 0000000000..c405177b01
--- /dev/null
+++ b/apps/opencs/view/filter/recordfilterbox.cpp
@@ -0,0 +1,27 @@
+
+#include "recordfilterbox.hpp"
+
+#include <QHBoxLayout>
+#include <QLabel>
+
+#include "editwidget.hpp"
+
+CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent)
+: QWidget (parent)
+{
+ QHBoxLayout *layout = new QHBoxLayout (this);
+
+ layout->setContentsMargins (0, 0, 0, 0);
+
+ layout->addWidget (new QLabel ("Record Filter", this));
+
+ EditWidget *editWidget = new EditWidget (data, this);
+
+ layout->addWidget (editWidget);
+
+ setLayout (layout);
+
+ connect (
+ editWidget, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
+ this, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)));
+}
diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp
new file mode 100644
index 0000000000..057d69518d
--- /dev/null
+++ b/apps/opencs/view/filter/recordfilterbox.hpp
@@ -0,0 +1,34 @@
+#ifndef CSV_FILTER_RECORDFILTERBOX_H
+#define CSV_FILTER_RECORDFILTERBOX_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <QWidget>
+
+#include <QHBoxLayout>
+
+#include "../../model/filter/node.hpp"
+
+namespace CSMWorld
+{
+ class Data;
+}
+
+namespace CSVFilter
+{
+ class RecordFilterBox : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0);
+
+ signals:
+
+ void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ };
+
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/settings/abstractblock.cpp b/apps/opencs/view/settings/abstractblock.cpp
new file mode 100644
index 0000000000..65825ce8be
--- /dev/null
+++ b/apps/opencs/view/settings/abstractblock.cpp
@@ -0,0 +1,112 @@
+#include "abstractblock.hpp"
+
+CSVSettings::AbstractBlock::AbstractBlock(QWidget* parent)
+ : QObject (parent), mBox ( new GroupBox (parent) ), mWidgetParent (parent)
+{}
+
+CSVSettings::AbstractBlock::AbstractBlock(bool isVisible, QWidget* parent)
+ : QObject (parent), mBox ( new GroupBox (isVisible, parent)), mWidgetParent (parent)
+{}
+
+QLayout *CSVSettings::AbstractBlock::createLayout (Orientation direction,
+ bool isZeroMargin, QWidget* parent)
+{
+ QLayout *layout = 0;
+
+ if (direction == Orient_Vertical)
+ layout = new QVBoxLayout (parent);
+ else
+ layout = new QHBoxLayout (parent);
+
+ if (isZeroMargin)
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ return layout;
+}
+
+QGroupBox *CSVSettings::AbstractBlock::getGroupBox()
+{
+ return mBox;
+}
+
+CSVSettings::AbstractWidget *CSVSettings::AbstractBlock::buildWidget (const QString& widgetName, WidgetDef &def,
+ QLayout *layout, bool isConnected) const
+{
+ AbstractWidget *widg = 0;
+
+ switch (def.type)
+ {
+
+ case Widget_RadioButton:
+ widg = new SettingWidget<QRadioButton> (def, layout, mBox);
+ break;
+
+ case Widget_SpinBox:
+ widg = new SettingWidget<QSpinBox> (def, layout, mBox);
+ break;
+
+ case Widget_CheckBox:
+ widg = new SettingWidget<QCheckBox> (def, layout, mBox);
+ break;
+
+ case Widget_LineEdit:
+ widg = new SettingWidget<QLineEdit> (def, layout, mBox);
+ break;
+
+ case Widget_ListBox:
+ widg = new SettingWidget<QListWidget> (def, layout, mBox);
+ break;
+
+ case Widget_ComboBox:
+ widg = new SettingWidget<QComboBox> (def, layout, mBox);
+ break;
+
+ default:
+ break;
+ };
+
+ if (!mBox->layout())
+ mBox->setLayout(widg->getLayout());
+
+ widg->widget()->setObjectName(widgetName);
+
+ if (isConnected)
+ connect (widg, SIGNAL (signalUpdateItem (const QString &)), this, SLOT (slotUpdate (const QString &)));
+ connect (this, SIGNAL (signalUpdateWidget (const QString &)), widg, SLOT (slotUpdateWidget (const QString &) ));
+
+ return widg;
+}
+
+void CSVSettings::AbstractBlock::setVisible (bool isVisible)
+{
+ mBox->setBorderVisibility (isVisible);
+}
+
+bool CSVSettings::AbstractBlock::isVisible () const
+{
+ return mBox->borderVisibile();
+}
+
+QWidget *CSVSettings::AbstractBlock::getParent() const
+{
+ return mWidgetParent;
+}
+
+void CSVSettings::AbstractBlock::slotUpdate (const QString &value)
+{
+ slotUpdateSetting (objectName(), value);
+}
+
+void CSVSettings::AbstractBlock::slotSetEnabled(bool value)
+{
+ mBox->setEnabled(value);
+}
+
+void CSVSettings::AbstractBlock::slotUpdateSetting (const QString &settingName, const QString &settingValue)
+{
+ bool doEmit = true;
+ updateBySignal (settingName, settingValue, doEmit);
+
+ if (doEmit)
+ emit signalUpdateSetting (settingName, settingValue);
+}
diff --git a/apps/opencs/view/settings/abstractblock.hpp b/apps/opencs/view/settings/abstractblock.hpp
new file mode 100644
index 0000000000..361339fe25
--- /dev/null
+++ b/apps/opencs/view/settings/abstractblock.hpp
@@ -0,0 +1,82 @@
+#ifndef ABSTRACTBLOCK_HPP
+#define ABSTRACTBLOCK_HPP
+
+#include <QObject>
+#include <QList>
+
+#include "settingwidget.hpp"
+#include "../../model/settings/settingsitem.hpp"
+#include "groupbox.hpp"
+
+namespace CSVSettings
+{
+
+ /// Abstract base class for all blocks
+ class AbstractBlock : public QObject
+ {
+ Q_OBJECT
+
+ protected:
+
+ typedef QMap<QString, CSMSettings::SettingsItem*> SettingsItemMap;
+ GroupBox *mBox;
+ QWidget *mWidgetParent;
+
+ public:
+
+ explicit AbstractBlock (QWidget *parent = 0);
+ explicit AbstractBlock (bool isVisible, QWidget *parent = 0);
+
+ QGroupBox *getGroupBox();
+ void setVisible (bool isVisible);
+ bool isVisible() const;
+
+ virtual CSMSettings::SettingList *getSettings() = 0;
+
+ /// update settings found in the passed map and are encapsulated by the block
+ virtual bool updateSettings (const CSMSettings::SettingMap &settings) = 0;
+
+ /// update callback function called from update slot
+ /// used for updating application-level settings in the editor
+ virtual bool updateBySignal (const QString &name, const QString &value, bool &doEmit)
+ { return false; }
+
+ protected:
+
+ /// Creates the layout for the block's QGroupBox
+ QLayout *createLayout (Orientation direction, bool isZeroMargin, QWidget* parent = 0);
+
+ /// Creates widgets that exist as direct children of the block
+ AbstractWidget *buildWidget (const QString &widgetName, WidgetDef &wDef,
+ QLayout *layout = 0, bool isConnected = true) const;
+
+ QWidget *getParent() const;
+
+ public slots:
+
+ /// enables / disables block-level widgets based on signals from other widgets
+ /// used in ToggleBlock
+ void slotSetEnabled (bool value);
+
+ /// receives updates to applicaion-level settings in the Editor
+ void slotUpdateSetting (const QString &settingName, const QString &settingValue);
+
+ private slots:
+
+ /// receives updates to a setting in the block pushed from the application level
+ void slotUpdate (const QString &value);
+
+ signals:
+
+ /// signal to UserSettings instance
+ void signalUpdateSetting (const QString &propertyName, const QString &propertyValue);
+
+ /// signal to widget for updating widget value
+ void signalUpdateWidget (const QString & value);
+
+ /// ProxyBlock use only.
+ /// Name and value correspond to settings for which the block is a proxy.
+ void signalUpdateProxySetting (const QString &propertyName, const QString &propertyValue);
+ };
+}
+#endif // ABSTRACTBLOCK_HPP
diff --git a/apps/opencs/view/settings/abstractpage.cpp b/apps/opencs/view/settings/abstractpage.cpp
new file mode 100644
index 0000000000..e6c605275d
--- /dev/null
+++ b/apps/opencs/view/settings/abstractpage.cpp
@@ -0,0 +1,44 @@
+#include "abstractpage.hpp"
+
+#include <QGroupBox>
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QRadioButton>
+#include <QCheckBox>
+#include <QSpinBox>
+#include <QComboBox>
+#include <QLineEdit>
+#include <QMargins>
+
+CSVSettings::AbstractPage::AbstractPage(QWidget *parent):
+ QWidget(parent)
+{
+ QGridLayout *pageLayout = new QGridLayout(this);
+ setLayout (pageLayout);
+}
+
+CSVSettings::AbstractPage::AbstractPage(const QString &pageName, QWidget *parent):
+ QWidget(parent)
+{
+ QWidget::setObjectName (pageName);
+
+ QGridLayout *pageLayout = new QGridLayout(this);
+ setLayout (pageLayout);
+}
+
+CSVSettings::AbstractPage::~AbstractPage()
+{
+}
+
+CSMSettings::SettingList *CSVSettings::AbstractPage::getSettings()
+{
+ CSMSettings::SettingList *settings = new CSMSettings::SettingList();
+
+ foreach (AbstractBlock *block, mAbstractBlocks)
+ {
+ CSMSettings::SettingList *groupSettings = block->getSettings();
+ settings->append (*groupSettings);
+ }
+
+ return settings;
+}
diff --git a/apps/opencs/view/settings/abstractpage.hpp b/apps/opencs/view/settings/abstractpage.hpp
new file mode 100644
index 0000000000..77ef4524f0
--- /dev/null
+++ b/apps/opencs/view/settings/abstractpage.hpp
@@ -0,0 +1,70 @@
+#ifndef ABSTRACTPAGE_HPP
+#define ABSTRACTPAGE_HPP
+
+#include <QWidget>
+#include <QList>
+#include <QLayout>
+
+#include "abstractblock.hpp"
+
+class SettingMap;
+class SettingList;
+
+namespace CSVSettings {
+
+ typedef QList<AbstractBlock *> AbstractBlockList;
+
+ /// Abstract base class for all setting pages in the dialog
+
+ /// \todo Scripted implementation of settings should eliminate the need
+ /// \todo derive page classes.
+ /// \todo AbstractPage should be replaced with a general page construction class.
+ class AbstractPage: public QWidget
+ {
+
+ protected:
+
+ AbstractBlockList mAbstractBlocks;
+
+ public:
+
+ AbstractPage(QWidget *parent = 0);
+ AbstractPage (const QString &pageName, QWidget* parent = 0);
+
+ ~AbstractPage();
+
+ virtual void setupUi() = 0;
+
+ /// triggers widgiet initialization at the page level. All widgets updated to
+ /// current setting values
+ virtual void initializeWidgets (const CSMSettings::SettingMap &settings) = 0;
+
+ /// retrieve the list of settings local to the page.
+ CSMSettings::SettingList *getSettings();
+
+ void setObjectName();
+
+ protected:
+
+ /// Create a block for the page.
+ /// Block is constructed using passed definition struct
+ /// Page level-layout is created and assigned
+ template <typename S, typename T>
+ AbstractBlock *buildBlock (T *def)
+ {
+ S *block = new S (this);
+ int ret = block->build (def);
+
+ if (ret < 0)
+ return 0;
+
+ QGroupBox *box = block->getGroupBox();
+ QWidget::layout()->addWidget (box);
+
+ return block;
+ }
+
+ };
+}
+
+#endif // ABSTRACTPAGE_HPP
diff --git a/apps/opencs/view/settings/abstractwidget.cpp b/apps/opencs/view/settings/abstractwidget.cpp
new file mode 100644
index 0000000000..f268d3b279
--- /dev/null
+++ b/apps/opencs/view/settings/abstractwidget.cpp
@@ -0,0 +1,78 @@
+#include "abstractwidget.hpp"
+
+#include <QLayout>
+#include <QLabel>
+
+void CSVSettings::AbstractWidget::build(QWidget *widget, WidgetDef &def, bool noLabel)
+{
+ if (!mLayout)
+ createLayout(def.orientation, true);
+
+ buildLabelAndWidget (widget, def, noLabel);
+
+}
+
+void CSVSettings::AbstractWidget::buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel)
+{
+ if (def.widgetWidth > -1)
+ widget->setFixedWidth (def.widgetWidth);
+
+ if (!(def.caption.isEmpty() || noLabel) )
+ {
+ QLabel *label = new QLabel (def.caption, &dynamic_cast<QWidget &>( *parent()));
+ label->setBuddy (widget);
+ mLayout->addWidget (label);
+
+ if (def.labelWidth > -1)
+ label->setFixedWidth(def.labelWidth);
+ }
+
+ mLayout->addWidget (widget);
+ mLayout->setAlignment (widget, getAlignment (def.widgetAlignment));
+}
+
+void CSVSettings::AbstractWidget::createLayout
+ (Orientation direction, bool isZeroMargin)
+{
+ if (direction == Orient_Vertical)
+ mLayout = new QVBoxLayout ();
+ else
+ mLayout = new QHBoxLayout ();
+
+ if (isZeroMargin)
+ mLayout->setContentsMargins(0, 0, 0, 0);
+}
+
+QFlags<Qt::AlignmentFlag> CSVSettings::AbstractWidget::getAlignment (CSVSettings::Alignment flag)
+{
+ return QFlags<Qt::AlignmentFlag>(static_cast<int>(flag));
+}
+
+QLayout *CSVSettings::AbstractWidget::getLayout()
+{
+ return mLayout;
+}
+
+void CSVSettings::AbstractWidget::slotUpdateWidget (const QString &value)
+{
+ updateWidget (value);
+}
+
+void CSVSettings::AbstractWidget::slotUpdateItem(const QString &value)
+{
+ emit signalUpdateItem (value);
+}
+
+void CSVSettings::AbstractWidget::slotUpdateItem(bool value)
+{
+ if (value)
+ emit signalUpdateItem (widget()->objectName());
+}
+
+void CSVSettings::AbstractWidget::slotUpdateItem(int value)
+{
+ emit signalUpdateItem (QString::number(value));
+}
+
+void CSVSettings::AbstractWidget::slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous)
+{}
diff --git a/apps/opencs/view/settings/abstractwidget.hpp b/apps/opencs/view/settings/abstractwidget.hpp
new file mode 100644
index 0000000000..325de2bd23
--- /dev/null
+++ b/apps/opencs/view/settings/abstractwidget.hpp
@@ -0,0 +1,69 @@
+#ifndef ABSTRACTWIDGET_HPP
+#define ABSTRACTWIDGET_HPP
+
+#include <QWidget>
+#include "support.hpp"
+
+class QLayout;
+
+namespace CSVSettings
+{
+ /// Abstract base class for widgets which are used in user preferences dialog
+ class AbstractWidget : public QObject
+ {
+ Q_OBJECT
+
+ QLayout *mLayout;
+
+ public:
+
+ /// Passed layout is assigned the constructed widget.
+ /// if no layout is passed, one is created.
+ explicit AbstractWidget (QLayout *layout = 0, QWidget* parent = 0)
+ : QObject (parent), mLayout (layout)
+ {}
+
+ /// retrieve layout for insertion into itemblock
+ QLayout *getLayout();
+
+ /// create the derived widget instance
+ void build (QWidget* widget, WidgetDef &def, bool noLabel = false);
+
+ /// reference to the derived widget instance
+ virtual QWidget *widget() = 0;
+
+ protected:
+
+ /// Callback called by receiving slot for widget udpates
+ virtual void updateWidget (const QString &value) = 0;
+
+ /// Converts user-defined enum to Qt equivalents
+ QFlags<Qt::AlignmentFlag> getAlignment (Alignment flag);
+
+ private:
+
+ /// Creates layout and assigns label and widget as appropriate
+ void createLayout (Orientation direction, bool isZeroMargin);
+
+ /// Creates label and widget according to passed definition
+ void buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel);
+
+
+ signals:
+
+ /// outbound update signal
+ void signalUpdateItem (const QString &value);
+
+ public slots:
+
+ /// receives inbound updates
+ void slotUpdateWidget (const QString &value);
+
+ /// Overloads for outbound updates from derived widget signal
+ void slotUpdateItem (const QString &value);
+ void slotUpdateItem (bool value);
+ void slotUpdateItem (int value);
+ void slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous);
+ };
+}
+#endif // ABSTRACTWIDGET_HPP
diff --git a/apps/opencs/view/settings/blankpage.cpp b/apps/opencs/view/settings/blankpage.cpp
new file mode 100644
index 0000000000..837a31bee1
--- /dev/null
+++ b/apps/opencs/view/settings/blankpage.cpp
@@ -0,0 +1,50 @@
+#include "blankpage.hpp"
+
+#include <QList>
+#include <QListView>
+#include <QGroupBox>
+#include <QRadioButton>
+#include <QDockWidget>
+#include <QVBoxLayout>
+#include <QGridLayout>
+#include <QStyle>
+
+#ifdef Q_OS_MAC
+#include <QPlastiqueStyle>
+#endif
+
+#include "../../model/settings/usersettings.hpp"
+#include "groupblock.hpp"
+#include "toggleblock.hpp"
+
+CSVSettings::BlankPage::BlankPage(QWidget *parent):
+ AbstractPage("Blank", parent)
+{
+
+}
+
+CSVSettings::BlankPage::BlankPage(const QString &title, QWidget *parent):
+ AbstractPage(title, parent)
+{
+ // Hacks to get the stylesheet look properly
+#ifdef Q_OS_MAC
+ QPlastiqueStyle *style = new QPlastiqueStyle;
+ //profilesComboBox->setStyle(style);
+#endif
+
+ setupUi();
+}
+
+void CSVSettings::BlankPage::setupUi()
+{
+ QGroupBox *pageBox = new QGroupBox(this);
+ layout()->addWidget(pageBox);
+}
+
+void CSVSettings::BlankPage::initializeWidgets (const CSMSettings::SettingMap &settings)
+{
+ //iterate each item in each blocks in this section
+ //validate the corresponding setting against the defined valuelist if any.
+ foreach (AbstractBlock *block, mAbstractBlocks)
+ block->updateSettings (settings);
+}
diff --git a/apps/opencs/view/settings/blankpage.hpp b/apps/opencs/view/settings/blankpage.hpp
new file mode 100644
index 0000000000..07049fb71f
--- /dev/null
+++ b/apps/opencs/view/settings/blankpage.hpp
@@ -0,0 +1,28 @@
+#ifndef BLANKPAGE_HPP
+#define BLANKPAGE_HPP
+
+#include "abstractpage.hpp"
+
+class QGroupBox;
+
+namespace CSVSettings {
+
+ class UserSettings;
+ class AbstractBlock;
+
+ /// Derived page with no widgets
+ /// Reference use only.
+ class BlankPage : public AbstractPage
+ {
+
+ public:
+
+ BlankPage (QWidget *parent = 0);
+ BlankPage (const QString &title, QWidget *parent);
+
+ void setupUi();
+ void initializeWidgets (const CSMSettings::SettingMap &settings);
+ };
+}
+
+#endif // BLANKPAGE_HPP
diff --git a/apps/opencs/view/settings/customblock.cpp b/apps/opencs/view/settings/customblock.cpp
new file mode 100644
index 0000000000..bbceafabe9
--- /dev/null
+++ b/apps/opencs/view/settings/customblock.cpp
@@ -0,0 +1,121 @@
+#include "customblock.hpp"
+#include "groupblock.hpp"
+#include "itemblock.hpp"
+#include "proxyblock.hpp"
+
+CSVSettings::CustomBlock::CustomBlock (QWidget *parent) : AbstractBlock (parent)
+{
+}
+
+int CSVSettings::CustomBlock::build(GroupBlockDefList &defList, GroupBlockDefList::iterator *it)
+{
+ int retVal = 0;
+
+ GroupBlockDefList::iterator defaultIt;
+ GroupBlockDefList::iterator listIt = defList.begin();
+ GroupBlockDefList::iterator proxyIt = defaultIt;
+
+ if (it)
+ listIt = *it;
+
+ ProxyBlock *proxyBlock = new ProxyBlock(getParent());
+
+ for (; listIt != defList.end(); ++listIt)
+ {
+ if (!(*listIt)->isProxy)
+ retVal = buildGroupBlock (*listIt);
+ else
+ {
+ mGroupList << proxyBlock;
+ proxyIt = listIt;
+ }
+ }
+
+ if (proxyIt != defaultIt)
+ retVal = buildProxyBlock (*proxyIt, proxyBlock);
+
+ return retVal;
+}
+
+CSVSettings::GroupBox *CSVSettings::CustomBlock::buildGroupBox (Orientation orientation)
+{
+ GroupBox *box = new GroupBox (false, mBox);
+ createLayout (orientation, true, box);
+
+ return box;
+}
+
+int CSVSettings::CustomBlock::buildGroupBlock(GroupBlockDef *def)
+{
+ GroupBlock *block = new GroupBlock (getParent());
+
+ mGroupList << block;
+
+ connect (block, SIGNAL (signalUpdateSetting(const QString &, const QString &)),
+ this, SLOT (slotUpdateSetting (const QString &, const QString &)));
+
+ return block->build(def);
+}
+
+int CSVSettings::CustomBlock::buildProxyBlock(GroupBlockDef *def, ProxyBlock *block)
+{
+ if (def->settingItems.size() != 1)
+ return -1;
+
+ int retVal = block->build(def);
+
+ if (retVal != 0)
+ return retVal;
+
+ // The first settingItem is the proxy setting, containing the list of settings bound to it.
+ foreach (QStringList *list, *(def->settingItems.at(0)->proxyList))
+ {
+ QString proxiedBlockName = list->at(0);
+
+ //iterate each group in the custom block, matching it to each proxied setting
+ //and connecting it appropriately
+ foreach (GroupBlock *groupBlock, mGroupList)
+ {
+ ItemBlock *proxiedBlock = groupBlock->getItemBlock (proxiedBlockName);
+
+ if (proxiedBlock)
+ {
+ block->addSetting(proxiedBlock, list);
+
+ //connect the proxy block's update signal to the custom block's slot
+ connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)),
+ this, SLOT (slotUpdateSetting (const QString &, const QString &)));
+ }
+ }
+ }
+
+ return 0;
+}
+
+CSMSettings::SettingList *CSVSettings::CustomBlock::getSettings()
+{
+ CSMSettings::SettingList *settings = new CSMSettings::SettingList();
+
+ foreach (GroupBlock *block, mGroupList)
+ {
+ CSMSettings::SettingList *groupSettings = block->getSettings();
+
+ if (groupSettings)
+ settings->append(*groupSettings);
+ }
+
+ return settings;
+}
+
+bool CSVSettings::CustomBlock::updateSettings (const CSMSettings::SettingMap &settings)
+{
+ bool success = true;
+
+ foreach (GroupBlock *block, mGroupList)
+ {
+ bool success2 = block->updateSettings (settings);
+ success = success && success2;
+ }
+
+ return success;
+}
diff --git a/apps/opencs/view/settings/customblock.hpp b/apps/opencs/view/settings/customblock.hpp
new file mode 100644
index 0000000000..54c50f395f
--- /dev/null
+++ b/apps/opencs/view/settings/customblock.hpp
@@ -0,0 +1,47 @@
+#ifndef CUSTOMBLOCK_HPP
+#define CUSTOMBLOCK_HPP
+
+#include "abstractblock.hpp"
+
+namespace CSVSettings
+{
+
+ class ProxyBlock;
+
+ /// Base class for customized user preference setting blocks
+ /// Special block classes should be derived from CustomBlock
+ class CustomBlock : public AbstractBlock
+ {
+
+ protected:
+
+ GroupBlockList mGroupList;
+
+ public:
+
+ explicit CustomBlock (QWidget *parent = 0);
+
+ /// Update settings local to the block
+ bool updateSettings (const CSMSettings::SettingMap &settings);
+
+ /// Retrieve settings local to the block
+ CSMSettings::SettingList *getSettings();
+
+ /// construct the block using the passed definition
+ int build (GroupBlockDefList &defList, GroupBlockDefList::Iterator *it = 0);
+
+ protected:
+
+ /// construct the block groupbox
+ GroupBox *buildGroupBox (Orientation orientation);
+
+ private:
+
+ /// Construction function for creating a standard GroupBlock child
+ int buildGroupBlock(GroupBlockDef *def);
+
+ /// Construction function for creating a standard ProxyBlock child
+ int buildProxyBlock(GroupBlockDef *def, ProxyBlock *block);
+ };
+}
+#endif // CUSTOMBLOCK_HPP
diff --git a/apps/opencs/view/settings/datadisplayformatpage.cpp b/apps/opencs/view/settings/datadisplayformatpage.cpp
new file mode 100755
index 0000000000..332b68f5c4
--- /dev/null
+++ b/apps/opencs/view/settings/datadisplayformatpage.cpp
@@ -0,0 +1,57 @@
+#include "datadisplayformatpage.hpp"
+#include "groupblock.hpp"
+#include "../../model/settings/usersettings.hpp"
+
+CSVSettings::DataDisplayFormatPage::DataDisplayFormatPage(QWidget* parent) :
+ AbstractPage("Display Format", parent)
+{
+ setupUi();
+}
+
+CSVSettings::GroupBlockDef *CSVSettings::DataDisplayFormatPage::setupDataDisplay( const QString &title)
+{
+ GroupBlockDef *statusBlock = new GroupBlockDef(QString(title));
+
+ SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon Only");
+ *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only");
+
+ WidgetDef statusWidget (Widget_RadioButton);
+ statusWidget.valueList = statusItem->valueList;
+
+ statusItem->widget = statusWidget;
+
+ statusBlock->settingItems << statusItem;
+
+ statusBlock->isZeroMargin = false;
+
+ return statusBlock;
+}
+
+
+void CSVSettings::DataDisplayFormatPage::setupUi()
+{
+
+ mAbstractBlocks << buildBlock<GroupBlock> (setupDataDisplay ("Record Status Display"));
+ mAbstractBlocks << buildBlock<GroupBlock> (setupDataDisplay ("Referenceable ID Type Display"));
+
+ foreach (AbstractBlock *block, mAbstractBlocks)
+ {
+ connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)),
+ this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) );
+ }
+
+ connect ( this,
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)),
+ &(CSMSettings::UserSettings::instance()),
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)));
+
+}
+
+void CSVSettings::DataDisplayFormatPage::initializeWidgets (const CSMSettings::SettingMap &settings)
+{
+ //iterate each item in each blocks in this section
+ //validate the corresponding setting against the defined valuelist if any.
+ for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin();
+ it_block != mAbstractBlocks.end(); ++it_block)
+ (*it_block)->updateSettings (settings);
+}
diff --git a/apps/opencs/view/settings/datadisplayformatpage.hpp b/apps/opencs/view/settings/datadisplayformatpage.hpp
new file mode 100755
index 0000000000..b785bbd238
--- /dev/null
+++ b/apps/opencs/view/settings/datadisplayformatpage.hpp
@@ -0,0 +1,33 @@
+#ifndef EDITORPAGE_HPP
+#define EDITORPAGE_HPP
+
+#include "support.hpp"
+#include "abstractpage.hpp"
+
+namespace CSVSettings
+{
+ class DataDisplayFormatPage : public AbstractPage
+ {
+ Q_OBJECT
+
+ public:
+ explicit DataDisplayFormatPage(QWidget *parent = 0);
+
+ void initializeWidgets (const CSMSettings::SettingMap &settings);
+ void setupUi();
+
+ private:
+
+ /// User preference view of the record status delegate's icon / text setting
+ GroupBlockDef *setupDataDisplay(const QString &);
+
+ signals:
+
+ /// Signals up for changes to editor application-level settings
+ void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ public slots:
+ };
+}
+
+#endif // EDITORPAGE_HPP
diff --git a/apps/opencs/view/settings/editorpage.cpp b/apps/opencs/view/settings/editorpage.cpp
new file mode 100644
index 0000000000..153ac1551d
--- /dev/null
+++ b/apps/opencs/view/settings/editorpage.cpp
@@ -0,0 +1,53 @@
+#include "editorpage.hpp"
+#include "groupblock.hpp"
+#include "../../model/settings/usersettings.hpp"
+
+CSVSettings::EditorPage::EditorPage(QWidget* parent) :
+ AbstractPage("Display Format", parent)
+{
+ setupUi();
+}
+
+CSVSettings::GroupBlockDef *CSVSettings::EditorPage::setupRecordStatusDisplay()
+{
+ GroupBlockDef *statusBlock = new GroupBlockDef(QString("Record Status Display"));
+
+ SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon and Text");
+ *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only");
+
+ WidgetDef statusWidget (Widget_RadioButton);
+ statusWidget.valueList = statusItem->valueList;
+
+ statusItem->widget = statusWidget;
+
+ statusBlock->settingItems << statusItem;
+
+ return statusBlock;
+}
+
+void CSVSettings::EditorPage::setupUi()
+{
+
+ mAbstractBlocks << buildBlock<GroupBlock>(setupRecordStatusDisplay());
+
+ foreach (AbstractBlock *block, mAbstractBlocks)
+ {
+ connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)),
+ this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) );
+ }
+
+ connect ( this,
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)),
+ &(CSMSettings::UserSettings::instance()),
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)));
+
+}
+
+void CSVSettings::EditorPage::initializeWidgets (const CSMSettings::SettingMap &settings)
+{
+ //iterate each item in each blocks in this section
+ //validate the corresponding setting against the defined valuelist if any.
+ for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin();
+ it_block != mAbstractBlocks.end(); ++it_block)
+ (*it_block)->updateSettings (settings);
+}
diff --git a/apps/opencs/view/settings/editorpage.hpp b/apps/opencs/view/settings/editorpage.hpp
new file mode 100644
index 0000000000..85215edabf
--- /dev/null
+++ b/apps/opencs/view/settings/editorpage.hpp
@@ -0,0 +1,33 @@
+#ifndef EDITORPAGE_HPP
+#define EDITORPAGE_HPP
+
+#include "support.hpp"
+#include "abstractpage.hpp"
+
+namespace CSVSettings
+{
+ class EditorPage : public AbstractPage
+ {
+ Q_OBJECT
+
+ public:
+ explicit EditorPage(QWidget *parent = 0);
+
+ void initializeWidgets (const CSMSettings::SettingMap &settings);
+ void setupUi();
+
+ private:
+
+ /// User preference view of the record status delegate's icon / text setting
+ GroupBlockDef *setupRecordStatusDisplay();
+
+ signals:
+
+ /// Signals up for changes to editor application-level settings
+ void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ public slots:
+ };
+}
+
+#endif // EDITORPAGE_HPP
diff --git a/apps/opencs/view/settings/groupblock.cpp b/apps/opencs/view/settings/groupblock.cpp
new file mode 100644
index 0000000000..e31e526c03
--- /dev/null
+++ b/apps/opencs/view/settings/groupblock.cpp
@@ -0,0 +1,108 @@
+#include "groupblock.hpp"
+#include "itemblock.hpp"
+
+CSVSettings::GroupBlock::GroupBlock (QWidget* parent)
+ : AbstractBlock (parent)
+{}
+
+CSVSettings::GroupBlock::GroupBlock (bool isVisible, QWidget *parent)
+ : AbstractBlock (isVisible, parent)
+{}
+
+int CSVSettings::GroupBlock::build (GroupBlockDef *def)
+{
+
+ if (def->settingItems.size() == 0)
+ return -1;
+
+ int retVal = 0;
+
+ setVisible (def->isVisible);
+
+ mBox->setLayout(createLayout (def->widgetOrientation, def->isZeroMargin));
+
+ setObjectName (def->title);
+ mBox->setTitle (def->title);
+
+ foreach (SettingsItemDef *itemDef, def->settingItems)
+ {
+ ItemBlock *block = new ItemBlock (mBox);
+
+ if (block->build (*itemDef) < 0)
+ {
+ retVal = -2;
+ break;
+ }
+
+ mItemBlockList << block;
+ mBox->layout()->addWidget (block->getGroupBox());
+
+ connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)),
+ this, SLOT (slotUpdateSetting (const QString &, const QString &) ));
+ }
+
+ return retVal;
+}
+
+CSMSettings::SettingList *CSVSettings::GroupBlock::getSettings()
+{
+ CSMSettings::SettingList *settings = 0;
+
+ foreach (ItemBlock *block, mItemBlockList)
+ {
+ if (!settings)
+ settings = new CSMSettings::SettingList();
+
+ settings->append(*(block->getSettings ()));
+ }
+
+ return settings;
+}
+
+CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (const QString &name, ItemBlockList *blockList)
+{
+ ItemBlock *retBlock = 0;
+
+ if (!blockList)
+ blockList = &mItemBlockList;
+
+ foreach (ItemBlock *block, *blockList)
+ {
+ if (block->objectName() == name)
+ {
+ retBlock = block;
+ break;
+ }
+ }
+
+ return retBlock;
+}
+
+CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (int index)
+{
+ ItemBlock *retBlock = 0;
+
+ if (mItemBlockList.size() > index)
+ retBlock = mItemBlockList.at(index);
+
+ return retBlock;
+}
+
+bool CSVSettings::GroupBlock::updateSettings (const CSMSettings::SettingMap &settings)
+{
+ bool success = true;
+
+ //update all non-proxy settings
+ foreach (ItemBlock *block, mItemBlockList)
+ {
+ CSMSettings::SettingContainer *setting = settings[block->objectName()];
+
+ if (setting)
+ {
+ bool success2 = block->update (setting->getValue());
+ success = success && success2;
+ }
+ }
+
+ return success;
+}
diff --git a/apps/opencs/view/settings/groupblock.hpp b/apps/opencs/view/settings/groupblock.hpp
new file mode 100644
index 0000000000..5c0754193c
--- /dev/null
+++ b/apps/opencs/view/settings/groupblock.hpp
@@ -0,0 +1,43 @@
+#ifndef GROUPBLOCK_HPP
+#define GROUPBLOCK_HPP
+
+#include <QList>
+#include "abstractblock.hpp"
+
+namespace CSVSettings
+{
+ class ItemBlock;
+
+ /// Base class for group blocks.
+ /// Derived block classes should use CustomBlock
+ class GroupBlock : public AbstractBlock
+ {
+ ItemBlockList mItemBlockList;
+
+ public:
+ GroupBlock (QWidget* parent = 0);
+ GroupBlock (bool isVisible, QWidget *parent = 0);
+
+ /// build the gorup block based on passed definition
+ int build (GroupBlockDef *def);
+
+ /// update settings local to the group block
+ bool updateSettings (const CSMSettings::SettingMap &settings);
+
+ /// retrieve setting list local to the group block
+ CSMSettings::SettingList *getSettings();
+
+ /// retrieve item block by name from the passed list or local list
+ ItemBlock *getItemBlock (const QString &name, ItemBlockList *blockList = 0);
+
+ /// retrieve the item block by index from the local list
+ ItemBlock *getItemBlock (int index);
+
+ protected:
+
+ /// create block layout based on passed definition
+ int buildLayout (GroupBlockDef &def);
+
+ };
+}
+#endif // GROUPBLOCK_HPP
diff --git a/apps/opencs/view/settings/groupbox.cpp b/apps/opencs/view/settings/groupbox.cpp
new file mode 100644
index 0000000000..da2cc25711
--- /dev/null
+++ b/apps/opencs/view/settings/groupbox.cpp
@@ -0,0 +1,56 @@
+#include "groupbox.hpp"
+
+const QString CSVSettings::GroupBox::INVISIBLE_BOX_STYLE =
+ QString::fromUtf8("QGroupBox { border: 0px; padding 0px; margin: 0px;}");
+
+CSVSettings::GroupBox::GroupBox(QWidget *parent) :
+ QGroupBox (parent)
+{
+ initBox();
+}
+
+CSVSettings::GroupBox::GroupBox (bool isVisible, QWidget *parent) :
+ QGroupBox (parent)
+{
+ initBox(isVisible);
+}
+
+void CSVSettings::GroupBox::initBox(bool isVisible)
+{
+ setFlat (true);
+ VISIBLE_BOX_STYLE = styleSheet();
+
+ if (!isVisible)
+ setStyleSheet (INVISIBLE_BOX_STYLE);
+}
+
+bool CSVSettings::GroupBox::borderVisibile() const
+{
+ return (styleSheet() != INVISIBLE_BOX_STYLE);
+}
+
+void CSVSettings::GroupBox::setTitle (const QString &title)
+{
+ if (borderVisibile() )
+ {
+ QGroupBox::setTitle (title);
+ setMinimumWidth();
+ }
+}
+
+void CSVSettings::GroupBox::setBorderVisibility (bool value)
+{
+ if (value)
+ setStyleSheet(VISIBLE_BOX_STYLE);
+ else
+ setStyleSheet(INVISIBLE_BOX_STYLE);
+}
+
+void CSVSettings::GroupBox::setMinimumWidth()
+{
+ //set minimum width to accommodate title, if needed
+ //1.5 multiplier to account for bold title.
+ QFontMetrics fm (font());
+ int minWidth = fm.width(title());
+ QGroupBox::setMinimumWidth (minWidth * 1.5);
+}
diff --git a/apps/opencs/view/settings/groupbox.hpp b/apps/opencs/view/settings/groupbox.hpp
new file mode 100644
index 0000000000..9d3a019363
--- /dev/null
+++ b/apps/opencs/view/settings/groupbox.hpp
@@ -0,0 +1,28 @@
+#ifndef GROUPBOX_HPP
+#define GROUPBOX_HPP
+
+#include <QGroupBox>
+
+namespace CSVSettings
+{
+ /// Custom implementation of QGroupBox to be used with block classes
+ class GroupBox : public QGroupBox
+ {
+ static const QString INVISIBLE_BOX_STYLE;
+ QString VISIBLE_BOX_STYLE; //not a const...
+
+ public:
+ explicit GroupBox (QWidget *parent = 0);
+ explicit GroupBox (bool isVisible, QWidget *parent = 0);
+
+ void setTitle (const QString &title);
+ void setBorderVisibility (bool value);
+ bool borderVisibile() const;
+
+ private:
+ void setMinimumWidth();
+ void initBox(bool isVisible = true);
+ };
+}
+
+#endif // GROUPBOX_HPP
diff --git a/apps/opencs/view/settings/itemblock.cpp b/apps/opencs/view/settings/itemblock.cpp
new file mode 100644
index 0000000000..9cb0ae1a1a
--- /dev/null
+++ b/apps/opencs/view/settings/itemblock.cpp
@@ -0,0 +1,115 @@
+#include "itemblock.hpp"
+
+#include <QFontMetrics>
+
+CSVSettings::ItemBlock::ItemBlock (QWidget* parent)
+ : mSetting (0), AbstractBlock (false, parent)
+{
+}
+
+int CSVSettings::ItemBlock::build(SettingsItemDef &iDef)
+{
+ buildItemBlock (iDef);
+ buildItemBlockWidgets (iDef);
+
+ return 0;
+}
+
+void CSVSettings::ItemBlock::buildItemBlockWidgets (SettingsItemDef &iDef)
+{
+ WidgetDef wDef = iDef.widget;
+ QLayout *blockLayout = 0;
+ QString defaultValue = iDef.defaultValue;
+
+ switch (wDef.type)
+ {
+
+ case Widget_CheckBox:
+ case Widget_RadioButton:
+
+ foreach (QString item, *(iDef.valueList))
+ {
+ wDef.caption = item;
+ wDef.isDefault = (item == defaultValue);
+
+ blockLayout = buildWidget (item, wDef, blockLayout)->getLayout();
+ }
+
+ break;
+
+ case Widget_ComboBox:
+ case Widget_ListBox:
+
+ //assign the item's value list to the widget's value list.
+ //pass through to default to finish widget construction.
+ if (!wDef.valueList)
+ wDef.valueList = iDef.valueList;
+
+ default:
+ //only one instance of this non-list widget type.
+ //Set it's value to the default value for the item and build the widget.
+
+ if (wDef.value.isEmpty())
+ wDef.value = iDef.defaultValue;
+
+ buildWidget (iDef.name, wDef);
+ }
+}
+
+void CSVSettings::ItemBlock::buildItemBlock (SettingsItemDef &iDef)
+{
+ QString defaultValue = iDef.defaultValue;
+
+ setObjectName(iDef.name);
+
+ mSetting = new CSMSettings::SettingsItem (objectName(),
+ iDef.hasMultipleValues, iDef.defaultValue,
+ parent());
+
+ if (iDef.valueList)
+ mSetting->setValueList(iDef.valueList);
+
+ if (!iDef.minMax.isEmpty())
+ mSetting->setValuePair(iDef.minMax);
+}
+
+
+bool CSVSettings::ItemBlock::update (const QString &value)
+{
+ bool success = updateItem (value);
+
+ if (success)
+ signalUpdateWidget (value);
+
+ return success;
+}
+
+
+bool CSVSettings::ItemBlock::updateItem (const QString &value)
+{
+ return mSetting->updateItem(value);
+}
+
+
+bool CSVSettings::ItemBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit)
+{
+ bool success = (mSetting->getValue() != value);
+
+ if (success)
+ success = updateItem(value);
+
+ return success;
+}
+
+CSMSettings::SettingList *CSVSettings::ItemBlock::getSettings ()
+{
+ CSMSettings::SettingList *list = new CSMSettings::SettingList();
+ list->push_back(mSetting);
+
+ return list;
+}
+
+QString CSVSettings::ItemBlock::getValue() const
+{
+ return mSetting->getValue();
+}
diff --git a/apps/opencs/view/settings/itemblock.hpp b/apps/opencs/view/settings/itemblock.hpp
new file mode 100644
index 0000000000..2d1d45d418
--- /dev/null
+++ b/apps/opencs/view/settings/itemblock.hpp
@@ -0,0 +1,48 @@
+#ifndef ITEMBLOCK_HPP
+#define ITEMBLOCK_HPP
+
+#include "abstractblock.hpp"
+
+namespace CSVSettings
+{
+
+ class ItemBlock : public AbstractBlock
+ {
+ CSMSettings::SettingsItem *mSetting;
+ WidgetList mWidgetList;
+
+ public:
+
+ ItemBlock (QWidget* parent = 0);
+
+ /// pure virtual function not implemented
+ bool updateSettings (const CSMSettings::SettingMap &settings) { return false; }
+
+ CSMSettings::SettingList *getSettings ();
+
+ QString getValue () const;
+
+ /// item blocks encapsulate only one setting
+ int getSettingCount();
+
+ /// update setting value and corresponding widget
+ bool update (const QString &value);
+
+ /// virtual construction function
+ int build(SettingsItemDef &iDef);
+
+ private:
+
+ /// custom construction function
+ void buildItemBlock (SettingsItemDef& iDef);
+ void buildItemBlockWidgets (SettingsItemDef& iDef);
+
+ /// update the setting value
+ bool updateItem (const QString &);
+
+ /// callback function triggered when update to application level is signalled
+ bool updateBySignal (const QString &name, const QString &value, bool &doEmit);
+ };
+}
+
+#endif // ITEMBLOCK_HPP
diff --git a/apps/opencs/view/settings/proxyblock.cpp b/apps/opencs/view/settings/proxyblock.cpp
new file mode 100644
index 0000000000..81cc54fca5
--- /dev/null
+++ b/apps/opencs/view/settings/proxyblock.cpp
@@ -0,0 +1,152 @@
+#include "proxyblock.hpp"
+#include "itemblock.hpp"
+
+CSVSettings::ProxyBlock::ProxyBlock (QWidget *parent)
+ : GroupBlock (parent)
+{
+}
+int CSVSettings::ProxyBlock::build (GroupBlockDef *proxyDef)
+{
+ //get the list of pre-defined values for the proxy
+ mValueList = proxyDef->settingItems.at(0)->valueList;
+
+ bool success = GroupBlock::build(proxyDef);
+
+ //connect the item block of the proxy setting to the proxy-update slot
+ connect (getItemBlock(0), SIGNAL (signalUpdateSetting(const QString &, const QString &)),
+ this, SLOT (slotUpdateProxySetting (const QString &, const QString &)));
+
+ return success;
+}
+
+void CSVSettings::ProxyBlock::addSetting (ItemBlock *settingBlock, QStringList *proxyList)
+{
+ //connect the item block of the proxied seting to the generic update slot
+ connect (settingBlock, SIGNAL (signalUpdateSetting(const QString &, const QString &)),
+ this, SLOT (slotUpdateProxySetting(const QString &, const QString &)));
+
+ mProxiedItemBlockList << settingBlock;
+ mProxyList << proxyList;
+}
+
+bool CSVSettings::ProxyBlock::updateSettings (const CSMSettings::SettingMap &settings)
+{
+ return updateByProxiedSettings(&settings);
+}
+
+bool CSVSettings::ProxyBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit)
+{
+ doEmit = false;
+ return updateProxiedSettings();
+}
+
+void CSVSettings::ProxyBlock::slotUpdateProxySetting (const QString &name, const QString &value)
+{
+ updateByProxiedSettings();
+}
+
+bool CSVSettings::ProxyBlock::updateProxiedSettings()
+{
+ foreach (ItemBlock *block, mProxiedItemBlockList)
+ {
+ QString value = getItemBlock(0)->getValue();
+
+ bool success = false;
+ int i = 0;
+
+ //find the value index of the selected value in the proxy setting
+ for (; i < mValueList->size(); ++i)
+ {
+ success = (value == mValueList->at(i));
+
+ if (success)
+ break;
+ }
+
+ if (!success)
+ return false;
+
+ // update the containing the proxied item's name
+ foreach (QStringList *list, mProxyList)
+ {
+ if ( list->at(0) == block->objectName())
+ block->update (list->at(++i));
+ }
+ }
+
+ return true;
+}
+
+bool CSVSettings::ProxyBlock::updateByProxiedSettings(const CSMSettings::SettingMap *settings)
+{
+ bool success = false;
+ int commonIndex = -1;
+
+ //update all proxy settings based on values from non-proxies
+ foreach (QStringList *list, mProxyList)
+ {
+ //Iterate each proxy item's proxied setting list, getting the current values
+ //Compare those value indices.
+ //If indices match, they correlate to one of the proxy's values in it's value list
+
+ //first value is always the name of the setting the proxy setting manages
+ QStringList::Iterator itProxyValue = list->begin();
+ QString proxiedSettingName = (*itProxyValue);
+ QString proxiedSettingValue = "";
+ itProxyValue++;
+
+ if (!settings)
+ {
+ //get the actual setting value
+ ItemBlock *block = getProxiedItemBlock (proxiedSettingName);
+
+ if (block)
+ proxiedSettingValue = block->getValue();
+ }
+ else
+ proxiedSettingValue = (*settings)[proxiedSettingName]->getValue();
+
+ int j = 0;
+
+ //iterate each value in the proxy string list
+ for (; itProxyValue != (list)->end(); ++itProxyValue)
+ {
+ success = ((*itProxyValue) == proxiedSettingValue);
+
+ if (success)
+ break;
+
+ j++;
+ }
+
+ //break if no match was found
+ if ( !success )
+ break;
+
+ if (commonIndex != -1)
+ success = (commonIndex == j);
+ else
+ commonIndex = j;
+
+ //break if indices were found, but mismatch
+ if (!success)
+ break;
+ }
+
+ //if successful, the proxied setting values match a pre-defined value in the
+ //proxy's value list. Set the proxy to that value index
+ if (success)
+ {
+ ItemBlock *block = getItemBlock(0);
+
+ if (block)
+ block->update (mValueList->at(commonIndex));
+ }
+
+ return success;
+}
+
+CSVSettings::ItemBlock *CSVSettings::ProxyBlock::getProxiedItemBlock (const QString &name)
+{
+ return getItemBlock (name, &mProxiedItemBlockList);
+}
diff --git a/apps/opencs/view/settings/proxyblock.hpp b/apps/opencs/view/settings/proxyblock.hpp
new file mode 100644
index 0000000000..90fb9bc97e
--- /dev/null
+++ b/apps/opencs/view/settings/proxyblock.hpp
@@ -0,0 +1,52 @@
+#ifndef PROXYBLOCK_HPP
+#define PROXYBLOCK_HPP
+
+#include "groupblock.hpp"
+
+namespace CSVSettings
+{
+ class ProxyBlock : public GroupBlock
+ {
+ Q_OBJECT
+
+ /// TODO: Combine mProxyItemBlockList and mProxyList.
+ ItemBlockList mProxiedItemBlockList;
+ ProxyList mProxyList;
+ QStringList *mValueList;
+
+ public:
+
+ explicit ProxyBlock (QWidget *parent = 0);
+ explicit ProxyBlock (ItemBlock *proxyItemBlock, QWidget *parent = 0);
+
+ /// Add a block that contains a proxied setting to the proxy block.
+ void addSetting (ItemBlock* settingBlock, QStringList *proxyList);
+
+ int build (GroupBlockDef *def);
+
+ CSMSettings::SettingList *getSettings() { return 0; }
+
+ /// Update settings local to the proxy block pushed from application level
+ bool updateSettings (const CSMSettings::SettingMap &settings);
+
+ /// callback function triggered when update to the application level is signaled.
+ bool updateBySignal (const QString &name, const QString &value, bool &doEmit);
+
+ private:
+
+ /// return the item block of a proxied setting
+ ItemBlock *getProxiedItemBlock (const QString &name);
+
+ /// update the proxy setting with data from the proxied settings
+ bool updateByProxiedSettings(const CSMSettings::SettingMap *settings = 0);
+
+ /// update proxied settings with data from the proxy setting
+ bool updateProxiedSettings();
+
+ private slots:
+
+ void slotUpdateProxySetting (const QString &name, const QString &value);
+
+ };
+}
+#endif // PROXYBLOCK_HPP
diff --git a/apps/opencs/view/settings/settingwidget.cpp b/apps/opencs/view/settings/settingwidget.cpp
new file mode 100644
index 0000000000..2c93986e75
--- /dev/null
+++ b/apps/opencs/view/settings/settingwidget.cpp
@@ -0,0 +1 @@
+#include "settingwidget.hpp"
diff --git a/apps/opencs/view/settings/settingwidget.hpp b/apps/opencs/view/settings/settingwidget.hpp
new file mode 100644
index 0000000000..9f45136716
--- /dev/null
+++ b/apps/opencs/view/settings/settingwidget.hpp
@@ -0,0 +1,214 @@
+#ifndef SETTINGWIDGET_HPP
+#define SETTINGWIDGET_HPP
+
+#include <QLabel>
+#include <QCheckBox>
+#include <QSpinBox>
+#include <QLineEdit>
+#include <QRadioButton>
+#include <QComboBox>
+#include <QListWidget>
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+
+#include "abstractwidget.hpp"
+
+namespace CSVSettings
+{
+
+ /// Generic template for radiobuttons / checkboxes
+ template <typename T1>
+ class SettingWidget : public AbstractWidget
+ {
+
+ T1 *mWidget;
+
+ public:
+
+ explicit SettingWidget (WidgetDef &def, QLayout *layout, QWidget* parent = 0)
+ : AbstractWidget (layout, parent), mWidget (new T1 (parent))
+ {
+ mWidget->setText(def.caption);
+ build (mWidget, def, true);
+ mWidget->setChecked(def.isDefault);
+
+ connect (mWidget, SIGNAL (toggled (bool)),
+ this, SLOT (slotUpdateItem (bool)));
+ }
+
+ QWidget *widget() { return mWidget; }
+
+ private:
+
+ void updateWidget (const QString &value)
+ {
+ if ( value == mWidget->objectName() && !mWidget->isChecked() )
+ mWidget->setChecked (true);
+ }
+ };
+
+ /// spin box template
+ template <>
+ class SettingWidget <QSpinBox>: public AbstractWidget
+ {
+
+ QSpinBox *mWidget;
+
+ public:
+
+ SettingWidget (WidgetDef &def, QLayout *layout, QWidget *parent = 0)
+ : AbstractWidget (layout, parent), mWidget (new QSpinBox (parent))
+ {
+ def.caption += tr(" (%1 to %2)").arg(def.minMax->left).arg(def.minMax->right);
+
+ mWidget->setMaximum (def.minMax->right.toInt());
+ mWidget->setMinimum (def.minMax->left.toInt());
+ mWidget->setValue (def.value.toInt());
+
+ build (mWidget, def);
+
+ connect (mWidget, SIGNAL (valueChanged (int)),
+ this, SLOT (slotUpdateItem (int)));
+
+ mWidget->setAlignment (getAlignment(def.valueAlignment));
+
+
+ }
+
+ QWidget *widget() { return mWidget; }
+
+ private:
+
+ void updateWidget (const QString &value)
+ {
+ int intVal = value.toInt();
+
+ if (intVal >= mWidget->minimum() && intVal <= mWidget->maximum() && intVal != mWidget->value())
+ mWidget->setValue (intVal);
+ }
+
+ signals:
+
+ };
+
+ /// combo box template
+ template <>
+ class SettingWidget <QComboBox>: public CSVSettings::AbstractWidget
+ {
+
+ QComboBox *mWidget;
+
+
+ public:
+
+ explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0)
+ : AbstractWidget (layout, parent), mWidget (new QComboBox (parent))
+ {
+ int i = 0;
+
+ foreach (QString item, *(def.valueList))
+ {
+ mWidget->addItem (item);
+
+ if (item == def.value)
+ mWidget->setCurrentIndex(i);
+
+ i++;
+ }
+
+ build (mWidget, def);
+
+ connect (mWidget, SIGNAL (currentIndexChanged (const QString &)),
+ this, SLOT (slotUpdateItem (const QString &)));
+
+ //center the combo box items
+ mWidget->setEditable (true);
+ mWidget->lineEdit()->setReadOnly (true);
+ mWidget->lineEdit()->setAlignment (getAlignment(def.valueAlignment));
+
+ QFlags<Qt::AlignmentFlag> alignment = mWidget->lineEdit()->alignment();
+
+ for (int j = 0; j < mWidget->count(); j++)
+ mWidget->setItemData (j, QVariant(alignment), Qt::TextAlignmentRole);
+ }
+
+ QWidget *widget() { return mWidget; }
+
+ private:
+
+ void updateWidget (const QString &value)
+ {
+ if (mWidget->currentText() != value)
+ mWidget->setCurrentIndex(mWidget->findText(value));
+ }
+
+ };
+
+ /// line edit template
+ template <>
+ class SettingWidget <QLineEdit>: public CSVSettings::AbstractWidget
+ {
+
+ QLineEdit *mWidget;
+
+ public:
+
+ explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0)
+ : AbstractWidget (layout, parent), mWidget (new QLineEdit (parent))
+ {
+ if (!def.inputMask.isEmpty())
+ mWidget->setInputMask (def.inputMask);
+
+ mWidget->setText (def.value);
+
+ build (mWidget, def);
+
+ connect (mWidget, SIGNAL (textChanged (const QString &)),
+ this, SLOT (slotUpdateItem (const QString &)));
+
+ mWidget->setAlignment (getAlignment(def.valueAlignment));
+ }
+
+ QWidget *widget() { return mWidget; }
+
+ void updateWidget (const QString &value)
+ {
+ if (mWidget->text() != value)
+ mWidget->setText(value);
+ }
+ };
+
+ /// list widget template
+ /// \todo Not fully implemented. Only widget supporting multi-valued settings
+ template <>
+ class SettingWidget <QListWidget>: public CSVSettings::AbstractWidget
+ {
+
+ QListWidget *mWidget;
+
+ public:
+
+ explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0 )
+ : AbstractWidget (layout, parent), mWidget (new QListWidget (parent))
+ {
+ int i = 0;
+
+ foreach (QString item, *(def.valueList))
+ {
+ mWidget->addItem (item);
+
+ if (item == def.value) {}
+ i++;
+ }
+ build (mWidget, def);
+ }
+
+ QWidget *widget() { return mWidget; }
+
+ private:
+ void updateWidget (const QString &value) {}
+ };
+
+}
+#endif // SETTINGWIDGET_HPP
diff --git a/apps/opencs/view/settings/support.cpp b/apps/opencs/view/settings/support.cpp
new file mode 100644
index 0000000000..d79edfdb33
--- /dev/null
+++ b/apps/opencs/view/settings/support.cpp
@@ -0,0 +1 @@
+#include "support.hpp"
diff --git a/apps/opencs/view/settings/support.hpp b/apps/opencs/view/settings/support.hpp
new file mode 100644
index 0000000000..5d954505c4
--- /dev/null
+++ b/apps/opencs/view/settings/support.hpp
@@ -0,0 +1,206 @@
+#ifndef VIEW_SUPPORT_HPP
+#define VIEW_SUPPORT_HPP
+
+#include <QList>
+#include <QStringList>
+
+#include "../../model/settings/support.hpp"
+
+namespace CSVSettings
+{
+ struct WidgetDef;
+ class ItemBlock;
+ class GroupBlock;
+ struct GroupBlockDef;
+
+ typedef QList<GroupBlockDef *> GroupBlockDefList;
+ typedef QList<GroupBlock *> GroupBlockList;
+ typedef QList<ItemBlock *> ItemBlockList;
+ typedef QList<QStringList *> ProxyList;
+ typedef QList<WidgetDef *> WidgetList;
+ typedef QMap<QString, ItemBlock *> ItemBlockMap;
+
+ enum Orientation
+ {
+ Orient_Horizontal,
+ Orient_Vertical
+ };
+
+ enum WidgetType
+ {
+ Widget_CheckBox,
+ Widget_ComboBox,
+ Widget_LineEdit,
+ Widget_ListBox,
+ Widget_RadioButton,
+ Widget_SpinBox,
+ Widget_Undefined
+ };
+
+ enum Alignment
+ {
+ Align_Left = Qt::AlignLeft,
+ Align_Center = Qt::AlignHCenter,
+ Align_Right = Qt::AlignRight
+ };
+
+ /// definition struct for widgets
+ struct WidgetDef
+ {
+ /// type of widget providing input
+ WidgetType type;
+
+ /// width of caption label
+ int labelWidth;
+
+ /// width of input widget
+ int widgetWidth;
+
+ /// label / widget orientation (horizontal / vertical)
+ Orientation orientation;
+
+ /// input mask (line edit only)
+ QString inputMask;
+
+ /// label caption. Leave empty for multiple items. See BlockDef::captionList
+ QString caption;
+
+ /// widget value. Leave empty for multiple items. See BlockDef::valueList
+ QString value;
+
+ /// Min/Max QString value pair. If empty, assigned to property item value pair.
+ CSMSettings::QStringPair *minMax;
+
+ /// value list for list widgets. If left empty, is assigned to property item value list during block build().
+ QStringList *valueList;
+
+ /// determined at runtime
+ bool isDefault;
+
+ /// left / center / right-justify text in widget
+ Alignment valueAlignment;
+
+ /// left / center / right-justify widget in group box
+ Alignment widgetAlignment;
+
+
+ WidgetDef() : labelWidth (-1), widgetWidth (-1),
+ orientation (Orient_Horizontal),
+ isDefault (true), valueAlignment (Align_Center),
+ widgetAlignment (Align_Right),
+ inputMask (""), value (""),
+ caption (""), valueList (0)
+ {}
+
+ WidgetDef (WidgetType widgType)
+ : type (widgType), orientation (Orient_Horizontal),
+ caption (""), value (""), valueAlignment (Align_Center),
+ widgetAlignment (Align_Right),
+ labelWidth (-1), widgetWidth (-1),
+ valueList (0), isDefault (true)
+ {}
+
+ };
+
+ /// Defines the attributes of the setting as it is represented in the config file
+ /// as well as the UI elements (group box and widget) that serve it.
+ /// Only one widget may serve as the input widget for the setting.
+ struct SettingsItemDef
+ {
+ /// setting name
+ QString name;
+
+ /// list of valid values for the setting
+ QStringList *valueList;
+
+ /// Used to populate option widget captions or list widget item lists (see WidgetDef::caption / value)
+ QString defaultValue;
+
+ /// flag indicating multi-valued setting
+ bool hasMultipleValues;
+
+ /// minimum / maximum value pair
+ CSMSettings::QStringPair minMax;
+
+ /// definition of the input widget for this setting
+ WidgetDef widget;
+
+ /// general orientation of the widget / label for this setting
+ Orientation orientation;
+
+ /// list of settings and corresponding default values for proxy widget
+ ProxyList *proxyList;
+
+ SettingsItemDef() : name (""), defaultValue (""), orientation (Orient_Vertical), hasMultipleValues (false)
+ {}
+
+ SettingsItemDef (QString propName, QString propDefault, Orientation propOrient = Orient_Vertical)
+ : name (propName), defaultValue (propDefault), orientation (propOrient),
+ hasMultipleValues(false), valueList (new QStringList), proxyList ( new ProxyList)
+ {}
+ };
+
+
+ /// Generic container block
+ struct GroupBlockDef
+ {
+ /// block title
+ QString title;
+
+ /// list of captions for widgets at the block level (not associated with any particular setting)
+ QStringList captions;
+
+ /// list of widgets at the block level (not associated with any particular setting)
+ WidgetList widgets;
+
+ /// list of the settings which are subordinate to the setting block.
+ QList<SettingsItemDef *> settingItems;
+
+ /// general orientation of widgets in group block
+ Orientation widgetOrientation;
+
+ /// determines whether or not box border/title are visible
+ bool isVisible;
+
+ /// indicates whether or not this block defines a proxy block
+ bool isProxy;
+
+ /// generic default value attribute
+ QString defaultValue;
+
+ /// shows / hides margins
+ bool isZeroMargin;
+
+ GroupBlockDef (): title(""), widgetOrientation (Orient_Vertical), isVisible (true), isProxy (false), defaultValue (""), isZeroMargin (true)
+ {}
+
+ GroupBlockDef (QString blockTitle)
+ : title (blockTitle), widgetOrientation (Orient_Vertical), isProxy (false), isVisible (true), defaultValue (""), isZeroMargin (true)
+ {}
+ };
+
+ /// used to create unique, complex blocks
+ struct CustomBlockDef
+ {
+ /// block title
+ QString title;
+
+ /// default value for widgets unique to the custom block
+ QString defaultValue;
+
+ /// list of settings groups that comprise the settings within the custom block
+ GroupBlockDefList blockDefList;
+
+ /// orientation of the widgets within the block
+ Orientation blockOrientation;
+
+ CustomBlockDef (): title (""), defaultValue (""), blockOrientation (Orient_Horizontal)
+ {}
+
+ CustomBlockDef (const QString &blockTitle)
+ : title (blockTitle), defaultValue (""), blockOrientation (Orient_Horizontal)
+ {}
+ };
+}
+
+#endif // VIEW_SUPPORT_HPP
diff --git a/apps/opencs/view/settings/toggleblock.cpp b/apps/opencs/view/settings/toggleblock.cpp
new file mode 100644
index 0000000000..3406a62c4d
--- /dev/null
+++ b/apps/opencs/view/settings/toggleblock.cpp
@@ -0,0 +1,80 @@
+#include "toggleblock.hpp"
+#include "groupblock.hpp"
+#include "groupbox.hpp"
+#include "itemblock.hpp"
+
+CSVSettings::ToggleBlock::ToggleBlock(QWidget *parent) :
+ CustomBlock(parent)
+{}
+
+int CSVSettings::ToggleBlock::build(CustomBlockDef *def)
+{
+ if (def->blockDefList.size()==0)
+ return -1;
+
+ QList<GroupBlockDef *>::Iterator it = def->blockDefList.begin();
+
+ //first def in the list is the def for the toggle block
+ GroupBlockDef *toggleDef = *it++;
+
+ if (toggleDef->captions.size() != def->blockDefList.size()-1 )
+ return -2;
+
+ if (toggleDef->widgets.size() == 0)
+ return -3;
+
+ //create the toogle block UI structure
+ QLayout *blockLayout = createLayout (def->blockOrientation, true);
+ GroupBox *propertyBox = buildGroupBox (toggleDef->widgetOrientation);
+
+ mBox->setLayout(blockLayout);
+ mBox->setTitle (toggleDef->title);
+
+ //build the blocks contained in the def list
+ //this manages proxy block construction.
+ //Any settings managed by the proxy setting
+ //must be included in the blocks defined in the list.
+ CustomBlock::build (def->blockDefList, &it);
+
+ for (GroupBlockList::iterator it = mGroupList.begin(); it != mGroupList.end(); ++it)
+ propertyBox->layout()->addWidget ((*it)->getGroupBox());
+
+ //build togle widgets, linking them to the settings
+ GroupBox *toggleBox = buildToggleWidgets (toggleDef, def->defaultValue);
+
+ blockLayout->addWidget(toggleBox);
+ blockLayout->addWidget(propertyBox);
+ blockLayout->setAlignment (propertyBox, Qt::AlignRight);
+
+ return 0;
+}
+
+CSVSettings::GroupBox *CSVSettings::ToggleBlock::buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle)
+{
+ GroupBox *box = new GroupBox (false, getParent());
+
+ QLayout *layout = createLayout (def->widgetOrientation, true, static_cast<QWidget *>(box));
+
+ for (int i = 0; i < def->widgets.size(); ++i)
+ {
+ QString caption = def->captions.at(i);
+ WidgetDef *wDef = def->widgets.at(i);
+
+ wDef->caption = caption;
+ wDef->widgetAlignment = Align_Left;
+
+ AbstractWidget *widg = buildWidget (caption, *wDef, layout, false);
+
+ GroupBlock *block = mGroupList.at(i);
+
+ //connect widget's update to the property block's enabled status
+ connect (widg->widget(), SIGNAL (toggled (bool)), block, SLOT (slotSetEnabled(bool)));
+
+ //enable the default toggle option
+ block->getGroupBox()->setEnabled( caption == defaultToggle );
+
+ layout = widg->getLayout();
+ }
+
+ return box;
+}
diff --git a/apps/opencs/view/settings/toggleblock.hpp b/apps/opencs/view/settings/toggleblock.hpp
new file mode 100644
index 0000000000..4b6e8e344d
--- /dev/null
+++ b/apps/opencs/view/settings/toggleblock.hpp
@@ -0,0 +1,29 @@
+#ifndef TOGGLEBLOCK_HPP
+#define TOGGLEBLOCK_HPP
+
+#include <QObject>
+
+#include "customblock.hpp"
+
+namespace CSVSettings
+{
+ class GroupBlock;
+ class GroupBox;
+ class ToggleWidget;
+ class ItemBlock;
+
+ class ToggleBlock : public CustomBlock
+ {
+
+ public:
+ explicit ToggleBlock(QWidget *parent = 0);
+
+ int build (CustomBlockDef *def);
+
+ private:
+ /// Constructor for toggle widgets that are specific to toggle block
+ /// Widgets are not a part of the user preference settings
+ GroupBox *buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle);
+ };
+}
+#endif // TOGGLEBLOCK_HPP
diff --git a/apps/opencs/view/settings/usersettingsdialog.cpp b/apps/opencs/view/settings/usersettingsdialog.cpp
new file mode 100644
index 0000000000..21311c2dab
--- /dev/null
+++ b/apps/opencs/view/settings/usersettingsdialog.cpp
@@ -0,0 +1,113 @@
+#include "usersettingsdialog.hpp"
+
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QWidget>
+#include <QTabWidget>
+#include <QMessageBox>
+#include <QTextCodec>
+#include <QFile>
+#include <QPushButton>
+#include <QDockWidget>
+
+#include <QGridLayout>
+
+#include "datadisplayformatpage.hpp"
+#include "windowpage.hpp"
+
+#include "../../model/settings/support.hpp"
+#include <boost/filesystem/path.hpp>
+#include "settingwidget.hpp"
+
+CSVSettings::UserSettingsDialog::UserSettingsDialog(QMainWindow *parent) :
+ QMainWindow (parent), mStackedWidget (0)
+{
+ setWindowTitle(QString::fromUtf8 ("User Settings"));
+ buildPages();
+ setWidgetStates ();
+
+ connect (mListWidget,
+ SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
+ this,
+ SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*)));
+}
+
+CSVSettings::UserSettingsDialog::~UserSettingsDialog()
+{
+}
+
+void CSVSettings::UserSettingsDialog::closeEvent (QCloseEvent *event)
+{
+ writeSettings();
+}
+
+void CSVSettings::UserSettingsDialog::setWidgetStates ()
+{
+ CSMSettings::UserSettings::instance().loadSettings("opencs.cfg");
+
+ //iterate the tabWidget's pages (sections)
+ for (int i = 0; i < mStackedWidget->count(); i++)
+ {
+ //get the settings defined for the entire section
+ //and update widget
+ QString pageName = mStackedWidget->widget(i)->objectName();
+
+ const CSMSettings::SettingMap *settings = CSMSettings::UserSettings::instance().getSettings(pageName);
+ AbstractPage &page = getAbstractPage (i);
+ page.initializeWidgets(*settings);
+ }
+}
+
+void CSVSettings::UserSettingsDialog::buildPages()
+{
+ //craete central widget with it's layout and immediate children
+ QWidget *centralWidget = new QWidget (this);
+
+ mListWidget = new QListWidget (centralWidget);
+ mStackedWidget = new QStackedWidget (centralWidget);
+
+ QGridLayout* dialogLayout = new QGridLayout();
+
+ mListWidget->setMinimumWidth(0);
+ mListWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding);
+
+ mStackedWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ dialogLayout->addWidget (mListWidget,0,0);
+ dialogLayout->addWidget (mStackedWidget,0,1, Qt::AlignTop);
+
+ centralWidget->setLayout (dialogLayout);
+
+ setCentralWidget (centralWidget);
+ setDockOptions (QMainWindow::AllowNestedDocks);
+
+ createPage<WindowPage>();
+ createPage<DataDisplayFormatPage>();
+
+}
+
+void CSVSettings::UserSettingsDialog::writeSettings()
+{
+ QMap<QString, CSMSettings::SettingList *> settings;
+
+ for (int i = 0; i < mStackedWidget->count(); ++i)
+ {
+ AbstractPage &page = getAbstractPage (i);
+ settings [page.objectName()] = page.getSettings();
+ }
+ CSMSettings::UserSettings::instance().writeSettings(settings);
+}
+
+CSVSettings::AbstractPage &CSVSettings::UserSettingsDialog::getAbstractPage (int index)
+{
+ return dynamic_cast<AbstractPage &> (*(mStackedWidget->widget (index)));
+}
+
+void CSVSettings::UserSettingsDialog::slotChangePage(QListWidgetItem *current, QListWidgetItem *previous)
+{
+ if (!current)
+ current = previous;
+
+ if (!(current == previous))
+ mStackedWidget->setCurrentIndex (mListWidget->row(current));
+}
diff --git a/apps/opencs/view/settings/usersettingsdialog.hpp b/apps/opencs/view/settings/usersettingsdialog.hpp
new file mode 100644
index 0000000000..3b3fa5b79e
--- /dev/null
+++ b/apps/opencs/view/settings/usersettingsdialog.hpp
@@ -0,0 +1,71 @@
+#ifndef USERSETTINGSDIALOG_H
+#define USERSETTINGSDIALOG_H
+
+#include <QMainWindow>
+#include <QStackedWidget>
+#include <QListWidgetItem>
+#include <QApplication>
+
+#include "../../model/settings/usersettings.hpp"
+#include "../../model/settings/support.hpp"
+
+class QHBoxLayout;
+class AbstractWidget;
+class QStackedWidget;
+class QListWidget;
+
+namespace CSVSettings {
+
+ class AbstractPage;
+
+ class UserSettingsDialog : public QMainWindow
+ {
+ Q_OBJECT
+
+ QListWidget *mListWidget;
+ QStackedWidget *mStackedWidget;
+
+ public:
+ UserSettingsDialog(QMainWindow *parent = 0);
+ ~UserSettingsDialog();
+
+ private:
+
+ /// Settings are written on close
+ void closeEvent (QCloseEvent *event);
+
+ /// return the setting page by name
+ /// performs dynamic cast to AbstractPage *
+ AbstractPage &getAbstractPage (int index);
+ void setWidgetStates ();
+ void buildPages();
+ void writeSettings();
+
+ /// Templated function to create a custom user preference page
+ template <typename T>
+ void createPage ()
+ {
+ T *page = new T(mStackedWidget);
+
+ mStackedWidget->addWidget (&dynamic_cast<QWidget &>(*page));
+
+ new QListWidgetItem (page->objectName(), mListWidget);
+
+ //finishing touches
+ QFontMetrics fm (QApplication::font());
+ int textWidth = fm.width(page->objectName());
+
+ if ((textWidth + 50) > mListWidget->minimumWidth())
+ mListWidget->setMinimumWidth(textWidth + 50);
+
+ resize (mStackedWidget->sizeHint());
+ }
+
+ public slots:
+
+ /// Called when a different page is selected in the left-hand list widget
+ void slotChangePage (QListWidgetItem*, QListWidgetItem*);
+ };
+
+}
+#endif // USERSETTINGSDIALOG_H
diff --git a/apps/opencs/view/settings/windowpage.cpp b/apps/opencs/view/settings/windowpage.cpp
new file mode 100644
index 0000000000..ae42623b78
--- /dev/null
+++ b/apps/opencs/view/settings/windowpage.cpp
@@ -0,0 +1,144 @@
+#include "windowpage.hpp"
+
+#include <QList>
+#include <QListView>
+#include <QGroupBox>
+#include <QRadioButton>
+#include <QDockWidget>
+#include <QVBoxLayout>
+#include <QGridLayout>
+#include <QStyle>
+
+#ifdef Q_OS_MAC
+#include <QPlastiqueStyle>
+#endif
+
+#include "../../model/settings/usersettings.hpp"
+#include "groupblock.hpp"
+#include "toggleblock.hpp"
+#include "../../view/settings/abstractblock.hpp"
+
+CSVSettings::WindowPage::WindowPage(QWidget *parent):
+ AbstractPage("Window Size", parent)
+{
+ // Hacks to get the stylesheet look properly
+#ifdef Q_OS_MAC
+ QPlastiqueStyle *style = new QPlastiqueStyle;
+ //profilesComboBox->setStyle(style);
+#endif
+
+ setupUi();
+}
+
+CSVSettings::GroupBlockDef * CSVSettings::WindowPage::buildDefinedWindowSize()
+{
+ GroupBlockDef *block = new GroupBlockDef ( "Defined Size");
+
+ SettingsItemDef *widthByHeightItem = new SettingsItemDef ("Window Size", "640x480");
+ WidgetDef widthByHeightWidget = WidgetDef (Widget_ComboBox);
+ widthByHeightWidget.widgetWidth = 90;
+ *(widthByHeightItem->valueList) << "640x480" << "800x600" << "1024x768" << "1440x900";
+
+ QStringList *widthProxy = new QStringList;
+ QStringList *heightProxy = new QStringList;
+
+ (*widthProxy) << "Width" << "640" << "800" << "1024" << "1440";
+ (*heightProxy) << "Height" << "480" << "600" << "768" << "900";
+
+ *(widthByHeightItem->proxyList) << widthProxy << heightProxy;
+
+ widthByHeightItem->widget = widthByHeightWidget;
+
+ block->settingItems << widthByHeightItem;
+ block->isProxy = true;
+ block->isVisible = false;
+
+ return block;
+}
+
+CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildCustomWindowSize()
+{
+ GroupBlockDef *block = new GroupBlockDef ("Custom Size");
+
+ //custom width
+ SettingsItemDef *widthItem = new SettingsItemDef ("Width", "640");
+ widthItem->widget = WidgetDef (Widget_LineEdit);
+ widthItem->widget.widgetWidth = 45;
+ widthItem->widget.inputMask = "9999";
+
+ //custom height
+ SettingsItemDef *heightItem = new SettingsItemDef ("Height", "480");
+ heightItem->widget = WidgetDef (Widget_LineEdit);
+ heightItem->widget.widgetWidth = 45;
+ heightItem->widget.caption = "x";
+ heightItem->widget.inputMask = "9999";
+
+ block->settingItems << widthItem << heightItem;
+ block->widgetOrientation = Orient_Horizontal;
+ block->isVisible = false;
+
+ return block;
+}
+
+CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildWindowSizeToggle()
+{
+ GroupBlockDef *block = new GroupBlockDef (objectName());
+
+ // window size toggle
+ block->captions << "Pre-Defined" << "Custom";
+ block->widgetOrientation = Orient_Vertical;
+ block->isVisible = false;
+
+ //define a widget for each group in the toggle
+ for (int i = 0; i < 2; i++)
+ block->widgets << new WidgetDef (Widget_RadioButton);
+
+ block->widgets.at(0)->isDefault = false;
+
+ return block;
+}
+
+CSVSettings::CustomBlockDef *CSVSettings::WindowPage::buildWindowSize(GroupBlockDef *toggle_def,
+ GroupBlockDef *defined_def,
+ GroupBlockDef *custom_def)
+{
+ CustomBlockDef *block = new CustomBlockDef(QString ("Window Size"));
+
+ block->blockDefList << toggle_def << defined_def << custom_def;
+ block->defaultValue = "Custom";
+
+ return block;
+
+}
+
+void CSVSettings::WindowPage::setupUi()
+{
+ CustomBlockDef *windowSize = buildWindowSize(buildWindowSizeToggle(),
+ buildDefinedWindowSize(),
+ buildCustomWindowSize()
+ );
+
+ mAbstractBlocks << buildBlock<ToggleBlock> (windowSize);
+
+ foreach (AbstractBlock *block, mAbstractBlocks)
+ {
+ connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)),
+ this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) );
+ }
+
+ connect ( this,
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)),
+ &(CSMSettings::UserSettings::instance()),
+ SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)));
+
+}
+
+
+void CSVSettings::WindowPage::initializeWidgets (const CSMSettings::SettingMap &settings)
+{
+ //iterate each item in each blocks in this section
+ //validate the corresponding setting against the defined valuelist if any.
+ for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin();
+ it_block != mAbstractBlocks.end(); ++it_block)
+ (*it_block)->updateSettings (settings);
+}
diff --git a/apps/opencs/view/settings/windowpage.hpp b/apps/opencs/view/settings/windowpage.hpp
new file mode 100644
index 0000000000..2f28306256
--- /dev/null
+++ b/apps/opencs/view/settings/windowpage.hpp
@@ -0,0 +1,34 @@
+#ifndef WINDOWPAGE_H
+#define WINDOWPAGE_H
+
+#include "abstractpage.hpp"
+
+class QGroupBox;
+
+namespace CSVSettings {
+
+ class UserSettings;
+ class AbstractBlock;
+
+ class WindowPage : public AbstractPage
+ {
+ Q_OBJECT
+
+ public:
+
+ WindowPage(QWidget *parent = 0);
+
+ void setupUi();
+ void initializeWidgets (const CSMSettings::SettingMap &settings);
+
+ ///
+ GroupBlockDef *buildCustomWindowSize();
+ GroupBlockDef *buildDefinedWindowSize();
+ GroupBlockDef *buildWindowSizeToggle();
+ CustomBlockDef *buildWindowSize (GroupBlockDef *, GroupBlockDef *, GroupBlockDef *);
+
+ signals:
+ void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue);
+ };
+}
+#endif //WINDOWPAGE_H
diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp
new file mode 100644
index 0000000000..fe1be85d79
--- /dev/null
+++ b/apps/opencs/view/tools/reportsubview.cpp
@@ -0,0 +1,32 @@
+
+#include "reportsubview.hpp"
+
+#include <QTableView>
+#include <QHeaderView>
+
+#include "../../model/tools/reportmodel.hpp"
+
+CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
+: CSVDoc::SubView (id), mModel (document.getReport (id))
+{
+ setWidget (mTable = new QTableView (this));
+ mTable->setModel (mModel);
+
+ mTable->horizontalHeader()->setResizeMode (QHeaderView::Interactive);
+ mTable->verticalHeader()->hide();
+ mTable->setSortingEnabled (true);
+ mTable->setSelectionBehavior (QAbstractItemView::SelectRows);
+ mTable->setSelectionMode (QAbstractItemView::ExtendedSelection);
+
+ connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&)));
+}
+
+void CSVTools::ReportSubView::setEditLock (bool locked)
+{
+ // ignored. We don't change document state anyway.
+}
+
+void CSVTools::ReportSubView::show (const QModelIndex& index)
+{
+ focusId (mModel->getUniversalId (index.row()));
+} \ No newline at end of file
diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp
new file mode 100644
index 0000000000..626ceb663d
--- /dev/null
+++ b/apps/opencs/view/tools/reportsubview.hpp
@@ -0,0 +1,42 @@
+#ifndef CSV_TOOLS_REPORTSUBVIEW_H
+#define CSV_TOOLS_REPORTSUBVIEW_H
+
+#include "../doc/subview.hpp"
+
+class QTableView;
+class QModelIndex;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSMTools
+{
+ class ReportModel;
+}
+
+namespace CSVTools
+{
+ class Table;
+
+ class ReportSubView : public CSVDoc::SubView
+ {
+ Q_OBJECT
+
+ CSMTools::ReportModel *mModel;
+ QTableView *mTable;
+
+ public:
+
+ ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
+
+ virtual void setEditLock (bool locked);
+
+ private slots:
+
+ void show (const QModelIndex& index);
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp
new file mode 100644
index 0000000000..781cf602e3
--- /dev/null
+++ b/apps/opencs/view/tools/subviews.cpp
@@ -0,0 +1,12 @@
+
+#include "subviews.hpp"
+
+#include "../doc/subviewfactoryimp.hpp"
+
+#include "reportsubview.hpp"
+
+void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
+{
+ manager.add (CSMWorld::UniversalId::Type_VerificationResults,
+ new CSVDoc::SubViewFactory<ReportSubView>);
+} \ No newline at end of file
diff --git a/apps/opencs/view/tools/subviews.hpp b/apps/opencs/view/tools/subviews.hpp
new file mode 100644
index 0000000000..1bac322282
--- /dev/null
+++ b/apps/opencs/view/tools/subviews.hpp
@@ -0,0 +1,14 @@
+#ifndef CSV_TOOLS_SUBVIEWS_H
+#define CSV_TOOLS_SUBVIEWS_H
+
+namespace CSVDoc
+{
+ class SubViewFactoryManager;
+}
+
+namespace CSVTools
+{
+ void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager);
+}
+
+#endif
diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp
new file mode 100644
index 0000000000..74fb068700
--- /dev/null
+++ b/apps/opencs/view/world/cellcreator.cpp
@@ -0,0 +1,81 @@
+
+#include "cellcreator.hpp"
+
+#include <limits>
+#include <sstream>
+
+#include <QComboBox>
+#include <QSpinBox>
+#include <QLabel>
+
+std::string CSVWorld::CellCreator::getId() const
+{
+ if (mType->currentIndex()==0)
+ return GenericCreator::getId();
+
+ std::ostringstream stream;
+
+ stream << "#" << mX->value() << " " << mY->value();
+
+ return stream.str();
+}
+
+CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: GenericCreator (data, undoStack, id)
+{
+ mY = new QSpinBox (this);
+ mY->setVisible (false);
+ mY->setMinimum (std::numeric_limits<int>::min());
+ mY->setMaximum (std::numeric_limits<int>::max());
+ connect (mY, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int)));
+ insertAtBeginning (mY, true);
+
+ mYLabel = new QLabel ("Y", this);
+ mYLabel->setVisible (false);
+ insertAtBeginning (mYLabel, false);
+
+ mX = new QSpinBox (this);
+ mX->setVisible (false);
+ mX->setMinimum (std::numeric_limits<int>::min());
+ mX->setMaximum (std::numeric_limits<int>::max());
+ connect (mX, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int)));
+ insertAtBeginning (mX, true);
+
+ mXLabel = new QLabel ("X", this);
+ mXLabel->setVisible (false);
+ insertAtBeginning (mXLabel, false);
+
+ mType = new QComboBox (this);
+
+ mType->addItem ("Interior Cell");
+ mType->addItem ("Exterior Cell");
+
+ connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int)));
+
+ insertAtBeginning (mType, false);
+}
+
+void CSVWorld::CellCreator::reset()
+{
+ mX->setValue (0);
+ mY->setValue (0);
+ mType->setCurrentIndex (0);
+ GenericCreator::reset();
+}
+
+void CSVWorld::CellCreator::setType (int index)
+{
+ setManualEditing (index==0);
+ mXLabel->setVisible (index==1);
+ mX->setVisible (index==1);
+ mYLabel->setVisible (index==1);
+ mY->setVisible (index==1);
+
+ update();
+}
+
+void CSVWorld::CellCreator::valueChanged (int index)
+{
+ update();
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp
new file mode 100644
index 0000000000..a5473e2c97
--- /dev/null
+++ b/apps/opencs/view/world/cellcreator.hpp
@@ -0,0 +1,40 @@
+#ifndef CSV_WORLD_CELLCREATOR_H
+#define CSV_WORLD_CELLCREATOR_H
+
+class QLabel;
+class QSpinBox;
+class QComboBox;
+
+#include "genericcreator.hpp"
+
+namespace CSVWorld
+{
+ class CellCreator : public GenericCreator
+ {
+ Q_OBJECT
+
+ QComboBox *mType;
+ QLabel *mXLabel;
+ QSpinBox *mX;
+ QLabel *mYLabel;
+ QSpinBox *mY;
+
+ protected:
+
+ virtual std::string getId() const;
+
+ public:
+
+ CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id);
+
+ virtual void reset();
+
+ private slots:
+
+ void setType (int index);
+
+ void valueChanged (int index);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp
new file mode 100644
index 0000000000..d753a2b476
--- /dev/null
+++ b/apps/opencs/view/world/creator.cpp
@@ -0,0 +1,13 @@
+
+#include "creator.hpp"
+
+CSVWorld::Creator:: ~Creator() {}
+
+CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {}
+
+
+CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMWorld::Data& data,
+ QUndoStack& undoStack, const CSMWorld::UniversalId& id) const
+{
+ return 0;
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp
new file mode 100644
index 0000000000..df9b116eed
--- /dev/null
+++ b/apps/opencs/view/world/creator.hpp
@@ -0,0 +1,86 @@
+#ifndef CSV_WORLD_CREATOR_H
+#define CSV_WORLD_CREATOR_H
+
+#include <QWidget>
+
+class QUndoStack;
+
+namespace CSMWorld
+{
+ class Data;
+ class UniversalId;
+}
+
+namespace CSVWorld
+{
+ /// \brief Record creator UI base class
+ class Creator : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ virtual ~Creator();
+
+ virtual void reset() = 0;
+
+ virtual void setEditLock (bool locked) = 0;
+
+ signals:
+
+ void done();
+
+ void requestFocus (const std::string& id);
+ ///< Request owner of this creator to focus the just created \a id. The owner may
+ /// ignore this request.
+ };
+
+ /// \brief Base class for Creator factory
+ class CreatorFactoryBase
+ {
+ public:
+
+ virtual ~CreatorFactoryBase();
+
+ virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id) const = 0;
+ ///< The ownership of the returned Creator is transferred to the caller.
+ ///
+ /// \note The function can return a 0-pointer, which means no UI for creating/deleting
+ /// records should be provided.
+ };
+
+ /// \brief Creator factory that does not produces any creator
+ class NullCreatorFactory : public CreatorFactoryBase
+ {
+ public:
+
+ virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id) const;
+ ///< The ownership of the returned Creator is transferred to the caller.
+ ///
+ /// \note The function always returns 0.
+ };
+
+ template<class CreatorT>
+ class CreatorFactory : public CreatorFactoryBase
+ {
+ public:
+
+ virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id) const;
+ ///< The ownership of the returned Creator is transferred to the caller.
+ ///
+ /// \note The function can return a 0-pointer, which means no UI for creating/deleting
+ /// records should be provided.
+ };
+
+ template<class CreatorT>
+ Creator *CreatorFactory<CreatorT>::makeCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id) const
+ {
+ return new CreatorT (data, undoStack, id);
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp
new file mode 100755
index 0000000000..d838395f6e
--- /dev/null
+++ b/apps/opencs/view/world/datadisplaydelegate.cpp
@@ -0,0 +1,110 @@
+#include "datadisplaydelegate.hpp"
+#include <QApplication>
+#include <QPainter>
+
+CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values,
+ const IconList &icons,
+ QUndoStack &undoStack, QObject *parent)
+ : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), mIcons (icons)
+ , mIconSize (QSize(16, 16)), mIconLeftOffset(3), mTextLeftOffset(8)
+{
+ mTextAlignment.setAlignment (Qt::AlignLeft | Qt::AlignVCenter );
+
+ buildPixmaps();
+}
+
+void CSVWorld::DataDisplayDelegate::buildPixmaps ()
+{
+ if (mPixmaps.size() > 0)
+ mPixmaps.clear();
+
+ IconList::iterator it = mIcons.begin();
+
+ while (it != mIcons.end())
+ {
+ mPixmaps.push_back (std::make_pair (it->first, it->second.pixmap (mIconSize) ) );
+ it++;
+ }
+}
+
+void CSVWorld::DataDisplayDelegate::setIconSize(const QSize size)
+{
+ mIconSize = size;
+ buildPixmaps();
+}
+
+void CSVWorld::DataDisplayDelegate::setIconLeftOffset(int offset)
+{
+ mIconLeftOffset = offset;
+}
+
+void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset)
+{
+ mTextLeftOffset = offset;
+}
+
+void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ painter->save();
+
+ //default to enum delegate's paint method for text-only conditions
+ if (mDisplayMode == Mode_TextOnly)
+ EnumDelegate::paint(painter, option, index);
+ else
+ {
+ unsigned int i = 0;
+
+ for (; i < mValues.size(); ++i)
+ {
+ if (mValues.at(i).first == index.data().toInt())
+ break;
+ }
+
+ if (i < mValues.size() )
+ paintIcon (painter, option, i);
+ }
+
+ painter->restore();
+}
+
+void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int index) const
+{
+ //function-level statics
+ QRect iconRect = option.rect;
+ QRect textRect = iconRect;
+
+ const QString &text = mValues.at(index).second;
+
+ iconRect.setSize (mIconSize);
+ iconRect.translate(mIconLeftOffset, (option.rect.height() - iconRect.height())/2);
+
+ if (mDisplayMode == Mode_IconAndText )
+ {
+ textRect.translate (iconRect.width() + mTextLeftOffset, 0 );
+ painter->drawText (textRect, text, mTextAlignment);
+ }
+ else
+ iconRect.translate( (option.rect.width() - iconRect.width()) / 2, 0);
+
+ painter->drawPixmap (iconRect, mPixmaps.at(index).second);
+}
+
+CSVWorld::DataDisplayDelegate::~DataDisplayDelegate()
+{
+ mIcons.clear();
+ mPixmaps.clear();
+}
+
+void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename)
+{
+ mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename)));
+ EnumDelegateFactory::add(enumValue, enumName);
+
+}
+
+CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+
+ return new DataDisplayDelegate (mValues, mIcons, undoStack, parent);
+}
diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp
new file mode 100755
index 0000000000..d23b86631c
--- /dev/null
+++ b/apps/opencs/view/world/datadisplaydelegate.hpp
@@ -0,0 +1,85 @@
+#ifndef DATADISPLAYDELEGATE_HPP
+#define DATADISPLAYDELEGATE_HPP
+
+#include <QTextOption>
+#include "enumdelegate.hpp"
+
+namespace CSVWorld
+{
+
+
+ class DataDisplayDelegate : public EnumDelegate
+ {
+ public:
+
+ typedef std::vector < std::pair < int, QIcon > > IconList;
+ typedef std::vector<std::pair<int, QString> > ValueList;
+
+ protected:
+
+ enum DisplayMode
+ {
+ Mode_TextOnly,
+ Mode_IconOnly,
+ Mode_IconAndText
+ };
+
+ DisplayMode mDisplayMode;
+ IconList mIcons;
+
+ private:
+
+ std::vector <std::pair <int, QPixmap> > mPixmaps;
+ QTextOption mTextAlignment;
+ QSize mIconSize;
+ int mIconLeftOffset;
+ int mTextLeftOffset;
+
+ public:
+ explicit DataDisplayDelegate (const ValueList & values,
+ const IconList & icons,
+ QUndoStack& undoStack, QObject *parent);
+
+ ~DataDisplayDelegate();
+
+ virtual void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+ /// pass a QSize defining height / width of icon. Default is QSize (16,16).
+ void setIconSize (const QSize icon);
+
+ /// offset the horizontal position of the icon from the left edge of the cell. Default is 3 pixels.
+ void setIconLeftOffset (int offset);
+
+ /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels.
+ void setTextLeftOffset (int offset);
+
+ private:
+
+ /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only.
+ void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const;
+
+ /// rebuild the list of pixmaps from the provided icons (called when icon size is changed)
+ void buildPixmaps();
+
+ };
+
+ class DataDisplayDelegateFactory : public EnumDelegateFactory
+ {
+ protected:
+
+ DataDisplayDelegate::IconList mIcons;
+
+ public:
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ protected:
+
+ void add (int enumValue,const QString enumName, const QString iconFilename);
+
+ };
+
+}
+
+#endif // DATADISPLAYDELEGATE_HPP
diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp
new file mode 100644
index 0000000000..cedb20de92
--- /dev/null
+++ b/apps/opencs/view/world/dialoguesubview.cpp
@@ -0,0 +1,98 @@
+
+#include "dialoguesubview.hpp"
+
+#include <QGridLayout>
+#include <QLabel>
+#include <QAbstractItemModel>
+#include <QDoubleSpinBox>
+#include <QSpinBox>
+#include <QLineEdit>
+#include <QDataWidgetMapper>
+
+#include "../../model/world/columnbase.hpp"
+#include "../../model/world/idtable.hpp"
+
+CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
+ bool createAndDelete)
+: SubView (id)
+{
+ QWidget *widget = new QWidget (this);
+
+ setWidget (widget);
+
+ QGridLayout *layout = new QGridLayout;
+
+ widget->setLayout (layout);
+
+ QAbstractItemModel *model = document.getData().getTableModel (id);
+
+ int columns = model->columnCount();
+
+ mWidgetMapper = new QDataWidgetMapper (this);
+ mWidgetMapper->setModel (model);
+
+ for (int i=0; i<columns; ++i)
+ {
+ int flags = model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt();
+
+ if (flags & CSMWorld::ColumnBase::Flag_Dialogue)
+ {
+ layout->addWidget (new QLabel (model->headerData (i, Qt::Horizontal).toString()), i, 0);
+
+ CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display>
+ (model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
+
+ QWidget *widget = 0;
+
+ if (model->flags (model->index (0, i)) & Qt::ItemIsEditable)
+ {
+ switch (display)
+ {
+ case CSMWorld::ColumnBase::Display_String:
+
+ layout->addWidget (widget = new QLineEdit, i, 1);
+ break;
+
+ case CSMWorld::ColumnBase::Display_Integer:
+
+ /// \todo configure widget properly (range)
+ layout->addWidget (widget = new QSpinBox, i, 1);
+ break;
+
+ case CSMWorld::ColumnBase::Display_Float:
+
+ /// \todo configure widget properly (range, format?)
+ layout->addWidget (widget = new QDoubleSpinBox, i, 1);
+ break;
+
+ default: break; // silence warnings for other times for now
+ }
+ }
+ else
+ {
+ switch (display)
+ {
+ case CSMWorld::ColumnBase::Display_String:
+ case CSMWorld::ColumnBase::Display_Integer:
+ case CSMWorld::ColumnBase::Display_Float:
+
+ layout->addWidget (widget = new QLabel, i, 1);
+ break;
+
+ default: break; // silence warnings for other times for now
+ }
+ }
+
+ if (widget)
+ mWidgetMapper->addMapping (widget, i);
+ }
+ }
+
+ mWidgetMapper->setCurrentModelIndex (
+ dynamic_cast<CSMWorld::IdTable&> (*model).getModelIndex (id.getId(), 0));
+}
+
+void CSVWorld::DialogueSubView::setEditLock (bool locked)
+{
+
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp
new file mode 100644
index 0000000000..64715f5b74
--- /dev/null
+++ b/apps/opencs/view/world/dialoguesubview.hpp
@@ -0,0 +1,27 @@
+#ifndef CSV_WORLD_DIALOGUESUBVIEW_H
+#define CSV_WORLD_DIALOGUESUBVIEW_H
+
+#include "../doc/subview.hpp"
+
+class QDataWidgetMapper;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSVWorld
+{
+ class DialogueSubView : public CSVDoc::SubView
+ {
+ QDataWidgetMapper *mWidgetMapper;
+
+ public:
+
+ DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete);
+
+ virtual void setEditLock (bool locked);
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp
new file mode 100644
index 0000000000..dd194abe9a
--- /dev/null
+++ b/apps/opencs/view/world/enumdelegate.cpp
@@ -0,0 +1,121 @@
+
+#include "enumdelegate.hpp"
+
+#include <cassert>
+#include <stdexcept>
+
+#include <QComboBox>
+#include <QApplication>
+#include <QUndoStack>
+
+#include "../../model/world/commands.hpp"
+
+void CSVWorld::EnumDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const
+{
+ if (QComboBox *comboBox = dynamic_cast<QComboBox *> (editor))
+ {
+ QString value = comboBox->currentText();
+
+ for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
+ iter!=mValues.end(); ++iter)
+ if (iter->second==value)
+ {
+ addCommands (model, index, iter->first);
+ break;
+ }
+ }
+}
+
+void CSVWorld::EnumDelegate::addCommands (QAbstractItemModel *model,
+ const QModelIndex& index, int type) const
+{
+ getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type));
+}
+
+
+CSVWorld::EnumDelegate::EnumDelegate (const std::vector<std::pair<int, QString> >& values,
+ QUndoStack& undoStack, QObject *parent)
+: CommandDelegate (undoStack, parent), mValues (values)
+{
+
+}
+
+QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const
+{
+ if (!index.data().isValid())
+ return 0;
+
+ QComboBox *comboBox = new QComboBox (parent);
+
+ for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
+ iter!=mValues.end(); ++iter)
+ comboBox->addItem (iter->second);
+
+ return comboBox;
+}
+
+void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index) const
+{
+ if (QComboBox *comboBox = dynamic_cast<QComboBox *> (editor))
+ {
+ int value = index.data (Qt::EditRole).toInt();
+
+ std::size_t size = mValues.size();
+
+ for (std::size_t i=0; i<size; ++i)
+ if (mValues[i].first==value)
+ {
+ comboBox->setCurrentIndex (i);
+ break;
+ }
+ }
+}
+
+void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const
+{
+ if (index.data().isValid())
+ {
+ QStyleOptionViewItemV4 option2 (option);
+
+ int value = index.data().toInt();
+
+ for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
+ iter!=mValues.end(); ++iter)
+ if (iter->first==value)
+ {
+ option2.text = iter->second;
+
+ QApplication::style()->drawControl (QStyle::CE_ItemViewItem, &option2, painter);
+
+ break;
+ }
+ }
+}
+
+
+CSVWorld::EnumDelegateFactory::EnumDelegateFactory() {}
+
+CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const char **names, bool allowNone)
+{
+ assert (names);
+
+ if (allowNone)
+ add (-1, "");
+
+ for (int i=0; names[i]; ++i)
+ add (i, names[i]);
+}
+
+CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+ return new EnumDelegate (mValues, undoStack, parent);
+}
+
+void CSVWorld::EnumDelegateFactory::add (int value, const QString& name)
+{
+ mValues.push_back (std::make_pair (value, name));
+}
diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp
new file mode 100644
index 0000000000..b79516a09b
--- /dev/null
+++ b/apps/opencs/view/world/enumdelegate.hpp
@@ -0,0 +1,66 @@
+#ifndef CSV_WORLD_ENUMDELEGATE_H
+#define CSV_WORLD_ENUMDELEGATE_H
+
+#include <vector>
+
+#include <QString>
+
+#include <components/esm/defs.hpp>
+
+#include "util.hpp"
+
+namespace CSVWorld
+{
+ /// \brief Integer value that represents an enum and is interacted with via a combobox
+ class EnumDelegate : public CommandDelegate
+ {
+ protected:
+
+ std::vector<std::pair<int, QString> > mValues;
+
+ private:
+
+ virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const;
+
+ virtual void addCommands (QAbstractItemModel *model,
+ const QModelIndex& index, int type) const;
+
+ public:
+
+ EnumDelegate (const std::vector<std::pair<int, QString> >& values,
+ QUndoStack& undoStack, QObject *parent);
+
+ virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const;
+
+ virtual void setEditorData (QWidget *editor, const QModelIndex& index) const;
+
+ virtual void paint (QPainter *painter, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const;
+
+ };
+
+ class EnumDelegateFactory : public CommandDelegateFactory
+ {
+ protected:
+ std::vector<std::pair<int, QString> > mValues;
+
+ public:
+
+ EnumDelegateFactory();
+
+ EnumDelegateFactory (const char **names, bool allowNone = false);
+ ///< \param names Array of char pointer with a 0-pointer as end mark
+ /// \param allowNone Use value of -1 for "none selected" (empty string)
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ void add (int value, const QString& name);
+ };
+
+
+}
+
+#endif
diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp
new file mode 100644
index 0000000000..df43d6c5f5
--- /dev/null
+++ b/apps/opencs/view/world/genericcreator.cpp
@@ -0,0 +1,135 @@
+
+#include "genericcreator.hpp"
+
+#include <memory>
+
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QLineEdit>
+#include <QUndoStack>
+
+#include "../../model/world/commands.hpp"
+#include "../../model/world/data.hpp"
+#include "../../model/world/idtable.hpp"
+
+#include "idvalidator.hpp"
+
+void CSVWorld::GenericCreator::update()
+{
+ mErrors = getErrors();
+
+ mCreate->setToolTip (QString::fromUtf8 (mErrors.c_str()));
+ mId->setToolTip (QString::fromUtf8 (mErrors.c_str()));
+
+ mCreate->setEnabled (mErrors.empty() && !mLocked);
+}
+
+void CSVWorld::GenericCreator::setManualEditing (bool enabled)
+{
+ mId->setVisible (enabled);
+}
+
+void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretched)
+{
+ mLayout->insertWidget (0, widget, stretched ? 1 : 0);
+}
+
+void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched)
+{
+ mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0);
+}
+
+std::string CSVWorld::GenericCreator::getId() const
+{
+ return mId->text().toUtf8().constData();
+}
+
+void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {}
+
+CSMWorld::Data& CSVWorld::GenericCreator::getData() const
+{
+ return mData;
+}
+
+const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const
+{
+ return mListId;
+}
+
+CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: 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));
+ mLayout->addWidget (mId, 1);
+
+ mCreate = new QPushButton ("Create");
+ mLayout->addWidget (mCreate);
+
+ QPushButton *cancelButton = new QPushButton ("Cancel");
+ mLayout->addWidget (cancelButton);
+
+ setLayout (mLayout);
+
+ connect (cancelButton, SIGNAL (clicked (bool)), this, SIGNAL (done()));
+ connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create()));
+
+ connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&)));
+}
+
+void CSVWorld::GenericCreator::setEditLock (bool locked)
+{
+ mLocked = locked;
+ update();
+}
+
+void CSVWorld::GenericCreator::reset()
+{
+ mId->setText ("");
+ update();
+}
+
+std::string CSVWorld::GenericCreator::getErrors() const
+{
+ std::string errors;
+
+ std::string id = getId();
+
+ if (id.empty())
+ {
+ errors = "Missing ID";
+ }
+ else if (mData.hasId (id))
+ {
+ errors = "ID is already in use";
+ }
+
+ return errors;
+}
+
+void CSVWorld::GenericCreator::textChanged (const QString& text)
+{
+ update();
+}
+
+void CSVWorld::GenericCreator::create()
+{
+ if (!mLocked)
+ {
+ std::string id = getId();
+
+ std::auto_ptr<CSMWorld::CreateCommand> command (new CSMWorld::CreateCommand (
+ dynamic_cast<CSMWorld::IdTable&> (*mData.getTableModel (mListId)), id));
+
+ configureCreateCommand (*command);
+
+ mUndoStack.push (command.release());
+
+ emit done();
+ emit requestFocus (id);
+ }
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp
new file mode 100644
index 0000000000..6752d8591f
--- /dev/null
+++ b/apps/opencs/view/world/genericcreator.hpp
@@ -0,0 +1,73 @@
+#ifndef CSV_WORLD_GENERICCREATOR_H
+#define CSV_WORLD_GENERICCREATOR_H
+
+class QPushButton;
+class QLineEdit;
+class QHBoxLayout;
+
+#include "creator.hpp"
+
+#include "../../model/world/universalid.hpp"
+
+namespace CSMWorld
+{
+ class CreateCommand;
+}
+
+namespace CSVWorld
+{
+ class GenericCreator : public Creator
+ {
+ Q_OBJECT
+
+ CSMWorld::Data& mData;
+ QUndoStack& mUndoStack;
+ CSMWorld::UniversalId mListId;
+ QPushButton *mCreate;
+ QLineEdit *mId;
+ std::string mErrors;
+ QHBoxLayout *mLayout;
+ bool mLocked;
+
+ protected:
+
+ void update();
+
+ virtual void setManualEditing (bool enabled);
+ ///< Enable/disable manual ID editing (enabled by default).
+
+ void insertAtBeginning (QWidget *widget, bool stretched);
+
+ void insertBeforeButtons (QWidget *widget, bool stretched);
+
+ virtual std::string getId() const;
+
+ virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const;
+
+ CSMWorld::Data& getData() const;
+
+ const CSMWorld::UniversalId& getCollectionId() const;
+
+ public:
+
+ GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id);
+
+ virtual void setEditLock (bool locked);
+
+ 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 textChanged (const QString& text);
+
+ void create();
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp
new file mode 100644
index 0000000000..cf6e5d77ba
--- /dev/null
+++ b/apps/opencs/view/world/idvalidator.cpp
@@ -0,0 +1,26 @@
+
+#include "idvalidator.hpp"
+
+bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const
+{
+ if (c.isLetter() || c=='_')
+ return true;
+
+ if (!first && (c.isDigit() || c.isSpace()))
+ return true;
+
+ return false;
+}
+
+CSVWorld::IdValidator::IdValidator (QObject *parent) : QValidator (parent) {}
+
+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))
+ 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
new file mode 100644
index 0000000000..db0ecb27a7
--- /dev/null
+++ b/apps/opencs/view/world/idvalidator.hpp
@@ -0,0 +1,23 @@
+#ifndef CSV_WORLD_IDVALIDATOR_H
+#define CSV_WORLD_IDVALIDATOR_H
+
+#include <QValidator>
+
+namespace CSVWorld
+{
+ class IdValidator : public QValidator
+ {
+ private:
+
+ bool isValid (const QChar& c, bool first) const;
+
+ public:
+
+ IdValidator (QObject *parent = 0);
+
+ virtual State validate (QString& input, int& pos) const;
+
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp
new file mode 100644
index 0000000000..a0ffd3063d
--- /dev/null
+++ b/apps/opencs/view/world/recordstatusdelegate.cpp
@@ -0,0 +1,45 @@
+#include "recordstatusdelegate.hpp"
+#include <QPainter>
+#include <QApplication>
+#include <QUndoStack>
+#include "../../model/settings/usersettings.hpp"
+
+CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values,
+ const IconList & icons,
+ QUndoStack &undoStack, QObject *parent)
+ : DataDisplayDelegate (values, icons, undoStack, parent)
+{}
+
+CSVWorld::CommandDelegate *CSVWorld::RecordStatusDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+ return new RecordStatusDelegate (mValues, mIcons, undoStack, parent);
+}
+
+bool CSVWorld::RecordStatusDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+ if (settingName == "Record Status Display")
+ {
+ if (settingValue == "Icon and Text")
+ mDisplayMode = Mode_IconAndText;
+
+ else if (settingValue == "Icon Only")
+ mDisplayMode = Mode_IconOnly;
+
+ else if (settingValue == "Text Only")
+ mDisplayMode = Mode_TextOnly;
+
+ return true;
+ }
+
+ return false;
+}
+
+CSVWorld::RecordStatusDelegateFactory::RecordStatusDelegateFactory()
+{
+ DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_BaseOnly, "Base", ":./base.png");
+ DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Deleted, "Deleted", ":./removed.png");
+ DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Erased, "Deleted", ":./removed.png");
+ DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_Modified, "Modified", ":./modified.png");
+ DataDisplayDelegateFactory::add ( CSMWorld::RecordBase::State_ModifiedOnly, "Added", ":./added.png");
+}
diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp
new file mode 100644
index 0000000000..d9126fee04
--- /dev/null
+++ b/apps/opencs/view/world/recordstatusdelegate.hpp
@@ -0,0 +1,40 @@
+#ifndef RECORDSTATUSDELEGATE_H
+#define RECORDSTATUSDELEGATE_H
+
+#include "util.hpp"
+#include <QTextOption>
+#include <QFont>
+
+#include "datadisplaydelegate.hpp"
+#include "../../model/world/record.hpp"
+
+class QIcon;
+class QFont;
+
+namespace CSVWorld
+{
+ class RecordStatusDelegate : public DataDisplayDelegate
+ {
+ public:
+
+ explicit RecordStatusDelegate(const ValueList& values,
+ const IconList& icons,
+ QUndoStack& undoStack, QObject *parent = 0);
+
+ virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ };
+
+ class RecordStatusDelegateFactory : public DataDisplayDelegateFactory
+ {
+ public:
+
+ RecordStatusDelegateFactory();
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ };
+}
+#endif // RECORDSTATUSDELEGATE_HPP
+
diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp
new file mode 100644
index 0000000000..718fe9ca7c
--- /dev/null
+++ b/apps/opencs/view/world/referenceablecreator.cpp
@@ -0,0 +1,43 @@
+
+#include "referenceablecreator.hpp"
+
+#include <QComboBox>
+#include <QLabel>
+
+#include "../../model/world/universalid.hpp"
+#include "../../model/world/commands.hpp"
+
+void CSVWorld::ReferenceableCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
+{
+ command.setType (
+ static_cast<CSMWorld::UniversalId::Type> (mType->itemData (mType->currentIndex()).toInt()));
+}
+
+CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: GenericCreator (data, undoStack, id)
+{
+ QLabel *label = new QLabel ("Type", this);
+ insertBeforeButtons (label, false);
+
+ std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
+
+ mType = new QComboBox (this);
+
+ for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
+ iter!=types.end(); ++iter)
+ {
+ CSMWorld::UniversalId id (*iter, "");
+
+ mType->addItem (QIcon (id.getIcon().c_str()), id.getTypeName().c_str(),
+ static_cast<int> (id.getType()));
+ }
+
+ insertBeforeButtons (mType, false);
+}
+
+void CSVWorld::ReferenceableCreator::reset()
+{
+ mType->setCurrentIndex (0);
+ GenericCreator::reset();
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp
new file mode 100644
index 0000000000..06e0e582be
--- /dev/null
+++ b/apps/opencs/view/world/referenceablecreator.hpp
@@ -0,0 +1,30 @@
+#ifndef CSV_WORLD_REFERENCEABLECREATOR_H
+#define CSV_WORLD_REFERENCEABLECREATOR_H
+
+class QComboBox;
+
+#include "genericcreator.hpp"
+
+namespace CSVWorld
+{
+ class ReferenceableCreator : public GenericCreator
+ {
+ Q_OBJECT
+
+ QComboBox *mType;
+
+ private:
+
+ virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const;
+
+ public:
+
+ ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id);
+
+ virtual void reset();
+
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp
new file mode 100644
index 0000000000..aef25a81d0
--- /dev/null
+++ b/apps/opencs/view/world/referencecreator.cpp
@@ -0,0 +1,75 @@
+
+#include "referencecreator.hpp"
+
+#include <QLabel>
+#include <QLineEdit>
+
+#include "../../model/world/data.hpp"
+#include "../../model/world/commands.hpp"
+#include "../../model/world/columns.hpp"
+#include "../../model/world/idtable.hpp"
+
+std::string CSVWorld::ReferenceCreator::getId() const
+{
+ return mId;
+}
+
+void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
+{
+ int index =
+ dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId())).
+ findColumnIndex (CSMWorld::Columns::ColumnId_Cell);
+
+ command.addValue (index, mCell->text());
+}
+
+CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack,
+ const CSMWorld::UniversalId& id)
+: GenericCreator (data, undoStack, id)
+{
+ QLabel *label = new QLabel ("Cell", this);
+ insertBeforeButtons (label, false);
+
+ mCell = new QLineEdit (this);
+ insertBeforeButtons (mCell, true);
+
+ setManualEditing (false);
+
+ connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged()));
+}
+
+void CSVWorld::ReferenceCreator::reset()
+{
+ mCell->setText ("");
+ mId = getData().getReferences().getNewId();
+ GenericCreator::reset();
+}
+
+std::string CSVWorld::ReferenceCreator::getErrors() const
+{
+ std::string errors = GenericCreator::getErrors();
+
+ std::string cell = mCell->text().toUtf8().constData();
+
+ if (cell.empty())
+ {
+ if (!errors.empty())
+ errors += "<br>";
+
+ errors += "Missing Cell ID";
+ }
+ else if (getData().getCells().searchId (cell)==-1)
+ {
+ if (!errors.empty())
+ errors += "<br>";
+
+ errors += "Invalid Cell ID";
+ }
+
+ return errors;
+}
+
+void CSVWorld::ReferenceCreator::cellChanged()
+{
+ update();
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp
new file mode 100644
index 0000000000..27f81564fd
--- /dev/null
+++ b/apps/opencs/view/world/referencecreator.hpp
@@ -0,0 +1,40 @@
+#ifndef CSV_WORLD_REFERENCECREATOR_H
+#define CSV_WORLD_REFERENCECREATOR_H
+
+#include "genericcreator.hpp"
+
+class QLineEdit;
+
+namespace CSVWorld
+{
+ class ReferenceCreator : public GenericCreator
+ {
+ Q_OBJECT
+
+ QLineEdit *mCell;
+ std::string mId;
+
+ private:
+
+ virtual std::string getId() const;
+
+ virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const;
+
+ public:
+
+ ReferenceCreator (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 cellChanged();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/refidtypedelegate.cpp b/apps/opencs/view/world/refidtypedelegate.cpp
new file mode 100755
index 0000000000..bf3acbb206
--- /dev/null
+++ b/apps/opencs/view/world/refidtypedelegate.cpp
@@ -0,0 +1,60 @@
+#include "refidtypedelegate.hpp"
+#include "../../model/world/universalid.hpp"
+
+CSVWorld::RefIdTypeDelegate::RefIdTypeDelegate
+ (const ValueList &values, const IconList &icons, QUndoStack& undoStack, QObject *parent)
+ : DataDisplayDelegate (values, icons, undoStack, parent)
+{}
+
+CSVWorld::RefIdTypeDelegateFactory::RefIdTypeDelegateFactory()
+{
+ UidTypeList uIdList = buildUidTypeList();
+
+ for (UidTypeList::const_iterator it = uIdList.begin(); it != uIdList.end(); it++)
+ {
+ int i = it->first;
+ DataDisplayDelegateFactory::add (i, QString::fromStdString(CSMWorld::UniversalId(it->first, "").getTypeName()), it->second);
+ }
+}
+
+CSVWorld::CommandDelegate *CSVWorld::RefIdTypeDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+ return new RefIdTypeDelegate (mValues, mIcons, undoStack, parent);
+}
+
+CSVWorld::RefIdTypeDelegateFactory::UidTypeList CSVWorld::RefIdTypeDelegateFactory::buildUidTypeList() const
+{
+ UidTypeList list;
+
+ std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
+
+ for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
+ iter!=types.end(); ++iter)
+ {
+ CSMWorld::UniversalId id (*iter, "");
+
+ list.push_back (std::make_pair (id.getType(), id.getIcon().c_str()));
+ }
+
+ return list;
+}
+
+bool CSVWorld::RefIdTypeDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+ if (settingName == "Referenceable ID Type Display")
+ {
+ if (settingValue == "Icon and Text")
+ mDisplayMode = Mode_IconAndText;
+
+ else if (settingValue == "Icon Only")
+ mDisplayMode = Mode_IconOnly;
+
+ else if (settingValue == "Text Only")
+ mDisplayMode = Mode_TextOnly;
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/apps/opencs/view/world/refidtypedelegate.hpp b/apps/opencs/view/world/refidtypedelegate.hpp
new file mode 100755
index 0000000000..384aebb98d
--- /dev/null
+++ b/apps/opencs/view/world/refidtypedelegate.hpp
@@ -0,0 +1,37 @@
+#ifndef REFIDTYPEDELEGATE_HPP
+#define REFIDTYPEDELEGATE_HPP
+
+#include "enumdelegate.hpp"
+#include "util.hpp"
+#include "../../model/world/universalid.hpp"
+#include "datadisplaydelegate.hpp"
+
+namespace CSVWorld
+{
+ class RefIdTypeDelegate : public DataDisplayDelegate
+ {
+ public:
+ RefIdTypeDelegate (const ValueList &mValues, const IconList &icons, QUndoStack& undoStack, QObject *parent);
+
+ virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ };
+
+ class RefIdTypeDelegateFactory : public DataDisplayDelegateFactory
+ {
+
+ typedef std::vector < std::pair <CSMWorld::UniversalId::Type, QString> > UidTypeList;
+
+ public:
+ RefIdTypeDelegateFactory();
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ private:
+ UidTypeList buildUidTypeList () const;
+
+ };
+}
+
+#endif // REFIDTYPEDELEGATE_HPP
diff --git a/apps/opencs/view/world/refrecordtypedelegate.cpp b/apps/opencs/view/world/refrecordtypedelegate.cpp
new file mode 100644
index 0000000000..2bcb7ca50e
--- /dev/null
+++ b/apps/opencs/view/world/refrecordtypedelegate.cpp
@@ -0,0 +1,25 @@
+#include "refrecordtypedelegate.hpp"
+#include "../../model/world/universalid.hpp"
+
+CSVWorld::RefRecordTypeDelegate::RefRecordTypeDelegate
+ (const std::vector<std::pair<int, QString> > &values, QUndoStack& undoStack, QObject *parent)
+ : EnumDelegate (values, undoStack, parent)
+{}
+
+CSVWorld::RefRecordTypeDelegateFactory::RefRecordTypeDelegateFactory()
+{
+ unsigned int argSize = CSMWorld::UniversalId::getIdArgSize();
+
+ for (unsigned int i = 0; i < argSize; i++)
+ {
+ std::pair<int, const char *> idPair = CSMWorld::UniversalId::getIdArgPair(i);
+
+ mValues.push_back (std::pair<int, QString>(idPair.first, QString::fromUtf8(idPair.second)));
+ }
+}
+
+CSVWorld::CommandDelegate *CSVWorld::RefRecordTypeDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+ return new RefRecordTypeDelegate (mValues, undoStack, parent);
+}
diff --git a/apps/opencs/view/world/refrecordtypedelegate.hpp b/apps/opencs/view/world/refrecordtypedelegate.hpp
new file mode 100644
index 0000000000..baec2cc2e9
--- /dev/null
+++ b/apps/opencs/view/world/refrecordtypedelegate.hpp
@@ -0,0 +1,58 @@
+#ifndef REFRECORDTYPEDELEGATE_HPP
+#define REFRECORDTYPEDELEGATE_HPP
+
+#include "enumdelegate.hpp"
+#include "util.hpp"
+
+namespace CSVWorld
+{
+ class RefRecordTypeDelegate : public EnumDelegate
+ {
+ public:
+ RefRecordTypeDelegate (const std::vector<std::pair<int, QString> > &mValues, QUndoStack& undoStack, QObject *parent);
+ };
+
+ class RefRecordTypeDelegateFactory : public CommandDelegateFactory
+ {
+
+ std::vector<std::pair<int, QString> > mValues;
+
+ public:
+ RefRecordTypeDelegateFactory();
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+ };
+}
+/*
+ class VarTypeDelegate : public EnumDelegate
+ {
+ private:
+
+ virtual void addCommands (QAbstractItemModel *model,
+ const QModelIndex& index, int type) const;
+
+ public:
+
+ VarTypeDelegate (const std::vector<std::pair<int, QString> >& values,
+ QUndoStack& undoStack, QObject *parent);
+ };
+
+ class VarTypeDelegateFactory : public CommandDelegateFactory
+ {
+ std::vector<std::pair<int, QString> > mValues;
+
+ public:
+
+ VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown,
+ ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown,
+ ESM::VarType type3 = ESM::VT_Unknown);
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ void add (ESM::VarType type);
+ };
+*/
+
+#endif // REFRECORDTYPEDELEGATE_HPP
diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp
new file mode 100644
index 0000000000..b82c1afb54
--- /dev/null
+++ b/apps/opencs/view/world/regionmapsubview.cpp
@@ -0,0 +1,29 @@
+
+#include "regionmapsubview.hpp"
+
+#include <QTableView>
+#include <QHeaderView>
+
+CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId,
+ CSMDoc::Document& document)
+: CSVDoc::SubView (universalId)
+{
+ mTable = new QTableView (this);
+
+ mTable->verticalHeader()->hide();
+ mTable->horizontalHeader()->hide();
+
+ mTable->setSelectionMode (QAbstractItemView::ExtendedSelection);
+
+ mTable->setModel (document.getData().getTableModel (universalId));
+
+ mTable->resizeColumnsToContents();
+ mTable->resizeRowsToContents();
+
+ setWidget (mTable);
+}
+
+void CSVWorld::RegionMapSubView::setEditLock (bool locked)
+{
+
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp
new file mode 100644
index 0000000000..1655107af3
--- /dev/null
+++ b/apps/opencs/view/world/regionmapsubview.hpp
@@ -0,0 +1,27 @@
+#ifndef CSV_WORLD_REGIONMAPSUBVIEW_H
+#define CSV_WORLD_REGIONMAPSUBVIEW_H
+
+#include "../doc/subview.hpp"
+
+class QTableView;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSVWorld
+{
+ class RegionMapSubView : public CSVDoc::SubView
+ {
+ QTableView *mTable;
+
+ public:
+
+ RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document);
+
+ virtual void setEditLock (bool locked);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp
new file mode 100644
index 0000000000..288a3d12ac
--- /dev/null
+++ b/apps/opencs/view/world/scripthighlighter.cpp
@@ -0,0 +1,118 @@
+
+#include "scripthighlighter.hpp"
+
+#include <sstream>
+
+#include <components/compiler/scanner.hpp>
+
+bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Int);
+ return true;
+}
+
+bool CSVWorld::ScriptHighlighter::parseFloat (float value, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Float);
+ return true;
+}
+
+bool CSVWorld::ScriptHighlighter::parseName (const std::string& name, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Name);
+ return true;
+}
+
+bool CSVWorld::ScriptHighlighter::parseKeyword (int keyword, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Keyword);
+ return true;
+}
+
+bool CSVWorld::ScriptHighlighter::parseSpecial (int code, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Special);
+ return true;
+}
+
+bool CSVWorld::ScriptHighlighter::parseComment (const std::string& comment,
+ const Compiler::TokenLoc& loc, Compiler::Scanner& scanner)
+{
+ highlight (loc, Type_Comment);
+ return true;
+}
+
+void CSVWorld::ScriptHighlighter::parseEOF (Compiler::Scanner& scanner)
+{}
+
+void CSVWorld::ScriptHighlighter::highlight (const Compiler::TokenLoc& loc, Type type)
+{
+ int length = static_cast<int> (loc.mLiteral.size());
+
+ int index = loc.mColumn;
+
+ // compensate for bug in Compiler::Scanner (position of token is the character after the token)
+ index -= length;
+
+ setFormat (index, length, mScheme[type]);
+}
+
+CSVWorld::ScriptHighlighter::ScriptHighlighter (QTextDocument *parent)
+: QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext)
+{
+ /// \ŧodo replace this with user settings
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::darkMagenta);
+ mScheme.insert (std::make_pair (Type_Int, format));
+ }
+
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::magenta);
+ mScheme.insert (std::make_pair (Type_Float, format));
+ }
+
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::gray);
+ mScheme.insert (std::make_pair (Type_Name, format));
+ }
+
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::red);
+ mScheme.insert (std::make_pair (Type_Keyword, format));
+ }
+
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::darkYellow);
+ mScheme.insert (std::make_pair (Type_Special, format));
+ }
+
+ {
+ QTextCharFormat format;
+ format.setForeground (Qt::green);
+ mScheme.insert (std::make_pair (Type_Comment, format));
+ }
+}
+
+void CSVWorld::ScriptHighlighter::highlightBlock (const QString& text)
+{
+ std::istringstream stream (text.toUtf8().constData());
+
+ Compiler::Scanner scanner (mErrorHandler, stream, mContext.getExtensions());
+
+ try
+ {
+ scanner.scan (*this);
+ }
+ catch (...) {} // ignore syntax errors
+
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp
new file mode 100644
index 0000000000..3ef6978097
--- /dev/null
+++ b/apps/opencs/view/world/scripthighlighter.hpp
@@ -0,0 +1,80 @@
+#ifndef CSV_WORLD_SCRIPTHIGHLIGHTER_H
+#define CSV_WORLD_SCRIPTHIGHLIGHTER_H
+
+#include <map>
+
+#include <QSyntaxHighlighter>
+
+#include <components/compiler/nullerrorhandler.hpp>
+#include <components/compiler/parser.hpp>
+
+#include "../../model/world/scriptcontext.hpp"
+
+namespace CSVWorld
+{
+ class ScriptHighlighter : public QSyntaxHighlighter, private Compiler::Parser
+ {
+ public:
+
+ enum Type
+ {
+ Type_Int,
+ Type_Float,
+ Type_Name,
+ Type_Keyword,
+ Type_Special,
+ Type_Comment
+ };
+
+ private:
+
+ Compiler::NullErrorHandler mErrorHandler;
+ CSMWorld::ScriptContext mContext;
+ std::map<Type, QTextCharFormat> mScheme;
+
+ private:
+
+ virtual bool parseInt (int value, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner);
+ ///< Handle an int token.
+ /// \return fetch another token?
+
+ virtual bool parseFloat (float value, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner);
+ ///< Handle a float token.
+ /// \return fetch another token?
+
+ virtual bool parseName (const std::string& name,
+ const Compiler::TokenLoc& loc, Compiler::Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ virtual bool parseComment (const std::string& comment, const Compiler::TokenLoc& loc,
+ Compiler::Scanner& scanner);
+ ///< Handle comment token.
+ /// \return fetch another token?
+
+ virtual void parseEOF (Compiler::Scanner& scanner);
+ ///< Handle EOF token.
+
+ void highlight (const Compiler::TokenLoc& loc, Type type);
+
+ public:
+
+ ScriptHighlighter (QTextDocument *parent);
+
+ virtual void highlightBlock (const QString& text);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp
new file mode 100644
index 0000000000..ab1c2d57c6
--- /dev/null
+++ b/apps/opencs/view/world/scriptsubview.cpp
@@ -0,0 +1,99 @@
+
+#include "scriptsubview.hpp"
+
+#include <stdexcept>
+
+#include <QTextEdit>
+
+#include "../../model/doc/document.hpp"
+#include "../../model/world/universalid.hpp"
+#include "../../model/world/data.hpp"
+#include "../../model/world/columnbase.hpp"
+#include "../../model/world/commands.hpp"
+#include "../../model/world/idtable.hpp"
+
+#include "scripthighlighter.hpp"
+
+CSVWorld::ScriptSubView::ChangeLock::ChangeLock (ScriptSubView& view) : mView (view)
+{
+ ++mView.mChangeLocked;
+}
+
+CSVWorld::ScriptSubView::ChangeLock::~ChangeLock()
+{
+ --mView.mChangeLocked;
+}
+
+CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
+: SubView (id), mDocument (document), mColumn (-1), mChangeLocked (0)
+{
+ setWidget (mEditor = new QTextEdit (this));
+
+ mEditor->setAcceptRichText (false);
+ mEditor->setLineWrapMode (QTextEdit::NoWrap);
+ mEditor->setTabStopWidth (4);
+ mEditor->setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead
+
+ mModel = &dynamic_cast<CSMWorld::IdTable&> (
+ *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts));
+
+ for (int i=0; i<mModel->columnCount(); ++i)
+ if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)==
+ CSMWorld::ColumnBase::Display_Script)
+ {
+ mColumn = i;
+ break;
+ }
+
+ if (mColumn==-1)
+ throw std::logic_error ("Can't find script column");
+
+ mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString());
+
+ connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged()));
+
+ connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
+ this, SLOT (dataChanged (const QModelIndex&, const QModelIndex&)));
+
+ connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
+ this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int)));
+
+ new ScriptHighlighter (mEditor->document());
+}
+
+void CSVWorld::ScriptSubView::setEditLock (bool locked)
+{
+ mEditor->setReadOnly (locked);
+}
+
+void CSVWorld::ScriptSubView::textChanged()
+{
+ ChangeLock lock (*this);
+
+ mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel,
+ mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText()));
+}
+
+void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
+{
+ if (mChangeLocked)
+ return;
+
+ QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
+
+ if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() &&
+ index.column()>=topLeft.column() && index.column()<=bottomRight.column())
+ {
+ QTextCursor cursor = mEditor->textCursor();
+ mEditor->setPlainText (mModel->data (index).toString());
+ mEditor->setTextCursor (cursor);
+ }
+}
+
+void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end)
+{
+ QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn);
+
+ if (!parent.isValid() && index.row()>=start && index.row()<=end)
+ deleteLater();
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp
new file mode 100644
index 0000000000..07d87d9476
--- /dev/null
+++ b/apps/opencs/view/world/scriptsubview.hpp
@@ -0,0 +1,62 @@
+#ifndef CSV_WORLD_SCRIPTSUBVIEW_H
+#define CSV_WORLD_SCRIPTSUBVIEW_H
+
+#include "../doc/subview.hpp"
+
+class QTextEdit;
+class QModelIndex;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSMWorld
+{
+ class IdTable;
+}
+
+namespace CSVWorld
+{
+ class ScriptSubView : public CSVDoc::SubView
+ {
+ Q_OBJECT
+
+ QTextEdit *mEditor;
+ CSMDoc::Document& mDocument;
+ CSMWorld::IdTable *mModel;
+ int mColumn;
+ int mChangeLocked;
+
+ class ChangeLock
+ {
+ ScriptSubView& mView;
+
+ ChangeLock (const ChangeLock&);
+ ChangeLock& operator= (const ChangeLock&);
+
+ public:
+
+ ChangeLock (ScriptSubView& view);
+ ~ChangeLock();
+ };
+
+ friend class ChangeLock;
+
+ public:
+
+ ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document);
+
+ virtual void setEditLock (bool locked);
+
+ private slots:
+
+ void textChanged();
+
+ void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
+
+ void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end);
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp
new file mode 100644
index 0000000000..d22e07d89c
--- /dev/null
+++ b/apps/opencs/view/world/subviews.cpp
@@ -0,0 +1,65 @@
+
+#include "subviews.hpp"
+
+#include "../doc/subviewfactoryimp.hpp"
+
+#include "../filter/filtercreator.hpp"
+
+#include "tablesubview.hpp"
+#include "dialoguesubview.hpp"
+#include "scriptsubview.hpp"
+#include "regionmapsubview.hpp"
+#include "genericcreator.hpp"
+#include "cellcreator.hpp"
+#include "referenceablecreator.hpp"
+#include "referencecreator.hpp"
+
+void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
+{
+ // Regular record tables (including references which are actually sub-records, but are promoted
+ // to top-level records within the editor)
+ manager.add (CSMWorld::UniversalId::Type_Gmsts,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, NullCreatorFactory>);
+
+ manager.add (CSMWorld::UniversalId::Type_Skills,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, NullCreatorFactory>);
+
+ static const CSMWorld::UniversalId::Type sTableTypes[] =
+ {
+ CSMWorld::UniversalId::Type_Globals,
+ CSMWorld::UniversalId::Type_Classes,
+ CSMWorld::UniversalId::Type_Factions,
+ CSMWorld::UniversalId::Type_Races,
+ CSMWorld::UniversalId::Type_Sounds,
+ CSMWorld::UniversalId::Type_Scripts,
+ CSMWorld::UniversalId::Type_Regions,
+ CSMWorld::UniversalId::Type_Birthsigns,
+ CSMWorld::UniversalId::Type_Spells,
+
+ CSMWorld::UniversalId::Type_None // end marker
+ };
+
+ for (int i=0; sTableTypes[i]!=CSMWorld::UniversalId::Type_None; ++i)
+ manager.add (sTableTypes[i],
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<GenericCreator> >);
+
+ manager.add (CSMWorld::UniversalId::Type_Cells,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<CellCreator> >);
+
+ manager.add (CSMWorld::UniversalId::Type_Referenceables,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<ReferenceableCreator> >);
+
+ manager.add (CSMWorld::UniversalId::Type_References,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<ReferenceCreator> >);
+
+ // Subviews for editing/viewing individual records
+ manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory<ScriptSubView>);
+
+ // Other stuff (combined record tables)
+ manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory<RegionMapSubView>);
+
+ manager.add (CSMWorld::UniversalId::Type_Filters,
+ new CSVDoc::SubViewFactoryWithCreator<TableSubView,
+ CreatorFactory<CSVFilter::FilterCreator> >);
+
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/subviews.hpp b/apps/opencs/view/world/subviews.hpp
new file mode 100644
index 0000000000..51e4cb0830
--- /dev/null
+++ b/apps/opencs/view/world/subviews.hpp
@@ -0,0 +1,14 @@
+#ifndef CSV_WORLD_SUBVIEWS_H
+#define CSV_WORLD_SUBVIEWS_H
+
+namespace CSVDoc
+{
+ class SubViewFactoryManager;
+}
+
+namespace CSVWorld
+{
+ void addSubViewFactories (CSVDoc::SubViewFactoryManager& manager);
+}
+
+#endif
diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp
new file mode 100644
index 0000000000..72e78c738e
--- /dev/null
+++ b/apps/opencs/view/world/table.cpp
@@ -0,0 +1,300 @@
+
+#include "table.hpp"
+
+#include <QHeaderView>
+
+#include <QAction>
+#include <QMenu>
+#include <QContextMenuEvent>
+
+#include "../../model/world/data.hpp"
+#include "../../model/world/commands.hpp"
+#include "../../model/world/idtableproxymodel.hpp"
+#include "../../model/world/idtable.hpp"
+#include "../../model/world/record.hpp"
+#include "recordstatusdelegate.hpp"
+#include "refidtypedelegate.hpp"
+#include "util.hpp"
+
+void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
+{
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ QMenu menu (this);
+
+ /// \todo add menu items for select all and clear selection
+
+ if (!mEditLock)
+ {
+ if (selectedRows.size()==1)
+ menu.addAction (mEditAction);
+
+ if (mCreateAction)
+ menu.addAction (mCreateAction);
+
+ if (listRevertableSelectedIds().size()>0)
+ menu.addAction (mRevertAction);
+
+ if (listDeletableSelectedIds().size()>0)
+ menu.addAction (mDeleteAction);
+ }
+
+ menu.exec (event->globalPos());
+}
+
+std::vector<std::string> CSVWorld::Table::listRevertableSelectedIds() const
+{
+ std::vector<std::string> revertableIds;
+
+ if (mProxyModel->columnCount()>0)
+ {
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end();
+ ++iter)
+ {
+ QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0));
+
+ CSMWorld::RecordBase::State state =
+ static_cast<CSMWorld::RecordBase::State> (
+ mModel->data (mModel->index (index.row(), 1)).toInt());
+
+ if (state!=CSMWorld::RecordBase::State_BaseOnly)
+ {
+ int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
+
+ std::string id = mModel->data (mModel->index (index.row(), columnIndex)).
+ toString().toUtf8().constData();
+
+ revertableIds.push_back (id);
+ }
+ }
+ }
+
+ return revertableIds;
+}
+
+std::vector<std::string> CSVWorld::Table::listDeletableSelectedIds() const
+{
+ std::vector<std::string> deletableIds;
+
+ if (mProxyModel->columnCount()>0)
+ {
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end();
+ ++iter)
+ {
+ QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0));
+
+ CSMWorld::RecordBase::State state =
+ static_cast<CSMWorld::RecordBase::State> (
+ mModel->data (mModel->index (index.row(), 1)).toInt());
+
+ if (state!=CSMWorld::RecordBase::State_Deleted)
+ {
+ int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id);
+
+ std::string id = mModel->data (mModel->index (index.row(), columnIndex)).
+ toString().toUtf8().constData();
+
+ deletableIds.push_back (id);
+ }
+ }
+ }
+
+ return deletableIds;
+}
+
+CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack,
+ bool createAndDelete)
+ : mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0)
+{
+ mModel = &dynamic_cast<CSMWorld::IdTable&> (*data.getTableModel (id));
+
+ mProxyModel = new CSMWorld::IdTableProxyModel (this);
+ mProxyModel->setSourceModel (mModel);
+
+ setModel (mProxyModel);
+ horizontalHeader()->setResizeMode (QHeaderView::Interactive);
+ verticalHeader()->hide();
+ setSortingEnabled (true);
+ setSelectionBehavior (QAbstractItemView::SelectRows);
+ setSelectionMode (QAbstractItemView::ExtendedSelection);
+
+ int columns = mModel->columnCount();
+
+ for (int i=0; i<columns; ++i)
+ {
+ int flags = mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt();
+
+ if (flags & CSMWorld::ColumnBase::Flag_Table)
+ {
+ CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display> (
+ mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
+
+ CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display,
+ undoStack, this);
+
+ mDelegates.push_back (delegate);
+ setItemDelegateForColumn (i, delegate);
+ }
+ else
+ hideColumn (i);
+ }
+
+ mEditAction = new QAction (tr ("Edit Record"), this);
+ connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
+ addAction (mEditAction);
+
+ if (createAndDelete)
+ {
+ mCreateAction = new QAction (tr ("Add Record"), this);
+ connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
+ addAction (mCreateAction);
+ }
+
+ mRevertAction = new QAction (tr ("Revert Record"), this);
+ connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord()));
+ addAction (mRevertAction);
+
+ mDeleteAction = new QAction (tr ("Delete Record"), this);
+ connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord()));
+ addAction (mDeleteAction);
+
+ connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
+ this, SLOT (tableSizeUpdate()));
+
+ /// \note This signal could instead be connected to a slot that filters out changes not affecting
+ /// the records status column (for permanence reasons)
+ connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
+ this, SLOT (tableSizeUpdate()));
+
+ connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)),
+ this, SLOT (selectionSizeUpdate ()));
+}
+
+void CSVWorld::Table::setEditLock (bool locked)
+{
+ for (std::vector<CommandDelegate *>::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter)
+ (*iter)->setEditLock (locked);
+
+ mEditLock = locked;
+}
+
+CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const
+{
+ return CSMWorld::UniversalId (
+ static_cast<CSMWorld::UniversalId::Type> (mProxyModel->data (mProxyModel->index (row, 2)).toInt()),
+ mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString());
+}
+
+void CSVWorld::Table::revertRecord()
+{
+ if (!mEditLock)
+ {
+ std::vector<std::string> revertableIds = listRevertableSelectedIds();
+
+ if (revertableIds.size()>0)
+ {
+ if (revertableIds.size()>1)
+ mUndoStack.beginMacro (tr ("Revert multiple records"));
+
+ for (std::vector<std::string>::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter)
+ mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter));
+
+ if (revertableIds.size()>1)
+ mUndoStack.endMacro();
+ }
+ }
+}
+
+void CSVWorld::Table::deleteRecord()
+{
+ if (!mEditLock)
+ {
+ std::vector<std::string> deletableIds = listDeletableSelectedIds();
+
+ if (deletableIds.size()>0)
+ {
+ if (deletableIds.size()>1)
+ mUndoStack.beginMacro (tr ("Delete multiple records"));
+
+ for (std::vector<std::string>::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter)
+ mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter));
+
+ if (deletableIds.size()>1)
+ mUndoStack.endMacro();
+ }
+ }
+}
+
+void CSVWorld::Table::editRecord()
+{
+ if (!mEditLock)
+ {
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ if (selectedRows.size()==1)
+ emit editRequest (selectedRows.begin()->row());
+ }
+}
+
+void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue)
+{
+ int columns = mModel->columnCount();
+
+ for (int i=0; i<columns; ++i)
+ if (QAbstractItemDelegate *delegate = itemDelegateForColumn (i))
+ if (dynamic_cast<CommandDelegate&> (*delegate).
+ updateEditorSetting (settingName, settingValue))
+ emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i));
+}
+
+void CSVWorld::Table::tableSizeUpdate()
+{
+ int size = 0;
+ int deleted = 0;
+ int modified = 0;
+
+ if (mProxyModel->columnCount()>0)
+ {
+ int rows = mProxyModel->rowCount();
+
+ for (int i=0; i<rows; ++i)
+ {
+ QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (i, 0));
+
+ int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification);
+ int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt();
+
+ switch (state)
+ {
+ case CSMWorld::RecordBase::State_BaseOnly: ++size; break;
+ case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break;
+ case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break;
+ case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break;
+ }
+ }
+ }
+
+ tableSizeChanged (size, deleted, modified);
+}
+
+void CSVWorld::Table::selectionSizeUpdate()
+{
+ selectionSizeChanged (selectionModel()->selectedRows().size());
+}
+
+void CSVWorld::Table::requestFocus (const std::string& id)
+{
+ QModelIndex index = mProxyModel->getModelIndex (id, 0);
+
+ if (index.isValid())
+ scrollTo (index, QAbstractItemView::PositionAtTop);
+}
+
+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
new file mode 100644
index 0000000000..d931090563
--- /dev/null
+++ b/apps/opencs/view/world/table.hpp
@@ -0,0 +1,94 @@
+#ifndef CSV_WORLD_TABLE_H
+#define CSV_WORLD_TABLE_H
+
+#include <vector>
+#include <string>
+
+#include <QTableView>
+
+#include "../../model/filter/node.hpp"
+
+class QUndoStack;
+class QAction;
+
+namespace CSMWorld
+{
+ class Data;
+ class UniversalId;
+ class IdTableProxyModel;
+ class IdTable;
+}
+
+namespace CSVWorld
+{
+ class CommandDelegate;
+
+ ///< Table widget
+ class Table : public QTableView
+ {
+ Q_OBJECT
+
+ std::vector<CommandDelegate *> mDelegates;
+ QUndoStack& mUndoStack;
+ QAction *mEditAction;
+ QAction *mCreateAction;
+ QAction *mRevertAction;
+ QAction *mDeleteAction;
+ CSMWorld::IdTableProxyModel *mProxyModel;
+ CSMWorld::IdTable *mModel;
+ bool mEditLock;
+ int mRecordStatusDisplay;
+
+ private:
+
+ void contextMenuEvent (QContextMenuEvent *event);
+
+ std::vector<std::string> listRevertableSelectedIds() const;
+
+ std::vector<std::string> listDeletableSelectedIds() const;
+
+ public:
+
+ Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete);
+ ///< \param createAndDelete Allow creation and deletion of records.
+
+ void setEditLock (bool locked);
+
+ CSMWorld::UniversalId getUniversalId (int row) const;
+
+ void updateEditorSetting (const QString &settingName, const QString &settingValue);
+
+ signals:
+
+ void editRequest (int row);
+
+ void selectionSizeChanged (int size);
+
+ void tableSizeChanged (int size, int deleted, int modified);
+ ///< \param size Number of not deleted records
+ /// \param deleted Number of deleted records
+ /// \param modified Number of added and modified records
+
+ void createRequest();
+
+ private slots:
+
+ void revertRecord();
+
+ void deleteRecord();
+
+ void editRecord();
+
+ public slots:
+
+ void tableSizeUpdate();
+
+ void selectionSizeUpdate();
+
+ void requestFocus (const std::string& id);
+
+ void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp
new file mode 100644
index 0000000000..3edf9af317
--- /dev/null
+++ b/apps/opencs/view/world/tablebottombox.cpp
@@ -0,0 +1,159 @@
+
+#include "tablebottombox.hpp"
+
+#include <sstream>
+
+#include <QStatusBar>
+#include <QStackedLayout>
+#include <QLabel>
+
+#include "creator.hpp"
+
+void CSVWorld::TableBottomBox::updateStatus()
+{
+ if (mShowStatusBar)
+ {
+ static const char *sLabels[4] = { "record", "deleted", "touched", "selected" };
+ static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" };
+
+ std::ostringstream stream;
+
+ bool first = true;
+
+ for (int i=0; i<4; ++i)
+ {
+ if (mStatusCount[i]>0)
+ {
+ if (first)
+ first = false;
+ else
+ stream << ", ";
+
+ stream
+ << mStatusCount[i] << ' '
+ << (mStatusCount[i]==1 ? sLabels[i] : sLabelsPlural[i]);
+ }
+ }
+
+ mStatus->setText (QString::fromUtf8 (stream.str().c_str()));
+ }
+}
+
+CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory,
+ CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent)
+: QWidget (parent), mShowStatusBar (false), mCreating (false)
+{
+ for (int i=0; i<4; ++i)
+ mStatusCount[i] = 0;
+
+ setVisible (false);
+
+ mLayout = new QStackedLayout;
+ mLayout->setContentsMargins (0, 0, 0, 0);
+
+ mStatus = new QLabel;
+
+ mStatusBar = new QStatusBar;
+
+ mStatusBar->addWidget (mStatus);
+
+ mLayout->addWidget (mStatusBar);
+
+ setLayout (mLayout);
+
+ mCreator = creatorFactory.makeCreator (data, undoStack, id);
+
+ if (mCreator)
+ {
+ mLayout->addWidget (mCreator);
+
+ connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone()));
+
+ connect (mCreator, SIGNAL (requestFocus (const std::string&)),
+ this, SIGNAL (requestFocus (const std::string&)));
+ }
+}
+
+void CSVWorld::TableBottomBox::setEditLock (bool locked)
+{
+ if (mCreator)
+ mCreator->setEditLock (locked);
+}
+
+CSVWorld::TableBottomBox::~TableBottomBox()
+{
+ delete mCreator;
+}
+
+void CSVWorld::TableBottomBox::setStatusBar (bool show)
+{
+ if (show!=mShowStatusBar)
+ {
+ setVisible (show || mCreating);
+
+ mShowStatusBar = show;
+
+ if (show)
+ updateStatus();
+ }
+}
+
+bool CSVWorld::TableBottomBox::canCreateAndDelete() const
+{
+ return mCreator;
+}
+
+void CSVWorld::TableBottomBox::createRequestDone()
+{
+ if (!mShowStatusBar)
+ setVisible (false);
+ else
+ updateStatus();
+
+ mLayout->setCurrentWidget (mStatusBar);
+
+ mCreating = false;
+}
+
+void CSVWorld::TableBottomBox::selectionSizeChanged (int size)
+{
+ if (mStatusCount[3]!=size)
+ {
+ mStatusCount[3] = size;
+ updateStatus();
+ }
+}
+
+void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modified)
+{
+ bool changed = false;
+
+ if (mStatusCount[0]!=size)
+ {
+ mStatusCount[0] = size;
+ changed = true;
+ }
+
+ if (mStatusCount[1]!=deleted)
+ {
+ mStatusCount[1] = deleted;
+ changed = true;
+ }
+
+ if (mStatusCount[2]!=modified)
+ {
+ mStatusCount[2] = modified;
+ changed = true;
+ }
+
+ if (changed)
+ updateStatus();
+}
+
+void CSVWorld::TableBottomBox::createRequest()
+{
+ mCreator->reset();
+ mLayout->setCurrentWidget (mCreator);
+ setVisible (true);
+ mCreating = true;
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp
new file mode 100644
index 0000000000..a5ae5e0bd9
--- /dev/null
+++ b/apps/opencs/view/world/tablebottombox.hpp
@@ -0,0 +1,82 @@
+#ifndef CSV_WORLD_BOTTOMBOX_H
+#define CSV_WORLD_BOTTOMBOX_H
+
+#include <QWidget>
+
+class QLabel;
+class QStackedLayout;
+class QStatusBar;
+class QUndoStack;
+
+namespace CSMWorld
+{
+ class Data;
+ class UniversalId;
+}
+
+namespace CSVWorld
+{
+ class CreatorFactoryBase;
+ class Creator;
+
+ class TableBottomBox : public QWidget
+ {
+ Q_OBJECT
+
+ bool mShowStatusBar;
+ QLabel *mStatus;
+ QStatusBar *mStatusBar;
+ int mStatusCount[4];
+ Creator *mCreator;
+ bool mCreating;
+ QStackedLayout *mLayout;
+
+ private:
+
+ // not implemented
+ TableBottomBox (const TableBottomBox&);
+ TableBottomBox& operator= (const TableBottomBox&);
+
+ void updateStatus();
+
+ public:
+
+ TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMWorld::Data& data,
+ QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent = 0);
+
+ virtual ~TableBottomBox();
+
+ void setEditLock (bool locked);
+
+ void setStatusBar (bool show);
+
+ bool canCreateAndDelete() const;
+ ///< Is record creation and deletion supported?
+ ///
+ /// \note The BotomBox does not partake in the deletion of records.
+
+ signals:
+
+ void requestFocus (const std::string& id);
+ ///< Request owner of this box to focus the just created \a id. The owner may
+ /// ignore this request.
+
+ private slots:
+
+ void createRequestDone();
+ ///< \note This slot being called does not imply success.
+
+ public slots:
+
+ void selectionSizeChanged (int size);
+
+ void tableSizeChanged (int size, int deleted, int modified);
+ ///< \param size Number of not deleted records
+ /// \param deleted Number of deleted records
+ /// \param modified Number of added and modified records
+
+ void createRequest();
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp
new file mode 100644
index 0000000000..1e05fbf51c
--- /dev/null
+++ b/apps/opencs/view/world/tablesubview.cpp
@@ -0,0 +1,78 @@
+
+#include "tablesubview.hpp"
+
+#include <QVBoxLayout>
+
+#include "../../model/doc/document.hpp"
+
+#include "../filter/filterbox.hpp"
+
+#include "table.hpp"
+#include "tablebottombox.hpp"
+#include "creator.hpp"
+
+CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
+ const CreatorFactoryBase& creatorFactory)
+: SubView (id)
+{
+ QVBoxLayout *layout = new QVBoxLayout;
+
+ layout->setContentsMargins (QMargins (0, 0, 0, 0));
+
+ layout->addWidget (mBottom =
+ new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0);
+
+ layout->insertWidget (0, mTable =
+ new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2);
+
+ CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this);
+
+ layout->insertWidget (0, filterBox);
+
+ QWidget *widget = new QWidget;
+
+ widget->setLayout (layout);
+
+ setWidget (widget);
+
+ connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int)));
+
+ connect (mTable, SIGNAL (selectionSizeChanged (int)),
+ mBottom, SLOT (selectionSizeChanged (int)));
+ connect (mTable, SIGNAL (tableSizeChanged (int, int, int)),
+ mBottom, SLOT (tableSizeChanged (int, int, int)));
+
+ mTable->tableSizeUpdate();
+ mTable->selectionSizeUpdate();
+
+ if (mBottom->canCreateAndDelete())
+ connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest()));
+
+ connect (mBottom, SIGNAL (requestFocus (const std::string&)),
+ mTable, SLOT (requestFocus (const std::string&)));
+
+ connect (filterBox,
+ SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)),
+ mTable, SLOT (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
+}
+
+void CSVWorld::TableSubView::setEditLock (bool locked)
+{
+ mTable->setEditLock (locked);
+ mBottom->setEditLock (locked);
+}
+
+void CSVWorld::TableSubView::editRequest (int row)
+{
+ focusId (mTable->getUniversalId (row));
+}
+
+void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue)
+{
+ mTable->updateEditorSetting(settingName, settingValue);
+}
+
+void CSVWorld::TableSubView::setStatusBar (bool show)
+{
+ mBottom->setStatusBar (show);
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp
new file mode 100644
index 0000000000..d61c789356
--- /dev/null
+++ b/apps/opencs/view/world/tablesubview.hpp
@@ -0,0 +1,43 @@
+#ifndef CSV_WORLD_TABLESUBVIEW_H
+#define CSV_WORLD_TABLESUBVIEW_H
+
+#include "../doc/subview.hpp"
+
+class QModelIndex;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSVWorld
+{
+ class Table;
+ class TableBottomBox;
+ class CreatorFactoryBase;
+
+ class TableSubView : public CSVDoc::SubView
+ {
+ Q_OBJECT
+
+ Table *mTable;
+ TableBottomBox *mBottom;
+
+ public:
+
+ TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document,
+ const CreatorFactoryBase& creatorFactory);
+
+ virtual void setEditLock (bool locked);
+
+ virtual void updateEditorSetting (const QString& key, const QString& value);
+
+ virtual void setStatusBar (bool show);
+
+ private slots:
+
+ void editRequest (int row);
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp
new file mode 100644
index 0000000000..97af3b99c5
--- /dev/null
+++ b/apps/opencs/view/world/util.cpp
@@ -0,0 +1,143 @@
+
+#include "util.hpp"
+
+#include <stdexcept>
+
+#include <QUndoStack>
+
+#include "../../model/world/commands.hpp"
+
+CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model)
+: mModel (model)
+{}
+
+int CSVWorld::NastyTableModelHack::rowCount (const QModelIndex & parent) const
+{
+ return mModel.rowCount (parent);
+}
+
+int CSVWorld::NastyTableModelHack::columnCount (const QModelIndex & parent) const
+{
+ return mModel.columnCount (parent);
+}
+
+QVariant CSVWorld::NastyTableModelHack::data (const QModelIndex & index, int role) const
+{
+ return mModel.data (index, role);
+}
+
+bool CSVWorld::NastyTableModelHack::setData ( const QModelIndex &index, const QVariant &value, int role)
+{
+ mData = value;
+ return true;
+}
+
+QVariant CSVWorld::NastyTableModelHack::getData() const
+{
+ return mData;
+}
+
+
+CSVWorld::CommandDelegateFactory::~CommandDelegateFactory() {}
+
+
+CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = 0;
+
+CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection()
+{
+ if (sThis)
+ throw std::logic_error ("multiple instances of CSVWorld::CommandDelegateFactoryCollection");
+
+ sThis = this;
+}
+
+CSVWorld::CommandDelegateFactoryCollection::~CommandDelegateFactoryCollection()
+{
+ sThis = 0;
+
+ for (std::map<CSMWorld::ColumnBase::Display, CommandDelegateFactory *>::iterator iter (
+ mFactories.begin());
+ iter!=mFactories.end(); ++iter)
+ delete iter->second;
+}
+
+void CSVWorld::CommandDelegateFactoryCollection::add (CSMWorld::ColumnBase::Display display,
+ CommandDelegateFactory *factory)
+{
+ mFactories.insert (std::make_pair (display, factory));
+}
+
+CSVWorld::CommandDelegate *CSVWorld::CommandDelegateFactoryCollection::makeDelegate (
+ CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, QObject *parent) const
+{
+ std::map<CSMWorld::ColumnBase::Display, CommandDelegateFactory *>::const_iterator iter =
+ mFactories.find (display);
+
+ if (iter!=mFactories.end())
+ return iter->second->makeDelegate (undoStack, parent);
+
+ return new CommandDelegate (undoStack, parent);
+}
+
+const CSVWorld::CommandDelegateFactoryCollection& CSVWorld::CommandDelegateFactoryCollection::get()
+{
+ if (!sThis)
+ throw std::logic_error ("no instance of CSVWorld::CommandDelegateFactoryCollection");
+
+ return *sThis;
+}
+
+
+QUndoStack& CSVWorld::CommandDelegate::getUndoStack() const
+{
+ return mUndoStack;
+}
+
+void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const
+{
+ NastyTableModelHack hack (*model);
+ QStyledItemDelegate::setModelData (editor, &hack, index);
+ mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData()));
+}
+
+CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent)
+: QStyledItemDelegate (parent), mUndoStack (undoStack), mEditLock (false)
+{}
+
+void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const
+{
+ if (!mEditLock)
+ {
+ setModelDataImp (editor, model, index);
+ }
+
+ ///< \todo provide some kind of feedback to the user, indicating that editing is currently not possible.
+}
+
+QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const
+{
+ if (!index.data().isValid())
+ return 0;
+
+ return QStyledItemDelegate::createEditor (parent, option, index);
+}
+
+
+void CSVWorld::CommandDelegate::setEditLock (bool locked)
+{
+ mEditLock = locked;
+}
+
+bool CSVWorld::CommandDelegate::isEditLocked() const
+{
+ return mEditLock;
+}
+
+bool CSVWorld::CommandDelegate::updateEditorSetting (const QString &settingName,
+ const QString &settingValue)
+{
+ return false;
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp
new file mode 100644
index 0000000000..87f118cd74
--- /dev/null
+++ b/apps/opencs/view/world/util.hpp
@@ -0,0 +1,120 @@
+#ifndef CSV_WORLD_UTIL_H
+#define CSV_WORLD_UTIL_H
+
+#include <map>
+
+#include <QAbstractTableModel>
+#include <QStyledItemDelegate>
+
+#include "../../model/world/columnbase.hpp"
+
+class QUndoStack;
+
+namespace CSVWorld
+{
+ ///< \brief Getting the data out of an editor widget
+ ///
+ /// Really, Qt? Really?
+ class NastyTableModelHack : public QAbstractTableModel
+ {
+ QAbstractItemModel& mModel;
+ QVariant mData;
+
+ public:
+
+ NastyTableModelHack (QAbstractItemModel& model);
+
+ int rowCount (const QModelIndex & parent = QModelIndex()) const;
+
+ int columnCount (const QModelIndex & parent = QModelIndex()) const;
+
+ QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+
+ bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ QVariant getData() const;
+ };
+
+ class CommandDelegate;
+
+ class CommandDelegateFactory
+ {
+ public:
+
+ virtual ~CommandDelegateFactory();
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const = 0;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+ };
+
+ class CommandDelegateFactoryCollection
+ {
+ static CommandDelegateFactoryCollection *sThis;
+ std::map<CSMWorld::ColumnBase::Display, CommandDelegateFactory *> mFactories;
+
+ private:
+
+ // not implemented
+ CommandDelegateFactoryCollection (const CommandDelegateFactoryCollection&);
+ CommandDelegateFactoryCollection& operator= (const CommandDelegateFactoryCollection&);
+
+ public:
+
+ CommandDelegateFactoryCollection();
+
+ ~CommandDelegateFactoryCollection();
+
+ void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory);
+ ///< The ownership of \æ factory is transferred to *this.
+ ///
+ /// This function must not be called more than once per value of \æ display.
+
+ CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, QUndoStack& undoStack,
+ QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+ ///
+ /// If no factory is registered for \a display, a CommandDelegate will be returned.
+
+ static const CommandDelegateFactoryCollection& get();
+
+ };
+
+ ///< \brief Use commands instead of manipulating the model directly
+ class CommandDelegate : public QStyledItemDelegate
+ {
+ Q_OBJECT
+
+ QUndoStack& mUndoStack;
+ bool mEditLock;
+
+ protected:
+
+ QUndoStack& getUndoStack() const;
+
+ virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const;
+
+ public:
+
+ CommandDelegate (QUndoStack& undoStack, QObject *parent);
+
+ virtual void setModelData (QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex& index) const;
+
+ virtual QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem& option,
+ const QModelIndex& index) const;
+
+ void setEditLock (bool locked);
+
+ bool isEditLocked() const;
+
+ virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue);
+ ///< \return Does column require update?
+
+ private slots:
+
+ virtual void slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) {}
+ };
+}
+
+#endif
diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp
new file mode 100644
index 0000000000..72cbaae428
--- /dev/null
+++ b/apps/opencs/view/world/vartypedelegate.cpp
@@ -0,0 +1,103 @@
+
+#include "vartypedelegate.hpp"
+
+#include <QUndoStack>
+
+#include "../../model/world/commands.hpp"
+
+void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type)
+ const
+{
+ QModelIndex next = model->index (index.row(), index.column()+1);
+
+ QVariant old = model->data (next);
+
+ QVariant value;
+
+ switch (type)
+ {
+ case ESM::VT_Short:
+ case ESM::VT_Int:
+ case ESM::VT_Long:
+
+ value = old.toInt();
+ break;
+
+ case ESM::VT_Float:
+
+ value = old.toFloat();
+ break;
+
+ case ESM::VT_String:
+
+ value = old.toString();
+ break;
+
+ default: break; // ignore the rest
+ }
+
+ getUndoStack().beginMacro (
+ "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString());
+
+ getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type));
+ getUndoStack().push (new CSMWorld::ModifyCommand (*model, next, value));
+
+ getUndoStack().endMacro();
+}
+
+CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector<std::pair<int, QString> >& values,
+ QUndoStack& undoStack, QObject *parent)
+: EnumDelegate (values, undoStack, parent)
+{}
+
+
+CSVWorld::VarTypeDelegateFactory::VarTypeDelegateFactory (ESM::VarType type0,
+ ESM::VarType type1, ESM::VarType type2, ESM::VarType type3)
+{
+ if (type0!=ESM::VT_Unknown)
+ add (type0);
+
+ if (type1!=ESM::VT_Unknown)
+ add (type1);
+
+ if (type2!=ESM::VT_Unknown)
+ add (type2);
+
+ if (type3!=ESM::VT_Unknown)
+ add (type3);
+}
+
+CSVWorld::CommandDelegate *CSVWorld::VarTypeDelegateFactory::makeDelegate (QUndoStack& undoStack,
+ QObject *parent) const
+{
+ return new VarTypeDelegate (mValues, undoStack, parent);
+}
+
+void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type)
+{
+ struct Name
+ {
+ ESM::VarType mType;
+ const char *mName;
+ };
+
+ static const Name sNames[] =
+ {
+ { ESM::VT_None, "empty" },
+ { ESM::VT_Short, "short" },
+ { ESM::VT_Int, "integer" },
+ { ESM::VT_Long, "long" },
+ { ESM::VT_Float, "float" },
+ { ESM::VT_String, "string" },
+ { ESM::VT_Unknown, 0 } // end marker
+ };
+
+ for (int i=0; sNames[i].mName; ++i)
+ if (sNames[i].mType==type)
+ {
+ mValues.push_back (std::make_pair (type, sNames[i].mName));
+ return;
+ }
+
+ throw std::logic_error ("Unsupported variable type");
+}
diff --git a/apps/opencs/view/world/vartypedelegate.hpp b/apps/opencs/view/world/vartypedelegate.hpp
new file mode 100644
index 0000000000..c8493f0291
--- /dev/null
+++ b/apps/opencs/view/world/vartypedelegate.hpp
@@ -0,0 +1,40 @@
+#ifndef CSV_WORLD_VARTYPEDELEGATE_H
+#define CSV_WORLD_VARTYPEDELEGATE_H
+
+#include <components/esm/variant.hpp>
+
+#include "enumdelegate.hpp"
+
+namespace CSVWorld
+{
+ class VarTypeDelegate : public EnumDelegate
+ {
+ private:
+
+ virtual void addCommands (QAbstractItemModel *model,
+ const QModelIndex& index, int type) const;
+
+ public:
+
+ VarTypeDelegate (const std::vector<std::pair<int, QString> >& values,
+ QUndoStack& undoStack, QObject *parent);
+ };
+
+ class VarTypeDelegateFactory : public CommandDelegateFactory
+ {
+ std::vector<std::pair<int, QString> > mValues;
+
+ public:
+
+ VarTypeDelegateFactory (ESM::VarType type0 = ESM::VT_Unknown,
+ ESM::VarType type1 = ESM::VT_Unknown, ESM::VarType type2 = ESM::VT_Unknown,
+ ESM::VarType type3 = ESM::VT_Unknown);
+
+ virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const;
+ ///< The ownership of the returned CommandDelegate is transferred to the caller.
+
+ void add (ESM::VarType type);
+ };
+}
+
+#endif
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
new file mode 100644
index 0000000000..a44fd4b343
--- /dev/null
+++ b/apps/openmw/CMakeLists.txt
@@ -0,0 +1,156 @@
+
+# config file
+configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp")
+
+# local files
+set(GAME
+ main.cpp
+ engine.cpp
+)
+set(GAME_HEADER
+ engine.hpp
+ config.hpp
+)
+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
+ )
+
+add_openmw_dir (mwinput
+ inputmanagerimp
+ )
+
+add_openmw_dir (mwgui
+ textinput widgets race class birth review windowmanagerimp console dialogue
+ windowbase statswindow messagebox journalwindow charactercreation
+ mapwindow windowpinnablebase tooltips scrollwindow bookwindow list
+ formatting inventorywindow container hud countdialog tradewindow settingswindow
+ confirmationdialog alchemywindow referenceinterface spellwindow mainmenu quickkeysmenu
+ itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog
+ 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
+ )
+
+add_openmw_dir (mwdialogue
+ dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper
+ )
+
+add_openmw_dir (mwscript
+ locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions
+ guiextensions soundextensions skyextensions statsextensions containerextensions
+ aiextensions controlextensions extensions globalscripts ref dialogueextensions
+ animationextensions transformationextensions consoleextensions userextensions locals
+ )
+
+add_openmw_dir (mwsound
+ soundmanagerimp openal_output audiere_decoder mpgsnd_decoder ffmpeg_decoder
+ )
+
+add_openmw_dir (mwworld
+ refdata worldimp physicssystem scene globals class action nullaction actionteleport
+ containerstore actiontalk actiontake manualref player cellfunctors failedaction
+ cells localscripts customdata weather inventorystore ptr actionopen actionread
+ actionequip timestamp actionalchemy cellstore actionapply actioneat
+ esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
+ )
+
+add_openmw_dir (mwclass
+ classes activator creature npc weapon armor potion apparatus book clothing container door
+ ingredient creaturelevlist itemlevlist light lockpick misc probe repair static
+ )
+
+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
+ )
+
+add_openmw_dir (mwbase
+ environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager
+ inputmanager windowmanager
+ )
+
+# Main executable
+
+IF(OGRE_STATIC)
+ADD_DEFINITIONS(-DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL)
+set(OGRE_STATIC_PLUGINS ${OGRE_Plugin_OctreeSceneManager_LIBRARIES} ${OGRE_Plugin_ParticleFX_LIBRARIES} ${OGRE_RenderSystem_GL_LIBRARIES})
+IF(WIN32)
+ADD_DEFINITIONS(-DENABLE_PLUGIN_Direct3D9)
+list (APPEND OGRE_STATIC_PLUGINS ${OGRE_RenderSystem_Direct3D9_LIBRARIES})
+ENDIF(WIN32)
+IF (Cg_FOUND)
+ADD_DEFINITIONS(-DENABLE_PLUGIN_CgProgramManager)
+list (APPEND OGRE_STATIC_PLUGINS ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${Cg_LIBRARIES})
+ENDIF (Cg_FOUND)
+ENDIF(OGRE_STATIC)
+
+add_executable(openmw
+ ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER}
+ ${OPENMW_FILES}
+ ${GAME} ${GAME_HEADER}
+ ${APPLE_BUNDLE_RESOURCES}
+)
+
+# Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING
+# when we change the backend.
+include_directories(${SOUND_INPUT_INCLUDES} ${BULLET_INCLUDE_DIRS})
+add_definitions(${SOUND_DEFINE})
+
+target_link_libraries(openmw
+ ${OGRE_LIBRARIES}
+ ${OGRE_STATIC_PLUGINS}
+ ${Boost_LIBRARIES}
+ ${OPENAL_LIBRARY}
+ ${SOUND_INPUT_LIBRARY}
+ ${BULLET_LIBRARIES}
+ ${MYGUI_LIBRARIES}
+ ${SDL2_LIBRARY}
+ ${MYGUI_PLATFORM_LIBRARIES}
+ ${SHINY_LIBRARIES}
+ "oics"
+ "sdl4ogre"
+ components
+)
+
+if (NOT UNIX)
+target_link_libraries(openmw ${SDL2MAIN_LIBRARY})
+endif()
+
+# Fix for not visible pthreads functions for linker with glibc 2.15
+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)
+ target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
+
+ if (FFMPEG_FOUND)
+ find_library(COREVIDEO_FRAMEWORK CoreVideo)
+ find_library(VDA_FRAMEWORK VideoDecodeAcceleration)
+ target_link_libraries(openmw ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
+ endif()
+endif(APPLE)
+
+if(DPKG_PROGRAM)
+ INSTALL(TARGETS openmw RUNTIME DESTINATION games COMPONENT openmw)
+endif(DPKG_PROGRAM)
+
+if (BUILD_WITH_CODE_COVERAGE)
+ add_definitions (--coverage)
+ target_link_libraries(openmw gcov)
+endif()
diff --git a/apps/openmw/config.hpp.cmake b/apps/openmw/config.hpp.cmake
new file mode 100644
index 0000000000..848fbe0eb1
--- /dev/null
+++ b/apps/openmw/config.hpp.cmake
@@ -0,0 +1,9 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@
+#define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@
+#define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@
+#define OPENMW_VERSION "@OPENMW_VERSION@"
+
+#endif
diff --git a/apps/openmw/doc.hpp b/apps/openmw/doc.hpp
new file mode 100644
index 0000000000..978f0f5fb2
--- /dev/null
+++ b/apps/openmw/doc.hpp
@@ -0,0 +1,44 @@
+// Note: This is not a regular source file.
+
+/// \ingroup apps
+/// \defgroup openmw OpenMW
+
+/// \namespace OMW
+/// \ingroup openmw
+/// \brief Integration of OpenMW-subsystems
+
+/// \namespace MWDialogue
+/// \ingroup openmw
+/// \brief NPC dialogues
+
+/// \namespace MWMechanics
+/// \ingroup openmw
+/// \brief Game mechanics and NPC-AI
+
+/// \namespace MWSound
+/// \ingroup openmw
+/// \brief Sound & music
+
+/// \namespace MWGUI
+/// \ingroup openmw
+/// \brief HUD and windows
+
+/// \namespace MWRender
+/// \ingroup openmw
+/// \brief Rendering via Ogre
+
+/// \namespace MWWorld
+/// \ingroup openmw
+/// \brief World data
+
+/// \namespace MWClass
+/// \ingroup openmw
+/// \brief Workaround for non-OOP design of the record system
+
+/// \namespace MWInput
+/// \ingroup openmw
+/// \brief User input and character controls
+
+/// \namespace MWScript
+/// \ingroup openmw
+/// \brief MW-specific script extentions and integration of the script system into OpenMW
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
new file mode 100644
index 0000000000..a2eccbaf9a
--- /dev/null
+++ b/apps/openmw/engine.cpp
@@ -0,0 +1,604 @@
+#include "engine.hpp"
+
+#include "components/esm/loadcell.hpp"
+
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+
+#include <MyGUI_WidgetManager.h>
+
+#include <components/compiler/extensions0.hpp>
+
+#include <components/bsa/bsa_archive.hpp>
+#include <components/files/configurationmanager.hpp>
+#include <components/translation/translation.hpp>
+#include <components/nif/niffile.hpp>
+#include <components/nifoverrides/nifoverrides.hpp>
+
+#include <components/nifbullet/bulletnifloader.hpp>
+#include <components/nifogre/ogrenifloader.hpp>
+
+#include "mwinput/inputmanagerimp.hpp"
+
+#include "mwgui/windowmanagerimp.hpp"
+
+#include "mwscript/scriptmanagerimp.hpp"
+#include "mwscript/extensions.hpp"
+#include "mwscript/interpretercontext.hpp"
+
+#include "mwsound/soundmanagerimp.hpp"
+
+#include "mwworld/class.hpp"
+#include "mwworld/player.hpp"
+#include "mwworld/worldimp.hpp"
+
+#include "mwclass/classes.hpp"
+
+#include "mwdialogue/dialoguemanagerimp.hpp"
+#include "mwdialogue/journalimp.hpp"
+
+#include "mwmechanics/mechanicsmanagerimp.hpp"
+
+
+#include <SDL.h>
+
+void OMW::Engine::executeLocalScripts()
+{
+ MWWorld::LocalScripts& localScripts = MWBase::Environment::get().getWorld()->getLocalScripts();
+
+ localScripts.startIteration();
+
+ while (!localScripts.isFinished())
+ {
+ std::pair<std::string, MWWorld::Ptr> script = localScripts.getNext();
+
+ MWScript::InterpreterContext interpreterContext (
+ &script.second.getRefData().getLocals(), script.second);
+ MWBase::Environment::get().getScriptManager()->run (script.first, interpreterContext);
+
+ if (MWBase::Environment::get().getWorld()->hasCellChanged())
+ break;
+ }
+
+ 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();
+ MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused);
+ MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame);
+ return true;
+}
+
+bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
+{
+ try
+ {
+ float frametime = std::min(evt.timeSinceLastFrame, 0.2f);
+
+ mEnvironment.setFrameDuration (frametime);
+
+ // update input
+ MWBase::Environment::get().getInputManager()->update(frametime, false);
+
+ // sound
+ if (mUseSound)
+ MWBase::Environment::get().getSoundManager()->update(frametime);
+
+ // global scripts
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
+
+ bool changed = MWBase::Environment::get().getWorld()->hasCellChanged();
+
+ // local scripts
+ executeLocalScripts(); // This does not handle the case where a global script causes a cell
+ // change, followed by a cell change in a local script during the same
+ // frame.
+
+ // passing of time
+ if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
+ MWBase::Environment::get().getWorld()->advanceTime(
+ frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600);
+
+
+ if (changed) // keep change flag for another frame, if cell changed happend in local script
+ MWBase::Environment::get().getWorld()->markCellAsUnchanged();
+
+ // update actors
+ MWBase::Environment::get().getMechanicsManager()->update(frametime,
+ MWBase::Environment::get().getWindowManager()->isGuiMode());
+
+ // update world
+ MWBase::Environment::get().getWorld()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode());
+
+ // update GUI
+ Ogre::RenderWindow* window = mOgre->getWindow();
+ unsigned int tri, batch;
+ MWBase::Environment::get().getWorld()->getTriangleBatchCount(tri, batch);
+ MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch);
+
+ MWBase::Environment::get().getWindowManager()->onFrame(frametime);
+ MWBase::Environment::get().getWindowManager()->update();
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Error in framelistener: " << e.what() << std::endl;
+ }
+
+ return true;
+}
+
+OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
+ : mOgre (0)
+ , mFpsLevel(0)
+ , mVerboseScripts (false)
+ , mNewGame (false)
+ , mUseSound (true)
+ , mCompileAll (false)
+ , mScriptContext (0)
+ , mFSStrict (false)
+ , mScriptConsoleMode (false)
+ , mCfgMgr(configurationManager)
+ , mEncoding(ToUTF8::WINDOWS_1252)
+ , mEncoder(NULL)
+ , mActivationDistanceOverride(-1)
+
+{
+ std::srand ( std::time(NULL) );
+ MWClass::registerClasses();
+
+ Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE;
+ if(SDL_WasInit(flags) == 0)
+ {
+ //kindly ask SDL not to trash our OGL context
+ //might this be related to http://bugzilla.libsdl.org/show_bug.cgi?id=748 ?
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
+ if(SDL_Init(flags) != 0)
+ {
+ throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError()));
+ }
+ }
+}
+
+OMW::Engine::~Engine()
+{
+ mEnvironment.cleanup();
+ delete mScriptContext;
+ delete mOgre;
+ SDL_Quit();
+}
+
+// Load BSA files
+
+void OMW::Engine::loadBSA()
+{
+ // We use separate resource groups to handle location priority.
+ const Files::PathContainer& dataDirs = mFileCollections.getPaths();
+
+ int i=0;
+ for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
+ {
+ // Last data dir has the highest priority
+ std::string groupName = "Data" + Ogre::StringConverter::toString(dataDirs.size()-i, 8, '0');
+ Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName);
+
+ std::string dataDirectory = iter->string();
+ std::cout << "Data dir " << dataDirectory << std::endl;
+ Bsa::addDir(dataDirectory, mFSStrict, groupName);
+ ++i;
+ }
+
+ i=0;
+ for (std::vector<std::string>::const_iterator archive = mArchives.begin(); archive != mArchives.end(); ++archive)
+ {
+ if (mFileCollections.doesExist(*archive))
+ {
+ // Last BSA has the highest priority
+ std::string groupName = "DataBSA" + Ogre::StringConverter::toString(mArchives.size()-i, 8, '0');
+
+ Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName);
+
+ const std::string archivePath = mFileCollections.getPath(*archive).string();
+ std::cout << "Adding BSA archive " << archivePath << std::endl;
+ Bsa::addBSA(archivePath, groupName);
+ ++i;
+ }
+ else
+ {
+ std::cout << "Archive " << *archive << " not found" << std::endl;
+ }
+ }
+}
+
+// add resources directory
+// \note This function works recursively.
+
+void OMW::Engine::addResourcesDirectory (const boost::filesystem::path& path)
+{
+ mOgre->getRoot()->addResourceLocation (path.string(), "FileSystem",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true);
+}
+
+void OMW::Engine::addZipResource (const boost::filesystem::path& path)
+{
+ mOgre->getRoot()->addResourceLocation (path.string(), "Zip",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, false);
+}
+
+void OMW::Engine::enableFSStrict(bool fsStrict)
+{
+ mFSStrict = fsStrict;
+}
+
+// Set data dir
+
+void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs)
+{
+ mDataDirs = dataDirs;
+ mFileCollections = Files::Collections (dataDirs, !mFSStrict);
+}
+
+// Add BSA archive
+void OMW::Engine::addArchive (const std::string& archive) {
+ mArchives.push_back(archive);
+}
+
+// Set resource dir
+void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir)
+{
+ mResDir = boost::filesystem::system_complete(parResDir);
+}
+
+// Set start cell name (only interiors for now)
+
+void OMW::Engine::setCell (const std::string& cellName)
+{
+ mCellName = cellName;
+}
+
+// Set master file (esm)
+// - If the given name does not have an extension, ".esm" is added automatically
+
+void OMW::Engine::addMaster (const std::string& master)
+{
+ mMaster.push_back(master);
+ std::string &str = mMaster.back();
+
+ // Append .esm if not already there
+ std::string::size_type sep = str.find_last_of (".");
+ if (sep == std::string::npos)
+ {
+ str += ".esm";
+ }
+}
+
+// Add plugin file (esp)
+void OMW::Engine::addPlugin (const std::string& plugin)
+{
+ mPlugins.push_back(plugin);
+ std::string &str = mPlugins.back();
+
+ // Append .esp if not already there
+ std::string::size_type sep = str.find_last_of (".");
+ if (sep == std::string::npos)
+ {
+ str += ".esp";
+ }
+}
+
+void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity)
+{
+ mVerboseScripts = scriptsVerbosity;
+}
+
+void OMW::Engine::setNewGame(bool newGame)
+{
+ mNewGame = newGame;
+}
+
+std::string OMW::Engine::loadSettings (Settings::Manager & settings)
+{
+ // Create the settings manager and load default settings file
+ const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg";
+ const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg";
+
+ // prefer local
+ if (boost::filesystem::exists(localdefault))
+ settings.loadDefault(localdefault);
+ else if (boost::filesystem::exists(globaldefault))
+ settings.loadDefault(globaldefault);
+ else
+ 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";
+ if (boost::filesystem::exists(settingspath))
+ settings.loadUser(settingspath);
+ else if (boost::filesystem::exists(localdefault))
+ settings.loadUser(localdefault);
+ else if (boost::filesystem::exists(globaldefault))
+ settings.loadUser(globaldefault);
+
+ mFpsLevel = settings.getInt("fps", "HUD");
+
+ // load nif overrides
+ NifOverrides::Overrides nifOverrides;
+ if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg"))
+ nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg");
+ else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"))
+ nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg");
+
+ settings.setBool("hardware cursors", "GUI", true);
+
+ return settingspath;
+}
+
+void OMW::Engine::prepareEngine (Settings::Manager & settings)
+{
+ Nif::NIFFile::CacheLock cachelock;
+
+ std::string renderSystem = settings.getString("render system", "Video");
+ if (renderSystem == "")
+ {
+#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ renderSystem = "Direct3D9 Rendering Subsystem";
+#else
+ renderSystem = "OpenGL Rendering Subsystem";
+#endif
+ }
+
+ mOgre = new OEngine::Render::OgreRenderer;
+
+ mOgre->configure(
+ mCfgMgr.getLogPath().string(),
+ renderSystem,
+ Settings::Manager::getString("opengl rtt mode", "Video"),
+ false);
+
+ // This has to be added BEFORE MyGUI is initialized, as it needs
+ // to find core.xml here.
+
+ //addResourcesDirectory(mResDir);
+
+ addResourcesDirectory(mCfgMgr.getCachePath ().string());
+
+ addResourcesDirectory(mResDir / "mygui");
+ addResourcesDirectory(mResDir / "water");
+ addResourcesDirectory(mResDir / "shadows");
+ addZipResource(mResDir / "mygui" / "Obliviontt.zip");
+
+ OEngine::Render::WindowSettings windowSettings;
+ windowSettings.fullscreen = settings.getBool("fullscreen", "Video");
+ windowSettings.window_x = settings.getInt("resolution x", "Video");
+ windowSettings.window_y = settings.getInt("resolution y", "Video");
+ windowSettings.screen = settings.getInt("screen", "Video");
+ windowSettings.vsync = settings.getBool("vsync", "Video");
+ windowSettings.icon = "openmw.png";
+ std::string aa = settings.getString("antialiasing", "Video");
+ windowSettings.fsaa = (aa.substr(0, 4) == "MSAA") ? aa.substr(5, aa.size()-5) : "0";
+
+ mOgre->createWindow("OpenMW", windowSettings);
+
+ loadBSA();
+
+
+ // 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();
+ bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
+ MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists);
+ mEnvironment.setInputManager (input);
+
+ MWGui::WindowManager* window = new MWGui::WindowManager(
+ mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"),
+ mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding);
+ mEnvironment.setWindowManager (window);
+ if (mNewGame)
+ mEnvironment.getWindowManager()->setNewGame(true);
+
+ // Create the world
+ mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins,
+ mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap,
+ mActivationDistanceOverride));
+ MWBase::Environment::get().getWorld()->setupPlayer();
+ input->setPlayer(&mEnvironment.getWorld()->getPlayer());
+
+ window->initUI();
+ window->renderWorldMap();
+
+ //Load translation data
+ mTranslationDataStorage.setEncoder(mEncoder);
+ for (size_t i = 0; i < mMaster.size(); i++)
+ mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[i]);
+
+ Compiler::registerExtensions (mExtensions);
+
+ // Create sound system
+ mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound));
+
+ // Create script system
+ mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
+ mScriptContext->setExtensions (&mExtensions);
+
+ mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(),
+ mVerboseScripts, *mScriptContext));
+
+ // Create game mechanics system
+ mEnvironment.setMechanicsManager (new MWMechanics::MechanicsManager);
+
+ // Create dialog system
+ mEnvironment.setJournal (new MWDialogue::Journal);
+ mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage));
+
+ mEnvironment.getWorld()->renderPlayer();
+
+ if (!mNewGame)
+ {
+ // load cell
+ ESM::Position pos;
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ if (world->findExteriorPosition(mCellName, pos)) {
+ world->changeToExteriorCell (pos);
+ }
+ else {
+ world->findInteriorPosition(mCellName, pos);
+ world->changeToInteriorCell (mCellName, pos);
+ }
+ }
+ else
+ mEnvironment.getWorld()->startNewGame();
+
+ Ogre::FrameEvent event;
+ event.timeSinceLastEvent = 0;
+ event.timeSinceLastFrame = 0;
+ frameRenderingQueued(event);
+ mOgre->getRoot()->addFrameListener (this);
+
+ // scripts
+ if (mCompileAll)
+ {
+ std::pair<int, int> result = MWBase::Environment::get().getScriptManager()->compileAll();
+
+ if (result.first)
+ std::cout
+ << "compiled " << result.second << " of " << result.first << " scripts ("
+ << 100*static_cast<double> (result.second)/result.first
+ << "%)"
+ << std::endl;
+ }
+}
+
+// Initialise and enter main loop.
+
+void OMW::Engine::go()
+{
+ assert (!mCellName.empty());
+ assert (!mMaster.empty());
+ assert (!mOgre);
+
+ Settings::Manager settings;
+ std::string settingspath;
+
+ settingspath = loadSettings (settings);
+
+ // Create encoder
+ ToUTF8::Utf8Encoder encoder (mEncoding);
+ mEncoder = &encoder;
+
+ prepareEngine (settings);
+
+ // Play some good 'ol tunes
+ MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
+
+ if (!mStartupScript.empty())
+ MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript);
+
+ // Start the main rendering loop
+ mOgre->start();
+
+ // Save user settings
+ settings.saveUser(settingspath);
+
+ std::cout << "Quitting peacefully." << std::endl;
+}
+
+void OMW::Engine::activate()
+{
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode())
+ return;
+
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject();
+
+ if (ptr.isEmpty())
+ return;
+
+ MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
+
+ boost::shared_ptr<MWWorld::Action> action =
+ MWWorld::Class::get (ptr).activate (ptr, MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ interpreterContext.activate (ptr, action);
+
+ std::string script = MWWorld::Class::get (ptr).getScript (ptr);
+
+ if (!script.empty())
+ {
+ MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr);
+ MWBase::Environment::get().getScriptManager()->run (script, interpreterContext);
+ }
+
+ if (!interpreterContext.hasActivationBeenHandled())
+ {
+ interpreterContext.executeActivation();
+ }
+}
+
+void OMW::Engine::screenshot()
+{
+ // Count screenshots.
+ int shotCount = 0;
+
+ const std::string screenshotPath = mCfgMgr.getUserPath().string();
+
+ // Find the first unused filename with a do-while
+ std::ostringstream stream;
+ do
+ {
+ // Reset the stream
+ stream.str("");
+ stream.clear();
+
+ stream << screenshotPath << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << ".png";
+
+ } while (boost::filesystem::exists(stream.str()));
+
+ mOgre->screenshot(stream.str());
+}
+
+void OMW::Engine::setCompileAll (bool all)
+{
+ mCompileAll = all;
+}
+
+void OMW::Engine::setSoundUsage(bool soundUsage)
+{
+ mUseSound = soundUsage;
+}
+
+void OMW::Engine::showFPS(int level)
+{
+ mFpsLevel = level;
+}
+
+void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding)
+{
+ mEncoding = encoding;
+}
+
+void OMW::Engine::setFallbackValues(std::map<std::string,std::string> fallbackMap)
+{
+ mFallbackMap = fallbackMap;
+}
+
+void OMW::Engine::setScriptConsoleMode (bool enabled)
+{
+ mScriptConsoleMode = enabled;
+}
+
+void OMW::Engine::setStartupScript (const std::string& path)
+{
+ mStartupScript = path;
+}
+
+
+void OMW::Engine::setActivationDistanceOverride (int distance)
+{
+ mActivationDistanceOverride = distance;
+}
diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp
new file mode 100644
index 0000000000..665b0094c1
--- /dev/null
+++ b/apps/openmw/engine.hpp
@@ -0,0 +1,191 @@
+#ifndef ENGINE_H
+#define ENGINE_H
+
+#include <OgreFrameListener.h>
+
+#include <components/compiler/extensions.hpp>
+#include <components/files/collections.hpp>
+#include <components/translation/translation.hpp>
+#include <components/settings/settings.hpp>
+
+#include "mwbase/environment.hpp"
+
+#include "mwworld/ptr.hpp"
+
+namespace Compiler
+{
+ class Context;
+}
+
+namespace MWScript
+{
+ class ScriptManager;
+}
+
+namespace MWSound
+{
+ class SoundManager;
+}
+
+namespace MWWorld
+{
+ class World;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+namespace OEngine
+{
+ namespace GUI
+ {
+ class MyGUIManager;
+ }
+
+ namespace Render
+ {
+ class OgreRenderer;
+ }
+}
+
+namespace Files
+{
+ struct ConfigurationManager;
+}
+
+namespace OMW
+{
+ /// \brief Main engine class, that brings together all the components of OpenMW
+ class Engine : private Ogre::FrameListener
+ {
+ MWBase::Environment mEnvironment;
+ ToUTF8::FromType mEncoding;
+ ToUTF8::Utf8Encoder* mEncoder;
+ Files::PathContainer mDataDirs;
+ std::vector<std::string> mArchives;
+ boost::filesystem::path mResDir;
+ OEngine::Render::OgreRenderer *mOgre;
+ std::string mCellName;
+ std::vector<std::string> mMaster;
+ std::vector<std::string> mPlugins;
+ int mFpsLevel;
+ bool mVerboseScripts;
+ bool mNewGame;
+ bool mUseSound;
+ bool mCompileAll;
+ std::string mFocusName;
+ std::map<std::string,std::string> mFallbackMap;
+ bool mScriptConsoleMode;
+ std::string mStartupScript;
+ int mActivationDistanceOverride;
+
+ Compiler::Extensions mExtensions;
+ Compiler::Context *mScriptContext;
+
+ Files::Collections mFileCollections;
+ bool mFSStrict;
+ Translation::Storage mTranslationDataStorage;
+
+ // not implemented
+ Engine (const Engine&);
+ Engine& operator= (const Engine&);
+
+ /// add resources directory
+ /// \note This function works recursively.
+ void addResourcesDirectory (const boost::filesystem::path& path);
+
+ /// add a .zip resource
+ void addZipResource (const boost::filesystem::path& path);
+
+ /// Load BSA files
+ void loadBSA();
+
+ void executeLocalScripts();
+
+ virtual bool frameRenderingQueued (const Ogre::FrameEvent& evt);
+ virtual bool frameStarted (const Ogre::FrameEvent& evt);
+
+ /// Load settings from various files, returns the path to the user settings file
+ std::string loadSettings (Settings::Manager & settings);
+
+ /// Prepare engine for game play
+ void prepareEngine (Settings::Manager & settings);
+
+ public:
+ Engine(Files::ConfigurationManager& configurationManager);
+ virtual ~Engine();
+
+ /// Enable strict filesystem mode (do not fold case)
+ ///
+ /// \attention The strict mode must be specified before any path-related settings
+ /// are given to the engine.
+ void enableFSStrict(bool fsStrict);
+
+ /// Set data dirs
+ void setDataDirs(const Files::PathContainer& dataDirs);
+
+ /// Add BSA archive
+ void addArchive(const std::string& archive);
+
+ /// Set resource dir
+ void setResourceDir(const boost::filesystem::path& parResDir);
+
+ /// Set start cell name (only interiors for now)
+ void setCell(const std::string& cellName);
+
+ /// Set master file (esm)
+ /// - If the given name does not have an extension, ".esm" is added automatically
+ void addMaster(const std::string& master);
+
+ /// Same as "addMaster", but for plugin files (esp)
+ /// - If the given name does not have an extension, ".esp" is added automatically
+ void addPlugin(const std::string& plugin);
+
+ /// Enable fps counter
+ void showFPS(int level);
+
+ /// Enable or disable verbose script output
+ void setScriptsVerbosity(bool scriptsVerbosity);
+
+ /// Disable or enable all sounds
+ void setSoundUsage(bool soundUsage);
+
+ /// Start as a new game.
+ void setNewGame(bool newGame);
+
+ /// Initialise and enter main loop.
+ void go();
+
+ /// Activate the focussed object.
+ void activate();
+
+ /// Write screenshot to file.
+ void screenshot();
+
+ /// Compile all scripts (excludign dialogue scripts) at startup?
+ void setCompileAll (bool all);
+
+ /// 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
+ void setScriptConsoleMode (bool enabled);
+
+ /// Set path for a script that is run on startup in the console.
+ void setStartupScript (const std::string& path);
+
+ /// Override the game setting specified activation distance.
+ void setActivationDistanceOverride (int distance);
+
+ private:
+ Files::ConfigurationManager& mCfgMgr;
+ };
+}
+
+#endif /* ENGINE_H */
diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp
new file mode 100644
index 0000000000..27afd734ae
--- /dev/null
+++ b/apps/openmw/main.cpp
@@ -0,0 +1,349 @@
+#include <iostream>
+
+#include <components/files/configurationmanager.hpp>
+
+#include <SDL_main.h>
+#include "engine.hpp"
+
+#if defined(_WIN32) && !defined(_CONSOLE)
+#include <boost/iostreams/concepts.hpp>
+#include <boost/iostreams/stream_buffer.hpp>
+
+// For OutputDebugString
+#include <Windows.h>
+// makes __argc and __argv available on windows
+#include <cstdlib>
+
+#endif
+
+// for Ogre::macBundlePath
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+#include <OSX/macUtils.h>
+#endif
+
+#include "config.hpp"
+
+#include <boost/version.hpp>
+/**
+ * Workaround for problems with whitespaces in paths in older versions of Boost library
+ */
+#if (BOOST_VERSION <= 104600)
+namespace boost
+{
+
+template<>
+inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
+{
+ return boost::filesystem::path(arg);
+}
+
+} /* namespace boost */
+#endif /* (BOOST_VERSION <= 104600) */
+
+struct FallbackMap {
+ std::map<std::string,std::string> mMap;
+};
+
+void validate(boost::any &v, std::vector<std::string> const &tokens, FallbackMap*, int)
+{
+ if(v.empty())
+ {
+ v = boost::any(FallbackMap());
+ }
+
+ FallbackMap *map = boost::any_cast<FallbackMap>(&v);
+
+ std::map<std::string,std::string>::iterator mapIt;
+ for(std::vector<std::string>::const_iterator it=tokens.begin(); it != tokens.end(); ++it)
+ {
+ int sep = it->find(",");
+ if(sep < 1 || sep == (int)it->length()-1)
+#if (BOOST_VERSION < 104200)
+ throw boost::program_options::validation_error("invalid value");
+#else
+ throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value);
+#endif
+
+ std::string key(it->substr(0,sep));
+ std::string value(it->substr(sep+1));
+
+ if((mapIt = map->mMap.find(key)) == map->mMap.end())
+ {
+ map->mMap.insert(std::make_pair (key,value));
+ }
+ }
+}
+
+
+/**
+ * \brief Parses application command line and calls \ref Cfg::ConfigurationManager
+ * to parse configuration files.
+ *
+ * Results are directly written to \ref Engine class.
+ *
+ * \retval true - Everything goes OK
+ * \retval false - Error
+ */
+bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::ConfigurationManager& cfgMgr)
+{
+ // Create a local alias for brevity
+ namespace bpo = boost::program_options;
+ typedef std::vector<std::string> StringsVector;
+
+ bpo::options_description desc("Syntax: openmw <options>\nAllowed options");
+
+ desc.add_options()
+ ("help", "print help message")
+ ("version", "print version information and quit")
+ ("data", bpo::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")
+ ->multitoken(), "set data directories (later directories have higher priority)")
+
+ ("data-local", bpo::value<std::string>()->default_value(""),
+ "set local data directory (highest priority)")
+
+ ("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
+ ->multitoken(), "set fallback BSA archives (later archives have higher priority)")
+
+ ("resources", bpo::value<std::string>()->default_value("resources"),
+ "set resources directory")
+
+ ("start", bpo::value<std::string>()->default_value("Beshara"),
+ "set initial cell")
+
+ ("master", bpo::value<StringsVector>()->default_value(StringsVector(), "")
+ ->multitoken(), "master file(s)")
+
+ ("plugin", bpo::value<StringsVector>()->default_value(StringsVector(), "")
+ ->multitoken(), "plugin file(s)")
+
+ ("anim-verbose", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "output animation indices files")
+
+ ("nosound", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "disable all sounds")
+
+ ("script-verbose", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "verbose script output")
+
+ ("script-all", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup")
+
+ ("script-console", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "enable console-only script functionality")
+
+ ("script-run", bpo::value<std::string>()->default_value(""),
+ "select a file containing a list of console commands that is executed on startup")
+
+ ("new-game", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "activate char gen/new game mechanics")
+
+ ("fs-strict", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "strict file system handling (no case folding)")
+
+ ( "encoding", bpo::value<std::string>()->
+ default_value("win1252"),
+ "Character encoding used in OpenMW game messages:\n"
+ "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
+ "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
+ "\n\twin1252 - Western European (Latin) alphabet, used by default")
+
+ ("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
+ ->multitoken()->composing(), "fallback values")
+
+ ("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override");
+
+ ;
+
+ bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
+ .options(desc).allow_unregistered().run();
+
+ bpo::variables_map variables;
+
+ // Runtime options override settings from all configs
+ bpo::store(valid_opts, variables);
+ bpo::notify(variables);
+
+ cfgMgr.readConfiguration(variables, desc);
+
+ bool run = true;
+
+ if (variables.count ("help"))
+ {
+ std::cout << desc << std::endl;
+ run = false;
+ }
+
+ if (variables.count ("version"))
+ {
+ std::cout << "OpenMW version " << OPENMW_VERSION << std::endl;
+ run = false;
+ }
+
+ if (!run)
+ return false;
+
+ // Font encoding settings
+ std::string encoding(variables["encoding"].as<std::string>());
+ std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl;
+ engine.setEncoding(ToUTF8::calculateEncoding(encoding));
+
+ // directory settings
+ engine.enableFSStrict(variables["fs-strict"].as<bool>());
+
+ Files::PathContainer dataDirs(variables["data"].as<Files::PathContainer>());
+
+ std::string local(variables["data-local"].as<std::string>());
+ if (!local.empty())
+ {
+ dataDirs.push_back(Files::PathContainer::value_type(local));
+ }
+
+ cfgMgr.processPaths(dataDirs);
+
+ engine.setDataDirs(dataDirs);
+
+ // fallback archives
+ StringsVector archives = variables["fallback-archive"].as<StringsVector>();
+ for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it)
+ {
+ engine.addArchive(*it);
+ }
+
+ engine.setResourceDir(variables["resources"].as<std::string>());
+
+ // master and plugin
+ StringsVector master = variables["master"].as<StringsVector>();
+ if (master.empty())
+ {
+ std::cout << "No master file given. Aborting...\n";
+ return false;
+ }
+
+ StringsVector plugin = variables["plugin"].as<StringsVector>();
+ // Removed check for 255 files, which would be the hard-coded limit in Morrowind.
+ // I'll keep the following variable in, maybe we can use it for something different.
+ // Say, a feedback like "loading file x/cnt".
+ // Commenting this out for now to silence compiler warning.
+ //int cnt = master.size() + plugin.size();
+
+ // Prepare loading master/plugin files (i.e. send filenames to engine)
+ for (std::vector<std::string>::size_type i = 0; i < master.size(); i++)
+ {
+ engine.addMaster(master[i]);
+ }
+ for (std::vector<std::string>::size_type i = 0; i < plugin.size(); i++)
+ {
+ engine.addPlugin(plugin[i]);
+ }
+
+ // startup-settings
+ engine.setCell(variables["start"].as<std::string>());
+ engine.setNewGame(variables["new-game"].as<bool>());
+
+ // other settings
+ engine.setSoundUsage(!variables["nosound"].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>());
+ engine.setActivationDistanceOverride (variables["activate-dist"].as<int>());
+
+ return true;
+}
+
+int main(int argc, char**argv)
+{
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ // set current dir to bundle path
+ boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path();
+ boost::filesystem::current_path(bundlePath);
+#endif
+
+ try
+ {
+ Files::ConfigurationManager cfgMgr;
+ OMW::Engine engine(cfgMgr);
+
+ if (parseOptions(argc, argv, engine, cfgMgr))
+ {
+ engine.go();
+ }
+ }
+ catch (std::exception &e)
+ {
+ std::cout << "\nERROR: " << e.what() << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+// Platform specific for Windows when there is no console built into the executable.
+// Windows will call the WinMain function instead of main in this case, the normal
+// main function is then called with the __argc and __argv parameters.
+// In addition if it is a debug build it will redirect cout to the debug console in Visual Studio
+#if defined(_WIN32) && !defined(_CONSOLE)
+
+#if defined(_DEBUG)
+class DebugOutput : public boost::iostreams::sink
+{
+public:
+ std::streamsize write(const char *str, std::streamsize size)
+ {
+ // Make a copy for null termination
+ std::string tmp (str, size);
+ // Write string to Visual Studio Debug output
+ OutputDebugString (tmp.c_str ());
+ return size;
+ }
+};
+#else
+class Logger : public boost::iostreams::sink
+{
+public:
+ Logger(std::ofstream &stream)
+ : out(stream)
+ {
+ }
+
+ std::streamsize write(const char *str, std::streamsize size)
+ {
+ out.write (str, size);
+ out.flush();
+ return size;
+ }
+
+private:
+ std::ofstream &out;
+};
+#endif
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ std::streambuf* old_rdbuf = std::cout.rdbuf ();
+
+ int ret = 0;
+#if defined(_DEBUG)
+ // Redirect cout to VS debug output when running in debug mode
+ {
+ boost::iostreams::stream_buffer<DebugOutput> sb;
+ sb.open(DebugOutput());
+#else
+ // Redirect cout to openmw.log
+ std::ofstream logfile ("openmw.log");
+ {
+ boost::iostreams::stream_buffer<Logger> sb;
+ sb.open (Logger (logfile));
+#endif
+ std::cout.rdbuf (&sb);
+
+ ret = main (__argc, __argv);
+
+ std::cout.rdbuf(old_rdbuf);
+ }
+ return ret;
+}
+
+#endif
diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp
new file mode 100644
index 0000000000..58731d1c78
--- /dev/null
+++ b/apps/openmw/mwbase/dialoguemanager.hpp
@@ -0,0 +1,58 @@
+#ifndef GAME_MWBASE_DIALOGUEMANAGER_H
+#define GAME_MWBASE_DIALOGUEMANAGER_H
+
+#include <string>
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for dialogue manager (implemented in MWDialogue)
+ class DialogueManager
+ {
+ DialogueManager (const DialogueManager&);
+ ///< not implemented
+
+ DialogueManager& operator= (const DialogueManager&);
+ ///< not implemented
+
+ public:
+
+ DialogueManager() {}
+
+ virtual void clear() = 0;
+
+ virtual ~DialogueManager() {}
+
+ virtual bool isInChoice() const = 0;
+
+ virtual void startDialogue (const MWWorld::Ptr& actor) = 0;
+
+ virtual void addTopic (const std::string& topic) = 0;
+
+ virtual void askQuestion (const std::string& question,int choice) = 0;
+
+ virtual void goodbye() = 0;
+
+ virtual MWWorld::Ptr getActor() const = 0;
+ ///< Return the actor the player is currently talking to.
+
+ virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const = 0;
+
+ //calbacks for the GUI
+ virtual void keywordSelected (const std::string& keyword) = 0;
+ virtual void goodbyeSelected() = 0;
+ virtual void questionAnswered (int answer) = 0;
+
+ virtual bool checkServiceRefused () = 0;
+
+ virtual void persuade (int type) = 0;
+ virtual int getTemporaryDispositionChange () const = 0;
+ virtual void applyTemporaryDispositionChange (int delta) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp
new file mode 100644
index 0000000000..5a13a50ec9
--- /dev/null
+++ b/apps/openmw/mwbase/environment.cpp
@@ -0,0 +1,160 @@
+
+#include "environment.hpp"
+
+#include <cassert>
+
+#include "world.hpp"
+#include "scriptmanager.hpp"
+#include "dialoguemanager.hpp"
+#include "journal.hpp"
+#include "soundmanager.hpp"
+#include "mechanicsmanager.hpp"
+#include "inputmanager.hpp"
+#include "windowmanager.hpp"
+
+MWBase::Environment *MWBase::Environment::sThis = 0;
+
+MWBase::Environment::Environment()
+: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
+ mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0)
+{
+ assert (!sThis);
+ sThis = this;
+}
+
+MWBase::Environment::~Environment()
+{
+ cleanup();
+ sThis = 0;
+}
+
+void MWBase::Environment::setWorld (World *world)
+{
+ mWorld = world;
+}
+
+void MWBase::Environment::setSoundManager (SoundManager *soundManager)
+{
+ mSoundManager = soundManager;
+}
+
+void MWBase::Environment::setScriptManager (ScriptManager *scriptManager)
+{
+ mScriptManager = scriptManager;
+}
+
+void MWBase::Environment::setWindowManager (WindowManager *windowManager)
+{
+ mWindowManager = windowManager;
+}
+
+void MWBase::Environment::setMechanicsManager (MechanicsManager *mechanicsManager)
+{
+ mMechanicsManager = mechanicsManager;
+}
+
+void MWBase::Environment::setDialogueManager (DialogueManager *dialogueManager)
+{
+ mDialogueManager = dialogueManager;
+}
+
+void MWBase::Environment::setJournal (Journal *journal)
+{
+ mJournal = journal;
+}
+
+void MWBase::Environment::setInputManager (InputManager *inputManager)
+{
+ mInputManager = inputManager;
+}
+
+void MWBase::Environment::setFrameDuration (float duration)
+{
+ mFrameDuration = duration;
+}
+
+MWBase::World *MWBase::Environment::getWorld() const
+{
+ assert (mWorld);
+ return mWorld;
+}
+
+MWBase::SoundManager *MWBase::Environment::getSoundManager() const
+{
+ assert (mSoundManager);
+ return mSoundManager;
+}
+
+MWBase::ScriptManager *MWBase::Environment::getScriptManager() const
+{
+ assert (mScriptManager);
+ return mScriptManager;
+}
+
+MWBase::WindowManager *MWBase::Environment::getWindowManager() const
+{
+ assert (mWindowManager);
+ return mWindowManager;
+}
+
+MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const
+{
+ assert (mMechanicsManager);
+ return mMechanicsManager;
+}
+
+MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const
+{
+ assert (mDialogueManager);
+ return mDialogueManager;
+}
+
+MWBase::Journal *MWBase::Environment::getJournal() const
+{
+ assert (mJournal);
+ return mJournal;
+}
+
+MWBase::InputManager *MWBase::Environment::getInputManager() const
+{
+ assert (mInputManager);
+ return mInputManager;
+}
+
+float MWBase::Environment::getFrameDuration() const
+{
+ return mFrameDuration;
+}
+
+void MWBase::Environment::cleanup()
+{
+ delete mMechanicsManager;
+ mMechanicsManager = 0;
+
+ delete mDialogueManager;
+ mDialogueManager = 0;
+
+ delete mJournal;
+ mJournal = 0;
+
+ delete mScriptManager;
+ mScriptManager = 0;
+
+ delete mWorld;
+ mWorld = 0;
+
+ delete mSoundManager;
+ mSoundManager = 0;
+
+ delete mWindowManager;
+ mWindowManager = 0;
+
+ delete mInputManager;
+ mInputManager = 0;
+}
+
+const MWBase::Environment& MWBase::Environment::get()
+{
+ assert (sThis);
+ return *sThis;
+}
diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp
new file mode 100644
index 0000000000..a80e7ef870
--- /dev/null
+++ b/apps/openmw/mwbase/environment.hpp
@@ -0,0 +1,92 @@
+#ifndef GAME_BASE_INVIRONMENT_H
+#define GAME_BASE_INVIRONMENT_H
+
+namespace MWBase
+{
+ class World;
+ class ScriptManager;
+ class DialogueManager;
+ class Journal;
+ class SoundManager;
+ class MechanicsManager;
+ class InputManager;
+ class WindowManager;
+
+ /// \brief Central hub for mw-subsystems
+ ///
+ /// This class allows each mw-subsystem to access any others subsystem's top-level manager class.
+ ///
+ /// \attention Environment takes ownership of the manager class instances it is handed over in
+ /// the set* functions.
+ class Environment
+ {
+ static Environment *sThis;
+
+ World *mWorld;
+ SoundManager *mSoundManager;
+ ScriptManager *mScriptManager;
+ WindowManager *mWindowManager;
+ MechanicsManager *mMechanicsManager;
+ DialogueManager *mDialogueManager;
+ Journal *mJournal;
+ InputManager *mInputManager;
+ float mFrameDuration;
+
+ Environment (const Environment&);
+ ///< not implemented
+
+ Environment& operator= (const Environment&);
+ ///< not implemented
+
+ public:
+
+ Environment();
+
+ ~Environment();
+
+ void setWorld (World *world);
+
+ void setSoundManager (SoundManager *soundManager);
+
+ void setScriptManager (MWBase::ScriptManager *scriptManager);
+
+ void setWindowManager (WindowManager *windowManager);
+
+ void setMechanicsManager (MechanicsManager *mechanicsManager);
+
+ void setDialogueManager (DialogueManager *dialogueManager);
+
+ void setJournal (Journal *journal);
+
+ void setInputManager (InputManager *inputManager);
+
+ void setFrameDuration (float duration);
+ ///< Set length of current frame in seconds.
+
+ World *getWorld() const;
+
+ SoundManager *getSoundManager() const;
+
+ ScriptManager *getScriptManager() const;
+
+ WindowManager *getWindowManager() const;
+
+ MechanicsManager *getMechanicsManager() const;
+
+ DialogueManager *getDialogueManager() const;
+
+ Journal *getJournal() const;
+
+ InputManager *getInputManager() const;
+
+ float getFrameDuration() const;
+
+ void cleanup();
+ ///< Delete all mw*-subsystems.
+
+ static const Environment& get();
+ ///< Return instance of this class.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp
new file mode 100644
index 0000000000..8293cbfa7e
--- /dev/null
+++ b/apps/openmw/mwbase/inputmanager.hpp
@@ -0,0 +1,45 @@
+#ifndef GAME_MWBASE_INPUTMANAGER_H
+#define GAME_MWBASE_INPUTMANAGER_H
+
+#include <string>
+
+#include <components/settings/settings.hpp>
+
+namespace MWBase
+{
+ /// \brief Interface for input manager (implemented in MWInput)
+ class InputManager
+ {
+ InputManager (const InputManager&);
+ ///< not implemented
+
+ InputManager& operator= (const InputManager&);
+ ///< not implemented
+
+ public:
+
+ InputManager() {}
+
+ virtual ~InputManager() {}
+
+ virtual void update(float dt, bool loading) = 0;
+
+ virtual void changeInputMode(bool guiMode) = 0;
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& changed) = 0;
+
+ virtual void setDragDrop(bool dragDrop) = 0;
+
+ virtual void toggleControlSwitch (const std::string& sw, bool value) = 0;
+ virtual bool getControlSwitch (const std::string& sw) = 0;
+
+ virtual std::string getActionDescription (int action) = 0;
+ virtual std::string getActionBindingName (int action) = 0;
+ virtual std::vector<int> getActionSorting () = 0;
+ virtual int getNumActions() = 0;
+ virtual void enableDetectingBindingMode (int action) = 0;
+ virtual void resetToDefaultBindings() = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp
new file mode 100644
index 0000000000..51e51edda9
--- /dev/null
+++ b/apps/openmw/mwbase/journal.hpp
@@ -0,0 +1,75 @@
+#ifndef GAME_MWBASE_JOURNAL_H
+#define GAME_MWBASE_JOURNAL_H
+
+#include <string>
+#include <deque>
+#include <map>
+
+#include "../mwdialogue/journalentry.hpp"
+#include "../mwdialogue/topic.hpp"
+#include "../mwdialogue/quest.hpp"
+
+namespace MWBase
+{
+ /// \brief Interface for the player's journal (implemented in MWDialogue)
+ class Journal
+ {
+ Journal (const Journal&);
+ ///< not implemented
+
+ Journal& operator= (const Journal&);
+ ///< not implemented
+
+ public:
+
+ typedef std::deque<MWDialogue::StampedJournalEntry> TEntryContainer;
+ typedef TEntryContainer::const_iterator TEntryIter;
+ typedef std::map<std::string, MWDialogue::Quest> TQuestContainer; // topc, quest
+ typedef TQuestContainer::const_iterator TQuestIter;
+ typedef std::map<std::string, MWDialogue::Topic> TTopicContainer; // topic-id, topic-content
+ typedef TTopicContainer::const_iterator TTopicIter;
+
+ public:
+
+ Journal() {}
+
+ virtual void clear() = 0;
+
+ virtual ~Journal() {}
+
+ virtual void addEntry (const std::string& id, int index) = 0;
+ ///< Add a journal entry.
+
+ virtual void setJournalIndex (const std::string& id, int index) = 0;
+ ///< Set the journal index without adding an entry.
+
+ virtual int getJournalIndex (const std::string& id) const = 0;
+ ///< Get the journal index.
+
+ virtual void addTopic (const std::string& topicId, const std::string& infoId) = 0;
+
+ virtual TEntryIter begin() const = 0;
+ ///< Iterator pointing to the begin of the main journal.
+ ///
+ /// \note Iterators to main journal entries will never become invalid.
+
+ virtual TEntryIter end() const = 0;
+ ///< Iterator pointing past the end of the main journal.
+
+ virtual TQuestIter questBegin() const = 0;
+ ///< Iterator pointing to the first quest (sorted by topic ID)
+
+ virtual TQuestIter questEnd() const = 0;
+ ///< Iterator pointing past the last quest.
+
+ virtual TTopicIter topicBegin() const = 0;
+ ///< Iterator pointing to the first topic (sorted by topic ID)
+ ///
+ /// \note The topic ID is identical with the user-visible topic string.
+
+ virtual TTopicIter topicEnd() const = 0;
+ ///< Iterator pointing past the last topic.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp
new file mode 100644
index 0000000000..7e09f9b4d7
--- /dev/null
+++ b/apps/openmw/mwbase/mechanicsmanager.hpp
@@ -0,0 +1,120 @@
+#ifndef GAME_MWBASE_MECHANICSMANAGER_H
+#define GAME_MWBASE_MECHANICSMANAGER_H
+
+#include <string>
+#include <vector>
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace ESM
+{
+ struct Class;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for game mechanics manager (implemented in MWMechanics)
+ class MechanicsManager
+ {
+ MechanicsManager (const MechanicsManager&);
+ ///< not implemented
+
+ MechanicsManager& operator= (const MechanicsManager&);
+ ///< not implemented
+
+ public:
+
+ MechanicsManager() {}
+
+ virtual ~MechanicsManager() {}
+
+ virtual void add (const MWWorld::Ptr& ptr) = 0;
+ ///< Register an object for management
+
+ virtual void remove (const MWWorld::Ptr& ptr) = 0;
+ ///< Deregister an object for management
+
+ virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0;
+ ///< Moves an object to a new cell
+
+ virtual void drop (const MWWorld::CellStore *cellStore) = 0;
+ ///< Deregister all objects in the given cell.
+
+ virtual void watchActor (const MWWorld::Ptr& ptr) = 0;
+ ///< On each update look for changes in a previously registered actor and update the
+ /// GUI accordingly.
+
+ virtual void update (float duration, bool paused) = 0;
+ ///< Update objects
+ ///
+ /// \param paused In game type does not currently advance (this usually means some GUI
+ /// component is up).
+
+ virtual void setPlayerName (const std::string& name) = 0;
+ ///< Set player name.
+
+ virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) = 0;
+ ///< Set player race.
+
+ virtual void setPlayerBirthsign (const std::string& id) = 0;
+ ///< Set player birthsign.
+
+ virtual void setPlayerClass (const std::string& id) = 0;
+ ///< Set player class to stock class.
+
+ virtual void setPlayerClass (const ESM::Class& class_) = 0;
+ ///< Set player class to custom class.
+
+ virtual void restoreDynamicStats() = 0;
+ ///< If the player is sleeping, this should be called every hour.
+
+ virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) = 0;
+ ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC.
+
+ virtual int getDerivedDisposition(const MWWorld::Ptr& ptr) = 0;
+ ///< Calculate the diposition of an NPC toward the player.
+
+ virtual int countDeaths (const std::string& id) const = 0;
+ ///< Return the number of deaths for actors with the given ID.
+
+ enum PersuasionType
+ {
+ PT_Admire,
+ PT_Intimidate,
+ PT_Taunt,
+ PT_Bribe10,
+ PT_Bribe100,
+ PT_Bribe1000
+ };
+ virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type,
+ float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) = 0;
+ ///< Perform a persuasion action on NPC
+
+ virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0;
+ ///< Forces an object to refresh its animation state.
+
+ virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0;
+ ///< Run animation for a MW-reference. Calls to this function for references that are currently not
+ /// in the scene should be ignored.
+ ///
+ /// \param mode 0 normal, 1 immediate start, 2 immediate loop
+ /// \param count How many times the animation should be run
+
+ virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0;
+ ///< Skip the animation for the given MW-reference for one frame. Calls to this function for
+ /// references that are currently not in the scene should be ignored.
+
+ virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp
new file mode 100644
index 0000000000..32df2bfa3b
--- /dev/null
+++ b/apps/openmw/mwbase/scriptmanager.hpp
@@ -0,0 +1,64 @@
+#ifndef GAME_MWBASE_SCRIPTMANAGER_H
+#define GAME_MWBASE_SCRIPTMANAGER_H
+
+#include <string>
+
+namespace Interpreter
+{
+ class Context;
+}
+
+namespace Compiler
+{
+ class Locals;
+}
+
+namespace MWScript
+{
+ class GlobalScripts;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for script manager (implemented in MWScript)
+ class ScriptManager
+ {
+ ScriptManager (const ScriptManager&);
+ ///< not implemented
+
+ ScriptManager& operator= (const ScriptManager&);
+ ///< not implemented
+
+ public:
+
+ ScriptManager() {}
+
+ virtual ~ScriptManager() {}
+
+ virtual void resetGlobalScripts() = 0;
+
+ virtual void run (const std::string& name, Interpreter::Context& interpreterContext) = 0;
+ ///< Run the script with the given name (compile first, if not compiled yet)
+
+ virtual bool compile (const std::string& name) = 0;
+ ///< Compile script with the given namen
+ /// \return Success?
+
+ virtual std::pair<int, int> compileAll() = 0;
+ ///< Compile all scripts
+ /// \return count, success
+
+ virtual Compiler::Locals& getLocals (const std::string& name) = 0;
+ ///< Return locals for script \a name.
+
+ virtual MWScript::GlobalScripts& getGlobalScripts() = 0;
+
+ virtual int getLocalIndex (const std::string& scriptId, const std::string& variable,
+ char type) = 0;
+ ///< Return index of the variable of the given name and type in the given script. Will
+ /// throw an exception, if there is no such script or variable or the type does not match.
+
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp
new file mode 100644
index 0000000000..4d764597cf
--- /dev/null
+++ b/apps/openmw/mwbase/soundmanager.hpp
@@ -0,0 +1,153 @@
+#ifndef GAME_MWBASE_SOUNDMANAGER_H
+#define GAME_MWBASE_SOUNDMANAGER_H
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <components/settings/settings.hpp>
+
+#include "../mwworld/ptr.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace MWWorld
+{
+ class CellStore;
+}
+
+namespace MWSound
+{
+ class Sound;
+ class Sound_Decoder;
+ typedef boost::shared_ptr<Sound_Decoder> DecoderPtr;
+}
+
+namespace MWBase
+{
+ typedef boost::shared_ptr<MWSound::Sound> SoundPtr;
+
+ /// \brief Interface for sound manager (implemented in MWSound)
+ class SoundManager
+ {
+ public:
+ /* These must all fit together */
+ enum PlayMode {
+ Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */
+ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */
+ Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */
+ Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position
+ * but do not keep it updated (the sound will not move with
+ * the object and will not stop when the object is deleted. */
+
+ Play_LoopNoEnv = Play_Loop | Play_NoEnv
+ };
+ enum PlayType {
+ Play_TypeSfx = 1<<3, /* Normal SFX sound */
+ Play_TypeVoice = 1<<4, /* Voice sound */
+ Play_TypeFoot = 1<<5, /* Footstep sound */
+ Play_TypeMusic = 1<<6, /* Music track */
+ Play_TypeMovie = 1<<7, /* Movie audio track */
+ Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeFoot|Play_TypeMusic|Play_TypeMovie
+ };
+
+ private:
+
+ SoundManager (const SoundManager&);
+ ///< not implemented
+
+ SoundManager& operator= (const SoundManager&);
+ ///< not implemented
+
+ public:
+
+ SoundManager() {}
+
+ virtual ~SoundManager() {}
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& settings) = 0;
+
+ virtual void stopMusic() = 0;
+ ///< Stops music if it's playing
+
+ virtual void streamMusic(const std::string& filename) = 0;
+ ///< Play a soundifle
+ /// \param filename name of a sound file in "Music/" in the data directory.
+
+ virtual void startRandomTitle() = 0;
+ ///< Starts a random track from the current playlist
+
+ virtual bool isMusicPlaying() = 0;
+ ///< Returns true if music is playing
+
+ virtual void playPlaylist(const std::string &playlist) = 0;
+ ///< Start playing music from the selected folder
+ /// \param name of the folder that contains the playlist
+
+ virtual void say(const MWWorld::Ptr &reference, const std::string& filename) = 0;
+ ///< Make an actor say some text.
+ /// \param filename name of a sound file in "Sound/" in the data directory.
+
+ virtual void say(const std::string& filename) = 0;
+ ///< Say some text, without an actor ref
+ /// \param filename name of a sound file in "Sound/" in the data directory.
+
+ virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const = 0;
+ ///< Is actor not speaking?
+
+ virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0;
+ ///< Stop an actor speaking
+
+ virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0;
+ ///< Play a 2D audio track, using a custom decoder
+
+ virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch,
+ PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal,
+ float offset=0) = 0;
+ ///< Play a sound, independently of 3D-position
+ ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+
+ virtual SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
+ float volume, float pitch, PlayType type=Play_TypeSfx,
+ PlayMode mode=Play_Normal, float offset=0) = 0;
+ ///< Play a sound from an object
+ ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+
+ virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0;
+ ///< Stop the given object from playing the given sound,
+
+ virtual void stopSound3D(const MWWorld::Ptr &reference) = 0;
+ ///< Stop the given object from playing all sounds.
+
+ virtual void stopSound(const MWWorld::CellStore *cell) = 0;
+ ///< Stop all sounds for the given cell.
+
+ virtual void stopSound(const std::string& soundId) = 0;
+ ///< Stop a non-3d looping sound
+
+ virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration) = 0;
+ ///< Fade out given sound (that is already playing) of given object
+ ///< @param reference Reference to object, whose sound is faded out
+ ///< @param soundId ID of the sound to fade out.
+ ///< @param duration Time until volume reaches 0.
+
+ virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const = 0;
+ ///< Is the given sound currently playing on the given object?
+ /// If you want to check if sound played with playSound is playing, use empty Ptr
+
+ virtual void pauseSounds(int types=Play_TypeMask) = 0;
+ ///< Pauses all currently playing sounds, including music.
+
+ virtual void resumeSounds(int types=Play_TypeMask) = 0;
+ ///< Resumes all previously paused sounds.
+
+ virtual void update(float duration) = 0;
+
+ virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp
new file mode 100644
index 0000000000..1cd8672230
--- /dev/null
+++ b/apps/openmw/mwbase/windowmanager.hpp
@@ -0,0 +1,289 @@
+#ifndef GAME_MWBASE_WINDOWMANAGER_H
+#define GAME_MWBASE_WINDOWMANAGER_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <components/settings/settings.hpp>
+
+#include <components/translation/translation.hpp>
+
+#include <components/loadinglistener/loadinglistener.hpp>
+
+#include "../mwmechanics/stat.hpp"
+
+#include "../mwgui/mode.hpp"
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+ class UString;
+}
+
+namespace OEngine
+{
+ namespace GUI
+ {
+ class Layout;
+ }
+}
+
+namespace ESM
+{
+ struct Class;
+}
+
+namespace MWWorld
+{
+ class CellStore;
+ class Ptr;
+}
+
+namespace MWGui
+{
+ class Console;
+ class SpellWindow;
+ class TradeWindow;
+ class TravelWindow;
+ class SpellBuyingWindow;
+ class ConfirmationDialog;
+ class CountDialog;
+ class ScrollWindow;
+ class BookWindow;
+ class InventoryWindow;
+ class ContainerWindow;
+ class DialogueWindow;
+}
+
+namespace SFO
+{
+ class CursorManager;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for widnow manager (implemented in MWGui)
+ class WindowManager
+ {
+ WindowManager (const WindowManager&);
+ ///< not implemented
+
+ WindowManager& operator= (const WindowManager&);
+ ///< not implemented
+
+ public:
+
+ typedef std::vector<int> SkillList;
+
+ WindowManager() {}
+
+ virtual ~WindowManager() {}
+
+ /**
+ * Should be called each frame to update windows/gui elements.
+ * This could mean updating sizes of gui elements or opening
+ * new dialogs.
+ */
+ virtual void update() = 0;
+
+ virtual void setNewGame(bool newgame) = 0;
+
+ virtual void pushGuiMode (MWGui::GuiMode mode) = 0;
+ virtual void popGuiMode() = 0;
+
+ virtual void removeGuiMode (MWGui::GuiMode mode) = 0;
+ ///< can be anywhere in the stack
+
+ virtual void updatePlayer() = 0;
+
+ virtual MWGui::GuiMode getMode() const = 0;
+ virtual bool containsMode(MWGui::GuiMode) const = 0;
+
+ virtual bool isGuiMode() const = 0;
+
+ virtual bool isConsoleMode() const = 0;
+
+ virtual void toggleVisible (MWGui::GuiWindow wnd) = 0;
+
+ virtual void forceHide(MWGui::GuiWindow wnd) = 0;
+ virtual void unsetForceHide(MWGui::GuiWindow wnd) = 0;
+
+ /// Disallow all inventory mode windows
+ virtual void disallowAll() = 0;
+
+ /// Allow one or more windows
+ virtual void allow (MWGui::GuiWindow wnd) = 0;
+
+ virtual bool isAllowed (MWGui::GuiWindow wnd) const = 0;
+
+ /// \todo investigate, if we really need to expose every single lousy UI element to the outside world
+ virtual MWGui::DialogueWindow* getDialogueWindow() = 0;
+ virtual MWGui::ContainerWindow* getContainerWindow() = 0;
+ virtual MWGui::InventoryWindow* getInventoryWindow() = 0;
+ virtual MWGui::BookWindow* getBookWindow() = 0;
+ virtual MWGui::ScrollWindow* getScrollWindow() = 0;
+ virtual MWGui::CountDialog* getCountDialog() = 0;
+ virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
+ virtual MWGui::TradeWindow* getTradeWindow() = 0;
+ virtual MWGui::SpellBuyingWindow* getSpellBuyingWindow() = 0;
+ virtual MWGui::TravelWindow* getTravelWindow() = 0;
+ virtual MWGui::SpellWindow* getSpellWindow() = 0;
+ virtual MWGui::Console* getConsole() = 0;
+
+ virtual MyGUI::Gui* getGui() const = 0;
+
+ virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) = 0;
+
+ /// Set value for the given ID.
+ virtual void setValue (const std::string& id, const MWMechanics::Stat<int>& value) = 0;
+ virtual void setValue (int parSkill, const MWMechanics::Stat<float>& value) = 0;
+ virtual void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) = 0;
+ virtual void setValue (const std::string& id, const std::string& value) = 0;
+ virtual void setValue (const std::string& id, int value) = 0;
+
+ /// Set time left for the player to start drowning (update the drowning bar)
+ /// @param time value from [0,20]
+ virtual void setDrowningTimeLeft (float time) =0;
+
+ virtual void setPlayerClass (const ESM::Class &class_) = 0;
+ ///< set current class of player
+
+ virtual void configureSkills (const SkillList& major, const SkillList& minor) = 0;
+ ///< configure skill groups, each set contains the skill ID for that group.
+
+ virtual void setReputation (int reputation) = 0;
+ ///< set the current reputation value
+
+ virtual void setBounty (int bounty) = 0;
+ ///< set the current bounty value
+
+ virtual void updateSkillArea() = 0;
+ ///< update display of skills, factions, birth sign, reputation and bounty
+
+ virtual void changeCell(MWWorld::CellStore* cell) = 0;
+ ///< change the active cell
+
+ virtual void setPlayerPos(const float x, const float y) = 0;
+ ///< set player position in map space
+
+ virtual void setPlayerDir(const float x, const float y) = 0;
+ ///< set player view direction in map space
+
+ virtual void setFocusObject(const MWWorld::Ptr& focus) = 0;
+ virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0;
+
+ virtual void setCursorVisible(bool visible) = 0;
+ virtual void getMousePosition(int &x, int &y) = 0;
+ virtual void getMousePosition(float &x, float &y) = 0;
+ virtual void setDragDrop(bool dragDrop) = 0;
+ virtual bool getWorldMouseOver() = 0;
+
+ virtual void toggleFogOfWar() = 0;
+
+ virtual void toggleFullHelp() = 0;
+ ///< show extra info in item tooltips (owner, script)
+
+ virtual bool getFullHelp() const = 0;
+
+ virtual void setInteriorMapTexture(const int x, const int y) = 0;
+ ///< set the index of the map texture that should be used (for interiors)
+
+ /// sets the visibility of the drowning bar
+ virtual void setDrowningBarVisibility(bool visible) = 0;
+
+ /// sets the visibility of the hud health/magicka/stamina bars
+ virtual void setHMSVisibility(bool visible) = 0;
+
+ /// sets the visibility of the hud minimap
+ virtual void setMinimapVisibility(bool visible) = 0;
+ virtual void setWeaponVisibility(bool visible) = 0;
+ virtual void setSpellVisibility(bool visible) = 0;
+ virtual void setSneakVisibility(bool visible) = 0;
+
+ virtual void activateQuickKey (int index) = 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;
+ virtual void unsetSelectedSpell() = 0;
+ virtual void unsetSelectedWeapon() = 0;
+
+ virtual void showCrosshair(bool show) = 0;
+ virtual bool getSubtitlesEnabled() = 0;
+ virtual void toggleHud() = 0;
+
+ virtual void disallowMouse() = 0;
+ virtual void allowMouse() = 0;
+ virtual void notifyInputActionBound() = 0;
+
+ virtual void addVisitedLocation(const std::string& name, int x, int y) = 0;
+
+ virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0;
+ ///< Hides dialog and schedules dialog to be deleted.
+
+ virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false) = 0;
+ virtual void staticMessageBox(const std::string& message) = 0;
+ virtual void removeStaticMessageBox() = 0;
+
+ virtual void enterPressed () = 0;
+ virtual void activateKeyPressed () = 0;
+ virtual int readPressedButton() = 0;
+ ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)
+
+ virtual void onFrame (float frameDuration) = 0;
+
+ /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed.
+ virtual std::map<int, MWMechanics::Stat<float> > getPlayerSkillValues() = 0;
+ virtual std::map<int, MWMechanics::Stat<int> > getPlayerAttributeValues() = 0;
+ virtual SkillList getPlayerMinorSkills() = 0;
+ virtual SkillList getPlayerMajorSkills() = 0;
+
+ /**
+ * Fetches a GMST string from the store, if there is no setting with the given
+ * ID or it is not a string the default string is returned.
+ *
+ * @param id Identifier for the GMST setting, e.g. "aName"
+ * @param default Default value if the GMST setting cannot be used.
+ */
+ virtual std::string getGameSettingString(const std::string &id, const std::string &default_) = 0;
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& changed) = 0;
+
+ virtual void windowResized(int x, int y) = 0;
+
+ virtual void executeInConsole (const std::string& path) = 0;
+
+ virtual void enableRest() = 0;
+ virtual bool getRestEnabled() = 0;
+ virtual bool getJournalAllowed() = 0;
+
+ virtual bool getPlayerSleeping() = 0;
+ virtual void wakeUpPlayer() = 0;
+
+ virtual void showCompanionWindow(MWWorld::Ptr actor) = 0;
+ virtual void startSpellMaking(MWWorld::Ptr actor) = 0;
+ virtual void startEnchanting(MWWorld::Ptr actor) = 0;
+ virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0;
+ virtual void startTraining(MWWorld::Ptr actor) = 0;
+ virtual void startRepair(MWWorld::Ptr actor) = 0;
+ virtual void startRepairItem(MWWorld::Ptr item) = 0;
+
+ virtual void showSoulgemDialog (MWWorld::Ptr item) = 0;
+
+ virtual void frameStarted(float dt) = 0;
+
+ virtual void changePointer (const std::string& name) = 0;
+
+ virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
+
+ virtual const Translation::Storage& getTranslationDataStorage() const = 0;
+
+ virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0;
+
+ virtual Loading::Listener* getLoadingScreen() = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
new file mode 100644
index 0000000000..6ca900a4d3
--- /dev/null
+++ b/apps/openmw/mwbase/world.hpp
@@ -0,0 +1,401 @@
+#ifndef GAME_MWBASE_WORLD_H
+#define GAME_MWBASE_WORLD_H
+
+#include <vector>
+
+#include <components/settings/settings.hpp>
+
+#include "../mwworld/globals.hpp"
+#include "../mwworld/ptr.hpp"
+
+namespace Ogre
+{
+ class Vector2;
+ class Vector3;
+}
+
+namespace OEngine
+{
+ namespace Render
+ {
+ class Fader;
+ }
+
+ namespace Physic
+ {
+ class PhysicEngine;
+ }
+}
+
+namespace ESM
+{
+ class ESMReader;
+ struct Position;
+ struct Cell;
+ struct Class;
+ struct Potion;
+ struct Spell;
+ struct NPC;
+}
+
+namespace MWRender
+{
+ class ExternalRendering;
+ class Animation;
+}
+
+namespace MWMechanics
+{
+ class Movement;
+}
+
+namespace MWWorld
+{
+ class Fallback;
+ class CellStore;
+ class Player;
+ class LocalScripts;
+ class TimeStamp;
+ class ESMStore;
+ class RefData;
+
+ typedef std::vector<std::pair<MWWorld::Ptr,MWMechanics::Movement> > PtrMovementList;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for the World (implemented in MWWorld)
+ class World
+ {
+ World (const World&);
+ ///< not implemented
+
+ World& operator= (const World&);
+ ///< not implemented
+
+ public:
+
+ enum RenderMode
+ {
+ Render_CollisionDebug,
+ Render_Wireframe,
+ Render_Pathgrid,
+ Render_Compositors,
+ Render_BoundingBoxes
+ };
+
+ struct DoorMarker
+ {
+ std::string name;
+ float x, y; // world position
+ };
+
+ World() {}
+
+ virtual ~World() {}
+
+ virtual void startNewGame() = 0;
+
+ virtual OEngine::Render::Fader* getFader() = 0;
+ ///< \ŧodo remove this function. Rendering details should not be exposed.
+
+ virtual MWWorld::CellStore *getExterior (int x, int y) = 0;
+
+ virtual MWWorld::CellStore *getInterior (const std::string& name) = 0;
+
+ virtual void setWaterHeight(const float height) = 0;
+
+ virtual void toggleWater() = 0;
+
+ virtual void adjustSky() = 0;
+
+ virtual void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) = 0;
+
+ virtual const MWWorld::Fallback *getFallback () const = 0;
+
+ virtual MWWorld::Player& getPlayer() = 0;
+
+ virtual const MWWorld::ESMStore& getStore() const = 0;
+
+ virtual std::vector<ESM::ESMReader>& getEsmReader() = 0;
+
+ virtual MWWorld::LocalScripts& getLocalScripts() = 0;
+
+ virtual bool hasCellChanged() const = 0;
+ ///< Has the player moved to a different cell, since the last frame?
+
+ virtual bool isCellExterior() const = 0;
+
+ virtual bool isCellQuasiExterior() const = 0;
+
+ 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;
+ ///< 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;
+ ///< see MWRender::LocalMap::getInteriorMapPosition
+
+ virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior) = 0;
+ ///< see MWRender::LocalMap::isPositionExplored
+
+ virtual MWWorld::Globals::Data& getGlobalVariable (const std::string& name) = 0;
+
+ virtual MWWorld::Globals::Data getGlobalVariable (const std::string& name) const = 0;
+
+ virtual char getGlobalVariableType (const std::string& name) const = 0;
+ ///< Return ' ', if there is no global variable with this name.
+
+ virtual std::vector<std::string> getGlobals () const = 0;
+
+ virtual std::string getCurrentCellName() const = 0;
+
+ virtual void removeRefScript (MWWorld::RefData *ref) = 0;
+ //< Remove the script attached to ref from mLocalScripts
+
+ virtual MWWorld::Ptr getPtr (const std::string& name, bool activeOnly) = 0;
+ ///< Return a pointer to a liveCellRef with the given name.
+ /// \param activeOnly do non search inactive cells.
+
+ virtual MWWorld::Ptr getPtrViaHandle (const std::string& handle) = 0;
+ ///< Return a pointer to a liveCellRef with the given Ogre handle.
+
+ virtual MWWorld::Ptr searchPtrViaHandle (const std::string& handle) = 0;
+ ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found
+
+ /// \todo enable reference in the OGRE scene
+ virtual void enable (const MWWorld::Ptr& ptr) = 0;
+
+ /// \todo disable reference in the OGRE scene
+ virtual void disable (const MWWorld::Ptr& ptr) = 0;
+
+ virtual void advanceTime (double hours) = 0;
+ ///< Advance in-game time.
+
+ virtual void setHour (double hour) = 0;
+ ///< Set in-game time hour.
+
+ virtual void setMonth (int month) = 0;
+ ///< Set in-game time month.
+
+ virtual void setDay (int day) = 0;
+ ///< Set in-game time day.
+
+ virtual int getDay() = 0;
+ virtual int getMonth() = 0;
+
+ virtual MWWorld::TimeStamp getTimeStamp() const = 0;
+ ///< Return current in-game time stamp.
+
+ virtual bool toggleSky() = 0;
+ ///< \return Resulting mode
+
+ virtual void changeWeather(const std::string& region, unsigned int id) = 0;
+
+ virtual int getCurrentWeather() const = 0;
+
+ virtual int getMasserPhase() const = 0;
+
+ virtual int getSecundaPhase() const = 0;
+
+ virtual void setMoonColour (bool red) = 0;
+
+ virtual void modRegion(const std::string &regionid, const std::vector<char> &chances) = 0;
+
+ virtual float getTimeScaleFactor() const = 0;
+
+ virtual void changeToInteriorCell (const std::string& cellName,
+ const ESM::Position& position) = 0;
+ ///< Move to interior cell.
+
+ virtual void changeToExteriorCell (const ESM::Position& position) = 0;
+ ///< Move to exterior cell.
+
+ virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0;
+ ///< Return a cell matching the given name or a 0-pointer, if there is no such cell.
+
+ virtual void markCellAsUnchanged() = 0;
+
+ virtual MWWorld::Ptr getFacedObject() = 0;
+ ///< Return pointer to the object the player is looking at, if it is within activation range
+
+ /// Returns a pointer to the object the provided object would hit (if within the
+ /// specified distance), and the point where the hit occurs. This will attempt to
+ /// use the "Head" node as a basis.
+ virtual std::pair<MWWorld::Ptr,Ogre::Vector3> getHitContact(const MWWorld::Ptr &ptr, float distance) = 0;
+
+ virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0;
+ ///< Adjust position after load to be on ground. Must be called after model load.
+
+ virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
+
+ virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
+
+ virtual void
+ moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore &newCell, float x, float y, float z) = 0;
+
+ virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
+
+ virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0;
+
+ 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;
+ ///< 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)
+ const = 0;
+ ///< Convert cell numbers to position.
+
+ virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0;
+ ///< Convert position to cell numbers
+
+ virtual void queueMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &velocity) = 0;
+ ///< Queues movement for \a ptr (in local space), to be applied in the next call to
+ /// doPhysics.
+
+ virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
+ ///< cast a Ray and return true if there is an object in the ray path.
+
+ virtual bool toggleCollisionMode() = 0;
+ ///< Toggle collision mode for player. If disabled player object should ignore
+ /// collisions and gravity.
+ /// \return Resulting mode
+
+ virtual bool toggleRenderMode (RenderMode mode) = 0;
+ ///< Toggle a render mode.
+ ///< \return Resulting mode
+
+ virtual const ESM::Potion *createRecord (const ESM::Potion& record) = 0;
+ ///< Create a new record (of type potion) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Spell *createRecord (const ESM::Spell& record) = 0;
+ ///< Create a new record (of type spell) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Class *createRecord (const ESM::Class& record) = 0;
+ ///< Create a new record (of type class) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Cell *createRecord (const ESM::Cell& record) = 0;
+ ///< Create a new record (of type cell) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::NPC *createRecord(const ESM::NPC &record) = 0;
+ ///< Create a new record (of type npc) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Armor *createRecord (const ESM::Armor& record) = 0;
+ ///< Create a new record (of type armor) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Weapon *createRecord (const ESM::Weapon& record) = 0;
+ ///< Create a new record (of type weapon) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Clothing *createRecord (const ESM::Clothing& record) = 0;
+ ///< Create a new record (of type clothing) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Enchantment *createRecord (const ESM::Enchantment& record) = 0;
+ ///< Create a new record (of type enchantment) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Book *createRecord (const ESM::Book& record) = 0;
+ ///< Create a new record (of type book) in the ESM store.
+ /// \return pointer to created record
+
+ 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
+ /// @param object
+ /// @param cursor X (relative 0-1)
+ /// @param cursor Y (relative 0-1)
+ /// @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 bool canPlaceObject (float cursorX, float cursorY) = 0;
+ ///< @return true if it is possible to place on object at specified cursor location
+
+ virtual void processChangedSettings (const Settings::CategorySettingVector& settings) = 0;
+
+ virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0;
+ virtual bool isSwimming(const MWWorld::Ptr &object) const = 0;
+ ///Is the head of the creature underwater?
+ virtual bool isSubmerged(const MWWorld::Ptr &object) const = 0;
+ virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const = 0;
+ virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0;
+
+ virtual void togglePOV() = 0;
+ virtual void togglePreviewMode(bool enable) = 0;
+ virtual bool toggleVanityMode(bool enable) = 0;
+ virtual void allowVanityMode(bool allow) = 0;
+ virtual void togglePlayerLooking(bool enable) = 0;
+ virtual void changeVanityModeScale(float factor) = 0;
+ virtual bool vanityRotateCamera(float * rot) = 0;
+ virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0;
+
+ virtual void setupPlayer() = 0;
+ virtual void renderPlayer() = 0;
+
+ virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door) = 0;
+ ///< if activated, should this door be opened or closed?
+ virtual void activateDoor(const MWWorld::Ptr& door) = 0;
+ ///< activate (open or close) an non-teleport door
+
+ virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object
+ virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object
+ virtual float getWindSpeed() = 0;
+
+ virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out) = 0;
+ ///< get all containers in active cells owned by this Npc
+ virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out) = 0;
+ ///< get all items in active cells owned by this Npc
+
+ virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
+
+ virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0;
+
+ virtual int canRest() = 0;
+ ///< check if the player is allowed to rest \n
+ /// 0 - yes \n
+ /// 1 - only waiting \n
+ /// 2 - player is underwater \n
+ /// 3 - enemies are nearby (not implemented)
+
+ /// \todo Probably shouldn't be here
+ virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0;
+
+ /// \todo this does not belong here
+ virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
+ virtual void stopVideo() = 0;
+ virtual void frameStarted (float dt, bool paused) = 0;
+
+ /// Find default position inside exterior cell specified by name
+ /// \return false if exterior with given name not exists, true otherwise
+ virtual bool findExteriorPosition(const std::string &name, ESM::Position &pos) = 0;
+
+ /// Find default position inside interior cell specified by name
+ /// \return false if interior with given name not exists, true otherwise
+ virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos) = 0;
+
+ /// Enables or disables use of teleport spell effects (recall, intervention, etc).
+ virtual void enableTeleporting(bool enable) = 0;
+
+ /// Returns true if teleport spell effects are allowed.
+ virtual bool isTeleportingEnabled() const = 0;
+
+ /// Turn actor into werewolf or normal form.
+ virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0;
+
+ /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST.
+ /// It only applies to the current form the NPC is in.
+ virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp
new file mode 100644
index 0000000000..583cb08d39
--- /dev/null
+++ b/apps/openmw/mwclass/activator.cpp
@@ -0,0 +1,129 @@
+
+#include "activator.hpp"
+
+#include <components/esm/loadacti.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld//cellstore.hpp"
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/action.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwrender/actors.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+
+namespace MWClass
+{
+ void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ MWRender::Actors& actors = renderingInterface.getActors();
+ actors.insertActivator(ptr);
+ }
+ }
+
+ void Activator::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr);
+ MWBase::Environment::get().getMechanicsManager()->add(ptr);
+ }
+
+ std::string Activator::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Activator::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+
+ return ref->mBase->mName;
+ }
+
+ std::string Activator::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+
+ return ref->mBase->mScript;
+ }
+
+ void Activator::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Activator);
+
+ registerClass (typeid (ESM::Activator).name(), instance);
+ }
+
+ bool Activator::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+
+ std::string text;
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp())
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ info.text = text;
+
+ return info;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const
+ {
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfActivator");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::NullAction);
+ }
+
+
+ MWWorld::Ptr
+ Activator::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Activator> *ref =
+ ptr.get<ESM::Activator>();
+
+ return MWWorld::Ptr(&cell.mActivators.insert(*ref), &cell);
+ }
+}
diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp
new file mode 100644
index 0000000000..1e772ef4f2
--- /dev/null
+++ b/apps/openmw/mwclass/activator.hpp
@@ -0,0 +1,43 @@
+#ifndef GAME_MWCLASS_ACTIVATOR_H
+#define GAME_MWCLASS_ACTIVATOR_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Activator : public MWWorld::Class
+ {
+
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp
new file mode 100644
index 0000000000..697b755792
--- /dev/null
+++ b/apps/openmw/mwclass/apparatus.cpp
@@ -0,0 +1,164 @@
+
+#include "apparatus.hpp"
+
+#include <components/esm/loadappa.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionalchemy.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+namespace MWClass
+{
+ void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Apparatus::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Apparatus::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Apparatus::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Apparatus::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Apparatus::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Apparatus::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Apparatus);
+
+ registerClass (typeid (ESM::Apparatus).name(), instance);
+ }
+
+ std::string Apparatus::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Apparatus Up");
+ }
+
+ std::string Apparatus::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Apparatus Down");
+ }
+
+ std::string Apparatus::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Apparatus::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ 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}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+ info.text = text;
+
+ return info;
+ }
+
+
+ boost::shared_ptr<MWWorld::Action> Apparatus::use (const MWWorld::Ptr& ptr) const
+ {
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionAlchemy());
+ }
+
+ MWWorld::Ptr
+ Apparatus::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+
+ return MWWorld::Ptr(&cell.mAppas.insert(*ref), &cell);
+ }
+
+ bool Apparatus::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Apparatus;
+ }
+
+ float Apparatus::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Apparatus> *ref =
+ ptr.get<ESM::Apparatus>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp
new file mode 100644
index 0000000000..17b8b9254f
--- /dev/null
+++ b/apps/openmw/mwclass/apparatus.hpp
@@ -0,0 +1,64 @@
+#ifndef GAME_MWCLASS_APPARATUS_H
+#define GAME_MWCLASS_APPARATUS_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Apparatus : public MWWorld::Class
+ {
+
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp
new file mode 100644
index 0000000000..a511207c41
--- /dev/null
+++ b/apps/openmw/mwclass/armor.cpp
@@ -0,0 +1,389 @@
+
+#include "armor.hpp"
+
+#include <components/esm/loadarmo.hpp>
+#include <components/esm/loadskil.hpp>
+#include <components/esm/loadgmst.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+namespace MWClass
+{
+ void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Armor::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Armor::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Armor::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Armor::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ bool Armor::hasItemHealth (const MWWorld::Ptr& ptr) const
+ {
+ return true;
+ }
+
+ int Armor::getItemMaxHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mData.mHealth;
+ }
+
+ std::string Armor::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Armor::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ std::vector<int> slots;
+
+ const int size = 11;
+
+ static const int sMapping[size][2] =
+ {
+ { ESM::Armor::Helmet, MWWorld::InventoryStore::Slot_Helmet },
+ { ESM::Armor::Cuirass, MWWorld::InventoryStore::Slot_Cuirass },
+ { ESM::Armor::LPauldron, MWWorld::InventoryStore::Slot_LeftPauldron },
+ { ESM::Armor::RPauldron, MWWorld::InventoryStore::Slot_RightPauldron },
+ { ESM::Armor::Greaves, MWWorld::InventoryStore::Slot_Greaves },
+ { ESM::Armor::Boots, MWWorld::InventoryStore::Slot_Boots },
+ { ESM::Armor::LGauntlet, MWWorld::InventoryStore::Slot_LeftGauntlet },
+ { ESM::Armor::RGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet },
+ { ESM::Armor::Shield, MWWorld::InventoryStore::Slot_CarriedLeft },
+ { ESM::Armor::LBracer, MWWorld::InventoryStore::Slot_LeftGauntlet },
+ { ESM::Armor::RBracer, MWWorld::InventoryStore::Slot_RightGauntlet }
+ };
+
+ for (int i=0; i<size; ++i)
+ if (sMapping[i][0]==ref->mBase->mData.mType)
+ {
+ slots.push_back (int (sMapping[i][1]));
+ break;
+ }
+
+ return std::make_pair (slots, false);
+ }
+
+ int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ std::string typeGmst;
+
+ switch (ref->mBase->mData.mType)
+ {
+ case ESM::Armor::Helmet: typeGmst = "iHelmWeight"; break;
+ case ESM::Armor::Cuirass: typeGmst = "iCuirassWeight"; break;
+ case ESM::Armor::LPauldron:
+ case ESM::Armor::RPauldron: typeGmst = "iPauldronWeight"; break;
+ case ESM::Armor::Greaves: typeGmst = "iGreavesWeight"; break;
+ case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break;
+ case ESM::Armor::LGauntlet:
+ case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break;
+ case ESM::Armor::Shield: typeGmst = "iShieldWeight"; break;
+ case ESM::Armor::LBracer:
+ case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break;
+ }
+
+ if (typeGmst.empty())
+ return -1;
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ float iWeight = gmst.find (typeGmst)->getInt();
+
+ if (iWeight * gmst.find ("fLightMaxMod")->getFloat()>=
+ ref->mBase->mData.mWeight)
+ return ESM::Skill::LightArmor;
+
+ if (iWeight * gmst.find ("fMedMaxMod")->getFloat()>=
+ ref->mBase->mData.mWeight)
+ return ESM::Skill::MediumArmor;
+
+ return ESM::Skill::HeavyArmor;
+ }
+
+ int Armor::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Armor::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Armor);
+
+ registerClass (typeid (ESM::Armor).name(), instance);
+ }
+
+ std::string Armor::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ int es = getEquipmentSkill(ptr);
+ if (es == ESM::Skill::LightArmor)
+ return std::string("Item Armor Light Up");
+ else if (es == ESM::Skill::MediumArmor)
+ return std::string("Item Armor Medium Up");
+ else
+ return std::string("Item Armor Heavy Up");
+ }
+
+ std::string Armor::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ int es = getEquipmentSkill(ptr);
+ if (es == ESM::Skill::LightArmor)
+ return std::string("Item Armor Light Down");
+ else if (es == ESM::Skill::MediumArmor)
+ return std::string("Item Armor Medium Down");
+ else
+ return std::string("Item Armor Heavy Down");
+ }
+
+ std::string Armor::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Armor::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ // get armor type string (light/medium/heavy)
+ int armorType = getEquipmentSkill(ptr);
+ std::string typeText;
+ if (armorType == ESM::Skill::LightArmor)
+ typeText = "#{sLight}";
+ else if (armorType == ESM::Skill::MediumArmor)
+ typeText = "#{sMedium}";
+ else
+ typeText = "#{sHeavy}";
+
+ text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(ref->mBase->mData.mArmor);
+
+ int remainingHealth = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mHealth;
+ text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/"
+ + 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}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.enchant = ref->mBase->mEnchant;
+ if (!info.enchant.empty())
+ info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge;
+
+ info.text = text;
+
+ return info;
+ }
+
+ std::string Armor::getEnchantment (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mEnchant;
+ }
+
+ void Armor::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ ESM::Armor newItem = *ref->mBase;
+ newItem.mId="";
+ newItem.mName=newName;
+ newItem.mData.mEnchant=enchCharge;
+ newItem.mEnchant=enchId;
+ const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
+ ref->mBase = record;
+ }
+
+ 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);
+
+ // slots that this item can be equipped in
+ std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr);
+
+ 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;
+
+ 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(*slot == MWWorld::InventoryStore::Slot_CarriedLeft)
+ {
+ MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+
+ if(weapon == invStore.end())
+ return std::make_pair(1,"");
+
+ if(weapon->getTypeName() == typeid(ESM::Weapon).name() &&
+ (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow))
+ {
+ return std::make_pair(3,"");
+ }
+ return std::make_pair(1,"");
+ }
+ }
+ return std::make_pair(1,"");
+ }
+
+ boost::shared_ptr<MWWorld::Action> Armor::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Armor::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return MWWorld::Ptr(&cell.mArmors.insert(*ref), &cell);
+ }
+
+ float Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+
+ return ref->mBase->mData.mEnchant/10.f;
+ }
+
+ bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Armor;
+ }
+
+ float Armor::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref =
+ ptr.get<ESM::Armor>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp
new file mode 100644
index 0000000000..d8d09d5bb0
--- /dev/null
+++ b/apps/openmw/mwclass/armor.hpp
@@ -0,0 +1,88 @@
+#ifndef GAME_MWCLASS_ARMOR_H
+#define GAME_MWCLASS_ARMOR_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Armor : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const;
+ ///< \return Item health data available?
+
+ virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const;
+ /// Return the index of the skill this item corresponds to when equiopped or -1, if there is
+ /// no such skill.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const;
+ ///< @return the enchantment ID if the object is enchanted, otherwise an empty string
+
+ virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
+
+ virtual std::pair<int, std::string> canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const;
+ ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n
+ /// Second item in the pair specifies the error message
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp
new file mode 100644
index 0000000000..a692b30d8f
--- /dev/null
+++ b/apps/openmw/mwclass/book.cpp
@@ -0,0 +1,209 @@
+#include "book.hpp"
+
+#include <components/esm/loadbook.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actionread.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+namespace MWClass
+{
+ void Book::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Book::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Book::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Book::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Book::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionRead(ptr));
+ }
+
+ std::string Book::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Book::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Book::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Book);
+
+ registerClass (typeid (ESM::Book).name(), instance);
+ }
+
+ std::string Book::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Book Up");
+ }
+
+ std::string Book::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Book Down");
+ }
+
+ std::string Book::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Book::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.enchant = ref->mBase->mEnchant;
+
+ info.text = text;
+
+ return info;
+ }
+
+ std::string Book::getEnchantment (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mEnchant;
+ }
+
+ void Book::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ ESM::Book newItem = *ref->mBase;
+ newItem.mId="";
+ newItem.mName=newName;
+ newItem.mData.mIsScroll = 1;
+ newItem.mData.mEnchant=enchCharge;
+ newItem.mEnchant=enchId;
+ const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
+ ref->mBase = record;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Book::use (const MWWorld::Ptr& ptr) const
+ {
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionRead(ptr));
+ }
+
+ MWWorld::Ptr
+ Book::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return MWWorld::Ptr(&cell.mBooks.insert(*ref), &cell);
+ }
+
+ float Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+
+ return ref->mBase->mData.mEnchant/10.f;
+ }
+
+ bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Books;
+ }
+
+ float Book::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Book> *ref =
+ ptr.get<ESM::Book>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp
new file mode 100644
index 0000000000..7fb8a95077
--- /dev/null
+++ b/apps/openmw/mwclass/book.hpp
@@ -0,0 +1,69 @@
+#ifndef GAME_MWCLASS_BOOK_H
+#define GAME_MWCLASS_BOOK_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Book : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const;
+ ///< @return the enchantment ID if the object is enchanted, otherwise an empty string
+
+ virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp
new file mode 100644
index 0000000000..e9538a6cb4
--- /dev/null
+++ b/apps/openmw/mwclass/classes.cpp
@@ -0,0 +1,50 @@
+
+#include "classes.hpp"
+
+#include "activator.hpp"
+#include "creature.hpp"
+#include "npc.hpp"
+#include "weapon.hpp"
+#include "armor.hpp"
+#include "potion.hpp"
+#include "apparatus.hpp"
+#include "book.hpp"
+#include "clothing.hpp"
+#include "container.hpp"
+#include "door.hpp"
+#include "ingredient.hpp"
+#include "creaturelevlist.hpp"
+#include "itemlevlist.hpp"
+#include "light.hpp"
+#include "lockpick.hpp"
+#include "misc.hpp"
+#include "probe.hpp"
+#include "repair.hpp"
+#include "static.hpp"
+
+namespace MWClass
+{
+ void registerClasses()
+ {
+ Activator::registerSelf();
+ Creature::registerSelf();
+ Npc::registerSelf();
+ Weapon::registerSelf();
+ Armor::registerSelf();
+ Potion::registerSelf();
+ Apparatus::registerSelf();
+ Book::registerSelf();
+ Clothing::registerSelf();
+ Container::registerSelf();
+ Door::registerSelf();
+ Ingredient::registerSelf();
+ CreatureLevList::registerSelf();
+ ItemLevList::registerSelf();
+ Light::registerSelf();
+ Lockpick::registerSelf();
+ Miscellaneous::registerSelf();
+ Probe::registerSelf();
+ Repair::registerSelf();
+ Static::registerSelf();
+ }
+}
diff --git a/apps/openmw/mwclass/classes.hpp b/apps/openmw/mwclass/classes.hpp
new file mode 100644
index 0000000000..0ab90b677b
--- /dev/null
+++ b/apps/openmw/mwclass/classes.hpp
@@ -0,0 +1,10 @@
+#ifndef GAME_MWCLASS_CLASSES_H
+#define GAME_MWCLASS_CLASSES_H
+
+namespace MWClass
+{
+ void registerClasses();
+ ///< register all known classes
+}
+
+#endif
diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp
new file mode 100644
index 0000000000..2dbb7ee6fd
--- /dev/null
+++ b/apps/openmw/mwclass/clothing.cpp
@@ -0,0 +1,308 @@
+
+#include "clothing.hpp"
+
+#include <components/esm/loadclot.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Clothing::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Clothing::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Clothing::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Clothing::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Clothing::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Clothing::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ 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));
+ }
+ else
+ {
+ const int size = 9;
+
+ static const int sMapping[size][2] =
+ {
+ { ESM::Clothing::Shirt, MWWorld::InventoryStore::Slot_Shirt },
+ { ESM::Clothing::Belt, MWWorld::InventoryStore::Slot_Belt },
+ { ESM::Clothing::Robe, MWWorld::InventoryStore::Slot_Robe },
+ { ESM::Clothing::Pants, MWWorld::InventoryStore::Slot_Pants },
+ { ESM::Clothing::Shoes, MWWorld::InventoryStore::Slot_Boots },
+ { ESM::Clothing::LGlove, MWWorld::InventoryStore::Slot_LeftGauntlet },
+ { ESM::Clothing::RGlove, MWWorld::InventoryStore::Slot_RightGauntlet },
+ { ESM::Clothing::Skirt, MWWorld::InventoryStore::Slot_Skirt },
+ { ESM::Clothing::Amulet, MWWorld::InventoryStore::Slot_Amulet }
+ };
+
+ for (int i=0; i<size; ++i)
+ if (sMapping[i][0]==ref->mBase->mData.mType)
+ {
+ slots.push_back (int (sMapping[i][1]));
+ break;
+ }
+ }
+
+ return std::make_pair (slots, false);
+ }
+
+ int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ if (ref->mBase->mData.mType==ESM::Clothing::Shoes)
+ return ESM::Skill::Unarmored;
+
+ return -1;
+ }
+
+ int Clothing::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Clothing::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Clothing);
+
+ registerClass (typeid (ESM::Clothing).name(), instance);
+ }
+
+ std::string Clothing::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ if (ref->mBase->mData.mType == 8)
+ {
+ return std::string("Item Ring Up");
+ }
+ return std::string("Item Clothes Up");
+ }
+
+ std::string Clothing::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ if (ref->mBase->mData.mType == 8)
+ {
+ return std::string("Item Ring Down");
+ }
+ return std::string("Item Clothes Down");
+ }
+
+ std::string Clothing::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Clothing::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.enchant = ref->mBase->mEnchant;
+ if (!info.enchant.empty())
+ info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge;
+
+ info.text = text;
+
+ return info;
+ }
+
+ std::string Clothing::getEnchantment (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mEnchant;
+ }
+
+ void Clothing::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ ESM::Clothing newItem = *ref->mBase;
+ newItem.mId="";
+ newItem.mName=newName;
+ newItem.mData.mEnchant=enchCharge;
+ newItem.mEnchant=enchId;
+ const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
+ ref->mBase = record;
+ }
+
+ 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::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;
+
+ 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}");
+ }
+ }
+ }
+ }
+ return std::make_pair (1, "");
+ }
+
+ boost::shared_ptr<MWWorld::Action> Clothing::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Clothing::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return MWWorld::Ptr(&cell.mClothes.insert(*ref), &cell);
+ }
+
+ float Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+
+ return ref->mBase->mData.mEnchant/10.f;
+ }
+
+ bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Clothing;
+ }
+
+ float Clothing::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Clothing> *ref =
+ ptr.get<ESM::Clothing>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp
new file mode 100644
index 0000000000..e2e1188a1a
--- /dev/null
+++ b/apps/openmw/mwclass/clothing.hpp
@@ -0,0 +1,82 @@
+#ifndef GAME_MWCLASS_CLOTHING_H
+#define GAME_MWCLASS_CLOTHING_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Clothing : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const;
+ /// Return the index of the skill this item corresponds to when equiopped or -1, if there is
+ /// no such skill.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const;
+ ///< @return the enchantment ID if the object is enchanted, otherwise an empty string
+
+ virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
+
+ virtual std::pair<int, std::string> canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const;
+ ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that.
+ /// Second item in the pair specifies the error message
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp
new file mode 100644
index 0000000000..783eabff68
--- /dev/null
+++ b/apps/openmw/mwclass/container.cpp
@@ -0,0 +1,263 @@
+
+#include "container.hpp"
+
+#include <components/esm/loadcont.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/customdata.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/actionapply.hpp"
+#include "../mwworld/actionopen.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+namespace
+{
+ struct CustomData : public MWWorld::CustomData
+ {
+ MWWorld::ContainerStore mContainerStore;
+
+ virtual MWWorld::CustomData *clone() const;
+ };
+
+ MWWorld::CustomData *CustomData::clone() const
+ {
+ return new CustomData (*this);
+ }
+}
+
+namespace MWClass
+{
+ void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
+ {
+ if (!ptr.getRefData().getCustomData())
+ {
+ std::auto_ptr<CustomData> data (new CustomData);
+
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ data->mContainerStore.fill(
+ ref->mBase->mInventory, ptr.getCellRef().mOwner, MWBase::Environment::get().getWorld()->getStore());
+
+ // store
+ ptr.getRefData().setCustomData (data.release());
+ }
+ }
+
+ void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Container::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr);
+ }
+
+ std::string Container::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ boost::shared_ptr<MWWorld::Action> Container::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ return boost::shared_ptr<MWWorld::Action> (new MWWorld::NullAction ());
+
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfContainer");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+
+ const std::string lockedSound = "LockedChest";
+ const std::string trapActivationSound = "Disarm Trap Fail";
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player);
+
+ bool needKey = ptr.getCellRef().mLockLevel>0;
+ bool hasKey = false;
+ std::string keyName;
+
+ // make key id lowercase
+ std::string keyId = ptr.getCellRef().mKey;
+ Misc::StringUtils::toLower(keyId);
+ for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it)
+ {
+ std::string refId = it->getCellRef().mRefID;
+ Misc::StringUtils::toLower(refId);
+ if (refId == keyId)
+ {
+ hasKey = true;
+ keyName = MWWorld::Class::get(*it).getName(*it);
+ }
+ }
+
+ if (needKey && hasKey)
+ {
+ MWBase::Environment::get().getWindowManager ()->messageBox (keyName + " #{sKeyUsed}");
+ ptr.getCellRef().mLockLevel = 0;
+ // using a key disarms the trap
+ ptr.getCellRef().mTrap = "";
+ }
+
+
+ if (!needKey || hasKey)
+ {
+ if(ptr.getCellRef().mTrap.empty())
+ {
+ boost::shared_ptr<MWWorld::Action> action (new MWWorld::ActionOpen(ptr));
+ return action;
+ }
+ 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));
+ action->setSound(trapActivationSound);
+ ptr.getCellRef().mTrap = "";
+ return action;
+ }
+ }
+ else
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction);
+ action->setSound(lockedSound);
+ return action;
+ }
+ }
+
+ std::string Container::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ return ref->mBase->mName;
+ }
+
+ MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr)
+ const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
+ }
+
+ std::string Container::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ return ref->mBase->mScript;
+ }
+
+ void Container::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Container);
+
+ registerClass (typeid (ESM::Container).name(), instance);
+ }
+
+ bool Container::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName;
+
+ std::string text;
+ if (ref->mRef.mLockLevel > 0)
+ text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel);
+ if (ref->mRef.mTrap != "")
+ text += "\n#{sTrapped}";
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ float Container::getCapacity (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ return ref->mBase->mWeight;
+ }
+
+ float Container::getEncumbrance (const MWWorld::Ptr& ptr) const
+ {
+ return getContainerStore (ptr).getWeight();
+ }
+
+ void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const
+ {
+ if (lockLevel<0)
+ lockLevel = 0;
+
+ ptr.getCellRef().mLockLevel = lockLevel;
+ }
+
+ void Container::unlock (const MWWorld::Ptr& ptr) const
+ {
+ ptr.getCellRef().mLockLevel = 0;
+ }
+
+ MWWorld::Ptr
+ Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Container> *ref =
+ ptr.get<ESM::Container>();
+
+ return MWWorld::Ptr(&cell.mContainers.insert(*ref), &cell);
+ }
+}
diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp
new file mode 100644
index 0000000000..006e4bd223
--- /dev/null
+++ b/apps/openmw/mwclass/container.hpp
@@ -0,0 +1,63 @@
+#ifndef GAME_MWCLASS_CONTAINER_H
+#define GAME_MWCLASS_CONTAINER_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Container : public MWWorld::Class
+ {
+ void ensureCustomData (const MWWorld::Ptr& ptr) const;
+
+
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const;
+ ///< Return container store
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual float getCapacity (const MWWorld::Ptr& ptr) const;
+ ///< Return total weight that fits into the object. Throws an exception, if the object can't
+ /// hold other objects.
+
+ virtual float getEncumbrance (const MWWorld::Ptr& ptr) const;
+ ///< Returns total weight of objects inside this object (including modifications from magic
+ /// effects). Throws an exception, if the object can't hold other objects.
+
+ virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const;
+ ///< Lock object
+
+ virtual void unlock (const MWWorld::Ptr& ptr) const;
+ ///< Unlock object
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp
new file mode 100644
index 0000000000..20f95ab0e9
--- /dev/null
+++ b/apps/openmw/mwclass/creature.cpp
@@ -0,0 +1,454 @@
+
+#include "creature.hpp"
+
+#include <components/esm/loadcrea.hpp>
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/magiceffects.hpp"
+#include "../mwmechanics/movement.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontalk.hpp"
+#include "../mwworld/actionopen.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/customdata.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+
+#include "../mwrender/renderinginterface.hpp"
+#include "../mwrender/actors.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+namespace
+{
+ struct CustomData : public MWWorld::CustomData
+ {
+ MWMechanics::CreatureStats mCreatureStats;
+ MWWorld::ContainerStore mContainerStore;
+ MWMechanics::Movement mMovement;
+
+ virtual MWWorld::CustomData *clone() const;
+ };
+
+ MWWorld::CustomData *CustomData::clone() const
+ {
+ return new CustomData (*this);
+ }
+}
+
+namespace MWClass
+{
+ void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
+ {
+ if (!ptr.getRefData().getCustomData())
+ {
+ std::auto_ptr<CustomData> data (new CustomData);
+
+ static bool inited = false;
+ if(!inited)
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ fMinWalkSpeedCreature = gmst.find("fMinWalkSpeedCreature");
+ fMaxWalkSpeedCreature = gmst.find("fMaxWalkSpeedCreature");
+
+ inited = true;
+ }
+
+ 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.setHealth (ref->mBase->mData.mHealth);
+ data->mCreatureStats.setMagicka (ref->mBase->mData.mMana);
+ data->mCreatureStats.setFatigue (ref->mBase->mData.mFatigue);
+
+ data->mCreatureStats.setLevel(ref->mBase->mData.mLevel);
+
+ data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage);
+
+ data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello);
+ data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight);
+ data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee);
+ data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm);
+
+ // spells
+ for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
+ iter!=ref->mBase->mSpells.mList.end(); ++iter)
+ data->mCreatureStats.getSpells().add (*iter);
+
+ // inventory
+ data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr),
+ MWBase::Environment::get().getWorld()->getStore());
+
+ // store
+ ptr.getRefData().setCustomData (data.release());
+ }
+ }
+
+ std::string Creature::getId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return ref->mBase->mId;
+ }
+
+ void Creature::adjustPosition(const MWWorld::Ptr& ptr) const
+ {
+ MWBase::Environment::get().getWorld()->adjustPosition(ptr);
+ }
+
+ void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ MWRender::Actors& actors = renderingInterface.getActors();
+ actors.insertCreature(ptr);
+ }
+
+ void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addActor(ptr);
+ MWBase::Environment::get().getMechanicsManager()->add(ptr);
+ }
+
+ std::string Creature::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+ assert (ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Creature::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return ref->mBase->mName;
+ }
+
+ MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
+ }
+
+
+ void Creature::hit(const MWWorld::Ptr& ptr, int type) const
+ {
+ }
+
+ void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
+ {
+ // NOTE: 'object' and/or 'attacker' may be empty.
+
+ if(!successful)
+ {
+ // TODO: Handle HitAttemptOnMe script function
+
+ // Missed
+ MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
+ return;
+ }
+
+ if(!object.isEmpty())
+ getCreatureStats(ptr).setLastHitObject(MWWorld::Class::get(object).getId(object));
+
+ if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
+ {
+ const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript;
+ /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
+ if(!script.empty())
+ ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
+ }
+
+ if(ishealth)
+ {
+ if(damage > 0.0f)
+ MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
+ float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
+ setActorHealth(ptr, health, attacker);
+ }
+ else
+ {
+ MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
+ fatigue.setCurrent(fatigue.getCurrent() - damage);
+ getCreatureStats(ptr).setFatigue(fatigue);
+ }
+ }
+
+ void Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
+ {
+ MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
+ bool wasDead = crstats.isDead();
+
+ MWMechanics::DynamicStat<float> stat(crstats.getHealth());
+ stat.setCurrent(health);
+ crstats.setHealth(stat);
+
+ if(!wasDead && crstats.isDead())
+ {
+ // actor was just killed
+ }
+ else if(wasDead && !crstats.isDead())
+ {
+ // actor was just resurrected
+ }
+ }
+
+
+ boost::shared_ptr<MWWorld::Action> Creature::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+
+ if(getCreatureStats(ptr).isDead())
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
+ }
+
+ MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr)
+ const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
+ }
+
+ std::string Creature::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return ref->mBase->mScript;
+ }
+
+ bool Creature::isEssential (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return ref->mBase->mFlags & ESM::Creature::Essential;
+ }
+
+ void Creature::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Creature);
+
+ registerClass (typeid (ESM::Creature).name(), instance);
+ }
+
+ bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ /// \todo We don't want tooltips for Creatures in combat mode.
+
+ return true;
+ }
+
+ float Creature::getSpeed(const MWWorld::Ptr &ptr) const
+ {
+ MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
+ float walkSpeed = fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified()
+ * (fMaxWalkSpeedCreature->getFloat() - fMinWalkSpeedCreature->getFloat());
+ /// \todo what about the rest?
+ return walkSpeed;
+ }
+
+ MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
+ }
+
+ Ogre::Vector3 Creature::getMovementVector (const MWWorld::Ptr& ptr) const
+ {
+ MWMechanics::Movement &movement = getMovementSettings(ptr);
+ Ogre::Vector3 vec(movement.mPosition);
+ movement.mPosition[0] = 0.0f;
+ movement.mPosition[1] = 0.0f;
+ movement.mPosition[2] = 0.0f;
+ return vec;
+ }
+
+ Ogre::Vector3 Creature::getRotationVector (const MWWorld::Ptr& ptr) const
+ {
+ MWMechanics::Movement &movement = getMovementSettings(ptr);
+ Ogre::Vector3 vec(movement.mRotation);
+ movement.mRotation[0] = 0.0f;
+ movement.mRotation[1] = 0.0f;
+ movement.mRotation[2] = 0.0f;
+ return vec;
+ }
+
+ MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName;
+
+ std::string text;
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp())
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ info.text = text;
+
+ return info;
+ }
+
+ float Creature::getArmorRating (const MWWorld::Ptr& ptr) const
+ {
+ /// \todo add Shield magic effect magnitude here, controlled by a GMST (Vanilla vs MCP behaviour)
+ return 0.f;
+ }
+
+ float Creature::getCapacity (const MWWorld::Ptr& ptr) const
+ {
+ const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
+ return stats.getAttribute(0).getModified()*5;
+ }
+
+ float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const
+ {
+ float weight = getContainerStore (ptr).getWeight();
+
+ const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
+
+ weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).mMagnitude;
+
+ weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).mMagnitude;
+
+ if (weight<0)
+ weight = 0;
+
+ return weight;
+ }
+
+
+ int Creature::getServices(const MWWorld::Ptr &actor) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
+ if (ref->mBase->mHasAI)
+ return ref->mBase->mAiData.mServices;
+ else
+ return 0;
+ }
+
+ bool Creature::isPersistent(const MWWorld::Ptr &actor) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
+ return ref->mBase->mPersistent;
+ }
+
+ std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
+ {
+ const MWWorld::Store<ESM::SoundGenerator> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::SoundGenerator>();
+
+ int type = getSndGenTypeFromName(ptr, name);
+ if(type >= 0)
+ {
+ std::vector<const ESM::SoundGenerator*> sounds;
+ sounds.reserve(8);
+
+ std::string ptrid = Creature::getId(ptr);
+ MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
+ while(sound != store.end())
+ {
+ if(type == sound->mType && !sound->mCreature.empty() &&
+ Misc::StringUtils::ciEqual(ptrid.substr(0, sound->mCreature.size()),
+ sound->mCreature))
+ sounds.push_back(&*sound);
+ ++sound;
+ }
+ if(!sounds.empty())
+ return sounds[(int)(rand()/(RAND_MAX+1.0)*sounds.size())]->mSound;
+ }
+
+ return "";
+ }
+
+ MWWorld::Ptr
+ Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return MWWorld::Ptr(&cell.mCreatures.insert(*ref), &cell);
+ }
+
+ int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name)
+ {
+ if(name == "left")
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ if(world->isUnderwater(ptr.getCell(), pos))
+ return 2;
+ if(world->isOnGround(ptr))
+ return 0;
+ return -1;
+ }
+ if(name == "right")
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ if(world->isUnderwater(ptr.getCell(), pos))
+ return 3;
+ if(world->isOnGround(ptr))
+ return 1;
+ return -1;
+ }
+ if(name == "swimleft")
+ return 2;
+ if(name == "swimright")
+ return 3;
+ if(name == "moan")
+ return 4;
+ if(name == "roar")
+ return 5;
+ if(name == "scream")
+ return 6;
+ if(name == "land")
+ return 7;
+
+ throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
+ }
+
+ const ESM::GameSetting* Creature::fMinWalkSpeedCreature;
+ const ESM::GameSetting* Creature::fMaxWalkSpeedCreature;
+}
diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp
new file mode 100644
index 0000000000..0d8694ff81
--- /dev/null
+++ b/apps/openmw/mwclass/creature.hpp
@@ -0,0 +1,105 @@
+#ifndef GAME_MWCLASS_CREATURE_H
+#define GAME_MWCLASS_CREATURE_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Creature : public MWWorld::Class
+ {
+ void ensureCustomData (const MWWorld::Ptr& ptr) const;
+
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name);
+
+ static const ESM::GameSetting *fMinWalkSpeedCreature;
+ static const ESM::GameSetting *fMaxWalkSpeedCreature;
+
+ public:
+
+ virtual std::string getId (const MWWorld::Ptr& ptr) const;
+ ///< Return ID of \a ptr
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual void adjustPosition(const MWWorld::Ptr& ptr) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const;
+ ///< Return creature stats
+
+ virtual void hit(const MWWorld::Ptr& ptr, int type) const;
+
+ virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
+
+ virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual MWWorld::ContainerStore& getContainerStore (
+ const MWWorld::Ptr& ptr) const;
+ ///< Return container store
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual float getCapacity (const MWWorld::Ptr& ptr) const;
+ ///< Return total weight that fits into the object. Throws an exception, if the object can't
+ /// hold other objects.
+
+ virtual float getEncumbrance (const MWWorld::Ptr& ptr) const;
+ ///< Returns total weight of objects inside this object (including modifications from magic
+ /// effects). Throws an exception, if the object can't hold other objects.
+
+ virtual float getArmorRating (const MWWorld::Ptr& ptr) const;
+ ///< @return combined armor rating of this actor
+
+ virtual bool isEssential (const MWWorld::Ptr& ptr) const;
+ ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable)
+
+ virtual int getServices (const MWWorld::Ptr& actor) const;
+
+ virtual bool isPersistent (const MWWorld::Ptr& ptr) const;
+
+ virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const;
+
+ virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const;
+ ///< Return desired movement.
+
+ virtual Ogre::Vector3 getMovementVector (const MWWorld::Ptr& ptr) const;
+ ///< Return desired movement vector (determined based on movement settings,
+ /// stance and stats).
+
+ virtual Ogre::Vector3 getRotationVector (const MWWorld::Ptr& ptr) const;
+ ///< Return desired rotations, as euler angles.
+
+ float getSpeed (const MWWorld::Ptr& ptr) const;
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool
+ isActor() const {
+ return true;
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp
new file mode 100644
index 0000000000..53dd34bb48
--- /dev/null
+++ b/apps/openmw/mwclass/creaturelevlist.cpp
@@ -0,0 +1,19 @@
+
+#include "creaturelevlist.hpp"
+
+#include <components/esm/loadlevlist.hpp>
+
+namespace MWClass
+{
+ std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
+ {
+ return "";
+ }
+
+ void CreatureLevList::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new CreatureLevList);
+
+ registerClass (typeid (ESM::CreatureLevList).name(), instance);
+ }
+}
diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp
new file mode 100644
index 0000000000..81965efd58
--- /dev/null
+++ b/apps/openmw/mwclass/creaturelevlist.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_MWCLASS_CREATURELEVLIST_H
+#define GAME_MWCLASS_CREATURELEVLIST_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class CreatureLevList : public MWWorld::Class
+ {
+ public:
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ static void registerSelf();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp
new file mode 100644
index 0000000000..3a0e4d0bab
--- /dev/null
+++ b/apps/openmw/mwclass/door.cpp
@@ -0,0 +1,278 @@
+
+#include "door.hpp"
+
+#include <components/esm/loaddoor.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#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 "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Door::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr);
+ }
+
+ std::string Door::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Door::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+
+ if (ref->mRef.mTeleport && !ref->mRef.mDestCell.empty()) // TODO doors that lead to exteriors
+ return ref->mRef.mDestCell;
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Door::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
+
+ const std::string &openSound = ref->mBase->mOpenSound;
+ const std::string &closeSound = ref->mBase->mCloseSound;
+ const std::string lockedSound = "LockedDoor";
+ const std::string trapActivationSound = "Disarm Trap Fail";
+
+ MWWorld::ContainerStore &invStore = get(actor).getContainerStore(actor);
+
+ bool needKey = ptr.getCellRef().mLockLevel>0;
+ bool hasKey = false;
+ std::string keyName;
+
+ // make key id lowercase
+ std::string keyId = ptr.getCellRef().mKey;
+ Misc::StringUtils::toLower(keyId);
+ for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it)
+ {
+ std::string refId = it->getCellRef().mRefID;
+ Misc::StringUtils::toLower(refId);
+ if (refId == keyId)
+ {
+ hasKey = true;
+ keyName = get(*it).getName(*it);
+ }
+ }
+
+ if (needKey && hasKey)
+ {
+ if(actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
+ MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}");
+ ptr.getCellRef().mLockLevel = 0;
+ // using a key disarms the trap
+ ptr.getCellRef().mTrap = "";
+ }
+
+ if (!needKey || hasKey)
+ {
+ 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));
+ action->setSound(trapActivationSound);
+ ptr.getCellRef().mTrap = "";
+
+ return action;
+ }
+
+ if (ref->mRef.mTeleport)
+ {
+ // teleport door
+ /// \todo remove this if clause once ActionTeleport can also support other actors
+ if (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()==actor)
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTeleport (ref->mRef.mDestCell, ref->mRef.mDoorDest));
+
+ action->setSound(openSound);
+
+ return action;
+ }
+ else
+ {
+ // another NPC or a creature is using the door
+ return boost::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction);
+ }
+ }
+ else
+ {
+ // animated door
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionDoor(ptr));
+ if (MWBase::Environment::get().getWorld()->getOpenOrCloseDoor(ptr))
+ {
+ MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
+ closeSound, 0.5);
+ float offset = ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265 * 2.0;
+ action->setSoundOffset(offset);
+ action->setSound(openSound);
+ }
+ else
+ {
+ MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
+ openSound, 0.5);
+ float offset = 1.0 - ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265 * 2.0;
+ //most if not all door have closing bang somewhere in the middle of the sound,
+ //so we divide offset by two
+ action->setSoundOffset(offset * 0.5);
+ action->setSound(closeSound);
+ }
+
+ return action;
+ }
+ }
+ else
+ {
+ // locked, and we can't open.
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction);
+ action->setSound(lockedSound);
+ return action;
+ }
+ }
+
+ void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const
+ {
+ if (lockLevel<0)
+ lockLevel = 0;
+
+ ptr.getCellRef().mLockLevel = lockLevel;
+ }
+
+ void Door::unlock (const MWWorld::Ptr& ptr) const
+ {
+ ptr.getCellRef().mLockLevel = 0;
+ }
+
+ std::string Door::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+
+ return ref->mBase->mScript;
+ }
+
+ void Door::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Door);
+
+ registerClass (typeid (ESM::Door).name(), instance);
+ }
+
+ bool Door::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName;
+
+ std::string text;
+
+ if (ref->mRef.mTeleport)
+ {
+ text += "\n#{sTo}";
+ text += "\n" + getDestination(*ref);
+ }
+
+ if (ref->mRef.mLockLevel > 0)
+ text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel);
+ if (ref->mRef.mTrap != "")
+ text += "\n#{sTrapped}";
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp())
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+
+ info.text = text;
+
+ return info;
+ }
+
+ std::string Door::getDestination (const MWWorld::LiveCellRef<ESM::Door>& door)
+ {
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ std::string dest;
+ if (door.mRef.mDestCell != "")
+ {
+ // door leads to an interior, use interior name as tooltip
+ dest = door.mRef.mDestCell;
+ }
+ else
+ {
+ // door leads to exterior, use cell name (if any), otherwise translated region name
+ int x,y;
+ MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.mDoorDest.pos[0], door.mRef.mDoorDest.pos[1], x, y);
+ const ESM::Cell* cell = store.get<ESM::Cell>().find(x,y);
+ if (cell->mName != "")
+ dest = cell->mName;
+ else
+ {
+ const ESM::Region* region =
+ store.get<ESM::Region>().find(cell->mRegion);
+
+ //name as is, not a token
+ return region->mName;
+ }
+ }
+
+ return "#{sCell=" + dest + "}";
+ }
+
+ MWWorld::Ptr
+ Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Door> *ref =
+ ptr.get<ESM::Door>();
+
+ return MWWorld::Ptr(&cell.mDoors.insert(*ref), &cell);
+ }
+}
diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp
new file mode 100644
index 0000000000..05ba0248b5
--- /dev/null
+++ b/apps/openmw/mwclass/door.hpp
@@ -0,0 +1,52 @@
+#ifndef GAME_MWCLASS_DOOR_H
+#define GAME_MWCLASS_DOOR_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Door : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ static std::string getDestination (const MWWorld::LiveCellRef<ESM::Door>& door);
+ ///< @return destination cell name or token
+
+ virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const;
+ ///< Lock object
+
+ virtual void unlock (const MWWorld::Ptr& ptr) const;
+ ///< Unlock object
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp
new file mode 100644
index 0000000000..f629cc15d1
--- /dev/null
+++ b/apps/openmw/mwclass/ingredient.cpp
@@ -0,0 +1,204 @@
+
+#include "ingredient.hpp"
+
+#include <components/esm/loadingr.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/actioneat.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ std::string Ingredient::getId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return ref->mBase->mId;
+ }
+
+ void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Ingredient::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Ingredient::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Ingredient::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Ingredient::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Ingredient::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+
+ boost::shared_ptr<MWWorld::Action> Ingredient::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action (new MWWorld::ActionEat (ptr));
+
+ action->setSound ("Swallow");
+
+ return action;
+ }
+
+ void Ingredient::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Ingredient);
+
+ registerClass (typeid (ESM::Ingredient).name(), instance);
+ }
+
+ std::string Ingredient::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Ingredient Up");
+ }
+
+ std::string Ingredient::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Ingredient Down");
+ }
+
+ std::string Ingredient::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Ingredient::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
+ int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
+
+ MWGui::Widgets::SpellEffectList list;
+ for (int i=0; i<4; ++i)
+ {
+ if (ref->mBase->mData.mEffectID[i] < 0)
+ continue;
+ MWGui::Widgets::SpellEffectParams params;
+ params.mEffectID = ref->mBase->mData.mEffectID[i];
+ 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));
+
+ list.push_back(params);
+ }
+ info.effects = list;
+
+ info.text = text;
+
+ return info;
+ }
+
+ MWWorld::Ptr
+ Ingredient::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+
+ return MWWorld::Ptr(&cell.mIngreds.insert(*ref), &cell);
+ }
+
+ bool Ingredient::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Ingredients;
+ }
+
+
+ float Ingredient::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ ptr.get<ESM::Ingredient>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp
new file mode 100644
index 0000000000..690dd601a7
--- /dev/null
+++ b/apps/openmw/mwclass/ingredient.hpp
@@ -0,0 +1,66 @@
+#ifndef GAME_MWCLASS_INGREDIENT_H
+#define GAME_MWCLASS_INGREDIENT_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Ingredient : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual std::string getId (const MWWorld::Ptr& ptr) const;
+ ///< Return ID of \a ptr
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp
new file mode 100644
index 0000000000..6ed9ab2e53
--- /dev/null
+++ b/apps/openmw/mwclass/itemlevlist.cpp
@@ -0,0 +1,19 @@
+
+#include "itemlevlist.hpp"
+
+#include <components/esm/loadlevlist.hpp>
+
+namespace MWClass
+{
+ std::string ItemLevList::getName (const MWWorld::Ptr& ptr) const
+ {
+ return "";
+ }
+
+ void ItemLevList::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new ItemLevList);
+
+ registerClass (typeid (ESM::ItemLevList).name(), instance);
+ }
+}
diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp
new file mode 100644
index 0000000000..0b71b072c8
--- /dev/null
+++ b/apps/openmw/mwclass/itemlevlist.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_MWCLASS_ITEMLEVLIST_H
+#define GAME_MWCLASS_ITEMLEVLIST_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class ItemLevList : public MWWorld::Class
+ {
+ public:
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ static void registerSelf();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp
new file mode 100644
index 0000000000..7eefc6167c
--- /dev/null
+++ b/apps/openmw/mwclass/light.cpp
@@ -0,0 +1,228 @@
+
+#include "light.hpp"
+
+#include <components/esm/loadligh.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+ assert (ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+
+ if(!model.empty())
+ physics.addObject(ptr,ref->mBase->mData.mFlags & ESM::Light::Carry);
+
+ if (!ref->mBase->mSound.empty())
+ MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,
+ MWBase::SoundManager::Play_TypeSfx,
+ MWBase::SoundManager::Play_Loop);
+ }
+
+ std::string Light::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+ assert (ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Light::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ if (ref->mBase->mModel.empty())
+ return "";
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Light::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::NullAction());
+
+ MWWorld::LiveCellRef<ESM::Light> *ref = ptr.get<ESM::Light>();
+ if(!(ref->mBase->mData.mFlags&ESM::Light::Carry))
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction());
+
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Light::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Light::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ std::vector<int> slots;
+
+ if (ref->mBase->mData.mFlags & ESM::Light::Carry)
+ slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft));
+
+ return std::make_pair (slots, false);
+ }
+
+ int Light::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Light::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Light);
+
+ registerClass (typeid (ESM::Light).name(), instance);
+ }
+
+ std::string Light::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Misc Up");
+ }
+
+ std::string Light::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Misc Down");
+ }
+
+
+ std::string Light::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Light::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Light::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Light::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+
+ return MWWorld::Ptr(&cell.mLights.insert(*ref), &cell);
+ }
+
+ bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Lights;
+ }
+
+ float Light::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Light> *ref =
+ ptr.get<ESM::Light>();
+ return ref->mBase->mData.mWeight;
+ }
+
+ std::pair<int, std::string> Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
+ {
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
+ MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+
+ if(weapon == invStore.end())
+ return std::make_pair(1,"");
+
+ /// \todo the 2h check is repeated many times; put it in a function
+ if(weapon->getTypeName() == typeid(ESM::Weapon).name() &&
+ (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow))
+ {
+ return std::make_pair(3,"");
+ }
+ return std::make_pair(1,"");
+ }
+}
diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp
new file mode 100644
index 0000000000..79d662763b
--- /dev/null
+++ b/apps/openmw/mwclass/light.hpp
@@ -0,0 +1,69 @@
+#ifndef GAME_MWCLASS_LIGHT_H
+#define GAME_MWCLASS_LIGHT_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Light : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+
+ std::pair<int, std::string> canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp
new file mode 100644
index 0000000000..5931a0102d
--- /dev/null
+++ b/apps/openmw/mwclass/lockpick.cpp
@@ -0,0 +1,190 @@
+
+#include "lockpick.hpp"
+
+#include <components/esm/loadlock.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Lockpick::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Lockpick::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Lockpick::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Lockpick::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ std::vector<int> slots;
+
+ slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+
+ return std::make_pair (slots, false);
+ }
+
+ int Lockpick::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Lockpick::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Lockpick);
+
+ registerClass (typeid (ESM::Lockpick).name(), instance);
+ }
+
+ std::string Lockpick::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Lockpick Up");
+ }
+
+ std::string Lockpick::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Lockpick Down");
+ }
+
+ std::string Lockpick::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Lockpick::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses;
+
+ 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}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Lockpick::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Lockpick::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return MWWorld::Ptr(&cell.mLockpicks.insert(*ref), &cell);
+ }
+
+ bool Lockpick::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Picks;
+ }
+
+ int Lockpick::getItemMaxHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+
+ return ref->mBase->mData.mUses;
+ }
+
+ float Lockpick::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Lockpick> *ref =
+ ptr.get<ESM::Lockpick>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp
new file mode 100644
index 0000000000..a7cf3791fc
--- /dev/null
+++ b/apps/openmw/mwclass/lockpick.hpp
@@ -0,0 +1,70 @@
+#ifndef GAME_MWCLASS_LOCKPICK_H
+#define GAME_MWCLASS_LOCKPICK_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Lockpick : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp
new file mode 100644
index 0000000000..6247191a92
--- /dev/null
+++ b/apps/openmw/mwclass/misc.cpp
@@ -0,0 +1,255 @@
+
+#include "misc.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <components/esm/loadmisc.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/actionsoulgem.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace
+{
+bool isGold (const MWWorld::Ptr& ptr)
+{
+ return Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_001")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_005")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_010")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100");
+}
+}
+
+namespace MWClass
+{
+ void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Miscellaneous::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Miscellaneous::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Miscellaneous::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Miscellaneous::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ int value = (ptr.getCellRef().mGoldValue == 1) ? ref->mBase->mData.mValue : ptr.getCellRef().mGoldValue;
+
+ if (ptr.getCellRef().mSoul != "")
+ {
+ const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(ref->mRef.mSoul);
+ value *= creature->mData.mSoul;
+ }
+
+ return value;
+ }
+
+ void Miscellaneous::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Miscellaneous);
+
+ registerClass (typeid (ESM::Miscellaneous).name(), instance);
+ }
+
+ std::string Miscellaneous::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ if (isGold(ptr))
+ return std::string("Item Gold Up");
+ return std::string("Item Misc Up");
+ }
+
+ std::string Miscellaneous::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ if (isGold(ptr))
+ return std::string("Item Gold Down");
+ return std::string("Item Misc Down");
+ }
+
+ std::string Miscellaneous::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Miscellaneous::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+
+ MWGui::ToolTipInfo info;
+
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ int count = ptr.getRefData().getCount();
+
+ bool gold = isGold(ptr);
+
+ if (gold && ptr.getCellRef().mGoldValue != 1)
+ count = ptr.getCellRef().mGoldValue;
+ else if (gold)
+ count *= ref->mBase->mData.mValue;
+
+ std::string countString;
+ if (!gold)
+ countString = MWGui::ToolTips::getCountString(count);
+ else // gold displays its count also if it's 1.
+ countString = " (" + boost::lexical_cast<std::string>(count) + ")";
+
+ info.caption = ref->mBase->mName + countString;
+ info.icon = ref->mBase->mIcon;
+
+ if (ref->mRef.mSoul != "")
+ {
+ const ESM::Creature *creature = store.get<ESM::Creature>().find(ref->mRef.mSoul);
+ info.caption += " (" + creature->mName + ")";
+ }
+
+ std::string text;
+
+ if (!gold)
+ {
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}");
+ }
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ MWWorld::Ptr
+ Miscellaneous::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::Ptr newPtr;
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ if (isGold(ptr)) {
+ int goldAmount = ptr.getRefData().getCount();
+
+ std::string base = "Gold_001";
+ if (goldAmount >= 100)
+ base = "Gold_100";
+ else if (goldAmount >= 25)
+ base = "Gold_025";
+ else if (goldAmount >= 10)
+ base = "Gold_010";
+ else if (goldAmount >= 5)
+ base = "Gold_005";
+
+ // Really, I have no idea why moving ref out of conditional
+ // scope causes list::push_back throwing std::bad_alloc
+ MWWorld::ManualRef newRef(store, base);
+ 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 =
+ ptr.get<ESM::Miscellaneous>();
+ newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell);
+ }
+ return newPtr;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Miscellaneous::use (const MWWorld::Ptr& ptr) const
+ {
+ if (ptr.getCellRef().mSoul == "")
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::NullAction());
+ else
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionSoulgem(ptr));
+ }
+
+ bool Miscellaneous::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ item.get<ESM::Miscellaneous>();
+
+ return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc);
+ }
+
+ float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
+ ptr.get<ESM::Miscellaneous>();
+ return ref->mBase->mData.mWeight;
+ }
+
+}
diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp
new file mode 100644
index 0000000000..16a8e8c055
--- /dev/null
+++ b/apps/openmw/mwclass/misc.hpp
@@ -0,0 +1,63 @@
+#ifndef GAME_MWCLASS_MISC_H
+#define GAME_MWCLASS_MISC_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Miscellaneous : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
new file mode 100644
index 0000000000..073d1b1b92
--- /dev/null
+++ b/apps/openmw/mwclass/npc.cpp
@@ -0,0 +1,1077 @@
+
+#include "npc.hpp"
+
+#include <memory>
+
+#include <boost/algorithm/string.hpp>
+
+#include <OgreSceneNode.h>
+
+#include <components/esm/loadnpc.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/movement.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontalk.hpp"
+#include "../mwworld/actionopen.hpp"
+#include "../mwworld/failedaction.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/customdata.hpp"
+#include "../mwworld/physicssystem.hpp"
+
+#include "../mwrender/actors.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+namespace
+{
+ const Ogre::Radian kOgrePi (Ogre::Math::PI);
+ const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
+
+ struct CustomData : public MWWorld::CustomData
+ {
+ MWMechanics::NpcStats mNpcStats;
+ MWMechanics::Movement mMovement;
+ MWWorld::InventoryStore mInventoryStore;
+
+ virtual MWWorld::CustomData *clone() const;
+ };
+
+ MWWorld::CustomData *CustomData::clone() const
+ {
+ return new CustomData (*this);
+ }
+
+ void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
+ {
+ // race bonus
+ const ESM::Race *race =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
+
+ 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);
+ }
+
+ // class bonus
+ const ESM::Class *class_ =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
+
+ for (int i=0; i<2; ++i)
+ {
+ int attribute = class_->mData.mAttribute[i];
+ if (attribute>=0 && attribute<8)
+ {
+ creatureStats.getAttribute(attribute).setBase (
+ creatureStats.getAttribute(attribute).getBase() + 10);
+ }
+ }
+
+ // skill bonus
+ for (int attribute=0; attribute<ESM::Attribute::Length; ++attribute)
+ {
+ float modifierSum = 0;
+
+ for (int j=0; j<ESM::Skill::Length; ++j)
+ {
+ const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j);
+
+ if (skill->mData.mAttribute != attribute)
+ continue;
+
+ // is this a minor or major skill?
+ float add=0.2;
+ for (int k=0; k<5; ++k)
+ {
+ if (class_->mData.mSkills[k][0] == j)
+ add=0.5;
+ }
+ for (int k=0; k<5; ++k)
+ {
+ if (class_->mData.mSkills[k][1] == j)
+ add=1.0;
+ }
+ modifierSum += add;
+ }
+ creatureStats.getAttribute(attribute).setBase ( std::min(creatureStats.getAttribute(attribute).getBase()
+ + static_cast<int>((level-1) * modifierSum+0.5), 100) );
+ }
+
+ // initial health
+ int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
+ int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
+
+ int multiplier = 3;
+
+ if (class_->mData.mSpecialization == ESM::Class::Combat)
+ multiplier += 2;
+ else if (class_->mData.mSpecialization == ESM::Class::Stealth)
+ multiplier += 1;
+
+ if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
+ || class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
+ multiplier += 1;
+
+ creatureStats.setHealth(static_cast<int> (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
+ }
+}
+
+namespace MWClass
+{
+ void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
+ {
+ static bool inited = false;
+ if(!inited)
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ fMinWalkSpeed = gmst.find("fMinWalkSpeed");
+ fMaxWalkSpeed = gmst.find("fMaxWalkSpeed");
+ fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect");
+ fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier");
+ fAthleticsRunBonus = gmst.find("fAthleticsRunBonus");
+ fBaseRunMultiplier = gmst.find("fBaseRunMultiplier");
+ fMinFlySpeed = gmst.find("fMinFlySpeed");
+ fMaxFlySpeed = gmst.find("fMaxFlySpeed");
+ fSwimRunBase = gmst.find("fSwimRunBase");
+ fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult");
+ fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase");
+ fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier");
+ fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase");
+ fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
+ fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
+ fWereWolfRunMult = gmst.find("fWereWolfRunMult");
+
+ inited = true;
+ }
+ if (!ptr.getRefData().getCustomData())
+ {
+ std::auto_ptr<CustomData> data(new CustomData);
+
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+
+ // NPC stats
+ if (!ref->mBase->mFaction.empty())
+ {
+ std::string faction = ref->mBase->mFaction;
+ Misc::StringUtils::toLower(faction);
+ if(ref->mBase->mNpdt52.mGold != -10)
+ {
+ data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank;
+ }
+ else
+ {
+ data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt12.mRank;
+ }
+ }
+
+ // creature stats
+ if(ref->mBase->mNpdt52.mGold != -10)
+ {
+ for (int i=0; i<27; ++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.setHealth (ref->mBase->mNpdt52.mHealth);
+ data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana);
+ data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue);
+
+ data->mNpcStats.setLevel(ref->mBase->mNpdt52.mLevel);
+ data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
+ data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
+ }
+ else
+ {
+ for (int i=0; i<3; ++i)
+ data->mNpcStats.setDynamic (i, 10);
+
+ data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
+ data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
+ data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
+
+ autoCalculateAttributes(ref->mBase, data->mNpcStats);
+ }
+
+ data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
+
+ data->mNpcStats.setAiSetting (0, ref->mBase->mAiData.mHello);
+ data->mNpcStats.setAiSetting (1, ref->mBase->mAiData.mFight);
+ data->mNpcStats.setAiSetting (2, ref->mBase->mAiData.mFlee);
+ data->mNpcStats.setAiSetting (3, ref->mBase->mAiData.mAlarm);
+
+ // spells
+ for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
+ iter!=ref->mBase->mSpells.mList.end(); ++iter)
+ data->mNpcStats.getSpells().add (*iter);
+
+ // inventory
+ data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr),
+ MWBase::Environment::get().getWorld()->getStore());
+
+ // store
+ ptr.getRefData().setCustomData (data.release());
+
+ getInventoryStore(ptr).autoEquip(ptr);
+ }
+ }
+
+ std::string Npc::getId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+
+ return ref->mBase->mId;
+ }
+
+ void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
+ {
+ MWBase::Environment::get().getWorld()->adjustPosition(ptr);
+ }
+
+ void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr));
+ }
+
+ void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ physics.addActor(ptr);
+ MWBase::Environment::get().getMechanicsManager()->add(ptr);
+ }
+
+ bool Npc::isPersistent(const MWWorld::Ptr &actor) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
+ return ref->mBase->mPersistent;
+ }
+
+ std::string Npc::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+ assert(ref->mBase != NULL);
+
+ //std::string headID = ref->mBase->mHead;
+
+ //int end = headID.find_last_of("head_") - 4;
+ //std::string bodyRaceID = headID.substr(0, end);
+
+ std::string model = "meshes\\base_anim.nif";
+ const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
+ if(race->mData.mFlags & ESM::Race::Beast)
+ model = "meshes\\base_animkna.nif";
+
+ return model;
+
+ }
+
+ std::string Npc::getName (const MWWorld::Ptr& ptr) const
+ {
+ if(getNpcStats(ptr).isWerewolf())
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ return gmst.find("sWerewolfPopup")->getString();
+ }
+
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+ return ref->mBase->mName;
+ }
+
+ MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
+ }
+
+ MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
+ }
+
+
+ void Npc::hit(const MWWorld::Ptr& ptr, int type) const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ // Get the weapon used (if hand-to-hand, weapon = inv.end())
+ MWWorld::InventoryStore &inv = getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr());
+ if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name())
+ weapon = MWWorld::Ptr();
+
+ float dist = 100.0f * (!weapon.isEmpty() ?
+ weapon.get<ESM::Weapon>()->mBase->mData.mReach :
+ gmst.find("fHandToHandReach")->getFloat());
+ // TODO: Use second to work out the hit angle and where to spawn the blood effect
+ MWWorld::Ptr victim = world->getHitContact(ptr, dist).first;
+ if(victim.isEmpty()) // Didn't hit anything
+ return;
+
+ const MWWorld::Class &othercls = MWWorld::Class::get(victim);
+ if(!othercls.isActor()) // Can't hit non-actors
+ return;
+ MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
+ if(otherstats.isDead()) // Can't hit dead actors
+ return;
+
+ if(ptr.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->setEnemy(victim);
+
+ int weapskill = ESM::Skill::HandToHand;
+ if(!weapon.isEmpty())
+ weapskill = get(weapon).getEquipmentSkill(weapon);
+
+ MWMechanics::NpcStats &stats = getNpcStats(ptr);
+ const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
+ float hitchance = stats.getSkill(weapskill).getModified() +
+ (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
+ (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
+ hitchance *= stats.getFatigueTerm();
+ hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude -
+ mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude;
+ hitchance -= otherstats.getEvasion();
+
+ if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
+ {
+ othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
+ return;
+ }
+
+ bool healthdmg;
+ float damage = 0.0f;
+ if(!weapon.isEmpty())
+ {
+ const bool weaphashealth = get(weapon).hasItemHealth(weapon);
+ const unsigned char *attack = NULL;
+ if(type == MWMechanics::CreatureStats::AT_Chop)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
+ else if(type == MWMechanics::CreatureStats::AT_Slash)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
+ else if(type == MWMechanics::CreatureStats::AT_Thrust)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
+ if(attack)
+ {
+ damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
+ damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
+ if(weaphashealth)
+ {
+ int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
+ if(weapon.getCellRef().mCharge == -1)
+ weapon.getCellRef().mCharge = weapmaxhealth;
+ damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth;
+ }
+ if(!othercls.hasDetected(victim, ptr))
+ {
+ damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
+ MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
+ }
+ weapon.getCellRef().mCharge -= std::min(std::max(1,
+ (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())),
+ weapon.getCellRef().mCharge);
+ }
+ healthdmg = true;
+ }
+ else
+ {
+ // Note: MCP contains an option to include Strength in hand-to-hand damage
+ // calculations. Some mods recommend using it, so we may want to include am
+ // option for it.
+ float minstrike = gmst.find("fMinHandToHandMult")->getFloat();
+ float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat();
+ damage = stats.getSkill(weapskill).getModified();
+ damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
+ if(!othercls.hasDetected(victim, ptr))
+ {
+ damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
+ MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
+ }
+
+ healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f);
+ if(stats.isWerewolf())
+ {
+ healthdmg = true;
+ // GLOB instead of GMST because it gets updated during a quest
+ const MWWorld::Store<ESM::Global> &glob = world->getStore().get<ESM::Global>();
+ damage *= glob.find("WerewolfClawMult")->mValue.getFloat();
+ }
+ if(healthdmg)
+ damage *= gmst.find("fHandtoHandHealthPer")->getFloat();
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(stats.isWerewolf())
+ {
+ const ESM::Sound *sound = world->getStore().get<ESM::Sound>().searchRandom("WolfHit");
+ if(sound)
+ sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
+ }
+ else
+ sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
+ }
+ if(ptr.getRefData().getHandle() == "player")
+ skillUsageSucceeded(ptr, weapskill, 0);
+
+ othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
+ }
+
+ void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+
+ // NOTE: 'object' and/or 'attacker' may be empty.
+
+ if(!successful)
+ {
+ // TODO: Handle HitAttemptOnMe script function
+
+ // Missed
+ sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
+ return;
+ }
+
+ if(!object.isEmpty())
+ getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
+
+ if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
+ {
+ const std::string &script = ptr.get<ESM::NPC>()->mBase->mScript;
+ /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
+ if(!script.empty())
+ ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
+ }
+
+ if(damage > 0.0f)
+ {
+ // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
+ // something, alert the character controller, scripts, etc.
+
+ MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
+
+ if(object.isEmpty())
+ {
+ if(ishealth)
+ damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
+ }
+ else if(ishealth)
+ {
+ // Hit percentages:
+ // cuirass = 30%
+ // shield, helmet, greaves, boots, pauldrons = 10% each
+ // guantlets = 5% each
+ static const int hitslots[20] = {
+ MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
+ MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
+ MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
+ MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft,
+ MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet,
+ MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves,
+ MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots,
+ MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron,
+ MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron,
+ MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet
+ };
+ int hitslot = hitslots[(int)(::rand()/(RAND_MAX+1.0)*20.0)];
+
+ float damagediff = damage;
+ damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
+ damagediff -= damage;
+
+ MWWorld::InventoryStore &inv = getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
+ MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
+ if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name())
+ {
+ ESM::CellRef &armorref = armor.getCellRef();
+ if(armorref.mCharge == -1)
+ armorref.mCharge = armor.get<ESM::Armor>()->mBase->mData.mHealth;
+ armorref.mCharge -= std::min(std::max(1, (int)damagediff),
+ armorref.mCharge);
+ switch(get(armor).getEquipmentSkill(armor))
+ {
+ case ESM::Skill::LightArmor:
+ sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
+ break;
+ case ESM::Skill::MediumArmor:
+ sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
+ break;
+ case ESM::Skill::HeavyArmor:
+ sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
+ break;
+ }
+ }
+ }
+ }
+
+ if(ishealth)
+ {
+ if(damage > 0.0f)
+ sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
+ float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
+ setActorHealth(ptr, health, attacker);
+ }
+ else
+ {
+ MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
+ fatigue.setCurrent(fatigue.getCurrent() - damage);
+ getCreatureStats(ptr).setFatigue(fatigue);
+ }
+ }
+
+ void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
+ {
+ MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
+ bool wasDead = crstats.isDead();
+
+ MWMechanics::DynamicStat<float> stat(crstats.getHealth());
+ stat.setCurrent(health);
+ crstats.setHealth(stat);
+
+ if(!wasDead && crstats.isDead())
+ {
+ // actor was just killed
+ }
+ else if(wasDead && !crstats.isDead())
+ {
+ // actor was just resurrected
+ }
+ }
+
+
+ boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+ if(getCreatureStats(ptr).isDead())
+ 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
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
+ }
+
+ MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
+ const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
+ }
+
+ MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr)
+ const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
+ }
+
+ std::string Npc::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+
+ return ref->mBase->mScript;
+ }
+
+ void Npc::setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const
+ {
+ MWMechanics::NpcStats& stats = getNpcStats (ptr);
+
+ switch (stance)
+ {
+ case Run:
+
+ stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force);
+ break;
+
+ case Sneak:
+
+ stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force);
+ break;
+
+ case Combat:
+
+ throw std::runtime_error ("combat stance not enforcable for NPCs");
+ }
+ }
+
+ void Npc::setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const
+ {
+ MWMechanics::NpcStats& stats = getNpcStats (ptr);
+
+ switch (stance)
+ {
+ case Run:
+
+ stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set);
+ break;
+
+ case Sneak:
+
+ stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set);
+ break;
+
+ case Combat:
+
+ // Combat stance ignored for now; need to be determined based on draw state instead of
+ // being maunally set.
+ break;
+ }
+ }
+
+ bool Npc::getStance (const MWWorld::Ptr& ptr, Stance stance, bool ignoreForce) const
+ {
+ MWMechanics::NpcStats& stats = getNpcStats (ptr);
+
+ switch (stance)
+ {
+ case Run:
+
+ if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun))
+ return true;
+
+ return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run);
+
+ case Sneak:
+
+ if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak))
+ return true;
+
+ return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak);
+
+ case Combat:
+
+ return false;
+ }
+
+ return false;
+ }
+
+ float Npc::getSpeed(const MWWorld::Ptr& ptr) const
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
+ const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
+
+ const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);
+
+ float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
+ (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat());
+ walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance;
+ walkSpeed = std::max(0.0f, walkSpeed);
+ if(Npc::getStance(ptr, Sneak, false))
+ walkSpeed *= fSneakSpeedMultiplier->getFloat();
+
+ float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
+ fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
+ if(npcdata->mNpcStats.isWerewolf())
+ runSpeed *= fWereWolfRunMult->getFloat();
+
+ float moveSpeed;
+ if(normalizedEncumbrance >= 1.0f)
+ moveSpeed = 0.0f;
+ else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0)
+ {
+ float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
+ mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
+ flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat());
+ flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance;
+ flySpeed = std::max(0.0f, flySpeed);
+ moveSpeed = flySpeed;
+ }
+ else if(world->isSwimming(ptr))
+ {
+ float swimSpeed = walkSpeed;
+ if(Npc::getStance(ptr, Run, false))
+ swimSpeed = runSpeed;
+ swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude;
+ swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()*
+ fSwimRunAthleticsMult->getFloat();
+ moveSpeed = swimSpeed;
+ }
+ else if(Npc::getStance(ptr, Run, false) && !Npc::getStance(ptr, Sneak, false))
+ moveSpeed = runSpeed;
+ else
+ moveSpeed = walkSpeed;
+ if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
+ moveSpeed *= 0.75f;
+
+ return moveSpeed;
+ }
+
+ float Npc::getJump(const MWWorld::Ptr &ptr) const
+ {
+ const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
+ const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
+ const float encumbranceTerm = fJumpEncumbranceBase->getFloat() +
+ fJumpEncumbranceMultiplier->getFloat() *
+ (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr));
+
+ float a = npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified();
+ float b = 0.0f;
+ if(a > 50.0f)
+ {
+ b = a - 50.0f;
+ a = 50.0f;
+ }
+
+ float x = fJumpAcrobaticsBase->getFloat() +
+ std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat());
+ x += 3.0f * b * fJumpAcroMultiplier->getFloat();
+ x += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude * 64;
+ x *= encumbranceTerm;
+
+ if(Npc::getStance(ptr, Run, false))
+ x *= fJumpRunMultiplier->getFloat();
+ x *= npcdata->mNpcStats.getFatigueTerm();
+ x -= -627.2f;/*gravity constant*/
+ x /= 3.0f;
+
+ return x;
+ }
+
+ MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const
+ {
+ ensureCustomData (ptr);
+
+ return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
+ }
+
+ Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const
+ {
+ MWMechanics::Movement &movement = getMovementSettings(ptr);
+ Ogre::Vector3 vec(movement.mPosition);
+ movement.mPosition[0] = 0.0f;
+ movement.mPosition[1] = 0.0f;
+ movement.mPosition[2] = 0.0f;
+ return vec;
+ }
+
+ Ogre::Vector3 Npc::getRotationVector (const MWWorld::Ptr& ptr) const
+ {
+ MWMechanics::Movement &movement = getMovementSettings(ptr);
+ Ogre::Vector3 vec(movement.mRotation);
+ movement.mRotation[0] = 0.0f;
+ movement.mRotation[1] = 0.0f;
+ movement.mRotation[2] = 0.0f;
+ return vec;
+ }
+
+ bool Npc::isEssential (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+
+ return ref->mBase->mFlags & ESM::NPC::Essential;
+ }
+
+ void Npc::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Npc);
+ registerClass (typeid (ESM::NPC).name(), instance);
+ }
+
+ bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ /// \todo We don't want tooltips for NPCs in combat mode.
+
+ return true;
+ }
+
+ MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+
+ bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp();
+ MWGui::ToolTipInfo info;
+
+ info.caption = getName(ptr);
+ if(fullHelp && getNpcStats(ptr).isWerewolf())
+ {
+ info.caption += " (";
+ info.caption += ref->mBase->mName;
+ info.caption += ")";
+ }
+
+ if(fullHelp)
+ info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+
+ return info;
+ }
+
+ float Npc::getCapacity (const MWWorld::Ptr& ptr) const
+ {
+ const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
+ return stats.getAttribute(0).getModified()*5;
+ }
+
+ float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const
+ {
+ const MWMechanics::NpcStats &stats = getNpcStats(ptr);
+
+ // According to UESP, inventory weight is ignored in werewolf form. Does that include
+ // feather and burden effects?
+ float weight = 0.0f;
+ if(!stats.isWerewolf())
+ {
+ weight = getContainerStore(ptr).getWeight();
+ weight -= stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).mMagnitude;
+ weight += stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).mMagnitude;
+ if(weight < 0.0f)
+ weight = 0.0f;
+ }
+
+ return weight;
+ }
+
+ 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);
+ }
+
+ void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
+ {
+ MWMechanics::NpcStats& stats = getNpcStats (ptr);
+
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+
+ const ESM::Class *class_ =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (
+ ref->mBase->mClass
+ );
+
+ stats.useSkill (skill, *class_, usageType);
+ }
+
+ float Npc::getArmorRating (const MWWorld::Ptr& ptr) const
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ MWMechanics::NpcStats &stats = getNpcStats(ptr);
+ MWWorld::InventoryStore &invStore = getInventoryStore(ptr);
+
+ int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt();
+ float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat();
+ float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat();
+ int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified();
+
+ int ratings[MWWorld::InventoryStore::Slots];
+ for(int i = 0;i < MWWorld::InventoryStore::Slots;i++)
+ {
+ MWWorld::ContainerStoreIterator it = invStore.getSlot(i);
+ if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name())
+ {
+ // unarmored
+ ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
+ }
+ else
+ {
+ MWWorld::LiveCellRef<ESM::Armor> *ref = it->get<ESM::Armor>();
+
+ int armorSkillType = MWWorld::Class::get(*it).getEquipmentSkill(*it);
+ int armorSkill = stats.getSkill(armorSkillType).getModified();
+
+ if(ref->mBase->mData.mWeight == 0)
+ ratings[i] = ref->mBase->mData.mArmor;
+ else
+ ratings[i] = ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill;
+ }
+ }
+
+ float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude;
+
+ return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f
+ + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet]
+ + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots]
+ + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron]
+ ) * 0.1f
+ + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet])
+ * 0.05f
+ + shield;
+ }
+
+
+ void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const
+ {
+ y = 0;
+ x = 0;
+ }
+
+ void Npc::adjustScale(const MWWorld::Ptr &ptr, float &scale) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+
+ const ESM::Race* race =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
+
+ if (ref->mBase->isMale())
+ scale *= race->mData.mHeight.mMale;
+ else
+ scale *= race->mData.mHeight.mFemale;
+ }
+
+ int Npc::getServices(const MWWorld::Ptr &actor) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
+ if (ref->mBase->mHasAI)
+ return ref->mBase->mAiData.mServices;
+ else
+ return 0;
+ }
+
+
+ std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
+ {
+ if(name == "left")
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ if(world->isSwimming(ptr))
+ return "Swim Left";
+ if(world->isUnderwater(ptr.getCell(), pos))
+ return "FootWaterLeft";
+ if(world->isOnGround(ptr))
+ {
+ MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
+ if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name())
+ return "FootBareLeft";
+
+ switch(Class::get(*boots).getEquipmentSkill(*boots))
+ {
+ case ESM::Skill::LightArmor:
+ return "FootLightLeft";
+ case ESM::Skill::MediumArmor:
+ return "FootMedLeft";
+ case ESM::Skill::HeavyArmor:
+ return "FootHeavyLeft";
+ }
+ }
+ return "";
+ }
+ if(name == "right")
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ if(world->isSwimming(ptr))
+ return "Swim Right";
+ if(world->isUnderwater(ptr.getCell(), pos))
+ return "FootWaterRight";
+ if(world->isOnGround(ptr))
+ {
+ MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
+ if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name())
+ return "FootBareRight";
+
+ switch(Class::get(*boots).getEquipmentSkill(*boots))
+ {
+ case ESM::Skill::LightArmor:
+ return "FootLightRight";
+ case ESM::Skill::MediumArmor:
+ return "FootMedRight";
+ case ESM::Skill::HeavyArmor:
+ return "FootHeavyRight";
+ }
+ }
+ return "";
+ }
+ if(name == "land")
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
+ if(world->isUnderwater(ptr.getCell(), pos))
+ return "DefaultLandWater";
+ if(world->isOnGround(ptr))
+ return "Body Fall Medium";
+ return "";
+ }
+ if(name == "swimleft")
+ return "Swim Left";
+ if(name == "swimright")
+ return "Swim Right";
+ // TODO: I have no idea what these are supposed to do for NPCs since they use
+ // voiced dialog for various conditions like health loss and combat taunts. Maybe
+ // only for biped creatures?
+ if(name == "moan")
+ return "";
+ if(name == "roar")
+ return "";
+ if(name == "scream")
+ return "";
+
+ throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
+ }
+
+ MWWorld::Ptr
+ Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref =
+ ptr.get<ESM::NPC>();
+
+ return MWWorld::Ptr(&cell.mNpcs.insert(*ref), &cell);
+ }
+
+ const ESM::GameSetting *Npc::fMinWalkSpeed;
+ const ESM::GameSetting *Npc::fMaxWalkSpeed;
+ const ESM::GameSetting *Npc::fEncumberedMoveEffect;
+ const ESM::GameSetting *Npc::fSneakSpeedMultiplier;
+ const ESM::GameSetting *Npc::fAthleticsRunBonus;
+ const ESM::GameSetting *Npc::fBaseRunMultiplier;
+ const ESM::GameSetting *Npc::fMinFlySpeed;
+ const ESM::GameSetting *Npc::fMaxFlySpeed;
+ const ESM::GameSetting *Npc::fSwimRunBase;
+ const ESM::GameSetting *Npc::fSwimRunAthleticsMult;
+ const ESM::GameSetting *Npc::fJumpEncumbranceBase;
+ const ESM::GameSetting *Npc::fJumpEncumbranceMultiplier;
+ const ESM::GameSetting *Npc::fJumpAcrobaticsBase;
+ const ESM::GameSetting *Npc::fJumpAcroMultiplier;
+ const ESM::GameSetting *Npc::fJumpRunMultiplier;
+ const ESM::GameSetting *Npc::fWereWolfRunMult;
+}
diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp
new file mode 100644
index 0000000000..3591d7c688
--- /dev/null
+++ b/apps/openmw/mwclass/npc.hpp
@@ -0,0 +1,157 @@
+#ifndef GAME_MWCLASS_NPC_H
+#define GAME_MWCLASS_NPC_H
+
+#include "../mwworld/class.hpp"
+
+namespace ESM
+{
+ class GameSetting;
+}
+
+namespace MWClass
+{
+ class Npc : public MWWorld::Class
+ {
+ void ensureCustomData (const MWWorld::Ptr& ptr) const;
+
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ static const ESM::GameSetting *fMinWalkSpeed;
+ static const ESM::GameSetting *fMaxWalkSpeed;
+ static const ESM::GameSetting *fEncumberedMoveEffect;
+ static const ESM::GameSetting *fSneakSpeedMultiplier;
+ static const ESM::GameSetting *fAthleticsRunBonus;
+ static const ESM::GameSetting *fBaseRunMultiplier;
+ static const ESM::GameSetting *fMinFlySpeed;
+ static const ESM::GameSetting *fMaxFlySpeed;
+ static const ESM::GameSetting *fSwimRunBase;
+ static const ESM::GameSetting *fSwimRunAthleticsMult;
+ static const ESM::GameSetting *fJumpEncumbranceBase;
+ static const ESM::GameSetting *fJumpEncumbranceMultiplier;
+ static const ESM::GameSetting *fJumpAcrobaticsBase;
+ static const ESM::GameSetting *fJumpAcroMultiplier;
+ static const ESM::GameSetting *fJumpRunMultiplier;
+ static const ESM::GameSetting *fWereWolfRunMult;
+
+ public:
+
+ virtual std::string getId (const MWWorld::Ptr& ptr) const;
+ ///< Return ID of \a ptr
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual void adjustPosition(const MWWorld::Ptr& ptr) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const;
+ ///< Return creature stats
+
+ virtual MWMechanics::NpcStats& getNpcStats (const MWWorld::Ptr& ptr) const;
+ ///< Return NPC stats
+
+ virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const;
+ ///< Return container store
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
+ ///< Return inventory store
+
+ virtual void hit(const MWWorld::Ptr& ptr, int type) const;
+
+ virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
+
+ virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual void setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const;
+ ///< Force or unforce a stance.
+
+ virtual void setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const;
+ ///< Set or unset a stance.
+
+ virtual bool getStance (const MWWorld::Ptr& ptr, Stance stance, bool ignoreForce = false)
+ const;
+ ///< Check if a stance is active or not.
+
+ virtual float getSpeed (const MWWorld::Ptr& ptr) const;
+ ///< Return movement speed.
+
+ virtual float getJump(const MWWorld::Ptr &ptr) const;
+ ///< Return jump velocity (not accounting for movement)
+
+ virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const;
+ ///< Return desired movement.
+
+ virtual Ogre::Vector3 getMovementVector (const MWWorld::Ptr& ptr) const;
+ ///< Return desired movement vector (determined based on movement settings,
+ /// stance and stats).
+
+ virtual Ogre::Vector3 getRotationVector (const MWWorld::Ptr& ptr) const;
+ ///< Return desired rotations, as euler angles.
+
+ virtual float getCapacity (const MWWorld::Ptr& ptr) const;
+ ///< Return total weight that fits into the object. Throws an exception, if the object can't
+ /// hold other objects.
+
+ virtual float getEncumbrance (const MWWorld::Ptr& ptr) const;
+ ///< Returns total weight of objects inside this object (including modifications from magic
+ /// effects). Throws an exception, if the object can't hold other objects.
+
+ virtual float getArmorRating (const MWWorld::Ptr& ptr) const;
+ ///< @return combined armor rating of this actor
+
+ virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id,
+ const MWWorld::Ptr& actor) const;
+ ///< Apply \a id on \a ptr.
+ /// \param actor Actor that is resposible for the ID being applied to \a ptr.
+ /// \return Any effect?
+
+ virtual void adjustScale (const MWWorld::Ptr &ptr, float &scale) const;
+
+ virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const;
+ ///< Inform actor \a ptr that a skill use has succeeded.
+
+ virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const;
+
+ virtual bool isEssential (const MWWorld::Ptr& ptr) const;
+ ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable)
+
+ virtual int getServices (const MWWorld::Ptr& actor) const;
+
+ virtual bool isPersistent (const MWWorld::Ptr& ptr) const;
+
+ virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const;
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool isActor() const {
+ return true;
+ }
+
+ virtual bool isNpc() const {
+ return true;
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp
new file mode 100644
index 0000000000..08683a6684
--- /dev/null
+++ b/apps/openmw/mwclass/potion.cpp
@@ -0,0 +1,199 @@
+
+#include "potion.hpp"
+
+#include <components/esm/loadalch.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionapply.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+namespace MWClass
+{
+ void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Potion::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Potion::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Potion::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Potion::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Potion::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Potion::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Potion::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Potion);
+
+ registerClass (typeid (ESM::Potion).name(), instance);
+ }
+
+ std::string Potion::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Potion Up");
+ }
+
+ std::string Potion::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Potion Down");
+ }
+
+ std::string Potion::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Potion::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects);
+
+ // hide effects the player doesnt know about
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
+ int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
+ int i=0;
+ 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));
+
+ ++i;
+ }
+
+ info.isPotion = true;
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Potion::use (const MWWorld::Ptr& ptr) const
+ {
+ 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();
+
+ boost::shared_ptr<MWWorld::Action> action (
+ new MWWorld::ActionApply (actor, ref->mBase->mId));
+
+ action->setSound ("Drink");
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Potion::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+
+ return MWWorld::Ptr(&cell.mPotions.insert(*ref), &cell);
+ }
+
+ bool Potion::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Potions;
+ }
+
+ float Potion::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Potion> *ref =
+ ptr.get<ESM::Potion>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp
new file mode 100644
index 0000000000..0f0578ca02
--- /dev/null
+++ b/apps/openmw/mwclass/potion.hpp
@@ -0,0 +1,62 @@
+#ifndef GAME_MWCLASS_POTION_H
+#define GAME_MWCLASS_POTION_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Potion : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) const;
+ ///< Generate action for using via inventory menu
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp
new file mode 100644
index 0000000000..951265f40e
--- /dev/null
+++ b/apps/openmw/mwclass/probe.cpp
@@ -0,0 +1,189 @@
+
+#include "probe.hpp"
+
+#include <components/esm/loadprob.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Probe::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Probe::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Probe::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return ref->mBase->mName;
+ }
+ boost::shared_ptr<MWWorld::Action> Probe::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Probe::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ std::vector<int> slots;
+
+ slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+
+ return std::make_pair (slots, false);
+ }
+
+ int Probe::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Probe::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Probe);
+
+ registerClass (typeid (ESM::Probe).name(), instance);
+ }
+
+ std::string Probe::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Probe Up");
+ }
+
+ std::string Probe::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Probe Down");
+ }
+
+ std::string Probe::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Probe::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses;
+
+ 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}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Probe::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Probe::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return MWWorld::Ptr(&cell.mProbes.insert(*ref), &cell);
+ }
+
+ bool Probe::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Probes;
+ }
+
+ int Probe::getItemMaxHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+
+ return ref->mBase->mData.mUses;
+ }
+
+ float Probe::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Probe> *ref =
+ ptr.get<ESM::Probe>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp
new file mode 100644
index 0000000000..832169f8b7
--- /dev/null
+++ b/apps/openmw/mwclass/probe.hpp
@@ -0,0 +1,70 @@
+#ifndef GAME_MWCLASS_PROBE_H
+#define GAME_MWCLASS_PROBE_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Probe : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp
new file mode 100644
index 0000000000..38c15ac92e
--- /dev/null
+++ b/apps/openmw/mwclass/repair.cpp
@@ -0,0 +1,181 @@
+
+#include "repair.hpp"
+
+#include <components/esm/loadrepa.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+#include "../mwworld/actionrepair.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Repair::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Repair::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Repair::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Repair::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ std::string Repair::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return ref->mBase->mScript;
+ }
+
+ int Repair::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Repair::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Repair);
+
+ registerClass (typeid (ESM::Repair).name(), instance);
+ }
+
+ std::string Repair::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Repair Up");
+ }
+
+ std::string Repair::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ return std::string("Item Repair Down");
+ }
+
+ std::string Repair::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Repair::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ bool Repair::hasItemHealth (const MWWorld::Ptr& ptr) const
+ {
+ return true;
+ }
+
+ int Repair::getItemMaxHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return ref->mBase->mData.mUses;
+ }
+
+ MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ std::string text;
+
+ int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses;
+
+ 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}");
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ MWWorld::Ptr
+ Repair::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+
+ return MWWorld::Ptr(&cell.mRepairs.insert(*ref), &cell);
+ }
+
+ boost::shared_ptr<MWWorld::Action> Repair::use (const MWWorld::Ptr& ptr) const
+ {
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionRepair(ptr));
+ }
+
+ bool Repair::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::RepairItem;
+ }
+
+ float Repair::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ ptr.get<ESM::Repair>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp
new file mode 100644
index 0000000000..28ca5ad4c0
--- /dev/null
+++ b/apps/openmw/mwclass/repair.hpp
@@ -0,0 +1,71 @@
+#ifndef GAME_MWCLASS_REPAIR_H
+#define GAME_MWCLASS_REPAIR_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Repair : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu (default implementation: return a
+ /// null action).
+
+ virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const;
+ ///< \return Item health data available? (default implementation: false)
+
+ virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+ /// (default implementation: throw an exceoption)
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp
new file mode 100644
index 0000000000..bd7deca889
--- /dev/null
+++ b/apps/openmw/mwclass/static.cpp
@@ -0,0 +1,62 @@
+
+#include "static.hpp"
+
+#include <components/esm/loadstat.hpp>
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/physicssystem.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Static::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr);
+ }
+
+ std::string Static::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Static> *ref =
+ ptr.get<ESM::Static>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Static::getName (const MWWorld::Ptr& ptr) const
+ {
+ return "";
+ }
+
+ void Static::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Static);
+
+ registerClass (typeid (ESM::Static).name(), instance);
+ }
+
+ MWWorld::Ptr
+ Static::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Static> *ref =
+ ptr.get<ESM::Static>();
+
+ return MWWorld::Ptr(&cell.mStatics.insert(*ref), &cell);
+ }
+}
diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp
new file mode 100644
index 0000000000..e36b3d1426
--- /dev/null
+++ b/apps/openmw/mwclass/static.hpp
@@ -0,0 +1,30 @@
+#ifndef GAME_MWCLASS_STATIC_H
+#define GAME_MWCLASS_STATIC_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Static : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ static void registerSelf();
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp
new file mode 100644
index 0000000000..4cb090328e
--- /dev/null
+++ b/apps/openmw/mwclass/weapon.cpp
@@ -0,0 +1,448 @@
+
+#include "weapon.hpp"
+
+#include <components/esm/loadweap.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/physicssystem.hpp"
+#include "../mwworld/nullaction.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwrender/objects.hpp"
+#include "../mwrender/renderinginterface.hpp"
+
+namespace MWClass
+{
+ std::string Weapon::getId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mId;
+ }
+
+ void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+ const std::string model = getModel(ptr);
+ if (!model.empty()) {
+ renderingInterface.getObjects().insertModel(ptr, model);
+ }
+ }
+
+ void Weapon::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+ const std::string model = getModel(ptr);
+ if(!model.empty())
+ physics.addObject(ptr,true);
+ }
+
+ std::string Weapon::getModel(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+ assert(ref->mBase != NULL);
+
+ const std::string &model = ref->mBase->mModel;
+ if (!model.empty()) {
+ return "meshes\\" + model;
+ }
+ return "";
+ }
+
+ std::string Weapon::getName (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mName;
+ }
+
+ boost::shared_ptr<MWWorld::Action> Weapon::activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const
+ {
+ return defaultItemActivate(ptr, actor);
+ }
+
+ bool Weapon::hasItemHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return (ref->mBase->mData.mType < 11); // thrown weapons and arrows/bolts don't have health, only quantity
+ }
+
+ int Weapon::getItemMaxHealth (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mData.mHealth;
+ }
+
+ std::string Weapon::getScript (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mScript;
+ }
+
+ std::pair<std::vector<int>, bool> Weapon::getEquipmentSlots (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ 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));
+ stack = true;
+ }
+ else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown)
+ {
+ slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+ stack = true;
+ }
+ else
+ slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
+
+ return std::make_pair (slots, stack);
+ }
+
+ int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ const int size = 12;
+
+ static const int sMapping[size][2] =
+ {
+ { ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade },
+ { ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade },
+ { ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade },
+ { ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon },
+ { ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon },
+ { ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon },
+ { ESM::Weapon::SpearTwoWide, ESM::Skill::Spear },
+ { ESM::Weapon::AxeOneHand, ESM::Skill::Axe },
+ { ESM::Weapon::AxeTwoHand, ESM::Skill::Axe },
+ { ESM::Weapon::MarksmanBow, ESM::Skill::Marksman },
+ { ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman },
+ { ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman }
+ };
+
+ for (int i=0; i<size; ++i)
+ if (sMapping[i][0]==ref->mBase->mData.mType)
+ return sMapping[i][1];
+
+ return -1;
+ }
+
+ int Weapon::getValue (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mData.mValue;
+ }
+
+ void Weapon::registerSelf()
+ {
+ boost::shared_ptr<Class> instance (new Weapon);
+
+ registerClass (typeid (ESM::Weapon).name(), instance);
+ }
+
+ std::string Weapon::getUpSoundId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ int type = ref->mBase->mData.mType;
+ // Ammo
+ if (type == 12 || type == 13)
+ {
+ return std::string("Item Ammo Up");
+ }
+ // Bow
+ if (type == 9)
+ {
+ return std::string("Item Weapon Bow Up");
+ }
+ // Crossbow
+ if (type == 10)
+ {
+ return std::string("Item Weapon Crossbow Up");
+ }
+ // Longblades, One hand and Two
+ if (type == 1 || type == 2)
+ {
+ return std::string("Item Weapon Longblade Up");
+ }
+ // Shortblade and thrown weapons
+ // thrown weapons may not be entirely correct
+ if (type == 0 || type == 11)
+ {
+ return std::string("Item Weapon Shortblade Up");
+ }
+ // Spear
+ if (type == 6)
+ {
+ return std::string("Item Weapon Spear Up");
+ }
+ // Blunts and Axes
+ if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8)
+ {
+ return std::string("Item Weapon Blunt Up");
+ }
+
+ return std::string("Item Misc Up");
+ }
+
+ std::string Weapon::getDownSoundId (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ int type = ref->mBase->mData.mType;
+ // Ammo
+ if (type == 12 || type == 13)
+ {
+ return std::string("Item Ammo Down");
+ }
+ // Bow
+ if (type == 9)
+ {
+ return std::string("Item Weapon Bow Down");
+ }
+ // Crossbow
+ if (type == 10)
+ {
+ return std::string("Item Weapon Crossbow Down");
+ }
+ // Longblades, One hand and Two
+ if (type == 1 || type == 2)
+ {
+ return std::string("Item Weapon Longblade Down");
+ }
+ // Shortblade and thrown weapons
+ // thrown weapons may not be entirely correct
+ if (type == 0 || type == 11)
+ {
+ return std::string("Item Weapon Shortblade Down");
+ }
+ // Spear
+ if (type == 6)
+ {
+ return std::string("Item Weapon Spear Down");
+ }
+ // Blunts and Axes
+ if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8)
+ {
+ return std::string("Item Weapon Blunt Down");
+ }
+
+ return std::string("Item Misc Down");
+ }
+
+ std::string Weapon::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mIcon;
+ }
+
+ bool Weapon::hasToolTip (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return (ref->mBase->mName != "");
+ }
+
+ MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ MWGui::ToolTipInfo info;
+ info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount());
+ info.icon = ref->mBase->mIcon;
+
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ std::string text;
+
+ // weapon type & damage. arrows / bolts don't have his info.
+ if (ref->mBase->mData.mType < 12)
+ {
+ text += "\n#{sType} ";
+
+ std::map <int, std::pair <std::string, std::string> > mapping;
+ mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded");
+ mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded");
+ mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded");
+ mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded");
+ mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded");
+ mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded");
+ mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded");
+ mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded");
+ mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded");
+ mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", "");
+ mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", "");
+ mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", "");
+
+ std::string type = mapping[ref->mBase->mData.mType].first;
+ std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second;
+
+ text += store.get<ESM::GameSetting>().find(type)->getString() +
+ ((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->getString() : "");
+
+ // weapon damage
+ if (ref->mBase->mData.mType >= 9)
+ {
+ // marksman
+ text += "\n#{sAttack}: "
+ + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0]))
+ + " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1]));
+ }
+ else
+ {
+ // Chop
+ text += "\n#{sChop}: "
+ + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0]))
+ + " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1]));
+ // Slash
+ text += "\n#{sSlash}: "
+ + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mSlash[0]))
+ + " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mSlash[1]));
+ // Thrust
+ text += "\n#{sThrust}: "
+ + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[0]))
+ + " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[1]));
+ }
+ }
+
+ if (ref->mBase->mData.mType < 11) // thrown weapons and arrows/bolts don't have health, only quantity
+ {
+ int remainingHealth = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mHealth;
+ text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/"
+ + MWGui::ToolTips::toString(ref->mBase->mData.mHealth);
+ }
+
+ text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight);
+ text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
+
+ info.enchant = ref->mBase->mEnchant;
+
+ if (!info.enchant.empty())
+ info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge;
+
+ if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
+
+ info.text = text;
+
+ return info;
+ }
+
+ std::string Weapon::getEnchantment (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mEnchant;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ // equip the item in the first free slot
+ for (std::vector<int>::const_iterator slot=slots.first.begin();
+ slot!=slots.first.end(); ++slot)
+ {
+ 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 (0, "");
+ }
+
+ boost::shared_ptr<MWWorld::Action> Weapon::use (const MWWorld::Ptr& ptr) const
+ {
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionEquip(ptr));
+
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Weapon::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return MWWorld::Ptr(&cell.mWeapons.insert(*ref), &cell);
+ }
+
+ float Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+
+ return ref->mBase->mData.mEnchant/10.f;
+ }
+
+ bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return npcServices & ESM::NPC::Weapon;
+ }
+
+ float Weapon::getWeight(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref =
+ ptr.get<ESM::Weapon>();
+ return ref->mBase->mData.mWeight;
+ }
+}
diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp
new file mode 100644
index 0000000000..181c637db6
--- /dev/null
+++ b/apps/openmw/mwclass/weapon.hpp
@@ -0,0 +1,91 @@
+#ifndef GAME_MWCLASS_WEAPON_H
+#define GAME_MWCLASS_WEAPON_H
+
+#include "../mwworld/class.hpp"
+
+namespace MWClass
+{
+ class Weapon : public MWWorld::Class
+ {
+ virtual MWWorld::Ptr
+ copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const;
+
+ public:
+
+ virtual std::string getId (const MWWorld::Ptr& ptr) const;
+ ///< Return ID of \a ptr
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
+
+ virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+
+ virtual std::string getName (const MWWorld::Ptr& ptr) const;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
+ const MWWorld::Ptr& actor) const;
+ ///< Generate action for activation
+
+ virtual bool hasToolTip (const MWWorld::Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const;
+ ///< \return Item health data available?
+
+ virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+
+ virtual std::string getScript (const MWWorld::Ptr& ptr) const;
+ ///< Return name of the script attached to ptr
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+
+ virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const;
+ /// Return the index of the skill this item corresponds to when equiopped or -1, if there is
+ /// no such skill.
+
+ virtual int getValue (const MWWorld::Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+
+ static void registerSelf();
+
+ virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the pick up sound Id
+
+ virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const;
+ ///< Return the put down sound Id
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const;
+ ///< @return the enchantment ID if the object is enchanted, otherwise an empty string
+
+ virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
+
+ virtual std::pair<int, std::string> canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const;
+ ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that.
+ /// Second item in the pair specifies the error message
+
+ virtual boost::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
new file mode 100644
index 0000000000..52493bf765
--- /dev/null
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -0,0 +1,632 @@
+
+#include "dialoguemanagerimp.hpp"
+
+#include <cctype>
+#include <cstdlib>
+#include <algorithm>
+#include <iterator>
+
+#include <components/esm/loaddial.hpp>
+#include <components/esm/loadinfo.hpp>
+
+#include <components/compiler/exception.hpp>
+#include <components/compiler/errorhandler.hpp>
+#include <components/compiler/scanner.hpp>
+#include <components/compiler/locals.hpp>
+#include <components/compiler/output.hpp>
+#include <components/compiler/scriptparser.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/defines.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/journal.hpp"
+#include "../mwbase/scriptmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwgui/dialogue.hpp"
+
+#include "../mwscript/compilercontext.hpp"
+#include "../mwscript/interpretercontext.hpp"
+#include "../mwscript/extensions.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "filter.hpp"
+
+namespace MWDialogue
+{
+ DialogueManager::DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage) :
+ mCompilerContext (MWScript::CompilerContext::Type_Dialgoue),
+ mErrorStream(std::cout.rdbuf()),mErrorHandler(mErrorStream)
+ , mTemporaryDispositionChange(0.f)
+ , mPermanentDispositionChange(0.f), mScriptVerbose (scriptVerbose)
+ , mTranslationDataStorage(translationDataStorage)
+ , mTalkedTo(false)
+ {
+ mChoice = -1;
+ mIsInChoice = false;
+ mCompilerContext.setExtensions (&extensions);
+
+ const MWWorld::Store<ESM::Dialogue> &dialogs =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ MWWorld::Store<ESM::Dialogue>::iterator it = dialogs.begin();
+ for (; it != dialogs.end(); ++it)
+ {
+ mDialogueMap[Misc::StringUtils::lowerCase(it->mId)] = *it;
+ }
+ }
+
+ void DialogueManager::clear()
+ {
+ mKnownTopics.clear();
+ mTalkedTo = false;
+ mTemporaryDispositionChange = 0;
+ mPermanentDispositionChange = 0;
+ }
+
+ void DialogueManager::addTopic (const std::string& topic)
+ {
+ mKnownTopics[Misc::StringUtils::lowerCase(topic)] = true;
+ }
+
+ void DialogueManager::parseText (const std::string& text)
+ {
+ std::vector<HyperTextToken> hypertext = ParseHyperText(text);
+
+ //calculation of standard form fir all hyperlinks
+ for (size_t i = 0; i < hypertext.size(); ++i)
+ {
+ if (hypertext[i].mLink)
+ {
+ size_t asterisk_count = MWDialogue::RemovePseudoAsterisks(hypertext[i].mText);
+ for(; asterisk_count > 0; --asterisk_count)
+ hypertext[i].mText.append("*");
+
+ hypertext[i].mText = mTranslationDataStorage.topicStandardForm(hypertext[i].mText);
+ }
+ }
+
+ for (size_t i = 0; i < hypertext.size(); ++i)
+ {
+ std::list<std::string>::iterator it;
+ for(it = mActorKnownTopics.begin(); it != mActorKnownTopics.end(); ++it)
+ {
+ if (hypertext[i].mLink)
+ {
+ if( hypertext[i].mText == *it )
+ {
+ mKnownTopics[hypertext[i].mText] = true;
+ }
+ }
+ else if( !mTranslationDataStorage.hasTranslation() )
+ {
+ size_t pos = Misc::StringUtils::lowerCase(hypertext[i].mText).find(*it, 0);
+ if(pos !=std::string::npos)
+ {
+ mKnownTopics[*it] = true;
+ }
+ }
+ }
+ }
+
+ updateTopics();
+ }
+
+ void DialogueManager::startDialogue (const MWWorld::Ptr& actor)
+ {
+ mLastTopic = "";
+
+ mChoice = -1;
+ mIsInChoice = false;
+
+ mActor = actor;
+
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
+ mTalkedTo = creatureStats.hasTalkedToPlayer();
+
+ mActorKnownTopics.clear();
+
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+ win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor));
+
+ //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI
+ updateTopics();
+
+ //greeting
+ const MWWorld::Store<ESM::Dialogue> &dialogs =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ Filter filter (actor, mChoice, mTalkedTo);
+
+ for (MWWorld::Store<ESM::Dialogue>::iterator it = dialogs.begin(); it != dialogs.end(); ++it)
+ {
+ if(it->mType == ESM::Dialogue::Greeting)
+ {
+ // Search a response (we do not accept a fallback to "Info refusal" here)
+ if (const ESM::DialInfo *info = filter.search (*it, false))
+ {
+ //initialise the GUI
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue);
+
+ creatureStats.talkedToPlayer();
+
+ if (!info->mSound.empty())
+ {
+ // TODO play sound
+ }
+
+ parseText (info->mResponse);
+
+ MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
+ win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext));
+ executeScript (info->mResultScript);
+ mLastTopic = Misc::StringUtils::lowerCase(it->mId);
+ break;
+ }
+ }
+ }
+ }
+
+ bool DialogueManager::compile (const std::string& cmd,std::vector<Interpreter::Type_Code>& code)
+ {
+ bool success = true;
+
+ try
+ {
+ mErrorHandler.reset();
+
+ std::istringstream input (cmd + "\n");
+
+ Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions());
+
+ Compiler::Locals locals;
+
+ std::string actorScript = MWWorld::Class::get (mActor).getScript (mActor);
+
+ if (!actorScript.empty())
+ {
+ // grab local variables from actor's script, if available.
+ locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript);
+ }
+
+ Compiler::ScriptParser parser(mErrorHandler,mCompilerContext, locals, false);
+
+ scanner.scan (parser);
+
+ if (!mErrorHandler.isGood())
+ success = false;
+
+ if (success)
+ parser.getCode (code);
+ }
+ catch (const Compiler::SourceException& /* error */)
+ {
+ // error has already been reported via error handler
+ success = false;
+ }
+ catch (const std::exception& error)
+ {
+ std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl;
+ success = false;
+ }
+
+ if (!success && mScriptVerbose)
+ {
+ std::cerr
+ << "compiling failed (dialogue script)" << std::endl
+ << cmd
+ << std::endl << std::endl;
+ }
+
+ return success;
+ }
+
+ void DialogueManager::executeScript (const std::string& script)
+ {
+ std::vector<Interpreter::Type_Code> code;
+ if(compile(script,code))
+ {
+ try
+ {
+ MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
+ Interpreter::Interpreter interpreter;
+ MWScript::installOpcodes (interpreter);
+ interpreter.run (&code[0], code.size(), interpreterContext);
+ }
+ catch (const std::exception& error)
+ {
+ std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what();
+ }
+ }
+ }
+
+ void DialogueManager::executeTopic (const std::string& topic, bool randomResponse)
+ {
+ Filter filter (mActor, mChoice, mTalkedTo);
+
+ const MWWorld::Store<ESM::Dialogue> &dialogues =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ const ESM::Dialogue& dialogue = *dialogues.find (topic);
+
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+
+ std::vector<const ESM::DialInfo *> infos = filter.list (dialogue, true, true);
+
+ if (!infos.empty())
+ {
+ const ESM::DialInfo* info = infos[randomResponse ? std::rand() % infos.size() : 0];
+
+ parseText (info->mResponse);
+
+ std::string title;
+ if (dialogue.mType==ESM::Dialogue::Persuasion)
+ {
+ std::string modifiedTopic = "s" + topic;
+
+ modifiedTopic.erase (std::remove (modifiedTopic.begin(), modifiedTopic.end(), ' '), modifiedTopic.end());
+
+ const MWWorld::Store<ESM::GameSetting>& gmsts =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ title = gmsts.find (modifiedTopic)->getString();
+ }
+ else
+ title = topic;
+
+ MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
+ win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title);
+ MWBase::Environment::get().getJournal()->addTopic (topic, info->mId);
+
+ executeScript (info->mResultScript);
+
+ mLastTopic = topic;
+ }
+ else
+ {
+ // no response found, print a fallback text
+ win->addResponse ("…", topic);
+ }
+ }
+
+ void DialogueManager::updateTopics()
+ {
+ std::list<std::string> keywordList;
+ int choice = mChoice;
+ mChoice = -1;
+ mActorKnownTopics.clear();
+
+ const MWWorld::Store<ESM::Dialogue> &dialogs =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ Filter filter (mActor, mChoice, mTalkedTo);
+
+ for (MWWorld::Store<ESM::Dialogue>::iterator iter = dialogs.begin(); iter != dialogs.end(); ++iter)
+ {
+ if (iter->mType == ESM::Dialogue::Topic)
+ {
+ if (filter.responseAvailable (*iter))
+ {
+ std::string lower = Misc::StringUtils::lowerCase(iter->mId);
+ mActorKnownTopics.push_back (lower);
+
+ //does the player know the topic?
+ if (mKnownTopics.find (lower) != mKnownTopics.end())
+ {
+ keywordList.push_back (iter->mId);
+ }
+ }
+ }
+ }
+
+ // check the available services of this actor
+ int services = 0;
+ if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWWorld::LiveCellRef<ESM::NPC>* ref = mActor.get<ESM::NPC>();
+ if (ref->mBase->mHasAI)
+ services = ref->mBase->mAiData.mServices;
+ }
+ else if (mActor.getTypeName() == typeid(ESM::Creature).name())
+ {
+ MWWorld::LiveCellRef<ESM::Creature>* ref = mActor.get<ESM::Creature>();
+ if (ref->mBase->mHasAI)
+ services = ref->mBase->mAiData.mServices;
+ }
+
+ int windowServices = 0;
+
+ if (services & ESM::NPC::Weapon
+ || services & ESM::NPC::Armor
+ || services & ESM::NPC::Clothing
+ || services & ESM::NPC::Books
+ || services & ESM::NPC::Ingredients
+ || services & ESM::NPC::Picks
+ || services & ESM::NPC::Probes
+ || services & ESM::NPC::Lights
+ || services & ESM::NPC::Apparatus
+ || services & ESM::NPC::RepairItem
+ || services & ESM::NPC::Misc)
+ windowServices |= MWGui::DialogueWindow::Service_Trade;
+
+ if(mActor.getTypeName() == typeid(ESM::NPC).name() && !mActor.get<ESM::NPC>()->mBase->mTransport.empty())
+ windowServices |= MWGui::DialogueWindow::Service_Travel;
+
+ if (services & ESM::NPC::Spells)
+ windowServices |= MWGui::DialogueWindow::Service_BuySpells;
+
+ if (services & ESM::NPC::Spellmaking)
+ windowServices |= MWGui::DialogueWindow::Service_CreateSpells;
+
+ if (services & ESM::NPC::Training)
+ windowServices |= MWGui::DialogueWindow::Service_Training;
+
+ if (services & ESM::NPC::Enchanting)
+ windowServices |= MWGui::DialogueWindow::Service_Enchant;
+
+ if (services & ESM::NPC::Repair)
+ windowServices |= MWGui::DialogueWindow::Service_Repair;
+
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+
+ win->setServices (windowServices);
+
+ // sort again, because the previous sort was case-sensitive
+ keywordList.sort(Misc::StringUtils::ciEqual);
+ win->setKeywords(keywordList);
+
+ mChoice = choice;
+ }
+
+ void DialogueManager::keywordSelected (const std::string& keyword)
+ {
+ if(!mIsInChoice)
+ {
+ if(mDialogueMap.find(keyword) != mDialogueMap.end())
+ {
+ ESM::Dialogue ndialogue = mDialogueMap[keyword];
+ if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic)
+ {
+ executeTopic (keyword);
+ }
+ }
+ }
+
+ updateTopics();
+ }
+
+ bool DialogueManager::isInChoice() const
+ {
+ return mIsInChoice;
+ }
+
+ 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
+ if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor);
+ npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
+ }
+ mPermanentDispositionChange = 0;
+ mTemporaryDispositionChange = 0;
+ }
+
+ void DialogueManager::questionAnswered (int answer)
+ {
+ mChoice = answer;
+
+ if (mDialogueMap.find(mLastTopic) != mDialogueMap.end())
+ {
+ Filter filter (mActor, mChoice, mTalkedTo);
+
+ if (mDialogueMap[mLastTopic].mType == ESM::Dialogue::Topic
+ || mDialogueMap[mLastTopic].mType == ESM::Dialogue::Greeting)
+ {
+ if (const ESM::DialInfo *info = filter.search (mDialogueMap[mLastTopic], true))
+ {
+ std::string text = info->mResponse;
+ parseText (text);
+
+ mChoice = -1;
+ mIsInChoice = false;
+ MWBase::Environment::get().getWindowManager()->getDialogueWindow()->clearChoices();
+
+ MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
+ MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext));
+ MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId);
+ executeScript (info->mResultScript);
+ }
+ }
+ }
+
+ updateTopics();
+ }
+
+ void DialogueManager::askQuestion (const std::string& question, int choice)
+ {
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+ win->addChoice(question, choice);
+ mIsInChoice = true;
+ }
+
+ MWWorld::Ptr DialogueManager::getActor() const
+ {
+ return mActor;
+ }
+
+ void DialogueManager::goodbye()
+ {
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+
+ win->goodbye();
+ }
+
+ void DialogueManager::persuade(int type)
+ {
+ bool success;
+ float temp, perm;
+ MWBase::Environment::get().getMechanicsManager()->getPersuasionDispositionChange(
+ mActor, MWBase::MechanicsManager::PersuasionType(type), mTemporaryDispositionChange,
+ success, temp, perm);
+ mTemporaryDispositionChange += temp;
+ mPermanentDispositionChange += perm;
+
+ // change temp disposition so that final disposition is between 0...100
+ int curDisp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor);
+ if (curDisp + mTemporaryDispositionChange < 0)
+ mTemporaryDispositionChange = -curDisp;
+ else if (curDisp + mTemporaryDispositionChange > 100)
+ mTemporaryDispositionChange = 100 - curDisp;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1);
+
+ std::string text;
+
+ if (type == MWBase::MechanicsManager::PT_Admire)
+ text = "Admire";
+ else if (type == MWBase::MechanicsManager::PT_Taunt)
+ text = "Taunt";
+ else if (type == MWBase::MechanicsManager::PT_Intimidate)
+ text = "Intimidate";
+ else{
+ text = "Bribe";
+ }
+
+ executeTopic (text + (success ? " Success" : " Fail"), true);
+ }
+
+ int DialogueManager::getTemporaryDispositionChange() const
+ {
+ return mTemporaryDispositionChange;
+ }
+
+ void DialogueManager::applyTemporaryDispositionChange(int delta)
+ {
+ mTemporaryDispositionChange += delta;
+ }
+
+ bool DialogueManager::checkServiceRefused()
+ {
+ Filter filter (mActor, mChoice, mTalkedTo);
+
+ const MWWorld::Store<ESM::Dialogue> &dialogues =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ const ESM::Dialogue& dialogue = *dialogues.find ("Service Refusal");
+ MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow();
+
+ std::vector<const ESM::DialInfo *> infos = filter.list (dialogue, false, false, true);
+ if (!infos.empty())
+ {
+ const ESM::DialInfo* info = infos[0];
+
+ parseText (info->mResponse);
+
+ const MWWorld::Store<ESM::GameSetting>& gmsts =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
+
+ win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext),
+ gmsts.find ("sServiceRefusal")->getString());
+
+ executeScript (info->mResultScript);
+ return true;
+ }
+ return false;
+ }
+
+ void DialogueManager::say(const MWWorld::Ptr &actor, const std::string &topic) const
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(!sndMgr->sayDone(actor))
+ {
+ // Actor is already saying something.
+ return;
+ }
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Dialogue *dial = store.get<ESM::Dialogue>().find(topic);
+
+ Filter filter(actor, 0, false);
+ const ESM::DialInfo *info = filter.search(*dial, false);
+ if(info != NULL)
+ {
+ MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
+ if(winMgr->getSubtitlesEnabled())
+ winMgr->messageBox(info->mResponse);
+ sndMgr->say(actor, info->mSound);
+ }
+ }
+
+
+ std::vector<HyperTextToken> ParseHyperText(const std::string& text)
+ {
+ std::vector<HyperTextToken> result;
+ MyGUI::UString utext(text);
+ size_t pos_begin, pos_end, iteration_pos = 0;
+ for(;;)
+ {
+ pos_begin = utext.find('@', iteration_pos);
+ if (pos_begin != std::string::npos)
+ pos_end = utext.find('#', pos_begin);
+
+ if (pos_begin != std::string::npos && pos_end != std::string::npos)
+ {
+ result.push_back( HyperTextToken(utext.substr(iteration_pos, pos_begin - iteration_pos), false) );
+
+ std::string link = utext.substr(pos_begin + 1, pos_end - pos_begin - 1);
+ result.push_back( HyperTextToken(link, true) );
+
+ iteration_pos = pos_end + 1;
+ }
+ else
+ {
+ result.push_back( HyperTextToken(utext.substr(iteration_pos), false) );
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ size_t RemovePseudoAsterisks(std::string& phrase)
+ {
+ size_t pseudoAsterisksCount = 0;
+ const char specialPseudoAsteriskCharacter = 127;
+
+ if( !phrase.empty() )
+ {
+ std::string::reverse_iterator rit = phrase.rbegin();
+
+ while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter )
+ {
+ pseudoAsterisksCount++;
+ ++rit;
+ }
+ }
+
+ phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount);
+
+ return pseudoAsterisksCount;
+ }
+}
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
new file mode 100644
index 0000000000..1b7abed45a
--- /dev/null
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
@@ -0,0 +1,98 @@
+#ifndef GAME_MWDIALOG_DIALOGUEMANAGERIMP_H
+#define GAME_MWDIALOG_DIALOGUEMANAGERIMP_H
+
+#include "../mwbase/dialoguemanager.hpp"
+
+#include <map>
+#include <list>
+
+#include <components/compiler/streamerrorhandler.hpp>
+#include <components/translation/translation.hpp>
+
+#include "../mwworld/ptr.hpp"
+
+#include "../mwscript/compilercontext.hpp"
+
+namespace MWDialogue
+{
+ class DialogueManager : public MWBase::DialogueManager
+ {
+ std::map<std::string, ESM::Dialogue> mDialogueMap;
+ std::map<std::string, bool> mKnownTopics;// Those are the topics the player knows.
+ std::list<std::string> mActorKnownTopics;
+
+ Translation::Storage& mTranslationDataStorage;
+ MWScript::CompilerContext mCompilerContext;
+ std::ostream mErrorStream;
+ Compiler::StreamErrorHandler mErrorHandler;
+
+ MWWorld::Ptr mActor;
+ bool mTalkedTo;
+
+ int mChoice;
+ std::string mLastTopic;
+ bool mIsInChoice;
+
+ float mTemporaryDispositionChange;
+ float mPermanentDispositionChange;
+ bool mScriptVerbose;
+
+ void parseText (const std::string& text);
+
+ void updateTopics();
+
+ bool compile (const std::string& cmd,std::vector<Interpreter::Type_Code>& code);
+ void executeScript (const std::string& script);
+
+ void executeTopic (const std::string& topic, bool randomResponse=false);
+
+ public:
+
+ DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage);
+
+ virtual void clear();
+
+ virtual bool isInChoice() const;
+
+ virtual void startDialogue (const MWWorld::Ptr& actor);
+
+ virtual void addTopic (const std::string& topic);
+
+ virtual void askQuestion (const std::string& question,int choice);
+
+ virtual void goodbye();
+
+ virtual MWWorld::Ptr getActor() const;
+ ///< Return the actor the player is currently talking to.
+
+ virtual bool checkServiceRefused ();
+
+ virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const;
+
+ //calbacks for the GUI
+ virtual void keywordSelected (const std::string& keyword);
+ virtual void goodbyeSelected();
+ virtual void questionAnswered (int answer);
+
+ virtual void persuade (int type);
+ virtual int getTemporaryDispositionChange () const;
+ virtual void applyTemporaryDispositionChange (int delta);
+ };
+
+
+ struct HyperTextToken
+ {
+ HyperTextToken(const std::string& text, bool link) : mText(text), mLink(link) {}
+
+ std::string mText;
+ bool mLink;
+ };
+
+ // In translations (at least Russian) the links are marked with @#, so
+ // it should be a function to parse it
+ std::vector<HyperTextToken> ParseHyperText(const std::string& text);
+
+ size_t RemovePseudoAsterisks(std::string& phrase);
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp
new file mode 100644
index 0000000000..a7b0f19244
--- /dev/null
+++ b/apps/openmw/mwdialogue/filter.cpp
@@ -0,0 +1,664 @@
+
+#include "filter.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/journal.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/magiceffects.hpp"
+
+#include "selectwrapper.hpp"
+
+bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
+{
+ bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name());
+
+ // actor id
+ if (!info.mActor.empty())
+ {
+ if ( Misc::StringUtils::lowerCase (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor))
+ return false;
+ }
+ else if (isCreature)
+ {
+ // Creatures must not have topics aside of those specific to their id
+ return false;
+ }
+
+ // NPC race
+ if (!info.mRace.empty())
+ {
+ if (isCreature)
+ return false;
+
+ MWWorld::LiveCellRef<ESM::NPC> *cellRef = mActor.get<ESM::NPC>();
+
+ if (Misc::StringUtils::lowerCase (info.mRace)!= Misc::StringUtils::lowerCase (cellRef->mBase->mRace))
+ return false;
+ }
+
+ // NPC class
+ if (!info.mClass.empty())
+ {
+ if (isCreature)
+ return false;
+
+ MWWorld::LiveCellRef<ESM::NPC> *cellRef = mActor.get<ESM::NPC>();
+
+ if ( Misc::StringUtils::lowerCase (info.mClass)!= Misc::StringUtils::lowerCase (cellRef->mBase->mClass))
+ return false;
+ }
+
+ // NPC faction
+ if (!info.mNpcFaction.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));
+
+ if (iter==stats.getFactionRanks().end())
+ return false;
+
+ // check rank
+ if (iter->second < info.mData.mRank)
+ return false;
+ }
+ else if (info.mData.mRank != -1)
+ {
+ // if there is a rank condition, but the NPC is not in a faction, always fail
+ return false;
+ }
+
+ // Gender
+ if (!isCreature)
+ {
+ MWWorld::LiveCellRef<ESM::NPC>* npc = mActor.get<ESM::NPC>();
+ if (info.mData.mGender==(npc->mBase->mFlags & npc->mBase->Female ? 0 : 1))
+ return false;
+ }
+
+ return true;
+}
+
+bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const
+{
+ const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ // check player faction
+ if (!info.mPcFaction.empty())
+ {
+ MWMechanics::NpcStats& stats = MWWorld::Class::get (player).getNpcStats (player);
+ std::map<std::string,int>::iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction));
+
+ if(iter==stats.getFactionRanks().end())
+ return false;
+
+ // check rank
+ if (iter->second < info.mData.mPCrank)
+ return false;
+ }
+
+ // check cell
+ if (!info.mCell.empty())
+ if (Misc::StringUtils::lowerCase (player.getCell()->mCell->mName) != Misc::StringUtils::lowerCase (info.mCell))
+ return false;
+
+ return true;
+}
+
+bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const
+{
+ for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator iter (info.mSelects.begin());
+ iter != info.mSelects.end(); ++iter)
+ if (!testSelectStruct (*iter))
+ return false;
+
+ return true;
+}
+
+bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert) const
+{
+ bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name());
+
+ if (isCreature)
+ return true;
+
+ int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor);
+ // For service refusal, the disposition check is inverted. However, a value of 0 still means "always succeed".
+ return invert ? (info.mData.mDisposition == 0 || actorDisposition < info.mData.mDisposition)
+ : (actorDisposition >= info.mData.mDisposition);
+}
+
+bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const
+{
+ if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name()))
+ // If the actor is a creature, we do not test the conditions applicable
+ // only to NPCs. Such conditions can never be satisfied, apart
+ // inverted ones (NotClass, NotRace, NotFaction return true
+ // because creatures are not of any race, class or faction).
+ return select.getType() == SelectWrapper::Type_Inverted;
+
+ switch (select.getType())
+ {
+ case SelectWrapper::Type_None: return true;
+ case SelectWrapper::Type_Integer: return select.selectCompare (getSelectStructInteger (select));
+ case SelectWrapper::Type_Numeric: return testSelectStructNumeric (select);
+ case SelectWrapper::Type_Boolean: return select.selectCompare (getSelectStructBoolean (select));
+
+ // We must not do the comparison for inverted functions (eg. Function_NotClass)
+ case SelectWrapper::Type_Inverted: return getSelectStructBoolean (select);
+ }
+
+ return true;
+}
+
+bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) const
+{
+ switch (select.getFunction())
+ {
+ case SelectWrapper::Function_Global:
+
+ // internally all globals are float :(
+ return select.selectCompare (
+ MWBase::Environment::get().getWorld()->getGlobalVariable (select.getName()).mFloat);
+
+ case SelectWrapper::Function_Local:
+ {
+ std::string scriptName = MWWorld::Class::get (mActor).getScript (mActor);
+
+ if (scriptName.empty())
+ return false; // no script
+
+ const ESM::Script *script =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptName);
+
+ std::string name = select.getName();
+
+ int i = 0;
+
+ for (; i<static_cast<int> (script->mVarNames.size()); ++i)
+ if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name)
+ break;
+
+ if (i>=static_cast<int> (script->mVarNames.size()))
+ return false; // script does not have a variable of this name
+
+ const MWScript::Locals& locals = mActor.getRefData().getLocals();
+
+ if (i<script->mData.mNumShorts)
+ return select.selectCompare (static_cast<int> (locals.mShorts[i]));
+
+ i -= script->mData.mNumShorts;
+
+ if (i<script->mData.mNumLongs)
+ return select.selectCompare (locals.mLongs[i]);
+
+ i -= script->mData.mNumLongs;
+
+ return select.selectCompare (locals.mFloats.at (i));
+ }
+
+ case SelectWrapper::Function_PcHealthPercent:
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
+ MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
+
+ return select.selectCompare (ratio);
+ }
+
+ case SelectWrapper::Function_PcDynamicStat:
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ float value = MWWorld::Class::get (player).getCreatureStats (player).
+ getDynamic (select.getArgument()).getCurrent();
+
+ return select.selectCompare (value);
+ }
+
+ case SelectWrapper::Function_HealthPercent:
+ {
+ float ratio = MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getCurrent() /
+ MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getModified();
+
+ return select.selectCompare (ratio);
+ }
+
+ default:
+
+ throw std::runtime_error ("unknown numeric select function");
+ }
+}
+
+int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const
+{
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ switch (select.getFunction())
+ {
+ case SelectWrapper::Function_Journal:
+
+ return MWBase::Environment::get().getJournal()->getJournalIndex (select.getName());
+
+ case SelectWrapper::Function_Item:
+ {
+ MWWorld::ContainerStore& store = MWWorld::Class::get (player).getContainerStore (player);
+
+ int sum = 0;
+
+ std::string name = select.getName();
+
+ for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter)
+ if (Misc::StringUtils::lowerCase(iter->getCellRef().mRefID) == name)
+ sum += iter->getRefData().getCount();
+
+ return sum;
+ }
+
+ case SelectWrapper::Function_Dead:
+
+ return MWBase::Environment::get().getMechanicsManager()->countDeaths (select.getName());
+
+ case SelectWrapper::Function_Choice:
+
+ return mChoice;
+
+ case SelectWrapper::Function_AiSetting:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (select.getArgument());
+
+ case SelectWrapper::Function_PcAttribute:
+
+ return MWWorld::Class::get (player).getCreatureStats (player).
+ getAttribute (select.getArgument()).getModified();
+
+ case SelectWrapper::Function_PcSkill:
+
+ return static_cast<int> (MWWorld::Class::get (player).
+ getNpcStats (player).getSkill (select.getArgument()).getModified());
+
+ case SelectWrapper::Function_FriendlyHit:
+ {
+ int hits = MWWorld::Class::get (mActor).getCreatureStats (mActor).getFriendlyHits();
+
+ return hits>4 ? 4 : hits;
+ }
+
+ case SelectWrapper::Function_PcLevel:
+
+ return MWWorld::Class::get (player).getCreatureStats (player).getLevel();
+
+ case SelectWrapper::Function_PcGender:
+
+ return player.get<ESM::NPC>()->mBase->isMale() ? 0 : 1;
+
+ case SelectWrapper::Function_PcClothingModifier:
+ {
+ MWWorld::InventoryStore& store = MWWorld::Class::get (player).getInventoryStore (player);
+
+ int value = 0;
+
+ for (int i=0; i<=15; ++i) // everything except thigns held in hands and amunition
+ {
+ MWWorld::ContainerStoreIterator slot = store.getSlot (i);
+
+ if (slot!=store.end())
+ value += MWWorld::Class::get (*slot).getValue (*slot);
+ }
+
+ return value;
+ }
+
+ case SelectWrapper::Function_PcCrimeLevel:
+
+ return MWWorld::Class::get (player).getNpcStats (player).getBounty();
+
+ case SelectWrapper::Function_RankRequirement:
+ {
+ if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty())
+ return 0;
+
+ std::string faction =
+ MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first;
+
+ int rank = getFactionRank (player, faction);
+
+ if (rank>=9)
+ return 0; // max rank
+
+ int result = 0;
+
+ if (hasFactionRankSkillRequirements (player, faction, rank+1))
+ result += 1;
+
+ if (hasFactionRankReputationRequirements (player, faction, rank+1))
+ result += 2;
+
+ return result;
+ }
+
+ case SelectWrapper::Function_Level:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).getLevel();
+
+ case SelectWrapper::Function_PCReputation:
+
+ return MWWorld::Class::get (player).getNpcStats (player).getReputation();
+
+ case SelectWrapper::Function_Weather:
+
+ return MWBase::Environment::get().getWorld()->getCurrentWeather();
+
+ case SelectWrapper::Function_Reputation:
+
+ return MWWorld::Class::get (mActor).getNpcStats (mActor).getReputation();
+
+ case SelectWrapper::Function_FactionRankDiff:
+ {
+ if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty())
+ return 0;
+
+ std::pair<std::string, int> faction =
+ *MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin();
+
+ int rank = getFactionRank (player, faction.first);
+
+ return rank-faction.second;
+ }
+
+ case SelectWrapper::Function_WerewolfKills:
+
+ return MWWorld::Class::get (player).getNpcStats (player).getWerewolfKills();
+
+ case SelectWrapper::Function_RankLow:
+ case SelectWrapper::Function_RankHigh:
+ {
+ bool low = select.getFunction()==SelectWrapper::Function_RankLow;
+
+ if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty())
+ return 0;
+
+ std::string factionId =
+ MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first;
+
+ int value = 0;
+
+ const ESM::Faction& faction =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find (factionId);
+
+ MWMechanics::NpcStats& playerStats = MWWorld::Class::get (player).getNpcStats (player);
+
+ for (std::vector<ESM::Faction::Reaction>::const_iterator iter (faction.mReactions.begin());
+ iter!=faction.mReactions.end(); ++iter)
+ if (playerStats.getFactionRanks().find (iter->mFaction)!=playerStats.getFactionRanks().end())
+ if (low ? iter->mReaction<value : iter->mReaction>value)
+ value = iter->mReaction;
+
+ return value;
+ }
+
+ default:
+
+ throw std::runtime_error ("unknown integer select function");
+ }
+}
+
+bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const
+{
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ switch (select.getFunction())
+ {
+ case SelectWrapper::Function_False:
+
+ return false;
+
+ case SelectWrapper::Function_NotId:
+
+ return select.getName()!=Misc::StringUtils::lowerCase (MWWorld::Class::get (mActor).getId (mActor));
+
+ case SelectWrapper::Function_NotFaction:
+
+ return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mFaction)!=select.getName();
+
+ case SelectWrapper::Function_NotClass:
+
+ return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mClass)!=select.getName();
+
+ case SelectWrapper::Function_NotRace:
+
+ return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mRace)!=select.getName();
+
+ case SelectWrapper::Function_NotCell:
+
+ return Misc::StringUtils::lowerCase (mActor.getCell()->mCell->mName)!=select.getName();
+
+ case SelectWrapper::Function_NotLocal:
+ {
+ std::string scriptName = MWWorld::Class::get (mActor).getScript (mActor);
+
+ if (scriptName.empty())
+ // This actor has no attached script, so there is no local variable
+ return true;
+
+ const ESM::Script *script =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptName);
+
+ std::string name = select.getName();
+
+ int i = 0;
+ for (; i < static_cast<int> (script->mVarNames.size()); ++i)
+ if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name)
+ break;
+
+ if (i >= static_cast<int> (script->mVarNames.size()))
+ return true; // script does not have a variable of this name
+
+ return false;
+ }
+
+ case SelectWrapper::Function_SameGender:
+
+ return (player.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female)==
+ (mActor.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female);
+
+ case SelectWrapper::Function_SameRace:
+
+ return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mRace)!=
+ Misc::StringUtils::lowerCase (player.get<ESM::NPC>()->mBase->mRace);
+
+ case SelectWrapper::Function_SameFaction:
+
+ return MWWorld::Class::get (mActor).getNpcStats (mActor).isSameFaction (
+ MWWorld::Class::get (player).getNpcStats (player));
+
+ case SelectWrapper::Function_PcCommonDisease:
+
+ return MWWorld::Class::get (player).getCreatureStats (player).hasCommonDisease();
+
+ case SelectWrapper::Function_PcBlightDisease:
+
+ return MWWorld::Class::get (player).getCreatureStats (player).hasBlightDisease();
+
+ case SelectWrapper::Function_PcCorprus:
+
+ return MWWorld::Class::get (player).getCreatureStats (player).
+ getMagicEffects().get (ESM::MagicEffect::Corprus).mMagnitude!=0;
+
+ case SelectWrapper::Function_PcExpelled:
+ {
+ if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty())
+ return false;
+
+ std::string faction =
+ MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first;
+
+ std::set<std::string>& expelled = MWWorld::Class::get (player).getNpcStats (player).getExpelled();
+
+ return expelled.find (faction)!=expelled.end();
+ }
+
+ case SelectWrapper::Function_PcVampire:
+
+ return MWWorld::Class::get (player).getNpcStats (player).isVampire();
+
+ case SelectWrapper::Function_TalkedToPc:
+
+ return mTalkedToPlayer;
+
+ case SelectWrapper::Function_Alarmed:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).isAlarmed();
+
+ case SelectWrapper::Function_Detected:
+
+ return MWWorld::Class::get (mActor).hasDetected (mActor, player);
+
+ case SelectWrapper::Function_Attacked:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAttacked();
+
+ case SelectWrapper::Function_ShouldAttack:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).isHostile();
+
+ case SelectWrapper::Function_CreatureTargetted:
+
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted();
+
+ case SelectWrapper::Function_Werewolf:
+
+ return MWWorld::Class::get (mActor).getNpcStats (mActor).isWerewolf();
+
+ default:
+
+ throw std::runtime_error ("unknown boolean select function");
+ }
+}
+
+int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const
+{
+ MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor);
+
+ std::map<std::string, int>::const_iterator iter = stats.getFactionRanks().find (factionId);
+
+ if (iter==stats.getFactionRanks().end())
+ return -1;
+
+ return iter->second;
+}
+
+bool MWDialogue::Filter::hasFactionRankSkillRequirements (const MWWorld::Ptr& actor,
+ const std::string& factionId, int rank) const
+{
+ if (rank<0 || rank>=10)
+ throw std::runtime_error ("rank index out of range");
+
+ if (!MWWorld::Class::get (actor).getNpcStats (actor).hasSkillsForRank (factionId, rank))
+ return false;
+
+ const ESM::Faction& faction =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find (factionId);
+
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get (actor).getCreatureStats (actor);
+
+ return stats.getAttribute (faction.mData.mAttribute[0]).getBase()>=faction.mData.mRankData[rank].mAttribute1 &&
+ stats.getAttribute (faction.mData.mAttribute[1]).getBase()>=faction.mData.mRankData[rank].mAttribute2;
+}
+
+bool MWDialogue::Filter::hasFactionRankReputationRequirements (const MWWorld::Ptr& actor,
+ const std::string& factionId, int rank) const
+{
+ if (rank<0 || rank>=10)
+ throw std::runtime_error ("rank index out of range");
+
+ MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor);
+
+ const ESM::Faction& faction =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find (factionId);
+
+ return stats.getFactionReputation (factionId)>=faction.mData.mRankData[rank].mFactReaction;
+}
+
+MWDialogue::Filter::Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer)
+: mActor (actor), mChoice (choice), mTalkedToPlayer (talkedToPlayer)
+{}
+
+const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const
+{
+ std::vector<const ESM::DialInfo *> suitableInfos = list (dialogue, fallbackToInfoRefusal, false);
+
+ if (suitableInfos.empty())
+ return NULL;
+ else
+ return suitableInfos[0];
+}
+
+std::vector<const ESM::DialInfo *> MWDialogue::Filter::list (const ESM::Dialogue& dialogue,
+ bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const
+{
+ std::vector<const ESM::DialInfo *> infos;
+
+ bool infoRefusal = false;
+
+ // Iterate over topic responses to find a matching one
+ for (std::vector<ESM::DialInfo>::const_iterator iter = dialogue.mInfo.begin();
+ iter!=dialogue.mInfo.end(); ++iter)
+ {
+ if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter))
+ {
+ if (testDisposition (*iter, invertDisposition)) {
+ infos.push_back(&*iter);
+ if (!searchAll)
+ break;
+ }
+ else
+ infoRefusal = true;
+ }
+ }
+
+ if (infos.empty() && infoRefusal && fallbackToInfoRefusal)
+ {
+ // No response is valid because of low NPC disposition,
+ // search a response in the topic "Info Refusal"
+
+ const MWWorld::Store<ESM::Dialogue> &dialogues =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
+
+ const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal");
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter = infoRefusalDialogue.mInfo.begin();
+ iter!=infoRefusalDialogue.mInfo.end(); ++iter)
+ if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter, invertDisposition)) {
+ infos.push_back(&*iter);
+ if (!searchAll)
+ break;
+ }
+ }
+
+ return infos;
+}
+
+bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const
+{
+ for (std::vector<ESM::DialInfo>::const_iterator iter = dialogue.mInfo.begin();
+ iter!=dialogue.mInfo.end(); ++iter)
+ {
+ if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter))
+ return true;
+ }
+
+ return false;
+}
diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp
new file mode 100644
index 0000000000..5c6d092ad9
--- /dev/null
+++ b/apps/openmw/mwdialogue/filter.hpp
@@ -0,0 +1,68 @@
+#ifndef GAME_MWDIALOGUE_FILTER_H
+#define GAME_MWDIALOGUE_FILTER_H
+
+#include <vector>
+
+#include "../mwworld/ptr.hpp"
+
+namespace ESM
+{
+ struct DialInfo;
+ struct Dialogue;
+}
+
+namespace MWDialogue
+{
+ class SelectWrapper;
+
+ class Filter
+ {
+ MWWorld::Ptr mActor;
+ int mChoice;
+ bool mTalkedToPlayer;
+
+ bool testActor (const ESM::DialInfo& info) const;
+ ///< Is this the right actor for this \a info?
+
+ bool testPlayer (const ESM::DialInfo& info) const;
+ ///< Do the player and the cell the player is currently in match \a info?
+
+ bool testSelectStructs (const ESM::DialInfo& info) const;
+ ///< Are all select structs matching?
+
+ bool testDisposition (const ESM::DialInfo& info, bool invert=false) const;
+ ///< Is the actor disposition toward the player high enough (or low enough, if \a invert is true)?
+
+ bool testSelectStruct (const SelectWrapper& select) const;
+
+ bool testSelectStructNumeric (const SelectWrapper& select) const;
+
+ int getSelectStructInteger (const SelectWrapper& select) const;
+
+ bool getSelectStructBoolean (const SelectWrapper& select) const;
+
+ int getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const;
+
+ bool hasFactionRankSkillRequirements (const MWWorld::Ptr& actor, const std::string& factionId,
+ int rank) const;
+
+ bool hasFactionRankReputationRequirements (const MWWorld::Ptr& actor, const std::string& factionId,
+ int rank) const;
+
+ public:
+
+ Filter (const MWWorld::Ptr& actor, int choice, bool talkedToPlayer);
+
+ std::vector<const ESM::DialInfo *> list (const ESM::Dialogue& dialogue,
+ bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const;
+
+ const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const;
+ ///< Get a matching response for the requested dialogue.
+ /// Redirect to "Info Refusal" topic if a response fulfills all conditions but disposition.
+
+ bool responseAvailable (const ESM::Dialogue& dialogue) const;
+ ///< Does a matching response exist? (disposition is ignored for this check)
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp
new file mode 100644
index 0000000000..5ffde54991
--- /dev/null
+++ b/apps/openmw/mwdialogue/journalentry.cpp
@@ -0,0 +1,69 @@
+
+#include "journalentry.hpp"
+
+#include <stdexcept>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+namespace MWDialogue
+{
+ JournalEntry::JournalEntry() {}
+
+ JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId)
+ : mTopic (topic), mInfoId (infoId)
+ {}
+
+ std::string JournalEntry::getText (const MWWorld::ESMStore& store) const
+ {
+ const ESM::Dialogue *dialogue =
+ store.get<ESM::Dialogue>().find (mTopic);
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mId == mInfoId)
+ return iter->mResponse;
+
+ throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + mTopic);
+ }
+
+ JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index)
+ {
+ return JournalEntry (topic, idFromIndex (topic, index));
+ }
+
+ std::string JournalEntry::idFromIndex (const std::string& topic, int index)
+ {
+ const ESM::Dialogue *dialogue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (topic);
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mData.mDisposition==index) /// \todo cleanup info structure
+ {
+ return iter->mId;
+ }
+
+ throw std::runtime_error ("unknown journal index for topic " + topic);
+ }
+
+ StampedJournalEntry::StampedJournalEntry()
+ : mDay (0), mMonth (0), mDayOfMonth (0)
+ {}
+
+ StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId,
+ int day, int month, int dayOfMonth)
+ : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth)
+ {}
+
+ StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index)
+ {
+ int day = MWBase::Environment::get().getWorld()->getGlobalVariable ("dayspassed").mLong;
+ int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong;
+ int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong;
+
+ return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth);
+ }
+}
diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp
new file mode 100644
index 0000000000..9d009b48b2
--- /dev/null
+++ b/apps/openmw/mwdialogue/journalentry.hpp
@@ -0,0 +1,46 @@
+#ifndef GAME_MWDIALOGUE_JOURNALENTRY_H
+#define GAME_MWDIALOGUE_JOURNALENTRY_H
+
+#include <string>
+
+namespace MWWorld
+{
+ struct ESMStore;
+}
+
+namespace MWDialogue
+{
+ /// \brief A quest or dialogue entry
+ struct JournalEntry
+ {
+ std::string mTopic;
+ std::string mInfoId;
+
+ JournalEntry();
+
+ JournalEntry (const std::string& topic, const std::string& infoId);
+
+ std::string getText (const MWWorld::ESMStore& store) const;
+
+ static JournalEntry makeFromQuest (const std::string& topic, int index);
+
+ static std::string idFromIndex (const std::string& topic, int index);
+ };
+
+ /// \brief A quest entry with a timestamp.
+ struct StampedJournalEntry : public JournalEntry
+ {
+ int mDay;
+ int mMonth;
+ int mDayOfMonth;
+
+ StampedJournalEntry();
+
+ StampedJournalEntry (const std::string& topic, const std::string& infoId,
+ int day, int month, int dayOfMonth);
+
+ static StampedJournalEntry makeFromQuest (const std::string& topic, int index);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp
new file mode 100644
index 0000000000..23cfb5fdd1
--- /dev/null
+++ b/apps/openmw/mwdialogue/journalimp.cpp
@@ -0,0 +1,121 @@
+
+#include "journalimp.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwgui/messagebox.hpp"
+
+namespace MWDialogue
+{
+ Quest& Journal::getQuest (const std::string& id)
+ {
+ TQuestContainer::iterator iter = mQuests.find (id);
+
+ if (iter==mQuests.end())
+ {
+ std::pair<TQuestContainer::iterator, bool> result =
+ mQuests.insert (std::make_pair (id, Quest (id)));
+
+ iter = result.first;
+ }
+
+ return iter->second;
+ }
+
+ Journal::Journal()
+ {}
+
+ void Journal::clear()
+ {
+ mJournal.clear();
+ mQuests.clear();
+ mTopics.clear();
+ }
+
+ void Journal::addEntry (const std::string& id, int index)
+ {
+ // bail out of we already have heard this...
+ std::string infoId = JournalEntry::idFromIndex (id, index);
+ for (TEntryIter i = mJournal.begin (); i != mJournal.end (); ++i)
+ if (i->mTopic == id && i->mInfoId == infoId)
+ return;
+
+ StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index);
+
+ mJournal.push_back (entry);
+
+ Quest& quest = getQuest (id);
+
+ quest.addEntry (entry); // we are doing slicing on purpose here
+
+ std::vector<std::string> empty;
+ std::string notification = "#{sJournalEntry}";
+ MWBase::Environment::get().getWindowManager()->messageBox (notification, empty);
+ }
+
+ void Journal::setJournalIndex (const std::string& id, int index)
+ {
+ Quest& quest = getQuest (id);
+
+ quest.setIndex (index);
+ }
+
+ void Journal::addTopic (const std::string& topicId, const std::string& infoId)
+ {
+ TTopicContainer::iterator iter = mTopics.find (topicId);
+
+ if (iter==mTopics.end())
+ {
+ std::pair<TTopicContainer::iterator, bool> result
+ = mTopics.insert (std::make_pair (topicId, Topic (topicId)));
+
+ iter = result.first;
+ }
+
+ iter->second.addEntry (JournalEntry (topicId, infoId));
+ }
+
+ int Journal::getJournalIndex (const std::string& id) const
+ {
+ TQuestContainer::const_iterator iter = mQuests.find (id);
+
+ if (iter==mQuests.end())
+ return 0;
+
+ return iter->second.getIndex();
+ }
+
+ Journal::TEntryIter Journal::begin() const
+ {
+ return mJournal.begin();
+ }
+
+ Journal::TEntryIter Journal::end() const
+ {
+ return mJournal.end();
+ }
+
+ Journal::TQuestIter Journal::questBegin() const
+ {
+ return mQuests.begin();
+ }
+
+ Journal::TQuestIter Journal::questEnd() const
+ {
+ return mQuests.end();
+ }
+
+ Journal::TTopicIter Journal::topicBegin() const
+ {
+ return mTopics.begin();
+ }
+
+ Journal::TTopicIter Journal::topicEnd() const
+ {
+ return mTopics.end();
+ }
+}
diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp
new file mode 100644
index 0000000000..f4f8eb1c29
--- /dev/null
+++ b/apps/openmw/mwdialogue/journalimp.hpp
@@ -0,0 +1,61 @@
+#ifndef GAME_MWDIALOG_JOURNAL_H
+#define GAME_MWDIALOG_JOURNAL_H
+
+#include "../mwbase/journal.hpp"
+
+#include "journalentry.hpp"
+#include "quest.hpp"
+
+namespace MWDialogue
+{
+ /// \brief The player's journal
+ class Journal : public MWBase::Journal
+ {
+ TEntryContainer mJournal;
+ TQuestContainer mQuests;
+ TTopicContainer mTopics;
+
+ Quest& getQuest (const std::string& id);
+
+ public:
+
+ Journal();
+
+ virtual void clear();
+
+ virtual void addEntry (const std::string& id, int index);
+ ///< Add a journal entry.
+
+ virtual void setJournalIndex (const std::string& id, int index);
+ ///< Set the journal index without adding an entry.
+
+ virtual int getJournalIndex (const std::string& id) const;
+ ///< Get the journal index.
+
+ virtual void addTopic (const std::string& topicId, const std::string& infoId);
+
+ virtual TEntryIter begin() const;
+ ///< Iterator pointing to the begin of the main journal.
+ ///
+ /// \note Iterators to main journal entries will never become invalid.
+
+ virtual TEntryIter end() const;
+ ///< Iterator pointing past the end of the main journal.
+
+ virtual TQuestIter questBegin() const;
+ ///< Iterator pointing to the first quest (sorted by topic ID)
+
+ virtual TQuestIter questEnd() const;
+ ///< Iterator pointing past the last quest.
+
+ virtual TTopicIter topicBegin() const;
+ ///< Iterator pointing to the first topic (sorted by topic ID)
+ ///
+ /// \note The topic ID is identical with the user-visible topic string.
+
+ virtual TTopicIter topicEnd() const;
+ ///< Iterator pointing past the last topic.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp
new file mode 100644
index 0000000000..5e2739be16
--- /dev/null
+++ b/apps/openmw/mwdialogue/quest.cpp
@@ -0,0 +1,90 @@
+
+#include "quest.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWDialogue
+{
+ Quest::Quest()
+ : Topic(), mIndex (0), mFinished (false)
+ {}
+
+ Quest::Quest (const std::string& topic)
+ : Topic (topic), mIndex (0), mFinished (false)
+ {}
+
+ const std::string Quest::getName() const
+ {
+ const ESM::Dialogue *dialogue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mQuestStatus==ESM::DialInfo::QS_Name)
+ return iter->mResponse;
+
+ return "";
+ }
+
+ int Quest::getIndex() const
+ {
+ return mIndex;
+ }
+
+ void Quest::setIndex (int index)
+ {
+ const ESM::Dialogue *dialogue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name)
+ {
+ mIndex = index;
+
+ if (iter->mQuestStatus==ESM::DialInfo::QS_Finished)
+ mFinished = true;
+ else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart)
+ mFinished = false;
+
+ return;
+ }
+
+ throw std::runtime_error ("unknown journal index for topic " + mTopic);
+ }
+
+ bool Quest::isFinished() const
+ {
+ return mFinished;
+ }
+
+ void Quest::addEntry (const JournalEntry& entry)
+ {
+ int index = -1;
+
+ const ESM::Dialogue *dialogue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (entry.mTopic);
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mId == entry.mInfoId)
+ {
+ index = iter->mData.mDisposition; /// \todo cleanup info structure
+ break;
+ }
+
+ if (index==-1)
+ throw std::runtime_error ("unknown journal entry for topic " + mTopic);
+
+ setIndex (index);
+
+ for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
+ if (*iter==entry.mInfoId)
+ return;
+
+ mEntries.push_back (entry.mInfoId);
+ }
+}
diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp
new file mode 100644
index 0000000000..3afa81fac0
--- /dev/null
+++ b/apps/openmw/mwdialogue/quest.hpp
@@ -0,0 +1,37 @@
+#ifndef GAME_MWDIALOG_QUEST_H
+#define GAME_MWDIALOG_QUEST_H
+
+#include "topic.hpp"
+
+namespace MWDialogue
+{
+ /// \brief A quest in progress or a compelted quest
+ class Quest : public Topic
+ {
+ int mIndex;
+ bool mFinished;
+
+ public:
+
+ Quest();
+
+ Quest (const std::string& topic);
+
+ const std::string getName() const;
+ ///< May be an empty string
+
+ int getIndex() const;
+
+ void setIndex (int index);
+ ///< Calling this function with a non-existant index while throw an exception.
+
+ bool isFinished() const;
+
+ virtual void addEntry (const JournalEntry& entry);
+ ///< Add entry and adjust index accordingly.
+ ///
+ /// \note Redundant entries are ignored, but the index is still adjusted.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp
new file mode 100644
index 0000000000..3f22998f0e
--- /dev/null
+++ b/apps/openmw/mwdialogue/selectwrapper.cpp
@@ -0,0 +1,305 @@
+
+#include "selectwrapper.hpp"
+
+#include <cctype>
+
+#include <stdexcept>
+#include <algorithm>
+#include <sstream>
+#include <iterator>
+
+#include <components/misc/stringops.hpp>
+
+namespace
+{
+ template<typename T1, typename T2>
+ bool selectCompareImp (char comp, T1 value1, T2 value2)
+ {
+ switch (comp)
+ {
+ case '0': return value1==value2;
+ case '1': return value1!=value2;
+ case '2': return value1>value2;
+ case '3': return value1>=value2;
+ case '4': return value1<value2;
+ case '5': return value1<=value2;
+ }
+
+ throw std::runtime_error ("unknown compare type in dialogue info select");
+ }
+
+ template<typename T>
+ bool selectCompareImp (const ESM::DialInfo::SelectStruct& select, T value1)
+ {
+ if (select.mValue.getType()==ESM::VT_Int)
+ {
+ return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getInteger());
+ }
+ else if (select.mValue.getType()==ESM::VT_Float)
+ {
+ return selectCompareImp (select.mSelectRule[4], value1, select.mValue.getFloat());
+ }
+ else
+ throw std::runtime_error (
+ "unsupported variable type in dialogue info select");
+ }
+}
+
+MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const
+{
+ int index = 0;
+
+ std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index;
+
+ switch (index)
+ {
+ case 0: return Function_RankLow;
+ case 1: return Function_RankHigh;
+ case 2: return Function_RankRequirement;
+ case 3: return Function_Reputation;
+ case 4: return Function_HealthPercent;
+ case 5: return Function_PCReputation;
+ case 6: return Function_PcLevel;
+ case 7: return Function_PcHealthPercent;
+ case 8: case 9: return Function_PcDynamicStat;
+ case 10: return Function_PcAttribute;
+ case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20:
+ case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30:
+ case 31: case 32: case 33: case 34: case 35: case 36: case 37: return Function_PcSkill;
+ case 38: return Function_PcGender;
+ case 39: return Function_PcExpelled;
+ case 40: return Function_PcCommonDisease;
+ case 41: return Function_PcBlightDisease;
+ case 42: return Function_PcClothingModifier;
+ case 43: return Function_PcCrimeLevel;
+ case 44: return Function_SameGender;
+ case 45: return Function_SameRace;
+ case 46: return Function_SameFaction;
+ case 47: return Function_FactionRankDiff;
+ case 48: return Function_Detected;
+ case 49: return Function_Alarmed;
+ case 50: return Function_Choice;
+ case 51: case 52: case 53: case 54: case 55: case 56: case 57: return Function_PcAttribute;
+ case 58: return Function_PcCorprus;
+ case 59: return Function_Weather;
+ case 60: return Function_PcVampire;
+ case 61: return Function_Level;
+ case 62: return Function_Attacked;
+ case 63: return Function_TalkedToPc;
+ case 64: return Function_PcDynamicStat;
+ case 65: return Function_CreatureTargetted;
+ case 66: return Function_FriendlyHit;
+ case 67: case 68: case 69: case 70: return Function_AiSetting;
+ case 71: return Function_ShouldAttack;
+ case 72: return Function_Werewolf;
+ case 73: return Function_WerewolfKills;
+ }
+
+ return Function_False;
+}
+
+MWDialogue::SelectWrapper::SelectWrapper (const ESM::DialInfo::SelectStruct& select) : mSelect (select) {}
+
+MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const
+{
+ char type = mSelect.mSelectRule[1];
+
+ switch (type)
+ {
+ case '1': return decodeFunction();
+ case '2': return Function_Global;
+ case '3': return Function_Local;
+ case '4': return Function_Journal;
+ case '5': return Function_Item;
+ case '6': return Function_Dead;
+ case '7': return Function_NotId;
+ case '8': return Function_NotFaction;
+ case '9': return Function_NotClass;
+ case 'A': return Function_NotRace;
+ case 'B': return Function_NotCell;
+ case 'C': return Function_NotLocal;
+ }
+
+ return Function_None;
+}
+
+int MWDialogue::SelectWrapper::getArgument() const
+{
+ if (mSelect.mSelectRule[1]!='1')
+ return 0;
+
+ int index = 0;
+
+ std::istringstream (mSelect.mSelectRule.substr(2,2)) >> index;
+
+ switch (index)
+ {
+ // AI settings
+ case 67: return 1;
+ case 68: return 0;
+ case 69: return 3;
+ case 70: return 2;
+
+ // attributes
+ case 10: return 0;
+ case 51: return 1;
+ case 52: return 2;
+ case 53: return 3;
+ case 54: return 4;
+ case 55: return 5;
+ case 56: return 6;
+ case 57: return 7;
+
+ // skills
+ case 11: return 0;
+ case 12: return 1;
+ case 13: return 2;
+ case 14: return 3;
+ case 15: return 4;
+ case 16: return 5;
+ case 17: return 6;
+ case 18: return 7;
+ case 19: return 8;
+ case 20: return 9;
+ case 21: return 10;
+ case 22: return 11;
+ case 23: return 12;
+ case 24: return 13;
+ case 25: return 14;
+ case 26: return 15;
+ case 27: return 16;
+ case 28: return 17;
+ case 29: return 18;
+ case 30: return 19;
+ case 31: return 20;
+ case 32: return 21;
+ case 33: return 22;
+ case 34: return 23;
+ case 35: return 24;
+ case 36: return 25;
+ case 37: return 26;
+
+ // dynamic stats
+ case 8: return 1;
+ case 9: return 2;
+ case 64: return 0;
+ }
+
+ return 0;
+}
+
+MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const
+{
+ static const Function integerFunctions[] =
+ {
+ Function_Journal, Function_Item, Function_Dead,
+ Function_Choice,
+ Function_AiSetting,
+ Function_PcAttribute, Function_PcSkill,
+ Function_FriendlyHit,
+ Function_PcLevel, Function_PcGender, Function_PcClothingModifier,
+ Function_PcCrimeLevel,
+ Function_RankRequirement,
+ Function_Level, Function_PCReputation,
+ Function_Weather,
+ Function_Reputation, Function_FactionRankDiff,
+ Function_WerewolfKills,
+ Function_RankLow, Function_RankHigh,
+ Function_None // end marker
+ };
+
+ static const Function numericFunctions[] =
+ {
+ Function_Global, Function_Local,
+ Function_PcDynamicStat, Function_PcHealthPercent,
+ Function_HealthPercent,
+ Function_None // end marker
+ };
+
+ static const Function booleanFunctions[] =
+ {
+ Function_False,
+ Function_SameGender, Function_SameRace, Function_SameFaction,
+ Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus,
+ Function_PcExpelled,
+ Function_PcVampire, Function_TalkedToPc,
+ Function_Alarmed, Function_Detected,
+ Function_Attacked, Function_ShouldAttack,
+ Function_CreatureTargetted,
+ Function_Werewolf,
+ Function_None // end marker
+ };
+
+ static const Function invertedBooleanFunctions[] =
+ {
+ Function_NotId, Function_NotFaction, Function_NotClass,
+ Function_NotRace, Function_NotCell, Function_NotLocal,
+ Function_None // end marker
+ };
+
+ Function function = getFunction();
+
+ for (int i=0; integerFunctions[i]!=Function_None; ++i)
+ if (integerFunctions[i]==function)
+ return Type_Integer;
+
+ for (int i=0; numericFunctions[i]!=Function_None; ++i)
+ if (numericFunctions[i]==function)
+ return Type_Numeric;
+
+ for (int i=0; booleanFunctions[i]!=Function_None; ++i)
+ if (booleanFunctions[i]==function)
+ return Type_Boolean;
+
+ for (int i=0; invertedBooleanFunctions[i]!=Function_None; ++i)
+ if (invertedBooleanFunctions[i]==function)
+ return Type_Inverted;
+
+ return Type_None;
+}
+
+bool MWDialogue::SelectWrapper::isNpcOnly() const
+{
+ static const Function functions[] =
+ {
+ Function_NotFaction, Function_NotClass, Function_NotRace,
+ Function_SameGender, Function_SameRace, Function_SameFaction,
+ Function_PcSkill,
+ Function_PcExpelled,
+ Function_PcVampire,
+ Function_PcCrimeLevel,
+ Function_RankRequirement,
+ Function_Reputation, Function_FactionRankDiff,
+ Function_Werewolf, Function_WerewolfKills,
+ Function_RankLow, Function_RankHigh,
+ Function_None // end marker
+ };
+
+ Function function = getFunction();
+
+ for (int i=0; functions[i]!=Function_None; ++i)
+ if (functions[i]==function)
+ return true;
+
+ return false;
+}
+
+bool MWDialogue::SelectWrapper::selectCompare (int value) const
+{
+ return selectCompareImp (mSelect, value);
+}
+
+bool MWDialogue::SelectWrapper::selectCompare (float value) const
+{
+ return selectCompareImp (mSelect, value);
+}
+
+bool MWDialogue::SelectWrapper::selectCompare (bool value) const
+{
+ return selectCompareImp (mSelect, static_cast<int> (value));
+}
+
+std::string MWDialogue::SelectWrapper::getName() const
+{
+ return Misc::StringUtils::lowerCase (mSelect.mSelectRule.substr (5));
+}
diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp
new file mode 100644
index 0000000000..ef787d8eec
--- /dev/null
+++ b/apps/openmw/mwdialogue/selectwrapper.hpp
@@ -0,0 +1,86 @@
+#ifndef GAME_MWDIALOGUE_SELECTWRAPPER_H
+#define GAME_MWDIALOGUE_SELECTWRAPPER_H
+
+#include <components/esm/loadinfo.hpp>
+
+namespace MWDialogue
+{
+ class SelectWrapper
+ {
+ const ESM::DialInfo::SelectStruct& mSelect;
+
+ public:
+
+ enum Function
+ {
+ Function_None, Function_False,
+ Function_Journal,
+ Function_Item,
+ Function_Dead,
+ Function_NotId,
+ Function_NotFaction,
+ Function_NotClass,
+ Function_NotRace,
+ Function_NotCell,
+ Function_NotLocal,
+ Function_Local,
+ Function_Global,
+ Function_SameGender, Function_SameRace, Function_SameFaction,
+ Function_Choice,
+ Function_PcCommonDisease, Function_PcBlightDisease, Function_PcCorprus,
+ Function_AiSetting,
+ Function_PcAttribute, Function_PcSkill,
+ Function_PcExpelled,
+ Function_PcVampire,
+ Function_FriendlyHit,
+ Function_TalkedToPc,
+ Function_PcLevel, Function_PcHealthPercent, Function_PcDynamicStat,
+ Function_PcGender, Function_PcClothingModifier, Function_PcCrimeLevel,
+ Function_RankRequirement,
+ Function_HealthPercent, Function_Level, Function_PCReputation,
+ Function_Weather,
+ Function_Reputation, Function_Alarmed, Function_FactionRankDiff, Function_Detected,
+ Function_Attacked, Function_ShouldAttack,
+ Function_CreatureTargetted,
+ Function_Werewolf, Function_WerewolfKills,
+ Function_RankLow, Function_RankHigh
+ };
+
+ enum Type
+ {
+ Type_None,
+ Type_Integer,
+ Type_Numeric,
+ Type_Boolean,
+ Type_Inverted
+ };
+
+ private:
+
+ Function decodeFunction() const;
+
+ public:
+
+ SelectWrapper (const ESM::DialInfo::SelectStruct& select);
+
+ Function getFunction() const;
+
+ int getArgument() const;
+
+ Type getType() const;
+
+ bool isNpcOnly() const;
+ ///< \attention Do not call any of the select functions for this select struct!
+
+ bool selectCompare (int value) const;
+
+ bool selectCompare (float value) const;
+
+ bool selectCompare (bool value) const;
+
+ std::string getName() const;
+ ///< Return case-smashed name.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp
new file mode 100644
index 0000000000..3253b20d66
--- /dev/null
+++ b/apps/openmw/mwdialogue/topic.cpp
@@ -0,0 +1,44 @@
+
+#include "topic.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+namespace MWDialogue
+{
+ Topic::Topic()
+ {}
+
+ Topic::Topic (const std::string& topic)
+ : mTopic (topic)
+ {}
+
+ Topic::~Topic()
+ {}
+
+ void Topic::addEntry (const JournalEntry& entry)
+ {
+ if (entry.mTopic!=mTopic)
+ throw std::runtime_error ("topic does not match: " + mTopic);
+
+ for (TEntryIter iter = begin(); iter!=end(); ++iter)
+ if (*iter==entry.mInfoId)
+ return;
+
+ mEntries.push_back (entry.mInfoId);
+ }
+
+ Topic::TEntryIter Topic::begin() const
+ {
+ return mEntries.begin();
+ }
+
+ Topic::TEntryIter Topic::end() const
+ {
+ return mEntries.end();
+ }
+
+ JournalEntry Topic::getEntry (const std::string& infoId) const
+ {
+ return JournalEntry (mTopic, infoId);
+ }
+}
diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp
new file mode 100644
index 0000000000..c3f0baabc2
--- /dev/null
+++ b/apps/openmw/mwdialogue/topic.hpp
@@ -0,0 +1,49 @@
+#ifndef GAME_MWDIALOG_TOPIC_H
+#define GAME_MWDIALOG_TOPIC_H
+
+#include <string>
+#include <vector>
+
+#include "journalentry.hpp"
+
+namespace MWDialogue
+{
+ /// \brief Collection of seen responses for a topic
+ class Topic
+ {
+ public:
+
+ typedef std::vector<std::string> TEntryContainer;
+ typedef TEntryContainer::const_iterator TEntryIter;
+
+ protected:
+
+ std::string mTopic;
+ TEntryContainer mEntries; // info-IDs
+
+ public:
+
+ Topic();
+
+ Topic (const std::string& topic);
+
+ virtual ~Topic();
+
+ virtual void addEntry (const JournalEntry& entry);
+ ///< Add entry
+ ///
+ /// \note Redundant entries are ignored.
+
+ std::string const & getName () const { return mTopic; }
+
+ TEntryIter begin() const;
+ ///< Iterator pointing to the begin of the journal for this topic.
+
+ TEntryIter end() const;
+ ///< Iterator pointing past the end of the journal for this topic.
+
+ JournalEntry getEntry (const std::string& infoId) const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp
new file mode 100644
index 0000000000..1e203dcd0b
--- /dev/null
+++ b/apps/openmw/mwgui/alchemywindow.cpp
@@ -0,0 +1,266 @@
+#include "alchemywindow.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "inventoryitemmodel.hpp"
+#include "sortfilteritemmodel.hpp"
+#include "itemview.hpp"
+
+namespace
+{
+ std::string getIconPath(MWWorld::Ptr ptr)
+ {
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(ptr).getInventoryIcon(ptr);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ return path;
+ }
+
+ std::string getCountString(const int count)
+ {
+ if (count == 1)
+ return "";
+ if (count > 9999)
+ return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
+ else
+ return boost::lexical_cast<std::string>(count);
+ }
+
+}
+
+namespace MWGui
+{
+ AlchemyWindow::AlchemyWindow()
+ : WindowBase("openmw_alchemy_window.layout")
+ , mApparatus (4)
+ , mIngredients (4)
+ , mSortModel(NULL)
+ {
+ mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ getWidget(mCreateButton, "CreateButton");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mIngredients[0], "Ingredient1");
+ getWidget(mIngredients[1], "Ingredient2");
+ getWidget(mIngredients[2], "Ingredient3");
+ getWidget(mIngredients[3], "Ingredient4");
+ getWidget(mApparatus[0], "Apparatus1");
+ getWidget(mApparatus[1], "Apparatus2");
+ getWidget(mApparatus[2], "Apparatus3");
+ getWidget(mApparatus[3], "Apparatus4");
+ getWidget(mEffectsBox, "CreatedEffects");
+ getWidget(mNameEdit, "NameEdit");
+ getWidget(mItemView, "ItemView");
+
+
+ mItemView->eventItemClicked += MyGUI::newDelegate(this, &AlchemyWindow::onSelectedItem);
+
+ mIngredients[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected);
+ mIngredients[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected);
+ mIngredients[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected);
+ mIngredients[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected);
+
+ mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked);
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
+
+ center();
+ }
+
+ void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ mAlchemy.clear();
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Inventory);
+ }
+
+ void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender)
+ {
+ std::string name = mNameEdit->getCaption();
+ boost::algorithm::trim(name);
+
+ MWMechanics::Alchemy::Result result = mAlchemy.create (mNameEdit->getCaption ());
+
+ if (result == MWMechanics::Alchemy::Result_NoName)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage37}");
+ return;
+ }
+
+ // check if mortar & pestle is available (always needed)
+ if (result == MWMechanics::Alchemy::Result_NoMortarAndPestle)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage45}");
+ return;
+ }
+
+ // make sure 2 or more ingredients were selected
+ if (result == MWMechanics::Alchemy::Result_LessThanTwoIngredients)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage6a}");
+ return;
+ }
+
+ if (result == MWMechanics::Alchemy::Result_NoEffects)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}");
+ MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f);
+ return;
+ }
+
+ if (result == MWMechanics::Alchemy::Result_Success)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sPotionSuccess}");
+ MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f);
+ }
+ else if (result == MWMechanics::Alchemy::Result_RandomFailure)
+ {
+ // potion failed
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}");
+ MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f);
+ }
+
+ // reduce count of the ingredients
+ for (int i=0; i<4; ++i)
+ if (mIngredients[i]->isUserString("ToolTipType"))
+ {
+ MWWorld::Ptr ingred = *mIngredients[i]->getUserData<MWWorld::Ptr>();
+ if (ingred.getRefData().getCount() == 0)
+ removeIngredient(mIngredients[i]);
+ }
+
+ update();
+ }
+
+ void AlchemyWindow::open()
+ {
+ InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mSortModel = new SortFilterItemModel(model);
+ mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
+ mItemView->setModel (mSortModel);
+
+ mNameEdit->setCaption("");
+
+ int index = 0;
+
+ mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools());
+ iter!=mAlchemy.endTools() && index<static_cast<int> (mApparatus.size()); ++iter, ++index)
+ {
+ if (!iter->isEmpty())
+ {
+ mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr");
+ mApparatus.at (index)->setUserData (*iter);
+ mApparatus.at (index)->setImageTexture (getIconPath (*iter));
+ }
+ }
+
+ update();
+ }
+
+ void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender)
+ {
+ removeIngredient(_sender);
+ update();
+ }
+
+ void AlchemyWindow::onSelectedItem(int index)
+ {
+ MWWorld::Ptr item = mSortModel->getItem(index).mBase;
+ int res = mAlchemy.addIngredient(item);
+
+ if (res != -1)
+ {
+ update();
+
+ std::string sound = MWWorld::Class::get(item).getUpSoundId(item);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+ }
+ }
+
+ void AlchemyWindow::update()
+ {
+ mSortModel->clearDragItems();
+
+ MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients ();
+ for (int i=0; i<4; ++i)
+ {
+ MyGUI::ImageBox* ingredient = mIngredients[i];
+
+ MWWorld::Ptr item;
+ if (it != mAlchemy.endIngredients ())
+ {
+ item = *it;
+ ++it;
+ }
+
+ if (!item.isEmpty())
+ mSortModel->addDragItem(item, item.getRefData().getCount());
+
+ if (ingredient->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0));
+
+ ingredient->setImageTexture("");
+ ingredient->clearUserStrings ();
+
+ if (item.isEmpty ())
+ continue;
+
+ ingredient->setUserString("ToolTipType", "ItemPtr");
+ ingredient->setUserData(item);
+ ingredient->setImageTexture(getIconPath(item));
+
+ MyGUI::TextBox* text = ingredient->createWidget<MyGUI::TextBox>("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label"));
+ text->setTextAlign(MyGUI::Align::Right);
+ text->setNeedMouseFocus(false);
+ text->setTextShadow(true);
+ text->setTextShadowColour(MyGUI::Colour(0,0,0));
+ text->setCaption(getCountString(ingredient->getUserData<MWWorld::Ptr>()->getRefData().getCount()));
+ }
+
+ mItemView->update();
+
+ std::vector<ESM::ENAMstruct> effects;
+ ESM::EffectList list;
+ list.mList = effects;
+ for (MWMechanics::Alchemy::TEffectsIterator it = mAlchemy.beginEffects (); it != mAlchemy.endEffects (); ++it)
+ {
+ list.mList.push_back(*it);
+ }
+
+ while (mEffectsBox->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mEffectsBox->getChildAt(0));
+
+ MyGUI::IntCoord coord(0, 0, mEffectsBox->getWidth(), 24);
+ Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget<Widgets::MWEffectList>
+ ("MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top);
+
+ Widgets::SpellEffectList _list = Widgets::MWEffectList::effectListFromESM(&list);
+ effectsWidget->setEffectList(_list);
+
+ std::vector<MyGUI::Widget*> effectItems;
+ effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0);
+ effectsWidget->setCoord(coord);
+ }
+
+ void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient)
+ {
+ for (int i=0; i<4; ++i)
+ if (mIngredients[i] == ingredient)
+ mAlchemy.removeIngredient (i);
+
+ update();
+ }
+}
diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp
new file mode 100644
index 0000000000..3a58ebf060
--- /dev/null
+++ b/apps/openmw/mwgui/alchemywindow.hpp
@@ -0,0 +1,51 @@
+#ifndef MWGUI_ALCHEMY_H
+#define MWGUI_ALCHEMY_H
+
+#include <vector>
+
+#include "../mwmechanics/alchemy.hpp"
+
+#include "widgets.hpp"
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class ItemView;
+ class SortFilterItemModel;
+
+ class AlchemyWindow : public WindowBase
+ {
+ public:
+ AlchemyWindow();
+
+ virtual void open();
+
+ private:
+ ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+
+ MyGUI::Button* mCreateButton;
+ MyGUI::Button* mCancelButton;
+
+ MyGUI::Widget* mEffectsBox;
+
+ MyGUI::EditBox* mNameEdit;
+
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onCreateButtonClicked(MyGUI::Widget* _sender);
+ void onIngredientSelected(MyGUI::Widget* _sender);
+
+ void onSelectedItem(int index);
+
+ void removeIngredient(MyGUI::Widget* ingredient);
+
+ void update();
+
+ MWMechanics::Alchemy mAlchemy;
+
+ std::vector<MyGUI::ImageBox *> mApparatus;
+ std::vector<MyGUI::ImageBox *> mIngredients;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp
new file mode 100644
index 0000000000..9656067097
--- /dev/null
+++ b/apps/openmw/mwgui/birth.cpp
@@ -0,0 +1,247 @@
+#include "birth.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "widgets.hpp"
+
+namespace
+{
+
+ bool sortBirthSigns(const std::pair<std::string, const ESM::BirthSign*>& left, const std::pair<std::string, const ESM::BirthSign*>& right)
+ {
+ return left.second->mName.compare (right.second->mName) < 0;
+ }
+
+}
+
+namespace MWGui
+{
+
+ BirthDialog::BirthDialog()
+ : WindowModal("openmw_chargen_birth.layout")
+ {
+ // Centre dialog
+ center();
+
+ getWidget(mSpellArea, "SpellArea");
+
+ getWidget(mBirthImage, "BirthsignImage");
+
+ getWidget(mBirthList, "BirthsignList");
+ mBirthList->setScrollVisible(true);
+ mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
+ mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
+ mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked);
+
+ updateBirths();
+ updateSpells();
+ }
+
+ void BirthDialog::setNextButtonShow(bool shown)
+ {
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ if (shown)
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
+ else
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ }
+
+ void BirthDialog::open()
+ {
+ WindowModal::open();
+ updateBirths();
+ updateSpells();
+ }
+
+
+ void BirthDialog::setBirthId(const std::string &birthId)
+ {
+ mCurrentBirthId = birthId;
+ mBirthList->setIndexSelected(MyGUI::ITEM_NONE);
+ size_t count = mBirthList->getItemCount();
+ for (size_t i = 0; i < count; ++i)
+ {
+ if (boost::iequals(*mBirthList->getItemDataAt<std::string>(i), birthId))
+ {
+ mBirthList->setIndexSelected(i);
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ break;
+ }
+ }
+
+ updateSpells();
+ }
+
+ // widget controls
+
+ void BirthDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
+ void BirthDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ void BirthDialog::onSelectBirth(MyGUI::ListBox* _sender, size_t _index)
+ {
+ if (_index == MyGUI::ITEM_NONE)
+ return;
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ const std::string *birthId = mBirthList->getItemDataAt<std::string>(_index);
+ if (boost::iequals(mCurrentBirthId, *birthId))
+ return;
+
+ mCurrentBirthId = *birthId;
+ updateSpells();
+ }
+
+ // update widget content
+
+ void BirthDialog::updateBirths()
+ {
+ mBirthList->removeAllItems();
+
+ const MWWorld::Store<ESM::BirthSign> &signs =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::BirthSign>();
+
+ // sort by name
+ std::vector < std::pair<std::string, const ESM::BirthSign*> > birthSigns;
+
+ MWWorld::Store<ESM::BirthSign>::iterator it = signs.begin();
+ for (; it != signs.end(); ++it)
+ {
+ birthSigns.push_back(std::make_pair(it->mId, &(*it)));
+ }
+ std::sort(birthSigns.begin(), birthSigns.end(), sortBirthSigns);
+
+ int index = 0;
+ for (std::vector<std::pair<std::string, const ESM::BirthSign*> >::const_iterator it2 = birthSigns.begin();
+ it2 != birthSigns.end(); ++it2, ++index)
+ {
+ mBirthList->addItem(it2->second->mName, it2->first);
+ if (mCurrentBirthId.empty())
+ {
+ mBirthList->setIndexSelected(index);
+ mCurrentBirthId = it2->first;
+ }
+ else if (boost::iequals(it2->first, mCurrentBirthId))
+ {
+ mBirthList->setIndexSelected(index);
+ }
+ }
+ }
+
+ void BirthDialog::updateSpells()
+ {
+ for (std::vector<MyGUI::Widget*>::iterator it = mSpellItems.begin(); it != mSpellItems.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ mSpellItems.clear();
+
+ if (mCurrentBirthId.empty())
+ return;
+
+ Widgets::MWSpellPtr spellWidget;
+ const int lineHeight = 18;
+ MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), 18);
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::BirthSign *birth =
+ store.get<ESM::BirthSign>().find(mCurrentBirthId);
+
+ std::string texturePath = std::string("textures\\") + birth->mTexture;
+ Widgets::fixTexturePath(texturePath);
+ mBirthImage->setImageTexture(texturePath);
+
+ std::vector<std::string> abilities, powers, spells;
+
+ std::vector<std::string>::const_iterator it = birth->mPowers.mList.begin();
+ std::vector<std::string>::const_iterator end = birth->mPowers.mList.end();
+ for (; it != end; ++it)
+ {
+ const std::string &spellId = *it;
+ const ESM::Spell *spell = store.get<ESM::Spell>().search(spellId);
+ if (!spell)
+ continue; // Skip spells which cannot be found
+ ESM::Spell::SpellType type = static_cast<ESM::Spell::SpellType>(spell->mData.mType);
+ if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power)
+ continue; // We only want spell, ability and powers.
+
+ if (type == ESM::Spell::ST_Ability)
+ abilities.push_back(spellId);
+ else if (type == ESM::Spell::ST_Power)
+ powers.push_back(spellId);
+ else if (type == ESM::Spell::ST_Spell)
+ spells.push_back(spellId);
+ }
+
+ int i = 0;
+
+ struct {
+ const std::vector<std::string> &spells;
+ const char *label;
+ }
+ categories[3] = {
+ {abilities, "sBirthsignmenu1"},
+ {powers, "sPowers"},
+ {spells, "sBirthsignmenu2"}
+ };
+
+ for (int category = 0; category < 3; ++category)
+ {
+ if (!categories[category].spells.empty())
+ {
+ MyGUI::TextBox* label = mSpellArea->createWidget<MyGUI::TextBox>("SandBrightText", coord, MyGUI::Align::Default, std::string("Label"));
+ label->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString(categories[category].label, ""));
+ mSpellItems.push_back(label);
+ coord.top += lineHeight;
+
+ std::vector<std::string>::const_iterator end = categories[category].spells.end();
+ for (std::vector<std::string>::const_iterator it = categories[category].spells.begin(); it != end; ++it)
+ {
+ const std::string &spellId = *it;
+ spellWidget = mSpellArea->createWidget<Widgets::MWSpell>("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + boost::lexical_cast<std::string>(i));
+ spellWidget->setSpellId(spellId);
+
+ mSpellItems.push_back(spellWidget);
+ coord.top += lineHeight;
+
+ MyGUI::IntCoord spellCoord = coord;
+ spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget in the layout as a template?
+ spellWidget->createEffectWidgets(mSpellItems, mSpellArea, spellCoord, (category == 0) ? Widgets::MWEffectList::EF_Constant : 0);
+ coord.top = spellCoord.top;
+
+ ++i;
+ }
+ }
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp
new file mode 100644
index 0000000000..cc958ddcaa
--- /dev/null
+++ b/apps/openmw/mwgui/birth.hpp
@@ -0,0 +1,56 @@
+#ifndef MWGUI_BIRTH_H
+#define MWGUI_BIRTH_H
+
+#include "windowbase.hpp"
+
+/*
+ This file contains the dialog for choosing a birth sign.
+ Layout is defined by resources/mygui/openmw_chargen_race.layout.
+ */
+
+namespace MWGui
+{
+ class BirthDialog : public WindowModal
+ {
+ public:
+ BirthDialog();
+
+ enum Gender
+ {
+ GM_Male,
+ GM_Female
+ };
+
+ const std::string &getBirthId() const { return mCurrentBirthId; }
+ void setBirthId(const std::string &raceId);
+
+ void setNextButtonShow(bool shown);
+ virtual void open();
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ protected:
+ void onSelectBirth(MyGUI::ListBox* _sender, size_t _index);
+
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ private:
+ void updateBirths();
+ void updateSpells();
+
+ MyGUI::ListBox* mBirthList;
+ MyGUI::Widget* mSpellArea;
+ MyGUI::ImageBox* mBirthImage;
+ std::vector<MyGUI::Widget*> mSpellItems;
+
+ std::string mCurrentBirthId;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp
new file mode 100644
index 0000000000..694970e23d
--- /dev/null
+++ b/apps/openmw/mwgui/bookpage.cpp
@@ -0,0 +1,1240 @@
+#include "bookpage.hpp"
+
+#include "MyGUI_FontManager.h"
+#include "MyGUI_RenderItem.h"
+#include "MyGUI_RenderManager.h"
+#include "MyGUI_TextureUtility.h"
+#include "MyGUI_FactoryManager.h"
+
+#include <platform/stdint.h>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <components/misc/utf8stream.hpp>
+
+namespace MWGui
+{
+struct TypesetBookImpl;
+struct PageDisplay;
+struct BookPageImpl;
+
+static bool ucsSpace (int codePoint);
+static bool ucsLineBreak (int codePoint);
+static bool ucsBreakingSpace (int codePoint);
+
+struct BookTypesetter::Style { virtual ~Style () {} };
+
+struct TypesetBookImpl : TypesetBook
+{
+ typedef std::vector <uint8_t> Content;
+ typedef std::list <Content> Contents;
+ typedef Utf8Stream::Point Utf8Point;
+ typedef std::pair <Utf8Point, Utf8Point> Range;
+
+ struct StyleImpl : BookTypesetter::Style
+ {
+ MyGUI::IFont* mFont;
+ MyGUI::Colour mHotColour;
+ MyGUI::Colour mActiveColour;
+ MyGUI::Colour mNormalColour;
+ InteractiveId mInteractiveId;
+
+ bool match (MyGUI::IFont* tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour,
+ MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId)
+ {
+ return (mFont == tstFont) &&
+ partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId);
+ }
+
+ bool match (char const * tstFont, MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour,
+ MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId)
+ {
+ return (mFont->getResourceName () == tstFont) &&
+ partal_match (tstHotColour, tstActiveColour, tstNormalColour, tstInteractiveId);
+ }
+
+ bool partal_match (MyGUI::Colour tstHotColour, MyGUI::Colour tstActiveColour,
+ MyGUI::Colour tstNormalColour, intptr_t tstInteractiveId)
+ {
+ return
+ (mHotColour == tstHotColour ) &&
+ (mActiveColour == tstActiveColour ) &&
+ (mNormalColour == tstNormalColour ) &&
+ (mInteractiveId == tstInteractiveId ) ;
+ }
+ };
+
+ typedef std::list <StyleImpl> Styles;
+
+ struct Run
+ {
+ StyleImpl* mStyle;
+ Range mRange;
+ int mLeft, mRight;
+ int mPrintableChars;
+ };
+
+ typedef std::vector <Run> Runs;
+
+ struct Line
+ {
+ Runs mRuns;
+ MyGUI::IntRect mRect;
+ };
+
+ typedef std::vector <Line> Lines;
+
+ struct Section
+ {
+ Lines mLines;
+ MyGUI::IntRect mRect;
+ };
+
+ typedef std::vector <Section> Sections;
+
+ typedef std::pair <int, int> Page;
+ typedef std::vector <Page> Pages;
+
+ Pages mPages;
+ Sections mSections;
+ Contents mContents;
+ Styles mStyles;
+ MyGUI::IntRect mRect;
+
+ virtual ~TypesetBookImpl () {}
+
+ Range addContent (BookTypesetter::Utf8Span text)
+ {
+ Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second));
+
+ if (i->empty())
+ return Range (Utf8Point (NULL), Utf8Point (NULL));
+
+ Utf8Point begin = &i->front ();
+ Utf8Point end = &i->front () + i->size ();
+
+ return Range (begin, end);
+ }
+
+ size_t pageCount () const { return mPages.size (); }
+
+ std::pair <unsigned int, unsigned int> getSize () const
+ {
+ return std::make_pair (mRect.width (), mRect.height ());
+ }
+
+ template <typename Visitor>
+ void visitRuns (int top, int bottom, MyGUI::IFont* Font, Visitor const & visitor) const
+ {
+ for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i)
+ {
+ if (top >= mRect.bottom || bottom <= i->mRect.top)
+ continue;
+
+ for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j)
+ {
+ if (top >= j->mRect.bottom || bottom <= j->mRect.top)
+ continue;
+
+ for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k)
+ if (!Font || k->mStyle->mFont == Font)
+ visitor (*i, *j, *k);
+ }
+ }
+ }
+
+ template <typename Visitor>
+ void visitRuns (int top, int bottom, Visitor const & visitor) const
+ {
+ visitRuns (top, bottom, NULL, visitor);
+ }
+
+ StyleImpl * hitTest (int left, int top) const
+ {
+ for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i)
+ {
+ if (top < i->mRect.top || top >= i->mRect.bottom)
+ continue;
+
+ int left1 = left - i->mRect.left;
+
+ for (Lines::const_iterator j = i->mLines.begin (); j != i->mLines.end (); ++j)
+ {
+ if (top < j->mRect.top || top >= j->mRect.bottom)
+ continue;
+
+ int left2 = left1 - j->mRect.left;
+
+ for (Runs::const_iterator k = j->mRuns.begin (); k != j->mRuns.end (); ++k)
+ {
+ if (left2 < k->mLeft || left2 >= k->mRight)
+ continue;
+
+ return k->mStyle;
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ MyGUI::IFont* affectedFont (StyleImpl* style)
+ {
+ for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i)
+ if (&*i == style)
+ return i->mFont;
+ return NULL;
+ }
+
+ struct Typesetter;
+};
+
+struct TypesetBookImpl::Typesetter : BookTypesetter
+{
+ typedef TypesetBookImpl Book;
+ typedef boost::shared_ptr <Book> BookPtr;
+
+ int mPageWidth;
+ int mPageHeight;
+
+ BookPtr mBook;
+ Section * mSection;
+ Line * mLine;
+ Run * mRun;
+
+ std::vector <Alignment> mSectionAlignment;
+
+ Book::Content const * mCurrentContent;
+ Alignment mCurrentAlignment;
+
+ Typesetter (size_t width, size_t height) :
+ mPageWidth (width), mPageHeight(height),
+ mSection (NULL), mLine (NULL), mRun (NULL),
+ mCurrentAlignment (AlignLeft),
+ mCurrentContent (NULL)
+ {
+ mBook = boost::make_shared <Book> ();
+ }
+
+ virtual ~Typesetter ()
+ {
+ }
+
+ Style * createStyle (char const * fontName, Colour fontColour)
+ {
+ if (strcmp(fontName, "") == 0)
+ return createStyle(MyGUI::FontManager::getInstance().getDefaultFont().c_str(), fontColour);
+
+ for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i)
+ if (i->match (fontName, fontColour, fontColour, fontColour, 0))
+ return &*i;
+
+ StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ());
+
+ style.mFont = MyGUI::FontManager::getInstance().getByName(fontName);
+ style.mHotColour = fontColour;
+ style.mActiveColour = fontColour;
+ style.mNormalColour = fontColour;
+ style.mInteractiveId = 0;
+
+ return &style;
+ }
+
+ Style* createHotStyle (Style* baseStyle, Colour normalColour, Colour hoverColour, Colour activeColour, InteractiveId id, bool unique)
+ {
+ StyleImpl* BaseStyle = dynamic_cast <StyleImpl*> (baseStyle);
+
+ if (!unique)
+ for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i)
+ if (i->match (BaseStyle->mFont, hoverColour, activeColour, normalColour, id))
+ return &*i;
+
+ StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ());
+
+ style.mFont = BaseStyle->mFont;
+ style.mHotColour = hoverColour;
+ style.mActiveColour = activeColour;
+ style.mNormalColour = normalColour;
+ style.mInteractiveId = id;
+
+ return &style;
+ }
+
+ void write (Style * style, Utf8Span text)
+ {
+ Range range = mBook->addContent (text);
+
+ writeImpl (dynamic_cast <StyleImpl*> (style), range.first, range.second);
+ }
+
+ intptr_t addContent (Utf8Span text, bool select)
+ {
+ Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second));
+
+ if (select)
+ mCurrentContent = &(*i);
+
+ return reinterpret_cast <intptr_t> (&(*i));
+ }
+
+ void selectContent (intptr_t contentHandle)
+ {
+ mCurrentContent = reinterpret_cast <Content const *> (contentHandle);
+ }
+
+ void write (Style * style, size_t begin, size_t end)
+ {
+ assert (mCurrentContent != NULL);
+ assert (end <= mCurrentContent->size ());
+ assert (begin <= mCurrentContent->size ());
+
+ Utf8Point begin_ = &mCurrentContent->front () + begin;
+ Utf8Point end_ = &mCurrentContent->front () + end ;
+
+ writeImpl (dynamic_cast <StyleImpl*> (style), begin_, end_);
+ }
+
+ void lineBreak (float margin)
+ {
+ assert (margin == 0); //TODO: figure out proper behavior here...
+
+ mRun = NULL;
+ mLine = NULL;
+ }
+
+ void sectionBreak (float margin)
+ {
+ if (mBook->mSections.size () > 0)
+ {
+ mRun = NULL;
+ mLine = NULL;
+ mSection = NULL;
+
+ if (mBook->mRect.bottom < (mBook->mSections.back ().mRect.bottom + margin))
+ mBook->mRect.bottom = (mBook->mSections.back ().mRect.bottom + margin);
+ }
+ }
+
+ void setSectionAlignment (Alignment sectionAlignment)
+ {
+ if (mSection != NULL)
+ mSectionAlignment.back () = sectionAlignment;
+ mCurrentAlignment = sectionAlignment;
+ }
+
+ TypesetBook::Ptr complete ()
+ {
+ int curPageStart = 0;
+ int curPageStop = 0;
+
+ std::vector <Alignment>::iterator sa = mSectionAlignment.begin ();
+ for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa)
+ {
+ // apply alignment to individual lines...
+ for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j)
+ {
+ int width = j->mRect.width ();
+ int excess = mPageWidth - width;
+
+ switch (*sa)
+ {
+ default:
+ case AlignLeft: j->mRect.left = 0; break;
+ case AlignCenter: j->mRect.left = excess/2; break;
+ case AlignRight: j->mRect.left = excess; break;
+ }
+
+ j->mRect.right = j->mRect.left + width;
+ }
+
+ if (curPageStop == curPageStart)
+ {
+ curPageStart = i->mRect.top;
+ curPageStop = i->mRect.top;
+ }
+
+ int spaceLeft = mPageHeight - (curPageStop - curPageStart);
+ int sectionHeight = i->mRect.height ();
+
+ if (sectionHeight <= mPageHeight)
+ {
+ if (sectionHeight > spaceLeft)
+ {
+ assert (curPageStart != curPageStop);
+
+ mBook->mPages.push_back (Page (curPageStart, curPageStop));
+
+ curPageStart = i->mRect.top;
+ curPageStop = i->mRect.bottom;
+ }
+ else
+ curPageStop = i->mRect.bottom;
+ }
+ else
+ {
+ //split section
+ }
+ }
+
+ if (curPageStart != curPageStop)
+ mBook->mPages.push_back (Page (curPageStart, curPageStop));
+
+ return mBook;
+ }
+
+ void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end)
+ {
+ int line_height = style->mFont->getDefaultHeight ();
+
+ Utf8Stream stream (_begin, _end);
+
+ while (!stream.eof ())
+ {
+ if (ucsLineBreak (stream.peek ()))
+ {
+ stream.consume ();
+ mLine = NULL, mRun = NULL;
+ continue;
+ }
+
+ int word_width = 0;
+ int word_height = 0;
+ int space_width = 0;
+ int character_count = 0;
+
+ Utf8Stream::Point lead = stream.current ();
+
+ while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ()))
+ {
+ MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ());
+ if (gi)
+ space_width += gi->advance + gi->bearingX;
+ stream.consume ();
+ }
+
+ Utf8Stream::Point origin = stream.current ();
+
+ while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ()))
+ {
+ MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ());
+ if (gi)
+ word_width += gi->advance + gi->bearingX;
+ word_height = line_height;
+ ++character_count;
+ stream.consume ();
+ }
+
+ Utf8Stream::Point extent = stream.current ();
+
+ if (lead == extent)
+ break;
+
+ int left = mLine ? mLine->mRect.right : 0;
+
+ if (left + space_width + word_width > mPageWidth)
+ {
+ mLine = NULL, mRun = NULL;
+
+ append_run (style, origin, extent, extent - origin, word_width, mBook->mRect.bottom + word_height);
+ }
+ else
+ {
+ int top = mLine ? mLine->mRect.top : mBook->mRect.bottom;
+
+ append_run (style, lead, extent, extent - origin, left + space_width + word_width, top + word_height);
+ }
+ }
+ }
+
+ void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom)
+ {
+ if (mSection == NULL)
+ {
+ mBook->mSections.push_back (Section ());
+ mSection = &mBook->mSections.back ();
+ mSection->mRect = MyGUI::IntRect (0, mBook->mRect.bottom, 0, mBook->mRect.bottom);
+ mSectionAlignment.push_back (mCurrentAlignment);
+ }
+
+ if (mLine == NULL)
+ {
+ mSection->mLines.push_back (Line ());
+ mLine = &mSection->mLines.back ();
+ mLine->mRect = MyGUI::IntRect (0, mSection->mRect.bottom, 0, mBook->mRect.bottom);
+ }
+
+ if (mBook->mRect.right < right)
+ mBook->mRect.right = right;
+
+ if (mBook->mRect.bottom < bottom)
+ mBook->mRect.bottom = bottom;
+
+ if (mSection->mRect.right < right)
+ mSection->mRect.right = right;
+
+ if (mSection->mRect.bottom < bottom)
+ mSection->mRect.bottom = bottom;
+
+ if (mLine->mRect.right < right)
+ mLine->mRect.right = right;
+
+ if (mLine->mRect.bottom < bottom)
+ mLine->mRect.bottom = bottom;
+
+ if (mRun == NULL || mRun->mStyle != style || mRun->mRange.second != begin)
+ {
+ int left = mRun ? mRun->mRight : mLine->mRect.left;
+
+ mLine->mRuns.push_back (Run ());
+ mRun = &mLine->mRuns.back ();
+ mRun->mStyle = style;
+ mRun->mLeft = left;
+ mRun->mRight = right;
+ mRun->mRange.first = begin;
+ mRun->mRange.second = end;
+ mRun->mPrintableChars = pc;
+ //Run->Locale = Locale;
+ }
+ else
+ {
+ mRun->mRight = right;
+ mRun->mRange.second = end;
+ mRun->mPrintableChars += pc;
+ }
+ }
+};
+
+BookTypesetter::Ptr BookTypesetter::create (int pageWidth, int pageHeight)
+{
+ return boost::make_shared <TypesetBookImpl::Typesetter> (pageWidth, pageHeight);
+}
+
+namespace
+{
+ struct RenderXform
+ {
+ public:
+
+ float clipTop;
+ float clipLeft;
+ float clipRight;
+ float clipBottom;
+
+ float absoluteLeft;
+ float absoluteTop;
+ float leftOffset;
+ float topOffset;
+
+ float pixScaleX;
+ float pixScaleY;
+ float hOffset;
+ float vOffset;
+
+ RenderXform (MyGUI::ICroppedRectangle* croppedParent, MyGUI::RenderTargetInfo const & renderTargetInfo)
+ {
+ clipTop = croppedParent->_getMarginTop ();
+ clipLeft = croppedParent->_getMarginLeft ();
+ clipRight = croppedParent->getWidth () - croppedParent->_getMarginRight ();
+ clipBottom = croppedParent->getHeight () - croppedParent->_getMarginBottom ();
+
+ absoluteLeft = croppedParent->getAbsoluteLeft();
+ absoluteTop = croppedParent->getAbsoluteTop();
+ leftOffset = renderTargetInfo.leftOffset;
+ topOffset = renderTargetInfo.topOffset;
+
+ pixScaleX = renderTargetInfo.pixScaleX;
+ pixScaleY = renderTargetInfo.pixScaleY;
+ hOffset = renderTargetInfo.hOffset;
+ vOffset = renderTargetInfo.vOffset;
+ }
+
+ bool clip (MyGUI::FloatRect & vr, MyGUI::FloatRect & tr)
+ {
+ if (vr.bottom <= clipTop || vr.right <= clipLeft ||
+ vr.left >= clipRight || vr.top >= clipBottom )
+ return false;
+
+ if (vr.top < clipTop)
+ {
+ tr.top += tr.height () * (clipTop - vr.top) / vr.height ();
+ vr.top = clipTop;
+ }
+
+ if (vr.left < clipLeft)
+ {
+ tr.left += tr.width () * (clipLeft - vr.left) / vr.width ();
+ vr.left = clipLeft;
+ }
+
+ if (vr.right > clipRight)
+ {
+ tr.right -= tr.width () * (vr.right - clipRight) / vr.width ();
+ vr.right = clipRight;
+ }
+
+ if (vr.bottom > clipBottom)
+ {
+ tr.bottom -= tr.height () * (vr.bottom - clipBottom) / vr.height ();
+ vr.bottom = clipBottom;
+ }
+
+ return true;
+ }
+
+ MyGUI::FloatPoint operator () (MyGUI::FloatPoint pt)
+ {
+ pt.left = absoluteLeft - leftOffset + pt.left;
+ pt.top = absoluteTop - topOffset + pt.top;
+
+ pt.left = +(((pixScaleX * pt.left + hOffset) * 2.0f) - 1.0f);
+ pt.top = -(((pixScaleY * pt.top + vOffset) * 2.0f) - 1.0f);
+
+ return pt;
+ }
+ };
+
+ struct GlyphStream
+ {
+ float mZ;
+ uint32_t mC;
+ MyGUI::IFont* mFont;
+ MyGUI::FloatPoint mOrigin;
+ MyGUI::FloatPoint mCursor;
+ MyGUI::Vertex* mVertices;
+ RenderXform mRenderXform;
+ MyGUI::VertexColourType mVertexColourType;
+
+ GlyphStream (MyGUI::IFont* font, float left, float top, float Z,
+ MyGUI::Vertex* vertices, RenderXform const & renderXform) :
+ mZ(Z), mOrigin (left, top),
+ mFont (font), mVertices (vertices),
+ mRenderXform (renderXform)
+ {
+ mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat();
+ }
+
+ ~GlyphStream ()
+ {
+ }
+
+ MyGUI::Vertex* end () const { return mVertices; }
+
+ void reset (float left, float top, MyGUI::Colour colour)
+ {
+ mC = MyGUI::texture_utility::toColourARGB(colour) | 0xFF000000;
+ MyGUI::texture_utility::convertColour(mC, mVertexColourType);
+
+ mCursor.left = mOrigin.left + left;
+ mCursor.top = mOrigin.top + top;
+ }
+
+ void emitGlyph (wchar_t ch)
+ {
+ MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch);
+
+ if (!gi)
+ return;
+
+ MyGUI::FloatRect vr;
+
+ vr.left = mCursor.left + gi->bearingX;
+ vr.top = mCursor.top + gi->bearingY;
+ vr.right = vr.left + gi->width;
+ vr.bottom = vr.top + gi->height;
+
+ MyGUI::FloatRect tr = gi->uvRect;
+
+ if (mRenderXform.clip (vr, tr))
+ quad (vr, tr);
+
+ mCursor.left += gi->bearingX + gi->advance;
+ }
+
+ void emitSpace (wchar_t ch)
+ {
+ MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch);
+
+ if (gi)
+ mCursor.left += gi->bearingX + gi->advance;
+ }
+
+ private:
+
+ void quad (const MyGUI::FloatRect& vr, const MyGUI::FloatRect& tr)
+ {
+ vertex (vr.left, vr.top, tr.left, tr.top);
+ vertex (vr.right, vr.top, tr.right, tr.top);
+ vertex (vr.left, vr.bottom, tr.left, tr.bottom);
+ vertex (vr.right, vr.top, tr.right, tr.top);
+ vertex (vr.left, vr.bottom, tr.left, tr.bottom);
+ vertex (vr.right, vr.bottom, tr.right, tr.bottom);
+ }
+
+ void vertex (float x, float y, float u, float v)
+ {
+ MyGUI::FloatPoint pt = mRenderXform (MyGUI::FloatPoint (x, y));
+
+ mVertices->x = pt.left;
+ mVertices->y = pt.top ;
+ mVertices->z = mZ;
+ mVertices->u = u;
+ mVertices->v = v;
+ mVertices->colour = mC;
+
+ ++mVertices;
+ }
+ };
+}
+
+class PageDisplay : public MyGUI::ISubWidgetText
+{
+ MYGUI_RTTI_DERIVED(PageDisplay)
+protected:
+
+ typedef TypesetBookImpl::Section Section;
+ typedef TypesetBookImpl::Line Line;
+ typedef TypesetBookImpl::Run Run;
+
+ struct TextFormat : ISubWidget
+ {
+ typedef MyGUI::IFont* Id;
+
+ Id mFont;
+ int mCountVertex;
+ MyGUI::ITexture* mTexture;
+ MyGUI::RenderItem* mRenderItem;
+ PageDisplay * mDisplay;
+
+ TextFormat (MyGUI::IFont* id, PageDisplay * display) :
+ mFont (id),
+ mTexture (NULL),
+ mRenderItem (NULL),
+ mDisplay (display),
+ mCountVertex (0)
+ {
+ }
+
+ void createDrawItem (MyGUI::ILayerNode* node)
+ {
+ assert (mRenderItem == NULL);
+
+ if (mTexture != NULL)
+ {
+ mRenderItem = node->addToRenderItem(mTexture, false, false);
+ mRenderItem->addDrawItem(this, mCountVertex);
+ }
+ }
+
+ void destroyDrawItem (MyGUI::ILayerNode* node)
+ {
+ assert (mTexture != NULL ? mRenderItem != NULL : mRenderItem == NULL);
+
+ if (mTexture != NULL)
+ {
+ mRenderItem->removeDrawItem (this);
+ mRenderItem = NULL;
+ }
+ }
+
+ void doRender() { mDisplay->doRender (*this); }
+
+ // this isn't really a sub-widget, its just a "drawitem" which
+ // should have its own interface
+ void createDrawItem(MyGUI::ITexture* _texture, MyGUI::ILayerNode* _node) {}
+ void destroyDrawItem() {};
+ };
+
+public:
+
+ typedef TypesetBookImpl::StyleImpl Style;
+ typedef std::map <TextFormat::Id, TextFormat*> ActiveTextFormats;
+
+ int mViewTop;
+ int mViewBottom;
+
+ Style* mFocusItem;
+ bool mItemActive;
+ MyGUI::MouseButton mLastDown;
+ boost::function <void (intptr_t)> mLinkClicked;
+
+
+ boost::shared_ptr <TypesetBookImpl> mBook;
+ size_t mPage;
+
+ MyGUI::ILayerNode* mNode;
+ ActiveTextFormats mActiveTextFormats;
+
+ PageDisplay ()
+ {
+ mPage = -1;
+ mViewTop = 0;
+ mViewBottom = 0;
+ mFocusItem = NULL;
+ mItemActive = false;
+ mNode = NULL;
+ }
+
+ void dirtyFocusItem ()
+ {
+ if (mFocusItem != 0)
+ {
+ MyGUI::IFont* Font = mBook->affectedFont (mFocusItem);
+
+ ActiveTextFormats::iterator i = mActiveTextFormats.find (Font);
+
+ mNode->outOfDate (i->second->mRenderItem);
+ }
+ }
+
+ void onMouseLostFocus ()
+ {
+ if (!mBook)
+ return;
+
+ dirtyFocusItem ();
+
+ mFocusItem = 0;
+ mItemActive = false;
+ }
+
+ void onMouseMove (int left, int top)
+ {
+ if (!mBook)
+ return;
+
+ left -= mCroppedParent->getAbsoluteLeft ();
+ top -= mCroppedParent->getAbsoluteTop ();
+
+ Style * Hit = mBook->hitTest (left, mViewTop + top);
+
+ if (mLastDown == MyGUI::MouseButton::None)
+ {
+ if (Hit != mFocusItem)
+ {
+ dirtyFocusItem ();
+
+ mFocusItem = Hit;
+ mItemActive = false;
+
+ dirtyFocusItem ();
+ }
+ }
+ else
+ if (mFocusItem != 0)
+ {
+ bool newItemActive = Hit == mFocusItem;
+
+ if (newItemActive != mItemActive)
+ {
+ mItemActive = newItemActive;
+
+ dirtyFocusItem ();
+ }
+ }
+ }
+
+ void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
+ {
+ if (!mBook)
+ return;
+
+ left -= mCroppedParent->getAbsoluteLeft ();
+ top -= mCroppedParent->getAbsoluteTop ();
+
+ if (mLastDown == MyGUI::MouseButton::None)
+ {
+ mFocusItem = mBook->hitTest (left, mViewTop + top);
+ mItemActive = true;
+
+ dirtyFocusItem ();
+
+ mLastDown = id;
+ }
+ }
+
+ void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
+ {
+ if (!mBook)
+ return;
+
+ left -= mCroppedParent->getAbsoluteLeft ();
+ top -= mCroppedParent->getAbsoluteTop ();
+
+ if (mLastDown == id)
+ {
+ Style * mItem = mBook->hitTest (left, mViewTop + top);
+
+ bool clicked = mFocusItem == mItem;
+
+ mItemActive = false;
+
+ dirtyFocusItem ();
+
+ mLastDown = MyGUI::MouseButton::None;
+
+ if (clicked && mLinkClicked && mItem && mItem->mInteractiveId != 0)
+ mLinkClicked (mItem->mInteractiveId);
+ }
+ }
+
+ void showPage (TypesetBook::Ptr book, size_t newPage)
+ {
+ boost::shared_ptr <TypesetBookImpl> newBook = boost::dynamic_pointer_cast <TypesetBookImpl> (book);
+
+ if (mBook != newBook)
+ {
+ mFocusItem = nullptr;
+ mItemActive = 0;
+
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ {
+ if (mNode != NULL)
+ i->second->destroyDrawItem (mNode);
+ delete i->second;
+ }
+
+ mActiveTextFormats.clear ();
+
+ if (newBook != NULL)
+ {
+ createActiveFormats (newBook);
+
+ mBook = newBook;
+ mPage = newPage;
+
+ if (newPage < mBook->mPages.size ())
+ {
+ mViewTop = mBook->mPages [newPage].first;
+ mViewBottom = mBook->mPages [newPage].second;
+ }
+ else
+ {
+ mViewTop = 0;
+ mViewBottom = 0;
+ }
+ }
+ else
+ {
+ mBook.reset ();
+ mPage = -1;
+ mViewTop = 0;
+ mViewBottom = 0;
+ }
+ }
+ else
+ if (mBook && mPage != newPage)
+ {
+ if (mNode != NULL)
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ mNode->outOfDate(i->second->mRenderItem);
+
+ mPage = newPage;
+
+ if (newPage < mBook->mPages.size ())
+ {
+ mViewTop = mBook->mPages [newPage].first;
+ mViewBottom = mBook->mPages [newPage].second;
+ }
+ else
+ {
+ mViewTop = 0;
+ mViewBottom = 0;
+ }
+ }
+ }
+
+ struct CreateActiveFormat
+ {
+ PageDisplay * this_;
+
+ CreateActiveFormat (PageDisplay * this_) : this_ (this_) {}
+
+ void operator () (Section const & section, Line const & line, Run const & run) const
+ {
+ MyGUI::IFont* Font = run.mStyle->mFont;
+
+ ActiveTextFormats::iterator j = this_->mActiveTextFormats.find (Font);
+
+ if (j == this_->mActiveTextFormats.end ())
+ {
+ TextFormat * textFormat = new TextFormat (Font, this_);
+
+ textFormat->mTexture = Font->getTextureFont ();
+
+ j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first;
+ }
+
+ j->second->mCountVertex += run.mPrintableChars * 6;
+ }
+ };
+
+ void createActiveFormats (boost::shared_ptr <TypesetBookImpl> newBook)
+ {
+ newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this));
+
+ if (mNode != NULL)
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ i->second->createDrawItem (mNode);
+ }
+
+ void setVisible (bool newVisible)
+ {
+ if (mVisible == newVisible)
+ return;
+
+ mVisible = newVisible;
+
+ if (mVisible)
+ {
+ // reset input state
+ mLastDown = MyGUI::MouseButton::None;
+ mFocusItem = nullptr;
+ mItemActive = 0;
+ }
+
+ if (nullptr != mNode)
+ {
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ mNode->outOfDate(i->second->mRenderItem);
+ }
+ }
+
+ void createDrawItem(MyGUI::ITexture* texture, MyGUI::ILayerNode* node)
+ {
+ //test ();
+
+ mNode = node;
+
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ i->second->createDrawItem (node);
+ }
+
+ struct RenderRun
+ {
+ PageDisplay * this_;
+ GlyphStream &glyphStream;
+
+ RenderRun (PageDisplay * this_, GlyphStream &glyphStream) :
+ this_(this_), glyphStream (glyphStream)
+ {
+ }
+
+ void operator () (Section const & section, Line const & line, Run const & run) const
+ {
+ bool isActive = run.mStyle->mInteractiveId && (run.mStyle == this_->mFocusItem);
+
+ MyGUI::Colour colour = isActive ? (this_->mItemActive ? run.mStyle->mActiveColour: run.mStyle->mHotColour) : run.mStyle->mNormalColour;
+
+ glyphStream.reset (section.mRect.left + line.mRect.left + run.mLeft, line.mRect.top, colour);
+
+ Utf8Stream stream (run.mRange);
+
+ while (!stream.eof ())
+ {
+ Utf8Stream::UnicodeChar code_point = stream.consume ();
+
+ if (!ucsSpace (code_point))
+ glyphStream.emitGlyph (code_point);
+ else
+ glyphStream.emitSpace (code_point);
+ }
+ }
+ };
+
+ /*
+ queue up rendering operations for this text format
+ */
+ void doRender(TextFormat & textFormat)
+ {
+ if (!mVisible)
+ return;
+
+ MyGUI::Vertex* vertices = textFormat.mRenderItem->getCurrentVertexBuffer();
+
+ RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo());
+
+ GlyphStream glyphStream (textFormat.mFont, mCoord.left, mCoord.top-mViewTop,
+ -1 /*mNode->getNodeDepth()*/, vertices, renderXform);
+
+ int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop ));
+ int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom));
+
+ mBook->visitRuns (visit_top, visit_bottom, textFormat.mFont, RenderRun (this, glyphStream));
+
+ textFormat.mRenderItem->setLastVertexCount(glyphStream.end () - vertices);
+ }
+
+ // ISubWidget should not necessarily be a drawitem
+ // in this case, it is not...
+ void doRender() { }
+
+ void _updateView ()
+ {
+ }
+
+ void _correctView()
+ {
+ _checkMargin ();
+
+ if (mNode != NULL)
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ mNode->outOfDate (i->second->mRenderItem);
+
+ }
+
+ void destroyDrawItem()
+ {
+ for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i)
+ i->second->destroyDrawItem (mNode);
+
+ mNode = NULL;
+ }
+};
+
+
+class BookPageImpl : public BookPage
+{
+MYGUI_RTTI_DERIVED(BookPage)
+public:
+
+ void showPage (TypesetBook::Ptr book, size_t page)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ pd->showPage (book, page);
+ else
+ throw std::runtime_error ("The main sub-widget for a BookPage must be a PageDisplay.");
+ }
+
+ void adviseLinkClicked (boost::function <void (InteractiveId)> linkClicked)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->mLinkClicked = linkClicked;
+ }
+ }
+
+ void unadviseLinkClicked ()
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->mLinkClicked = boost::function <void (InteractiveId)> ();
+ }
+ }
+
+protected:
+ void onMouseLostFocus(Widget* _new)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->onMouseLostFocus ();
+ }
+ else
+ Widget::onMouseLostFocus (_new);
+ }
+
+ void onMouseMove(int left, int top)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->onMouseMove (left, top);
+ }
+ else
+ Widget::onMouseMove (left, top);
+ }
+
+ void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->onMouseButtonPressed (left, top, id);
+ }
+ else
+ Widget::onMouseButtonPressed (left, top, id);
+ }
+
+ void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
+ {
+ if (PageDisplay* pd = dynamic_cast <PageDisplay*> (getSubWidgetText ()))
+ {
+ pd->onMouseButtonReleased (left, top, id);
+ }
+ else
+ Widget::onMouseButtonReleased (left, top, id);
+ }
+};
+
+void BookPage::registerMyGUIComponents ()
+{
+ MyGUI::FactoryManager & factory = MyGUI::FactoryManager::getInstance();
+
+ factory.registerFactory<BookPageImpl>("Widget");
+ factory.registerFactory<PageDisplay>("BasisSkin");
+}
+
+static bool ucsLineBreak (int codePoint)
+{
+ return codePoint == '\n';
+}
+
+static bool ucsSpace (int codePoint)
+{
+ switch (codePoint)
+ {
+ case 0x0020: // SPACE
+ case 0x00A0: // NO-BREAK SPACE
+ case 0x1680: // OGHAM SPACE MARK
+ case 0x180E: // MONGOLIAN VOWEL SEPARATOR
+ case 0x2000: // EN QUAD
+ case 0x2001: // EM QUAD
+ case 0x2002: // EN SPACE
+ case 0x2003: // EM SPACE
+ case 0x2004: // THREE-PER-EM SPACE
+ case 0x2005: // FOUR-PER-EM SPACE
+ case 0x2006: // SIX-PER-EM SPACE
+ case 0x2007: // FIGURE SPACE
+ case 0x2008: // PUNCTUATION SPACE
+ case 0x2009: // THIN SPACE
+ case 0x200A: // HAIR SPACE
+ case 0x200B: // ZERO WIDTH SPACE
+ case 0x202F: // NARROW NO-BREAK SPACE
+ case 0x205F: // MEDIUM MATHEMATICAL SPACE
+ case 0x3000: // IDEOGRAPHIC SPACE
+ case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ucsBreakingSpace (int codePoint)
+{
+ switch (codePoint)
+ {
+ case 0x0020: // SPACE
+ //case 0x00A0: // NO-BREAK SPACE
+ case 0x1680: // OGHAM SPACE MARK
+ case 0x180E: // MONGOLIAN VOWEL SEPARATOR
+ case 0x2000: // EN QUAD
+ case 0x2001: // EM QUAD
+ case 0x2002: // EN SPACE
+ case 0x2003: // EM SPACE
+ case 0x2004: // THREE-PER-EM SPACE
+ case 0x2005: // FOUR-PER-EM SPACE
+ case 0x2006: // SIX-PER-EM SPACE
+ case 0x2007: // FIGURE SPACE
+ case 0x2008: // PUNCTUATION SPACE
+ case 0x2009: // THIN SPACE
+ case 0x200A: // HAIR SPACE
+ case 0x200B: // ZERO WIDTH SPACE
+ case 0x202F: // NARROW NO-BREAK SPACE
+ case 0x205F: // MEDIUM MATHEMATICAL SPACE
+ case 0x3000: // IDEOGRAPHIC SPACE
+ //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
+ return true;
+ default:
+ return false;
+ }
+}
+
+}
diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp
new file mode 100644
index 0000000000..28aa371cf3
--- /dev/null
+++ b/apps/openmw/mwgui/bookpage.hpp
@@ -0,0 +1,122 @@
+#ifndef MWGUI_BOOKPAGE_HPP
+#define MWGUI_BOOKPAGE_HPP
+
+#include "MyGUI_Colour.h"
+#include "MyGUI_Widget.h"
+
+#include <functional>
+#include <platform/stdint.h>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace MWGui
+{
+ /// A formatted and paginated document to be used with
+ /// the book page widget.
+ struct TypesetBook
+ {
+ typedef boost::shared_ptr <TypesetBook> Ptr;
+ typedef intptr_t InteractiveId;
+
+ /// Returns the number of pages in the document.
+ virtual size_t pageCount () const = 0;
+
+ /// Return the area covered by the document. The first
+ /// integer is the maximum with of any line. This is not
+ /// the largest coordinate of the right edge of any line,
+ /// it is the largest distance from the left edge to the
+ /// right edge. The second integer is the height of all
+ /// text combined prior to pagination.
+ virtual std::pair <unsigned int, unsigned int> getSize () const = 0;
+ };
+
+ /// A factory class for creating a typeset book instance.
+ struct BookTypesetter
+ {
+ typedef boost::shared_ptr <BookTypesetter> Ptr;
+ typedef TypesetBook::InteractiveId InteractiveId;
+ typedef MyGUI::Colour Colour;
+ typedef uint8_t const * Utf8Point;
+ typedef std::pair <Utf8Point, Utf8Point> Utf8Span;
+
+ enum Alignment {
+ AlignLeft = -1,
+ AlignCenter = 0,
+ AlignRight = +1
+ };
+
+ /// Styles are used to control the character level formatting
+ /// of text added to a typeset book. Their lifetime is equal
+ /// to the lifetime of the book-typesetter instance that created
+ /// them.
+ struct Style;
+
+ /// A factory function for creating the default implementation of a book typesetter
+ static Ptr create (int pageWidth, int pageHeight);
+
+ /// Create a simple text style consisting of a font and a text color.
+ virtual Style* createStyle (char const * Font, Colour Colour) = 0;
+
+ /// Create a hyper-link style with a user-defined identifier based on an
+ /// existing style. The unique flag forces a new instance of this style
+ /// to be created even if an existing instance is present.
+ virtual Style* createHotStyle (Style * BaseStyle, Colour NormalColour, Colour HoverColour, Colour ActiveColour, InteractiveId Id, bool Unique = true) = 0;
+
+ /// Insert a line break into the document. Newline characters in the input
+ /// text have the same affect. The margin parameter adds additional space
+ /// before the next line of text.
+ virtual void lineBreak (float margin = 0) = 0;
+
+ /// Insert a section break into the document. This causes a new section
+ /// to begin when additional text is inserted. Pagination attempts to keep
+ /// sections together on a single page. The margin parameter adds additional space
+ /// before the next line of text.
+ virtual void sectionBreak (float margin = 0) = 0;
+
+ /// Changes the alignment for the current section of text.
+ virtual void setSectionAlignment (Alignment sectionAlignment) = 0;
+
+ // Layout a block of text with the specified style into the document.
+ virtual void write (Style * Style, Utf8Span Text) = 0;
+
+ /// Adds a content block to the document without laying it out. An
+ /// identifier is returned that can be used to refer to it. If select
+ /// is true, the block is activated to be references by future writes.
+ virtual intptr_t addContent (Utf8Span Text, bool Select = true) = 0;
+
+ /// Select a previously created content block for future writes.
+ virtual void selectContent (intptr_t contentHandle) = 0;
+
+ /// Layout a span of the selected content block into the document
+ /// using the specified style.
+ virtual void write (Style * Style, size_t Begin, size_t End) = 0;
+
+ /// Finalize the document layout, and return a pointer to it.
+ virtual TypesetBook::Ptr complete () = 0;
+ };
+
+ /// An interface to the BookPage widget.
+ class BookPage : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED(BookPage)
+ public:
+
+ typedef TypesetBook::InteractiveId InteractiveId;
+ typedef boost::function <void (InteractiveId)> ClickCallback;
+
+ /// Make the widget display the specified page from the specified book.
+ virtual void showPage (TypesetBook::Ptr Book, size_t Page) = 0;
+
+ /// Set the callback for a clicking a hyper-link in the document.
+ virtual void adviseLinkClicked (ClickCallback callback) = 0;
+
+ /// Clear the hyper-link click callback.
+ virtual void unadviseLinkClicked () = 0;
+
+ /// Register the widget and associated sub-widget with MyGUI. Should be
+ /// called once near the beginning of the program.
+ static void registerMyGUIComponents ();
+ };
+}
+
+#endif // MWGUI_BOOKPAGE_HPP
diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp
new file mode 100644
index 0000000000..efe089689b
--- /dev/null
+++ b/apps/openmw/mwgui/bookwindow.cpp
@@ -0,0 +1,208 @@
+#include "bookwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/player.hpp"
+
+#include "formatting.hpp"
+
+namespace MWGui
+{
+
+ BookWindow::BookWindow ()
+ : WindowBase("openmw_book.layout")
+ , mTakeButtonShow(true)
+ , mTakeButtonAllowed(true)
+ , mCurrentPage(0)
+ {
+ getWidget(mCloseButton, "CloseButton");
+ mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onCloseButtonClicked);
+
+ getWidget(mTakeButton, "TakeButton");
+ mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onTakeButtonClicked);
+
+ getWidget(mNextPageButton, "NextPageBTN");
+ mNextPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onNextPageButtonClicked);
+
+ getWidget(mPrevPageButton, "PrevPageBTN");
+ mPrevPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onPrevPageButtonClicked);
+
+ getWidget(mLeftPageNumber, "LeftPageNumber");
+ getWidget(mRightPageNumber, "RightPageNumber");
+
+ getWidget(mLeftPage, "LeftPage");
+ getWidget(mRightPage, "RightPage");
+
+ adjustButton(mCloseButton);
+ adjustButton(mTakeButton);
+ adjustButton(mNextPageButton);
+ adjustButton(mPrevPageButton);
+
+ if (mNextPageButton->getSize().width == 64)
+ {
+ // english button has a 7 pixel wide strip of garbage on its right edge
+ mNextPageButton->setSize(64-7, mNextPageButton->getSize().height);
+ mNextPageButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,mNextPageButton->getSize().height));
+ }
+
+ center();
+ }
+
+ void BookWindow::clearPages()
+ {
+ for (std::vector<MyGUI::Widget*>::iterator it=mPages.begin();
+ it!=mPages.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ mPages.clear();
+ }
+
+ void BookWindow::open (MWWorld::Ptr book)
+ {
+ mBook = book;
+
+ clearPages();
+ mCurrentPage = 0;
+
+ MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0);
+
+ MWWorld::LiveCellRef<ESM::Book> *ref = mBook.get<ESM::Book>();
+
+ BookTextParser parser;
+ std::vector<std::string> results = parser.split(ref->mBase->mText, mLeftPage->getSize().width, mLeftPage->getSize().height);
+
+ int i=0;
+ for (std::vector<std::string>::iterator it=results.begin();
+ it!=results.end(); ++it)
+ {
+ MyGUI::Widget* parent;
+ if (i%2 == 0)
+ parent = mLeftPage;
+ else
+ parent = mRightPage;
+
+ MyGUI::Widget* pageWidget = parent->createWidgetReal<MyGUI::Widget>("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast<std::string>(i));
+ parser.parsePage(*it, pageWidget, mLeftPage->getSize().width);
+ mPages.push_back(pageWidget);
+ ++i;
+ }
+
+ updatePages();
+
+ setTakeButtonShow(true);
+ }
+
+ void BookWindow::setTakeButtonShow(bool show)
+ {
+ mTakeButtonShow = show;
+ mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed);
+ }
+
+ void BookWindow::setInventoryAllowed(bool allowed)
+ {
+ mTakeButtonAllowed = allowed;
+ mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed);
+ }
+
+ void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender)
+ {
+ // no 3d sounds because the object could be in a container.
+ MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0);
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book);
+ }
+
+ void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0);
+
+ MWWorld::ActionTake take(mBook);
+ take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book);
+ }
+
+ void BookWindow::onNextPageButtonClicked (MyGUI::Widget* sender)
+ {
+ nextPage();
+ }
+
+ void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* sender)
+ {
+ prevPage();
+ }
+
+ void BookWindow::updatePages()
+ {
+ mLeftPageNumber->setCaption( boost::lexical_cast<std::string>(mCurrentPage*2 + 1) );
+ mRightPageNumber->setCaption( boost::lexical_cast<std::string>(mCurrentPage*2 + 2) );
+
+ unsigned int i=0;
+ for (std::vector<MyGUI::Widget*>::iterator it = mPages.begin();
+ it != mPages.end(); ++it)
+ {
+ if (mCurrentPage*2 == i || mCurrentPage*2+1 == i)
+ (*it)->setVisible(true);
+ else
+ {
+ (*it)->setVisible(false);
+ }
+ ++i;
+ }
+
+ //If it is the last page, hide the button "Next Page"
+ if ( (mCurrentPage+1)*2 == mPages.size()
+ || (mCurrentPage+1)*2 == mPages.size() + 1)
+ {
+ mNextPageButton->setVisible(false);
+ } else {
+ mNextPageButton->setVisible(true);
+ }
+ //If it is the fist page, hide the button "Prev Page"
+ if (mCurrentPage == 0) {
+ mPrevPageButton->setVisible(false);
+ } else {
+ mPrevPageButton->setVisible(true);
+ }
+ }
+
+ void BookWindow::adjustButton (MWGui::ImageButton* button)
+ {
+ MyGUI::IntSize diff = button->getSize() - button->getRequestedSize();
+ button->setSize(button->getRequestedSize());
+
+ if (button->getAlign().isRight())
+ button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0));
+ }
+
+ void BookWindow::nextPage()
+ {
+ if ((mCurrentPage+1)*2 < mPages.size())
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("book page2", 1.0, 1.0);
+
+ ++mCurrentPage;
+
+ updatePages();
+ }
+ }
+ void BookWindow::prevPage()
+ {
+ if (mCurrentPage > 0)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("book page", 1.0, 1.0);
+
+ --mCurrentPage;
+
+ updatePages();
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp
new file mode 100644
index 0000000000..ef87dd9c4c
--- /dev/null
+++ b/apps/openmw/mwgui/bookwindow.hpp
@@ -0,0 +1,54 @@
+#ifndef MWGUI_BOOKWINDOW_H
+#define MWGUI_BOOKWINDOW_H
+
+#include "windowbase.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+#include "imagebutton.hpp"
+
+namespace MWGui
+{
+ class BookWindow : public WindowBase
+ {
+ public:
+ BookWindow();
+
+ void open(MWWorld::Ptr book);
+ void setTakeButtonShow(bool show);
+ void nextPage();
+ void prevPage();
+ void setInventoryAllowed(bool allowed);
+
+ protected:
+ void onNextPageButtonClicked (MyGUI::Widget* sender);
+ void onPrevPageButtonClicked (MyGUI::Widget* sender);
+ void onCloseButtonClicked (MyGUI::Widget* sender);
+ void onTakeButtonClicked (MyGUI::Widget* sender);
+
+ void updatePages();
+ void clearPages();
+ void adjustButton(MWGui::ImageButton* button);
+
+ private:
+ MWGui::ImageButton* mCloseButton;
+ MWGui::ImageButton* mTakeButton;
+ MWGui::ImageButton* mNextPageButton;
+ MWGui::ImageButton* mPrevPageButton;
+ MyGUI::TextBox* mLeftPageNumber;
+ MyGUI::TextBox* mRightPageNumber;
+ MyGUI::Widget* mLeftPage;
+ MyGUI::Widget* mRightPage;
+
+ unsigned int mCurrentPage; // 0 is first page
+ std::vector<MyGUI::Widget*> mPages;
+
+ MWWorld::Ptr mBook;
+
+ bool mTakeButtonShow;
+ bool mTakeButtonAllowed;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp
new file mode 100644
index 0000000000..816f42e3d1
--- /dev/null
+++ b/apps/openmw/mwgui/charactercreation.cpp
@@ -0,0 +1,757 @@
+#include "charactercreation.hpp"
+
+#include "textinput.hpp"
+#include "race.hpp"
+#include "class.hpp"
+#include "birth.hpp"
+#include "review.hpp"
+#include "inventorywindow.hpp"
+#include <boost/lexical_cast.hpp>
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/fallback.hpp"
+#include "../mwworld/player.hpp"
+
+namespace
+{
+ struct Step
+ {
+ const std::string mText;
+ const std::string mButtons[3];
+ const std::string mSound;
+ };
+
+ const ESM::Class::Specialization mSpecializations[3]={ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}; // The specialization for each answer
+ Step sGenerateClassSteps(int number) {
+ number++;
+ const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback();
+ Step step = {fallback->getFallbackString("Question_"+boost::lexical_cast<std::string>(number)+"_Question"),
+ {fallback->getFallbackString("Question_"+boost::lexical_cast<std::string>(number)+"_AnswerOne"),
+ fallback->getFallbackString("Question_"+boost::lexical_cast<std::string>(number)+"_AnswerTwo"),
+ fallback->getFallbackString("Question_"+boost::lexical_cast<std::string>(number)+"_AnswerThree")},
+ "vo\\misc\\chargen qa"+boost::lexical_cast<std::string>(number)+".wav"
+ };
+ return step;
+ }
+
+ struct ClassPoint
+ {
+ const char *id;
+ // Specialization points to match, in order: Stealth, Combat, Magic
+ // Note: Order is taken from http://www.uesp.net/wiki/Morrowind:Class_Quiz
+ unsigned int points[3];
+ };
+
+ void updatePlayerHealth()
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats(player);
+
+ creatureStats.updateHealth();
+ }
+}
+
+namespace MWGui
+{
+
+ CharacterCreation::CharacterCreation()
+ : mNameDialog(0)
+ , mRaceDialog(0)
+ , mClassChoiceDialog(0)
+ , mGenerateClassQuestionDialog(0)
+ , mGenerateClassResultDialog(0)
+ , mPickClassDialog(0)
+ , mCreateClassDialog(0)
+ , mBirthSignDialog(0)
+ , mReviewDialog(0)
+ , mGenerateClassStep(0)
+ {
+ mCreationStage = CSE_NotStarted;
+ mGenerateClassSpecializations[0] = 0;
+ mGenerateClassSpecializations[1] = 0;
+ mGenerateClassSpecializations[2] = 0;
+ }
+
+ void CharacterCreation::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ {
+ if (mReviewDialog)
+ {
+ static const char *ids[] =
+ {
+ "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5",
+ "AttribVal6", "AttribVal7", "AttribVal8",
+ 0
+ };
+
+ for (int i=0; ids[i]; ++i)
+ {
+ if (ids[i]==id)
+ mReviewDialog->setAttribute(ESM::Attribute::AttributeID(i), value);
+ }
+ }
+ }
+
+ void CharacterCreation::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
+ {
+ if (mReviewDialog)
+ {
+ if (id == "HBar")
+ {
+ mReviewDialog->setHealth (value);
+ }
+ else if (id == "MBar")
+ {
+ mReviewDialog->setMagicka (value);
+ }
+ else if (id == "FBar")
+ {
+ mReviewDialog->setFatigue (value);
+ }
+ }
+ }
+
+ void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value)
+ {
+ if (mReviewDialog)
+ mReviewDialog->setSkillValue(parSkill, value);
+ }
+
+ void CharacterCreation::configureSkills (const SkillList& major, const SkillList& minor)
+ {
+ if (mReviewDialog)
+ mReviewDialog->configureSkills(major, minor);
+ }
+
+ void CharacterCreation::spawnDialog(const char id)
+ {
+ switch (id)
+ {
+ case GM_Name:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
+ mNameDialog = 0;
+ mNameDialog = new TextInputDialog();
+ mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name"));
+ mNameDialog->setTextInput(mPlayerName);
+ mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen);
+ mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone);
+ mNameDialog->setVisible(true);
+ break;
+
+ case GM_Race:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
+ mRaceDialog = 0;
+ mRaceDialog = new RaceDialog();
+ mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen);
+ mRaceDialog->setRaceId(mPlayerRaceId);
+ mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone);
+ mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack);
+ mRaceDialog->setVisible(true);
+ if (mCreationStage < CSE_NameChosen)
+ mCreationStage = CSE_NameChosen;
+ break;
+
+ case GM_Class:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog);
+ mClassChoiceDialog = 0;
+ mClassChoiceDialog = new ClassChoiceDialog();
+ mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice);
+ mClassChoiceDialog->setVisible(true);
+ if (mCreationStage < CSE_RaceChosen)
+ mCreationStage = CSE_RaceChosen;
+ break;
+
+ case GM_ClassPick:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
+ mPickClassDialog = 0;
+ mPickClassDialog = new PickClassDialog();
+ mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
+ mPickClassDialog->setClassId(mPlayerClass.mName);
+ mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone);
+ mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack);
+ mPickClassDialog->setVisible(true);
+ if (mCreationStage < CSE_RaceChosen)
+ mCreationStage = CSE_RaceChosen;
+ break;
+
+ case GM_Birth:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
+ mBirthSignDialog = 0;
+ mBirthSignDialog = new BirthDialog();
+ mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen);
+ mBirthSignDialog->setBirthId(mPlayerBirthSignId);
+ mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone);
+ mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack);
+ mBirthSignDialog->setVisible(true);
+ if (mCreationStage < CSE_ClassChosen)
+ mCreationStage = CSE_ClassChosen;
+ break;
+
+ case GM_ClassCreate:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
+ mCreateClassDialog = 0;
+ mCreateClassDialog = new CreateClassDialog();
+ mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen);
+ mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone);
+ mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack);
+ mCreateClassDialog->setVisible(true);
+ if (mCreationStage < CSE_RaceChosen)
+ mCreationStage = CSE_RaceChosen;
+ break;
+ case GM_ClassGenerate:
+ mGenerateClassStep = 0;
+ mGenerateClass = "";
+ mGenerateClassSpecializations[0] = 0;
+ mGenerateClassSpecializations[1] = 0;
+ mGenerateClassSpecializations[2] = 0;
+ showClassQuestionDialog();
+ if (mCreationStage < CSE_RaceChosen)
+ mCreationStage = CSE_RaceChosen;
+ break;
+ case GM_Review:
+ MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
+ mReviewDialog = 0;
+ mReviewDialog = new ReviewDialog();
+ mReviewDialog->setPlayerName(mPlayerName);
+ mReviewDialog->setRace(mPlayerRaceId);
+ mReviewDialog->setClass(mPlayerClass);
+ mReviewDialog->setBirthSign(mPlayerBirthSignId);
+
+ mReviewDialog->setHealth(mPlayerHealth);
+ mReviewDialog->setMagicka(mPlayerMagicka);
+ mReviewDialog->setFatigue(mPlayerFatigue);
+
+ {
+ std::map<int, MWMechanics::Stat<int> > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
+ for (std::map<int, MWMechanics::Stat<int> >::iterator it = attributes.begin();
+ it != attributes.end(); ++it)
+ {
+ mReviewDialog->setAttribute(static_cast<ESM::Attribute::AttributeID> (it->first), it->second);
+ }
+ }
+
+ {
+ std::map<int, MWMechanics::Stat<float> > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues();
+ for (std::map<int, MWMechanics::Stat<float> >::iterator it = skills.begin();
+ it != skills.end(); ++it)
+ {
+ mReviewDialog->setSkillValue(static_cast<ESM::Skill::SkillEnum> (it->first), it->second);
+ }
+ mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills());
+ }
+
+ mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone);
+ mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack);
+ mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog);
+ mReviewDialog->setVisible(true);
+ if (mCreationStage < CSE_BirthSignChosen)
+ mCreationStage = CSE_BirthSignChosen;
+ break;
+ }
+ }
+
+ void CharacterCreation::doRenderUpdate()
+ {
+ if (mRaceDialog)
+ 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);
+ mReviewDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ }
+
+ void CharacterCreation::onReviewDialogBack()
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
+ mReviewDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
+ }
+
+ void CharacterCreation::onReviewActivateDialog(int parDialog)
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog);
+ mReviewDialog = 0;
+ mCreationStage = CSE_ReviewNext;
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+
+ switch(parDialog)
+ {
+ case ReviewDialog::NAME_DIALOG:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name);
+ break;
+ case ReviewDialog::RACE_DIALOG:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race);
+ break;
+ case ReviewDialog::CLASS_DIALOG:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ break;
+ case ReviewDialog::BIRTHSIGN_DIALOG:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth);
+ };
+ }
+
+ void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow)
+ {
+ if (mPickClassDialog)
+ {
+ const std::string &classId = mPickClassDialog->getClassId();
+ if (!classId.empty())
+ MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId);
+
+ const ESM::Class *klass =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(classId);
+ if (klass)
+ {
+ mPlayerClass = *klass;
+ MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass);
+ }
+ MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
+ mPickClassDialog = 0;
+ }
+
+ //TODO This bit gets repeated a few times; wrap it in a function
+ 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()
+ {
+ if (mPickClassDialog)
+ {
+ const std::string classId = mPickClassDialog->getClassId();
+ if (!classId.empty())
+ MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId);
+ MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog);
+ mPickClassDialog = 0;
+ }
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ }
+
+ void CharacterCreation::onClassChoice(int _index)
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog);
+ mClassChoiceDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+
+ switch(_index)
+ {
+ case ClassChoiceDialog::Class_Generate:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassGenerate);
+ break;
+ case ClassChoiceDialog::Class_Pick:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassPick);
+ break;
+ case ClassChoiceDialog::Class_Create:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_ClassCreate);
+ break;
+ case ClassChoiceDialog::Class_Back:
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race);
+ break;
+
+ };
+ }
+
+ void CharacterCreation::onNameDialogDone(WindowBase* parWindow)
+ {
+ if (mNameDialog)
+ {
+ mPlayerName = mNameDialog->getTextInput();
+ MWBase::Environment::get().getWindowManager()->setValue("name", mPlayerName);
+ MWBase::Environment::get().getMechanicsManager()->setPlayerName(mPlayerName);
+ MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog);
+ mNameDialog = 0;
+ }
+
+ 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();
+ }
+ }
+
+ void CharacterCreation::onRaceDialogBack()
+ {
+ if (mRaceDialog)
+ {
+ const ESM::NPC &data = mRaceDialog->getResult();
+ mPlayerRaceId = data.mRace;
+ if (!mPlayerRaceId.empty()) {
+ MWBase::Environment::get().getMechanicsManager()->setPlayerRace(
+ data.mRace,
+ data.isMale(),
+ data.mHead,
+ data.mHair
+ );
+ }
+ MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
+ mRaceDialog = 0;
+ }
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name);
+ }
+
+ void CharacterCreation::onRaceDialogDone(WindowBase* parWindow)
+ {
+ if (mRaceDialog)
+ {
+ const ESM::NPC &data = mRaceDialog->getResult();
+ mPlayerRaceId = data.mRace;
+ if (!mPlayerRaceId.empty()) {
+ MWBase::Environment::get().getMechanicsManager()->setPlayerRace(
+ data.mRace,
+ data.isMale(),
+ data.mHead,
+ data.mHair
+ );
+ }
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->rebuildAvatar();
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog);
+ mRaceDialog = 0;
+ }
+
+ 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)
+ {
+ if (mBirthSignDialog)
+ {
+ mPlayerBirthSignId = mBirthSignDialog->getBirthId();
+ if (!mPlayerBirthSignId.empty())
+ MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mPlayerBirthSignId);
+ MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
+ mBirthSignDialog = 0;
+ }
+
+ 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()
+ {
+ if (mBirthSignDialog)
+ {
+ MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mBirthSignDialog->getBirthId());
+ MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog);
+ mBirthSignDialog = 0;
+ }
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ }
+
+ void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow)
+ {
+ if (mCreateClassDialog)
+ {
+ ESM::Class klass;
+ klass.mName = mCreateClassDialog->getName();
+ klass.mDescription = mCreateClassDialog->getDescription();
+ klass.mData.mSpecialization = mCreateClassDialog->getSpecializationId();
+ klass.mData.mIsPlayable = 0x1;
+
+ std::vector<int> attributes = mCreateClassDialog->getFavoriteAttributes();
+ assert(attributes.size() == 2);
+ klass.mData.mAttribute[0] = attributes[0];
+ klass.mData.mAttribute[1] = attributes[1];
+
+ std::vector<ESM::Skill::SkillEnum> majorSkills = mCreateClassDialog->getMajorSkills();
+ std::vector<ESM::Skill::SkillEnum> minorSkills = mCreateClassDialog->getMinorSkills();
+ assert(majorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0]));
+ assert(minorSkills.size() >= sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0]));
+ for (size_t i = 0; i < sizeof(klass.mData.mSkills)/sizeof(klass.mData.mSkills[0]); ++i)
+ {
+ klass.mData.mSkills[i][1] = majorSkills[i];
+ klass.mData.mSkills[i][0] = minorSkills[i];
+ }
+
+ MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass);
+ mPlayerClass = klass;
+ MWBase::Environment::get().getWindowManager()->setPlayerClass(klass);
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
+ mCreateClassDialog = 0;
+ }
+
+ 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()
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog);
+ mCreateClassDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ }
+
+ void CharacterCreation::onClassQuestionChosen(int _index)
+ {
+ MWBase::Environment::get().getSoundManager()->stopSay();
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog);
+ mGenerateClassQuestionDialog = 0;
+
+ if (_index < 0 || _index >= 3)
+ {
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ return;
+ }
+
+ ESM::Class::Specialization specialization = mSpecializations[_index];
+ if (specialization == ESM::Class::Stealth)
+ ++mGenerateClassSpecializations[0];
+ else if (specialization == ESM::Class::Combat)
+ ++mGenerateClassSpecializations[1];
+ else if (specialization == ESM::Class::Magic)
+ ++mGenerateClassSpecializations[2];
+ ++mGenerateClassStep;
+ showClassQuestionDialog();
+ }
+
+ void CharacterCreation::showClassQuestionDialog()
+ {
+ if (mGenerateClassStep == 10)
+ {
+ static boost::array<ClassPoint, 23> classes = { {
+ {"Acrobat", {6, 2, 2}},
+ {"Agent", {6, 1, 3}},
+ {"Archer", {3, 5, 2}},
+ {"Archer", {5, 5, 0}},
+ {"Assassin", {6, 3, 1}},
+ {"Barbarian", {3, 6, 1}},
+ {"Bard", {3, 3, 3}},
+ {"Battlemage", {1, 3, 6}},
+ {"Crusader", {1, 6, 3}},
+ {"Healer", {3, 1, 6}},
+ {"Knight", {2, 6, 2}},
+ {"Monk", {5, 3, 2}},
+ {"Nightblade", {4, 2, 4}},
+ {"Pilgrim", {5, 2, 3}},
+ {"Rogue", {3, 4, 3}},
+ {"Rogue", {4, 4, 2}},
+ {"Rogue", {5, 4, 1}},
+ {"Scout", {2, 5, 3}},
+ {"Sorcerer", {2, 2, 6}},
+ {"Spellsword", {2, 4, 4}},
+ {"Spellsword", {5, 1, 4}},
+ {"Witchhunter", {2, 3, 5}},
+ {"Witchhunter", {5, 0, 5}}
+ } };
+
+ int match = -1;
+ for (unsigned i = 0; i < classes.size(); ++i)
+ {
+ if (mGenerateClassSpecializations[0] == classes[i].points[0] &&
+ mGenerateClassSpecializations[1] == classes[i].points[1] &&
+ mGenerateClassSpecializations[2] == classes[i].points[2])
+ {
+ match = i;
+ mGenerateClass = classes[i].id;
+ break;
+ }
+ }
+
+ if (match == -1)
+ {
+ if (mGenerateClassSpecializations[0] >= 7)
+ mGenerateClass = "Thief";
+ else if (mGenerateClassSpecializations[1] >= 7)
+ mGenerateClass = "Warrior";
+ else if (mGenerateClassSpecializations[2] >= 7)
+ mGenerateClass = "Mage";
+ else
+ {
+ std::cerr << "Failed to deduce class from chosen answers in generate class dialog" << std::endl;
+ mGenerateClass = "Thief";
+ }
+ }
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog);
+ mGenerateClassResultDialog = 0;
+
+ mGenerateClassResultDialog = new GenerateClassResultDialog();
+ mGenerateClassResultDialog->setClassId(mGenerateClass);
+ mGenerateClassResultDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassBack);
+ mGenerateClassResultDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onGenerateClassDone);
+ mGenerateClassResultDialog->setVisible(true);
+ return;
+ }
+
+ if (mGenerateClassStep > 10)
+ {
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ return;
+ }
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog);
+ mGenerateClassQuestionDialog = 0;
+
+ mGenerateClassQuestionDialog = new InfoBoxDialog();
+
+ InfoBoxDialog::ButtonList buttons;
+ mGenerateClassQuestionDialog->setText(sGenerateClassSteps(mGenerateClassStep).mText);
+ buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[0]);
+ buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[1]);
+ buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[2]);
+ mGenerateClassQuestionDialog->setButtons(buttons);
+ mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
+ mGenerateClassQuestionDialog->setVisible(true);
+
+ MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound);
+ }
+
+ void CharacterCreation::onGenerateClassBack()
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog);
+ mGenerateClassResultDialog = 0;
+
+ MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass);
+
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class);
+ }
+
+ void CharacterCreation::onGenerateClassDone(WindowBase* parWindow)
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog);
+ mGenerateClassResultDialog = 0;
+
+ MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass);
+
+ const ESM::Class *klass =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mGenerateClass);
+
+ mPlayerClass = *klass;
+ MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass);
+
+ 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()
+ {
+ delete mNameDialog;
+ delete mRaceDialog;
+ delete mClassChoiceDialog;
+ delete mGenerateClassQuestionDialog;
+ delete mGenerateClassResultDialog;
+ delete mPickClassDialog;
+ delete mCreateClassDialog;
+ delete mBirthSignDialog;
+ delete mReviewDialog;
+ }
+
+}
diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp
new file mode 100644
index 0000000000..bd88266776
--- /dev/null
+++ b/apps/openmw/mwgui/charactercreation.hpp
@@ -0,0 +1,114 @@
+#ifndef CHARACTER_CREATION_HPP
+#define CHARACTER_CREATION_HPP
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWGui
+{
+ class WindowBase;
+
+ class TextInputDialog;
+ class InfoBoxDialog;
+ class RaceDialog;
+ class DialogueWindow;
+ class ClassChoiceDialog;
+ class GenerateClassResultDialog;
+ class PickClassDialog;
+ class CreateClassDialog;
+ class BirthDialog;
+ class ReviewDialog;
+ class MessageBoxManager;
+
+ class CharacterCreation
+ {
+ public:
+ typedef std::vector<int> SkillList;
+
+ CharacterCreation();
+ ~CharacterCreation();
+
+ //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);
+ void configureSkills (const SkillList& major, const SkillList& minor);
+ void doRenderUpdate();
+
+ private:
+ //Dialogs
+ TextInputDialog* mNameDialog;
+ RaceDialog* mRaceDialog;
+ ClassChoiceDialog* mClassChoiceDialog;
+ InfoBoxDialog* mGenerateClassQuestionDialog;
+ GenerateClassResultDialog* mGenerateClassResultDialog;
+ PickClassDialog* mPickClassDialog;
+ CreateClassDialog* mCreateClassDialog;
+ BirthDialog* mBirthSignDialog;
+ ReviewDialog* mReviewDialog;
+
+ //Player data
+ std::string mPlayerName;
+ 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
+ unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen
+ std::string mGenerateClass; // In order: Stealth, Combat, Magic
+
+ ////Dialog events
+ //Name dialog
+ void onNameDialogDone(WindowBase* parWindow);
+
+ //Race dialog
+ void onRaceDialogDone(WindowBase* parWindow);
+ void onRaceDialogBack();
+
+ //Class dialogs
+ void onClassChoice(int _index);
+ void onPickClassDialogDone(WindowBase* parWindow);
+ void onPickClassDialogBack();
+ void onCreateClassDialogDone(WindowBase* parWindow);
+ void onCreateClassDialogBack();
+ void showClassQuestionDialog();
+ void onClassQuestionChosen(int _index);
+ void onGenerateClassBack();
+ void onGenerateClassDone(WindowBase* parWindow);
+
+ //Birthsign dialog
+ void onBirthSignDialogDone(WindowBase* parWindow);
+ void onBirthSignDialogBack();
+
+ //Review dialog
+ void onReviewDialogDone(WindowBase* parWindow);
+ void onReviewDialogBack();
+ void onReviewActivateDialog(int parDialog);
+
+ enum CSE //Creation Stage Enum
+ {
+ CSE_NotStarted,
+ CSE_NameChosen,
+ CSE_RaceChosen,
+ CSE_ClassChosen,
+ CSE_BirthSignChosen,
+ CSE_ReviewNext
+ };
+
+ CSE mCreationStage; // Which state the character creating is in, controls back/next/ok buttons
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp
new file mode 100644
index 0000000000..c33e54d6b4
--- /dev/null
+++ b/apps/openmw/mwgui/class.cpp
@@ -0,0 +1,886 @@
+#include "class.hpp"
+
+#include <boost/algorithm/string.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "tooltips.hpp"
+
+#undef min
+#undef max
+
+namespace MWGui
+{
+
+ /* GenerateClassResultDialog */
+
+ GenerateClassResultDialog::GenerateClassResultDialog()
+ : WindowModal("openmw_chargen_generate_class_result.layout")
+ {
+ // Centre dialog
+ center();
+
+ setText("ReflectT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sMessageQuestionAnswer1", ""));
+
+ getWidget(mClassImage, "ClassImage");
+ getWidget(mClassName, "ClassName");
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked);
+ }
+
+ std::string GenerateClassResultDialog::getClassId() const
+ {
+ return mClassName->getCaption();
+ }
+
+ void GenerateClassResultDialog::setClassId(const std::string &classId)
+ {
+ mCurrentClassId = classId;
+ mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds");
+ mClassName->setCaption(MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(mCurrentClassId)->mName);
+ }
+
+ // widget controls
+
+ void GenerateClassResultDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ eventDone(this);
+ }
+
+ void GenerateClassResultDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ /* PickClassDialog */
+
+ PickClassDialog::PickClassDialog()
+ : WindowModal("openmw_chargen_class.layout")
+ {
+ // Centre dialog
+ center();
+
+ getWidget(mSpecializationName, "SpecializationName");
+
+ getWidget(mFavoriteAttribute[0], "FavoriteAttribute0");
+ getWidget(mFavoriteAttribute[1], "FavoriteAttribute1");
+
+ for(int i = 0; i < 5; i++)
+ {
+ char theIndex = '0'+i;
+ getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex));
+ getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex));
+ }
+
+ getWidget(mClassList, "ClassList");
+ mClassList->setScrollVisible(true);
+ mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
+ mClassList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
+ mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
+
+ getWidget(mClassImage, "ClassImage");
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onOkClicked);
+
+ updateClasses();
+ updateStats();
+ }
+
+ void PickClassDialog::setNextButtonShow(bool shown)
+ {
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ if (shown)
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
+ else
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ }
+
+ void PickClassDialog::open()
+ {
+ WindowModal::open ();
+ updateClasses();
+ updateStats();
+ }
+
+
+ void PickClassDialog::setClassId(const std::string &classId)
+ {
+ mCurrentClassId = classId;
+ mClassList->setIndexSelected(MyGUI::ITEM_NONE);
+ size_t count = mClassList->getItemCount();
+ for (size_t i = 0; i < count; ++i)
+ {
+ if (boost::iequals(*mClassList->getItemDataAt<std::string>(i), classId))
+ {
+ mClassList->setIndexSelected(i);
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ break;
+ }
+ }
+
+ updateStats();
+ }
+
+ // widget controls
+
+ void PickClassDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
+ void PickClassDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index)
+ {
+ if (_index == MyGUI::ITEM_NONE)
+ return;
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ const std::string *classId = mClassList->getItemDataAt<std::string>(_index);
+ if (boost::iequals(mCurrentClassId, *classId))
+ return;
+
+ mCurrentClassId = *classId;
+ updateStats();
+ }
+
+ // update widget content
+
+ void PickClassDialog::updateClasses()
+ {
+ mClassList->removeAllItems();
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+
+ int index = 0;
+ MWWorld::Store<ESM::Class>::iterator it = store.get<ESM::Class>().begin();
+ for (; it != store.get<ESM::Class>().end(); ++it)
+ {
+ bool playable = (it->mData.mIsPlayable != 0);
+ if (!playable) // Only display playable classes
+ continue;
+
+ const std::string &id = it->mId;
+ mClassList->addItem(it->mName, id);
+ if (mCurrentClassId.empty())
+ {
+ mCurrentClassId = id;
+ mClassList->setIndexSelected(index);
+ }
+ else if (boost::iequals(id, mCurrentClassId))
+ {
+ mClassList->setIndexSelected(index);
+ }
+ ++index;
+ }
+ }
+
+ void PickClassDialog::updateStats()
+ {
+ if (mCurrentClassId.empty())
+ return;
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Class *klass = store.get<ESM::Class>().search(mCurrentClassId);
+ if (!klass)
+ return;
+
+ ESM::Class::Specialization specialization = static_cast<ESM::Class::Specialization>(klass->mData.mSpecialization);
+
+ static const char *specIds[3] = {
+ "sSpecializationCombat",
+ "sSpecializationMagic",
+ "sSpecializationStealth"
+ };
+ std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[specialization], specIds[specialization]);
+ mSpecializationName->setCaption(specName);
+ ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization);
+
+ mFavoriteAttribute[0]->setAttributeId(klass->mData.mAttribute[0]);
+ mFavoriteAttribute[1]->setAttributeId(klass->mData.mAttribute[1]);
+ ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId());
+ ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId());
+
+ for (int i = 0; i < 5; ++i)
+ {
+ mMinorSkill[i]->setSkillNumber(klass->mData.mSkills[i][0]);
+ mMajorSkill[i]->setSkillNumber(klass->mData.mSkills[i][1]);
+ ToolTips::createSkillToolTip(mMinorSkill[i], klass->mData.mSkills[i][0]);
+ ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]);
+ }
+
+ mClassImage->setImageTexture(std::string("textures\\levelup\\") + mCurrentClassId + ".dds");
+ }
+
+ /* InfoBoxDialog */
+
+ void InfoBoxDialog::fitToText(MyGUI::TextBox* widget)
+ {
+ MyGUI::IntCoord inner = widget->getTextRegion();
+ MyGUI::IntCoord outer = widget->getCoord();
+ MyGUI::IntSize size = widget->getTextSize();
+ size.width += outer.width - inner.width;
+ size.height += outer.height - inner.height;
+ widget->setSize(size);
+ }
+
+ void InfoBoxDialog::layoutVertically(MyGUI::Widget* widget, int margin)
+ {
+ size_t count = widget->getChildCount();
+ int pos = 0;
+ pos += margin;
+ int width = 0;
+ for (unsigned i = 0; i < count; ++i)
+ {
+ MyGUI::Widget* child = widget->getChildAt(i);
+ if (!child->getVisible())
+ continue;
+
+ child->setPosition(child->getLeft(), pos);
+ width = std::max(width, child->getWidth());
+ pos += child->getHeight() + margin;
+ }
+ width += margin*2;
+ widget->setSize(width, pos);
+ }
+
+ InfoBoxDialog::InfoBoxDialog()
+ : WindowModal("openmw_infobox.layout")
+ , mCurrentButton(-1)
+ {
+ getWidget(mTextBox, "TextBox");
+ getWidget(mText, "Text");
+ mText->getSubWidgetText()->setWordWrap(true);
+ getWidget(mButtonBar, "ButtonBar");
+
+ center();
+ }
+
+ void InfoBoxDialog::setText(const std::string &str)
+ {
+ mText->setCaption(str);
+ mTextBox->setVisible(!str.empty());
+ fitToText(mText);
+ }
+
+ std::string InfoBoxDialog::getText() const
+ {
+ return mText->getCaption();
+ }
+
+ void InfoBoxDialog::setButtons(ButtonList &buttons)
+ {
+ for (std::vector<MyGUI::Button*>::iterator it = this->mButtons.begin(); it != this->mButtons.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ this->mButtons.clear();
+ mCurrentButton = -1;
+
+ // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget
+ MyGUI::Button* button;
+ MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10);
+ ButtonList::const_iterator end = buttons.end();
+ for (ButtonList::const_iterator it = buttons.begin(); it != end; ++it)
+ {
+ const std::string &text = *it;
+ button = mButtonBar->createWidget<MyGUI::Button>("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, "");
+ button->getSubWidgetText()->setWordWrap(true);
+ button->setCaption(text);
+ fitToText(button);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &InfoBoxDialog::onButtonClicked);
+ coord.top += button->getHeight();
+ this->mButtons.push_back(button);
+ }
+ }
+
+ void InfoBoxDialog::open()
+ {
+ WindowModal::open();
+ // Fix layout
+ layoutVertically(mTextBox, 4);
+ layoutVertically(mButtonBar, 6);
+ layoutVertically(mMainWidget, 4 + 6);
+
+ center();
+ }
+
+ int InfoBoxDialog::getChosenButton() const
+ {
+ return mCurrentButton;
+ }
+
+ void InfoBoxDialog::onButtonClicked(MyGUI::Widget* _sender)
+ {
+ std::vector<MyGUI::Button*>::const_iterator end = mButtons.end();
+ int i = 0;
+ for (std::vector<MyGUI::Button*>::const_iterator it = mButtons.begin(); it != end; ++it)
+ {
+ if (*it == _sender)
+ {
+ mCurrentButton = i;
+ eventButtonSelected(i);
+ return;
+ }
+ ++i;
+ }
+ }
+
+ /* ClassChoiceDialog */
+
+ ClassChoiceDialog::ClassChoiceDialog()
+ : InfoBoxDialog()
+ {
+ setText("");
+ ButtonList buttons;
+ buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu1", ""));
+ buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu2", ""));
+ buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sClassChoiceMenu3", ""));
+ buttons.push_back(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBack", ""));
+ setButtons(buttons);
+ }
+
+ /* CreateClassDialog */
+
+ CreateClassDialog::CreateClassDialog()
+ : WindowModal("openmw_chargen_create_class.layout")
+ , mSpecDialog(NULL)
+ , mAttribDialog(NULL)
+ , mSkillDialog(NULL)
+ , mDescDialog(NULL)
+ , mAffectedAttribute(NULL)
+ , mAffectedSkill(NULL)
+ {
+ // Centre dialog
+ center();
+
+ setText("SpecializationT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu1", "Specialization"));
+ getWidget(mSpecializationName, "SpecializationName");
+ mSpecializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked);
+
+ setText("FavoriteAttributesT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sChooseClassMenu2", "Favorite Attributes:"));
+ getWidget(mFavoriteAttribute0, "FavoriteAttribute0");
+ getWidget(mFavoriteAttribute1, "FavoriteAttribute1");
+ mFavoriteAttribute0->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked);
+ mFavoriteAttribute1->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeClicked);
+
+ setText("MajorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMajor", ""));
+ setText("MinorSkillT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillClassMinor", ""));
+ for(int i = 0; i < 5; i++)
+ {
+ char theIndex = '0'+i;
+ getWidget(mMajorSkill[i], std::string("MajorSkill").append(1, theIndex));
+ getWidget(mMinorSkill[i], std::string("MinorSkill").append(1, theIndex));
+ mSkills.push_back(mMajorSkill[i]);
+ mSkills.push_back(mMinorSkill[i]);
+ }
+
+ std::vector<Widgets::MWSkillPtr>::const_iterator end = mSkills.end();
+ for (std::vector<Widgets::MWSkillPtr>::const_iterator it = mSkills.begin(); it != end; ++it)
+ {
+ (*it)->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked);
+ }
+
+ setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", ""));
+ getWidget(mEditName, "EditName");
+
+ // Make sure the edit box has focus
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mEditName);
+
+ MyGUI::Button* descriptionButton;
+ getWidget(descriptionButton, "DescriptionButton");
+ descriptionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionClicked);
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onOkClicked);
+
+ // Set default skills, attributes
+
+ mFavoriteAttribute0->setAttributeId(ESM::Attribute::Strength);
+ mFavoriteAttribute1->setAttributeId(ESM::Attribute::Agility);
+
+ mMajorSkill[0]->setSkillId(ESM::Skill::Block);
+ mMajorSkill[1]->setSkillId(ESM::Skill::Armorer);
+ mMajorSkill[2]->setSkillId(ESM::Skill::MediumArmor);
+ mMajorSkill[3]->setSkillId(ESM::Skill::HeavyArmor);
+ mMajorSkill[4]->setSkillId(ESM::Skill::BluntWeapon);
+
+ mMinorSkill[0]->setSkillId(ESM::Skill::LongBlade);
+ mMinorSkill[1]->setSkillId(ESM::Skill::Axe);
+ mMinorSkill[2]->setSkillId(ESM::Skill::Spear);
+ mMinorSkill[3]->setSkillId(ESM::Skill::Athletics);
+ mMinorSkill[4]->setSkillId(ESM::Skill::Enchant);
+
+ setSpecialization(0);
+ update();
+ }
+
+ CreateClassDialog::~CreateClassDialog()
+ {
+ delete mSpecDialog;
+ delete mAttribDialog;
+ delete mSkillDialog;
+ delete mDescDialog;
+ }
+
+ void CreateClassDialog::update()
+ {
+ for (int i = 0; i < 5; ++i)
+ {
+ ToolTips::createSkillToolTip(mMajorSkill[i], mMajorSkill[i]->getSkillId());
+ ToolTips::createSkillToolTip(mMinorSkill[i], mMinorSkill[i]->getSkillId());
+ }
+
+ ToolTips::createAttributeToolTip(mFavoriteAttribute0, mFavoriteAttribute0->getAttributeId());
+ ToolTips::createAttributeToolTip(mFavoriteAttribute1, mFavoriteAttribute1->getAttributeId());
+ }
+
+ std::string CreateClassDialog::getName() const
+ {
+ return mEditName->getOnlyText();
+ }
+
+ std::string CreateClassDialog::getDescription() const
+ {
+ return mDescription;
+ }
+
+ ESM::Class::Specialization CreateClassDialog::getSpecializationId() const
+ {
+ return mSpecializationId;
+ }
+
+ std::vector<int> CreateClassDialog::getFavoriteAttributes() const
+ {
+ std::vector<int> v;
+ v.push_back(mFavoriteAttribute0->getAttributeId());
+ v.push_back(mFavoriteAttribute1->getAttributeId());
+ return v;
+ }
+
+ std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMajorSkills() const
+ {
+ std::vector<ESM::Skill::SkillEnum> v;
+ for(int i = 0; i < 5; i++)
+ {
+ v.push_back(mMajorSkill[i]->getSkillId());
+ }
+ return v;
+ }
+
+ std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMinorSkills() const
+ {
+ std::vector<ESM::Skill::SkillEnum> v;
+ for(int i=0; i < 5; i++)
+ {
+ v.push_back(mMinorSkill[i]->getSkillId());
+ }
+ return v;
+ }
+
+ void CreateClassDialog::setNextButtonShow(bool shown)
+ {
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ if (shown)
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
+ else
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ }
+
+ // widget controls
+
+ void CreateClassDialog::onDialogCancel()
+ {
+ MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog);
+ mSpecDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog);
+ mAttribDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog);
+ mSkillDialog = 0;
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog);
+ mDescDialog = 0;
+ }
+
+ void CreateClassDialog::onSpecializationClicked(MyGUI::Widget* _sender)
+ {
+ delete mSpecDialog;
+ mSpecDialog = new SelectSpecializationDialog();
+ mSpecDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel);
+ mSpecDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected);
+ mSpecDialog->setVisible(true);
+ }
+
+ void CreateClassDialog::onSpecializationSelected()
+ {
+ mSpecializationId = mSpecDialog->getSpecializationId();
+ setSpecialization(mSpecializationId);
+
+ MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog);
+ mSpecDialog = 0;
+ }
+
+ void CreateClassDialog::setSpecialization(int id)
+ {
+ mSpecializationId = (ESM::Class::Specialization) id;
+ static const char *specIds[3] = {
+ "sSpecializationCombat",
+ "sSpecializationMagic",
+ "sSpecializationStealth"
+ };
+ std::string specName = MWBase::Environment::get().getWindowManager()->getGameSettingString(specIds[mSpecializationId], specIds[mSpecializationId]);
+ mSpecializationName->setCaption(specName);
+ ToolTips::createSpecializationToolTip(mSpecializationName, specName, mSpecializationId);
+ }
+
+ void CreateClassDialog::onAttributeClicked(Widgets::MWAttributePtr _sender)
+ {
+ delete mAttribDialog;
+ mAttribDialog = new SelectAttributeDialog();
+ mAffectedAttribute = _sender;
+ mAttribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel);
+ mAttribDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onAttributeSelected);
+ mAttribDialog->setVisible(true);
+ }
+
+ void CreateClassDialog::onAttributeSelected()
+ {
+ ESM::Attribute::AttributeID id = mAttribDialog->getAttributeId();
+ if (mAffectedAttribute == mFavoriteAttribute0)
+ {
+ if (mFavoriteAttribute1->getAttributeId() == id)
+ mFavoriteAttribute1->setAttributeId(mFavoriteAttribute0->getAttributeId());
+ }
+ else if (mAffectedAttribute == mFavoriteAttribute1)
+ {
+ if (mFavoriteAttribute0->getAttributeId() == id)
+ mFavoriteAttribute0->setAttributeId(mFavoriteAttribute1->getAttributeId());
+ }
+ mAffectedAttribute->setAttributeId(id);
+ MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog);
+ mAttribDialog = 0;
+
+ update();
+ }
+
+ void CreateClassDialog::onSkillClicked(Widgets::MWSkillPtr _sender)
+ {
+ delete mSkillDialog;
+ mSkillDialog = new SelectSkillDialog();
+ mAffectedSkill = _sender;
+ mSkillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel);
+ mSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSkillSelected);
+ mSkillDialog->setVisible(true);
+ }
+
+ void CreateClassDialog::onSkillSelected()
+ {
+ ESM::Skill::SkillEnum id = mSkillDialog->getSkillId();
+
+ // Avoid duplicate skills by swapping any skill field that matches the selected one
+ std::vector<Widgets::MWSkillPtr>::const_iterator end = mSkills.end();
+ for (std::vector<Widgets::MWSkillPtr>::const_iterator it = mSkills.begin(); it != end; ++it)
+ {
+ if (*it == mAffectedSkill)
+ continue;
+ if ((*it)->getSkillId() == id)
+ {
+ (*it)->setSkillId(mAffectedSkill->getSkillId());
+ break;
+ }
+ }
+
+ mAffectedSkill->setSkillId(mSkillDialog->getSkillId());
+ MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog);
+ mSkillDialog = 0;
+ update();
+ }
+
+ void CreateClassDialog::onDescriptionClicked(MyGUI::Widget* _sender)
+ {
+ mDescDialog = new DescriptionDialog();
+ mDescDialog->setTextInput(mDescription);
+ mDescDialog->eventDone += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionEntered);
+ mDescDialog->setVisible(true);
+ }
+
+ void CreateClassDialog::onDescriptionEntered(WindowBase* parWindow)
+ {
+ mDescription = mDescDialog->getTextInput();
+ MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog);
+ mDescDialog = 0;
+ }
+
+ void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ if(getName().size() <= 0)
+ return;
+ eventDone(this);
+ }
+
+ void CreateClassDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ /* SelectSpecializationDialog */
+
+ SelectSpecializationDialog::SelectSpecializationDialog()
+ : WindowModal("openmw_chargen_select_specialization.layout")
+ {
+ // Centre dialog
+ center();
+
+ setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationMenu1", ""));
+
+ getWidget(mSpecialization0, "Specialization0");
+ getWidget(mSpecialization1, "Specialization1");
+ getWidget(mSpecialization2, "Specialization2");
+ std::string combat = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Combat], "");
+ std::string magic = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Magic], "");
+ std::string stealth = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Class::sGmstSpecializationIds[ESM::Class::Stealth], "");
+
+ mSpecialization0->setCaption(combat);
+ mSpecialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked);
+ mSpecialization1->setCaption(magic);
+ mSpecialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked);
+ mSpecialization2->setCaption(stealth);
+ mSpecialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked);
+ mSpecializationId = ESM::Class::Combat;
+
+ ToolTips::createSpecializationToolTip(mSpecialization0, combat, ESM::Class::Combat);
+ ToolTips::createSpecializationToolTip(mSpecialization1, magic, ESM::Class::Magic);
+ ToolTips::createSpecializationToolTip(mSpecialization2, stealth, ESM::Class::Stealth);
+
+ MyGUI::Button* cancelButton;
+ getWidget(cancelButton, "CancelButton");
+ cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", ""));
+ cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked);
+ }
+
+ SelectSpecializationDialog::~SelectSpecializationDialog()
+ {
+ }
+
+ // widget controls
+
+ void SelectSpecializationDialog::onSpecializationClicked(MyGUI::Widget* _sender)
+ {
+ if (_sender == mSpecialization0)
+ mSpecializationId = ESM::Class::Combat;
+ else if (_sender == mSpecialization1)
+ mSpecializationId = ESM::Class::Magic;
+ else if (_sender == mSpecialization2)
+ mSpecializationId = ESM::Class::Stealth;
+ else
+ return;
+
+ eventItemSelected();
+ }
+
+ void SelectSpecializationDialog::onCancelClicked(MyGUI::Widget* _sender)
+ {
+ eventCancel();
+ }
+
+ /* SelectAttributeDialog */
+
+ SelectAttributeDialog::SelectAttributeDialog()
+ : WindowModal("openmw_chargen_select_attribute.layout")
+ , mAttributeId(ESM::Attribute::Strength)
+ {
+ // Centre dialog
+ center();
+
+ setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sAttributesMenu1", ""));
+
+ for (int i = 0; i < 8; ++i)
+ {
+ Widgets::MWAttributePtr attribute;
+ char theIndex = '0'+i;
+
+ getWidget(attribute, std::string("Attribute").append(1, theIndex));
+ attribute->setAttributeId(ESM::Attribute::sAttributeIds[i]);
+ attribute->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked);
+ ToolTips::createAttributeToolTip(attribute, attribute->getAttributeId());
+ }
+
+ MyGUI::Button* cancelButton;
+ getWidget(cancelButton, "CancelButton");
+ cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", ""));
+ cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked);
+ }
+
+ SelectAttributeDialog::~SelectAttributeDialog()
+ {
+ }
+
+ // widget controls
+
+ void SelectAttributeDialog::onAttributeClicked(Widgets::MWAttributePtr _sender)
+ {
+ // TODO: Change MWAttribute to set and get AttributeID enum instead of int
+ mAttributeId = static_cast<ESM::Attribute::AttributeID>(_sender->getAttributeId());
+ eventItemSelected();
+ }
+
+ void SelectAttributeDialog::onCancelClicked(MyGUI::Widget* _sender)
+ {
+ eventCancel();
+ }
+
+
+ /* SelectSkillDialog */
+
+ SelectSkillDialog::SelectSkillDialog()
+ : WindowModal("openmw_chargen_select_skill.layout")
+ {
+ // Centre dialog
+ center();
+
+ setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSkillsMenu1", ""));
+ setText("CombatLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationCombat", ""));
+ setText("MagicLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationMagic", ""));
+ setText("StealthLabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sSpecializationStealth", ""));
+
+ for(int i = 0; i < 9; i++)
+ {
+ char theIndex = '0'+i;
+ getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex));
+ getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex));
+ getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex));
+ }
+
+ struct {Widgets::MWSkillPtr widget; ESM::Skill::SkillEnum skillId;} mSkills[3][9] = {
+ {
+ {mCombatSkill[0], ESM::Skill::Block},
+ {mCombatSkill[1], ESM::Skill::Armorer},
+ {mCombatSkill[2], ESM::Skill::MediumArmor},
+ {mCombatSkill[3], ESM::Skill::HeavyArmor},
+ {mCombatSkill[4], ESM::Skill::BluntWeapon},
+ {mCombatSkill[5], ESM::Skill::LongBlade},
+ {mCombatSkill[6], ESM::Skill::Axe},
+ {mCombatSkill[7], ESM::Skill::Spear},
+ {mCombatSkill[8], ESM::Skill::Athletics}
+ },
+ {
+ {mMagicSkill[0], ESM::Skill::Enchant},
+ {mMagicSkill[1], ESM::Skill::Destruction},
+ {mMagicSkill[2], ESM::Skill::Alteration},
+ {mMagicSkill[3], ESM::Skill::Illusion},
+ {mMagicSkill[4], ESM::Skill::Conjuration},
+ {mMagicSkill[5], ESM::Skill::Mysticism},
+ {mMagicSkill[6], ESM::Skill::Restoration},
+ {mMagicSkill[7], ESM::Skill::Alchemy},
+ {mMagicSkill[8], ESM::Skill::Unarmored}
+ },
+ {
+ {mStealthSkill[0], ESM::Skill::Security},
+ {mStealthSkill[1], ESM::Skill::Sneak},
+ {mStealthSkill[2], ESM::Skill::Acrobatics},
+ {mStealthSkill[3], ESM::Skill::LightArmor},
+ {mStealthSkill[4], ESM::Skill::ShortBlade},
+ {mStealthSkill[5] ,ESM::Skill::Marksman},
+ {mStealthSkill[6] ,ESM::Skill::Mercantile},
+ {mStealthSkill[7] ,ESM::Skill::Speechcraft},
+ {mStealthSkill[8] ,ESM::Skill::HandToHand}
+ }
+ };
+
+ for (int spec = 0; spec < 3; ++spec)
+ {
+ for (int i = 0; i < 9; ++i)
+ {
+ mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId);
+ mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked);
+ ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId());
+ }
+ }
+
+ MyGUI::Button* cancelButton;
+ getWidget(cancelButton, "CancelButton");
+ cancelButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sCancel", ""));
+ cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked);
+ }
+
+ SelectSkillDialog::~SelectSkillDialog()
+ {
+ }
+
+ // widget controls
+
+ void SelectSkillDialog::onSkillClicked(Widgets::MWSkillPtr _sender)
+ {
+ mSkillId = _sender->getSkillId();
+ eventItemSelected();
+ }
+
+ void SelectSkillDialog::onCancelClicked(MyGUI::Widget* _sender)
+ {
+ eventCancel();
+ }
+
+ /* DescriptionDialog */
+
+ DescriptionDialog::DescriptionDialog()
+ : WindowModal("openmw_chargen_class_description.layout")
+ {
+ // Centre dialog
+ center();
+
+ getWidget(mTextEdit, "TextEdit");
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked);
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", ""));
+
+ // Make sure the edit box has focus
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit);
+ }
+
+ DescriptionDialog::~DescriptionDialog()
+ {
+ }
+
+ // widget controls
+
+ void DescriptionDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ eventDone(this);
+ }
+
+}
diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp
new file mode 100644
index 0000000000..15fc89658f
--- /dev/null
+++ b/apps/openmw/mwgui/class.hpp
@@ -0,0 +1,302 @@
+#ifndef MWGUI_CLASS_H
+#define MWGUI_CLASS_H
+
+
+#include "widgets.hpp"
+#include "windowbase.hpp"
+
+/*
+ This file contains the dialogs for choosing a class.
+ Layout is defined by resources/mygui/openmw_chargen_class.layout.
+ */
+
+namespace MWGui
+{
+ class InfoBoxDialog : public WindowModal
+ {
+ public:
+ InfoBoxDialog();
+
+ typedef std::vector<std::string> ButtonList;
+
+ void setText(const std::string &str);
+ std::string getText() const;
+ void setButtons(ButtonList &buttons);
+
+ virtual void open();
+ int getChosenButton() const;
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate1<int> EventHandle_Int;
+
+ /** Event : Button was clicked.\n
+ signature : void method(int index)\n
+ */
+ EventHandle_Int eventButtonSelected;
+
+ protected:
+ void onButtonClicked(MyGUI::Widget* _sender);
+
+ private:
+
+ void fitToText(MyGUI::TextBox* widget);
+ void layoutVertically(MyGUI::Widget* widget, int margin);
+ int mCurrentButton;
+ MyGUI::Widget* mTextBox;
+ MyGUI::TextBox* mText;
+ MyGUI::Widget* mButtonBar;
+ std::vector<MyGUI::Button*> mButtons;
+ };
+
+ // Lets the player choose between 3 ways of creating a class
+ class ClassChoiceDialog : public InfoBoxDialog
+ {
+ public:
+ // Corresponds to the buttons that can be clicked
+ enum ClassChoice
+ {
+ Class_Generate = 0,
+ Class_Pick = 1,
+ Class_Create = 2,
+ Class_Back = 3
+ };
+ ClassChoiceDialog();
+ };
+
+ class GenerateClassResultDialog : public WindowModal
+ {
+ public:
+ GenerateClassResultDialog();
+
+ std::string getClassId() const;
+ void setClassId(const std::string &classId);
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ protected:
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ private:
+ MyGUI::ImageBox* mClassImage;
+ MyGUI::TextBox* mClassName;
+
+ std::string mCurrentClassId;
+ };
+
+ class PickClassDialog : public WindowModal
+ {
+ public:
+ PickClassDialog();
+
+ const std::string &getClassId() const { return mCurrentClassId; }
+ void setClassId(const std::string &classId);
+
+ void setNextButtonShow(bool shown);
+ virtual void open();
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ protected:
+ void onSelectClass(MyGUI::ListBox* _sender, size_t _index);
+
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ private:
+ void updateClasses();
+ void updateStats();
+
+ MyGUI::ImageBox* mClassImage;
+ MyGUI::ListBox* mClassList;
+ MyGUI::TextBox* mSpecializationName;
+ Widgets::MWAttributePtr mFavoriteAttribute[2];
+ Widgets::MWSkillPtr mMajorSkill[5];
+ Widgets::MWSkillPtr mMinorSkill[5];
+
+ std::string mCurrentClassId;
+ };
+
+ class SelectSpecializationDialog : public WindowModal
+ {
+ public:
+ SelectSpecializationDialog();
+ ~SelectSpecializationDialog();
+
+ ESM::Class::Specialization getSpecializationId() const { return mSpecializationId; }
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Cancel button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventCancel;
+
+ /** Event : Dialog finished, specialization selected.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventItemSelected;
+
+ protected:
+ void onSpecializationClicked(MyGUI::Widget* _sender);
+ void onCancelClicked(MyGUI::Widget* _sender);
+
+ private:
+ MyGUI::TextBox *mSpecialization0, *mSpecialization1, *mSpecialization2;
+
+ ESM::Class::Specialization mSpecializationId;
+ };
+
+ class SelectAttributeDialog : public WindowModal
+ {
+ public:
+ SelectAttributeDialog();
+ ~SelectAttributeDialog();
+
+ ESM::Attribute::AttributeID getAttributeId() const { return mAttributeId; }
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Cancel button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventCancel;
+
+ /** Event : Dialog finished, attribute selected.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventItemSelected;
+
+ protected:
+ void onAttributeClicked(Widgets::MWAttributePtr _sender);
+ void onCancelClicked(MyGUI::Widget* _sender);
+
+ private:
+ ESM::Attribute::AttributeID mAttributeId;
+ };
+
+ class SelectSkillDialog : public WindowModal
+ {
+ public:
+ SelectSkillDialog();
+ ~SelectSkillDialog();
+
+ ESM::Skill::SkillEnum getSkillId() const { return mSkillId; }
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Cancel button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventCancel;
+
+ /** Event : Dialog finished, skill selected.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventItemSelected;
+
+ protected:
+ void onSkillClicked(Widgets::MWSkillPtr _sender);
+ void onCancelClicked(MyGUI::Widget* _sender);
+
+ private:
+ Widgets::MWSkillPtr mCombatSkill[9];
+ Widgets::MWSkillPtr mMagicSkill[9];
+ Widgets::MWSkillPtr mStealthSkill[9];
+
+ ESM::Skill::SkillEnum mSkillId;
+ };
+
+ class DescriptionDialog : public WindowModal
+ {
+ public:
+ DescriptionDialog();
+ ~DescriptionDialog();
+
+ std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; }
+ void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); }
+
+ protected:
+ void onOkClicked(MyGUI::Widget* _sender);
+
+ private:
+ MyGUI::EditBox* mTextEdit;
+ };
+
+ class CreateClassDialog : public WindowModal
+ {
+ public:
+ CreateClassDialog();
+ virtual ~CreateClassDialog();
+
+ std::string getName() const;
+ std::string getDescription() const;
+ ESM::Class::Specialization getSpecializationId() const;
+ std::vector<int> getFavoriteAttributes() const;
+ std::vector<ESM::Skill::SkillEnum> getMajorSkills() const;
+ std::vector<ESM::Skill::SkillEnum> getMinorSkills() const;
+
+ void setNextButtonShow(bool shown);
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ protected:
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ void onSpecializationClicked(MyGUI::Widget* _sender);
+ void onSpecializationSelected();
+ void onAttributeClicked(Widgets::MWAttributePtr _sender);
+ void onAttributeSelected();
+ void onSkillClicked(Widgets::MWSkillPtr _sender);
+ void onSkillSelected();
+ void onDescriptionClicked(MyGUI::Widget* _sender);
+ void onDescriptionEntered(WindowBase* parWindow);
+ void onDialogCancel();
+
+ void setSpecialization(int id);
+
+ void update();
+
+ private:
+ MyGUI::EditBox* mEditName;
+ MyGUI::TextBox* mSpecializationName;
+ Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1;
+ Widgets::MWSkillPtr mMajorSkill[5];
+ Widgets::MWSkillPtr mMinorSkill[5];
+ std::vector<Widgets::MWSkillPtr> mSkills;
+ std::string mDescription;
+
+ SelectSpecializationDialog *mSpecDialog;
+ SelectAttributeDialog *mAttribDialog;
+ SelectSkillDialog *mSkillDialog;
+ DescriptionDialog *mDescDialog;
+
+ ESM::Class::Specialization mSpecializationId;
+
+ Widgets::MWAttributePtr mAffectedAttribute;
+ Widgets::MWSkillPtr mAffectedSkill;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp
new file mode 100644
index 0000000000..3212ed7018
--- /dev/null
+++ b/apps/openmw/mwgui/companionitemmodel.cpp
@@ -0,0 +1,34 @@
+#include "companionitemmodel.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwworld/class.hpp"
+
+namespace MWGui
+{
+ CompanionItemModel::CompanionItemModel(const MWWorld::Ptr &actor)
+ : InventoryItemModel(actor)
+ {
+ }
+
+ void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
+ {
+ if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
+ stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
+ }
+
+ InventoryItemModel::copyItem(item, count);
+ }
+
+ void CompanionItemModel::removeItem (const ItemStack& item, size_t count)
+ {
+ if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
+ stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
+ }
+
+ InventoryItemModel::removeItem(item, count);
+ }
+}
diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp
new file mode 100644
index 0000000000..c11e11c325
--- /dev/null
+++ b/apps/openmw/mwgui/companionitemmodel.hpp
@@ -0,0 +1,22 @@
+#ifndef MWGUI_COMPANION_ITEM_MODEL_H
+#define MWGUI_COMPANION_ITEM_MODEL_H
+
+#include "inventoryitemmodel.hpp"
+
+namespace MWGui
+{
+
+ /// @brief The companion item model keeps track of the companion's profit by
+ /// monitoring which items are being added to and removed from the model.
+ class CompanionItemModel : public InventoryItemModel
+ {
+ public:
+ CompanionItemModel (const MWWorld::Ptr& actor);
+
+ virtual void copyItem (const ItemStack& item, size_t count);
+ virtual void removeItem (const ItemStack& item, size_t count);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp
new file mode 100644
index 0000000000..9698608d69
--- /dev/null
+++ b/apps/openmw/mwgui/companionwindow.cpp
@@ -0,0 +1,152 @@
+#include "companionwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwworld/class.hpp"
+
+#include "messagebox.hpp"
+#include "itemview.hpp"
+#include "sortfilteritemmodel.hpp"
+#include "companionitemmodel.hpp"
+#include "container.hpp"
+#include "countdialog.hpp"
+
+namespace MWGui
+{
+
+CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* manager)
+ : WindowBase("openmw_companion_window.layout")
+ , mDragAndDrop(dragAndDrop)
+ , mMessageBoxManager(manager)
+ , mSelectedItem(-1)
+ , mModel(NULL)
+ , mSortModel(NULL)
+{
+ getWidget(mCloseButton, "CloseButton");
+ getWidget(mProfitLabel, "ProfitLabel");
+ getWidget(mEncumbranceBar, "EncumbranceBar");
+ getWidget(mItemView, "ItemView");
+ mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected);
+ mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected);
+
+ mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked);
+
+ setCoord(200,0,600,300);
+}
+
+void CompanionWindow::onItemSelected(int index)
+{
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ mDragAndDrop->drop(mModel, mItemView);
+ updateEncumbranceBar();
+ return;
+ }
+
+ const ItemStack& item = mSortModel->getItem(index);
+
+ MWWorld::Ptr object = item.mBase;
+ int count = item.mCount;
+ bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
+ if (MyGUI::InputManager::getInstance().isControlPressed())
+ count = 1;
+
+ mSelectedItem = mSortModel->mapToSource(index);
+
+ if (count > 1 && !shift)
+ {
+ CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
+ dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count);
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem);
+ }
+ else
+ dragItem (NULL, count);
+}
+
+void CompanionWindow::dragItem(MyGUI::Widget* sender, int count)
+{
+ mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count);
+}
+
+void CompanionWindow::onBackgroundSelected()
+{
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ mDragAndDrop->drop(mModel, mItemView);
+ updateEncumbranceBar();
+ }
+}
+
+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);
+}
+
+void CompanionWindow::onFrame()
+{
+ updateEncumbranceBar();
+}
+
+void CompanionWindow::updateEncumbranceBar()
+{
+ if (mPtr.isEmpty())
+ return;
+ float capacity = MWWorld::Class::get(mPtr).getCapacity(mPtr);
+ float encumbrance = MWWorld::Class::get(mPtr).getEncumbrance(mPtr);
+ mEncumbranceBar->setValue(encumbrance, capacity);
+
+ if (mPtr.getTypeName() != typeid(ESM::NPC).name())
+ mProfitLabel->setCaption("");
+ else
+ {
+ MWMechanics::NpcStats& stats = MWWorld::Class::get(mPtr).getNpcStats(mPtr);
+ mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + boost::lexical_cast<std::string>(stats.getProfit()));
+ }
+}
+
+void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
+{
+ if (mPtr.getTypeName() == typeid(ESM::NPC).name() && MWWorld::Class::get(mPtr).getNpcStats(mPtr).getProfit() < 0)
+ {
+ std::vector<std::string> buttons;
+ buttons.push_back("#{sCompanionWarningButtonOne}");
+ buttons.push_back("#{sCompanionWarningButtonTwo}");
+ mMessageBoxManager->createInteractiveMessageBox("#{sCompanionWarningMessage}", buttons);
+ mMessageBoxManager->eventButtonPressed += MyGUI::newDelegate(this, &CompanionWindow::onMessageBoxButtonClicked);
+ }
+ else
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion);
+}
+
+void CompanionWindow::onMessageBoxButtonClicked(int button)
+{
+ if (button == 0)
+ {
+ mPtr.getRefData().getLocals().setVarByInt(MWWorld::Class::get(mPtr).getScript(mPtr),
+ "minimumProfit", MWWorld::Class::get(mPtr).getNpcStats(mPtr).getProfit());
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion);
+ MWBase::Environment::get().getDialogueManager()->startDialogue (mPtr);
+ }
+}
+
+void CompanionWindow::onReferenceUnavailable()
+{
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion);
+}
+
+
+
+}
diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp
new file mode 100644
index 0000000000..7fdfc069f3
--- /dev/null
+++ b/apps/openmw/mwgui/companionwindow.hpp
@@ -0,0 +1,52 @@
+#ifndef OPENMW_MWGUI_COMPANIONWINDOW_H
+#define OPENMW_MWGUI_COMPANIONWINDOW_H
+
+#include "widgets.hpp"
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+
+namespace MWGui
+{
+ class MessageBoxManager;
+ class ItemView;
+ class DragAndDrop;
+ class SortFilterItemModel;
+ class CompanionItemModel;
+
+ class CompanionWindow : public WindowBase, public ReferenceInterface
+ {
+ public:
+ CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager);
+
+ void open(const MWWorld::Ptr& npc);
+ void onFrame ();
+
+ private:
+ ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+ CompanionItemModel* mModel;
+ size_t mSelectedItem;
+
+ DragAndDrop* mDragAndDrop;
+
+ MyGUI::Button* mCloseButton;
+ MyGUI::TextBox* mProfitLabel;
+ Widgets::MWDynamicStat* mEncumbranceBar;
+ MessageBoxManager* mMessageBoxManager;
+
+ void onItemSelected(int index);
+ void onBackgroundSelected();
+ void dragItem(MyGUI::Widget* sender, int count);
+
+ void onMessageBoxButtonClicked(int button);
+
+ void updateEncumbranceBar();
+
+ void onCloseButtonClicked(MyGUI::Widget* _sender);
+
+ virtual void onReferenceUnavailable();
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp
new file mode 100644
index 0000000000..f431f2f64c
--- /dev/null
+++ b/apps/openmw/mwgui/confirmationdialog.cpp
@@ -0,0 +1,44 @@
+#include "confirmationdialog.hpp"
+
+namespace MWGui
+{
+ ConfirmationDialog::ConfirmationDialog() :
+ WindowModal("openmw_confirmation_dialog.layout")
+ {
+ getWidget(mMessage, "Message");
+ getWidget(mOkButton, "OkButton");
+ getWidget(mCancelButton, "CancelButton");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onCancelButtonClicked);
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked);
+ }
+
+ void ConfirmationDialog::open(const std::string& message)
+ {
+ setVisible(true);
+
+ mMessage->setCaptionWithReplacing(message);
+
+ int height = mMessage->getTextSize().height + 72;
+
+ mMainWidget->setSize(mMainWidget->getWidth(), height);
+
+ mMessage->setSize(mMessage->getWidth(), mMessage->getTextSize().height+24);
+
+ center();
+ }
+
+ void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ eventCancelClicked();
+
+ setVisible(false);
+ }
+
+ void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender)
+ {
+ eventOkClicked();
+
+ setVisible(false);
+ }
+}
diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp
new file mode 100644
index 0000000000..47b256017f
--- /dev/null
+++ b/apps/openmw/mwgui/confirmationdialog.hpp
@@ -0,0 +1,33 @@
+#ifndef MWGUI_CONFIRMATIONDIALOG_H
+#define MWGUI_CONFIRMATIONDIALOG_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class ConfirmationDialog : public WindowModal
+ {
+ public:
+ ConfirmationDialog();
+ void open(const std::string& message);
+
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Ok button was clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventOkClicked;
+ EventHandle_Void eventCancelClicked;
+
+ private:
+ MyGUI::EditBox* mMessage;
+ MyGUI::Button* mOkButton;
+ MyGUI::Button* mCancelButton;
+
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onOkButtonClicked(MyGUI::Widget* _sender);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp
new file mode 100644
index 0000000000..a1e3fb7381
--- /dev/null
+++ b/apps/openmw/mwgui/console.cpp
@@ -0,0 +1,422 @@
+#include "console.hpp"
+
+#include <components/compiler/exception.hpp>
+#include <components/compiler/extensions0.hpp>
+
+#include "../mwscript/extensions.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWGui
+{
+ class ConsoleInterpreterContext : public MWScript::InterpreterContext
+ {
+ Console& mConsole;
+
+ public:
+
+ ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference);
+
+ virtual void report (const std::string& message);
+ };
+
+ ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console,
+ MWWorld::Ptr reference)
+ : MWScript::InterpreterContext (
+ reference.isEmpty() ? 0 : &reference.getRefData().getLocals(), reference),
+ mConsole (console)
+ {}
+
+ void ConsoleInterpreterContext::report (const std::string& message)
+ {
+ mConsole.printOK (message);
+ }
+
+ bool Console::compile (const std::string& cmd, Compiler::Output& output)
+ {
+ try
+ {
+ ErrorHandler::reset();
+
+ std::istringstream input (cmd + '\n');
+
+ Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions());
+
+ Compiler::LineParser parser (*this, mCompilerContext, output.getLocals(),
+ output.getLiterals(), output.getCode(), true);
+
+ scanner.scan (parser);
+
+ return isGood();
+ }
+ catch (const Compiler::SourceException&)
+ {
+ // error has already been reported via error handler
+ }
+ catch (const std::exception& error)
+ {
+ printError (std::string ("An exception has been thrown: ") + error.what());
+ }
+
+ return false;
+ }
+
+ void Console::report (const std::string& message, const Compiler::TokenLoc& loc, Type type)
+ {
+ std::ostringstream error;
+ error << "column " << loc.mColumn << " (" << loc.mLiteral << "):";
+
+ printError (error.str());
+ printError ((type==ErrorMessage ? "error: " : "warning: ") + message);
+ }
+
+ void Console::report (const std::string& message, Type type)
+ {
+ printError ((type==ErrorMessage ? "error: " : "warning: ") + message);
+ }
+
+ void Console::listNames()
+ {
+ if (mNames.empty())
+ {
+ // keywords
+ std::istringstream input ("");
+
+ Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions());
+
+ scanner.listKeywords (mNames);
+
+ // identifier
+ const MWWorld::ESMStore& store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ for (MWWorld::ESMStore::iterator it = store.begin(); it != store.end(); ++it)
+ {
+ it->second->listIdentifier (mNames);
+ }
+
+ // sort
+ std::sort (mNames.begin(), mNames.end());
+ }
+ }
+
+ Console::Console(int w, int h, bool consoleOnlyScripts)
+ : WindowBase("openmw_console.layout"),
+ mCompilerContext (MWScript::CompilerContext::Type_Console),
+ mConsoleOnlyScripts (consoleOnlyScripts)
+ {
+ setCoord(10,10, w-10, h/2);
+
+ getWidget(mCommandLine, "edit_Command");
+ getWidget(mHistory, "list_History");
+
+ // Set up the command line box
+ mCommandLine->eventEditSelectAccept +=
+ newDelegate(this, &Console::acceptCommand);
+ mCommandLine->eventKeyButtonPressed +=
+ newDelegate(this, &Console::keyPress);
+
+ // Set up the log window
+ mHistory->setOverflowToTheLeft(true);
+ mHistory->setEditStatic(true);
+ mHistory->setVisibleVScroll(true);
+
+ // compiler
+ Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts);
+ mCompilerContext.setExtensions (&mExtensions);
+ }
+
+ void Console::open()
+ {
+ // Give keyboard focus to the combo box whenever the console is
+ // turned on
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
+ }
+
+ void Console::close()
+ {
+ // Apparently, hidden widgets can retain key focus
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
+ }
+
+ void Console::setFont(const std::string &fntName)
+ {
+ mHistory->setFontName(fntName);
+ mCommandLine->setFontName(fntName);
+ }
+
+ void Console::clearHistory()
+ {
+ mHistory->setCaption("");
+ }
+
+ void Console::print(const std::string &msg)
+ {
+ mHistory->addText(msg);
+ }
+
+ void Console::printOK(const std::string &msg)
+ {
+ print("#FF00FF" + msg + "\n");
+ }
+
+ void Console::printError(const std::string &msg)
+ {
+ print("#FF2222" + msg + "\n");
+ }
+
+ void Console::execute (const std::string& command)
+ {
+ // Log the command
+ print("#FFFFFF> " + command + "\n");
+
+ Compiler::Locals locals;
+ Compiler::Output output (locals);
+
+ if (compile (command + "\n", output))
+ {
+ try
+ {
+ ConsoleInterpreterContext interpreterContext (*this, mPtr);
+ Interpreter::Interpreter interpreter;
+ MWScript::installOpcodes (interpreter, mConsoleOnlyScripts);
+ std::vector<Interpreter::Type_Code> code;
+ output.getCode (code);
+ interpreter.run (&code[0], code.size(), interpreterContext);
+ }
+ catch (const std::exception& error)
+ {
+ printError (std::string ("An exception has been thrown: ") + error.what());
+ }
+ }
+ }
+
+ void Console::executeFile (const std::string& path)
+ {
+ std::ifstream stream (path.c_str());
+
+ if (!stream.is_open())
+ printError ("failed to open file: " + path);
+ else
+ {
+ std::string line;
+
+ while (std::getline (stream, line))
+ execute (line);
+ }
+ }
+
+ void Console::keyPress(MyGUI::Widget* _sender,
+ MyGUI::KeyCode key,
+ MyGUI::Char _char)
+ {
+ if( key == MyGUI::KeyCode::Tab)
+ {
+ std::vector<std::string> matches;
+ listNames();
+ mCommandLine->setCaption(complete( mCommandLine->getCaption(), matches ));
+#if 0
+ int i = 0;
+ for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end(); ++it,++i )
+ {
+ printOK( *it );
+ if( i == 50 )
+ break;
+ }
+#endif
+ }
+
+ if(mCommandHistory.empty()) return;
+
+ // Traverse history with up and down arrows
+ if(key == MyGUI::KeyCode::ArrowUp)
+ {
+ // If the user was editing a string, store it for later
+ if(mCurrent == mCommandHistory.end())
+ mEditString = mCommandLine->getCaption();
+
+ if(mCurrent != mCommandHistory.begin())
+ {
+ --mCurrent;
+ mCommandLine->setCaption(*mCurrent);
+ }
+ }
+ else if(key == MyGUI::KeyCode::ArrowDown)
+ {
+ if(mCurrent != mCommandHistory.end())
+ {
+ --mCurrent;
+
+ if(mCurrent != mCommandHistory.end())
+ mCommandLine->setCaption(*mCurrent);
+ else
+ // Restore the edit string
+ mCommandLine->setCaption(mEditString);
+ }
+ }
+ }
+
+ void Console::acceptCommand(MyGUI::EditBox* _sender)
+ {
+ const std::string &cm = mCommandLine->getCaption();
+ if(cm.empty()) return;
+
+ // Add the command to the history, and set the current pointer to
+ // the end of the list
+ mCommandHistory.push_back(cm);
+ mCurrent = mCommandHistory.end();
+ mEditString.clear();
+
+ execute (cm);
+
+ mCommandLine->setCaption("");
+ }
+
+ std::string Console::complete( std::string input, std::vector<std::string> &matches )
+ {
+ std::string output = input;
+ std::string tmp = input;
+ bool has_front_quote = false;
+
+ /* Does the input string contain things that don't have to be completed? If yes erase them. */
+ /* Are there quotation marks? */
+ if( tmp.find('"') != std::string::npos ) {
+ int numquotes=0;
+ for(std::string::iterator it=tmp.begin(); it < tmp.end(); ++it) {
+ if( *it == '"' )
+ numquotes++;
+ }
+
+ /* Is it terminated?*/
+ if( numquotes % 2 ) {
+ tmp.erase( 0, tmp.rfind('"')+1 );
+ has_front_quote = true;
+ }
+ else {
+ size_t pos;
+ if( ( ((pos = tmp.rfind(' ')) != std::string::npos ) ) && ( pos > tmp.rfind('"') ) ) {
+ tmp.erase( 0, tmp.rfind(' ')+1);
+ }
+ else {
+ tmp.clear();
+ }
+ has_front_quote = false;
+ }
+ }
+ /* No quotation marks. Are there spaces?*/
+ else {
+ size_t rpos;
+ if( (rpos=tmp.rfind(' ')) != std::string::npos ) {
+ if( rpos == 0 ) {
+ tmp.clear();
+ }
+ else {
+ tmp.erase(0, rpos+1);
+ }
+ }
+ }
+ /* Erase the input from the output string so we can easily append the completed form later. */
+ output.erase(output.end()-tmp.length(), output.end());
+
+ /* Is there still something in the input string? If not just display all commands and return the unchanged input. */
+ if( tmp.length() == 0 ) {
+ matches=mNames;
+ return input;
+ }
+
+ /* Iterate through the vector. */
+ for(std::vector<std::string>::iterator it=mNames.begin(); it < mNames.end();++it) {
+ bool string_different=false;
+
+ /* Is the string shorter than the input string? If yes skip it. */
+ if( (*it).length() < tmp.length() )
+ continue;
+
+ /* Is the beginning of the string different from the input string? If yes skip it. */
+ for( std::string::iterator iter=tmp.begin(), iter2=(*it).begin(); iter < tmp.end();++iter, ++iter2) {
+ if( tolower(*iter) != tolower(*iter2) ) {
+ string_different=true;
+ break;
+ }
+ }
+
+ if( string_different )
+ continue;
+
+ /* The beginning of the string matches the input string, save it for the next test. */
+ matches.push_back(*it);
+ }
+
+ /* There are no matches. Return the unchanged input. */
+ if( matches.empty() )
+ {
+ return input;
+ }
+
+ /* Only one match. We're done. */
+ if( matches.size() == 1 ) {
+ /* Adding quotation marks when the input string started with a quotation mark or has spaces in it*/
+ if( ( matches.front().find(' ') != std::string::npos ) ) {
+ if( !has_front_quote )
+ output.append(std::string("\""));
+ return output.append(matches.front() + std::string("\" "));
+ }
+ else if( has_front_quote ) {
+ return output.append(matches.front() + std::string("\" "));
+ }
+ else {
+ return output.append(matches.front() + std::string(" "));
+ }
+ }
+
+ /* Check if all matching strings match further than input. If yes complete to this match. */
+ int i = tmp.length();
+
+ for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) {
+ for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end();++it) {
+ if( tolower((*it)[i]) != tolower(*iter) ) {
+ /* Append the longest match to the end of the output string*/
+ output.append(matches.front().substr( 0, i));
+ return output;
+ }
+ }
+ }
+
+ /* All keywords match with the shortest. Append it to the output string and return it. */
+ return output.append(matches.front());
+ }
+
+ void Console::onResChange(int width, int height)
+ {
+ setCoord(10,10, width-10, height/2);
+ }
+
+ void Console::setSelectedObject(const MWWorld::Ptr& object)
+ {
+ if (!object.isEmpty())
+ {
+ if (object == mPtr)
+ {
+ setTitle("#{sConsoleTitle}");
+ mPtr=MWWorld::Ptr();
+ }
+ else
+ {
+ setTitle("#{sConsoleTitle} (" + object.getCellRef().mRefID + ")");
+ mPtr = object;
+ }
+ }
+ else
+ {
+ setTitle("#{sConsoleTitle}");
+ mPtr = MWWorld::Ptr();
+ }
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
+ }
+
+ void Console::onReferenceUnavailable()
+ {
+ setSelectedObject(MWWorld::Ptr());
+ }
+}
diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp
new file mode 100644
index 0000000000..8901763630
--- /dev/null
+++ b/apps/openmw/mwgui/console.hpp
@@ -0,0 +1,106 @@
+#ifndef MWGUI_CONSOLE_H
+#define MWGUI_CONSOLE_H
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <components/compiler/errorhandler.hpp>
+#include <components/compiler/lineparser.hpp>
+#include <components/compiler/scanner.hpp>
+#include <components/compiler/locals.hpp>
+#include <components/compiler/output.hpp>
+#include <components/compiler/extensions.hpp>
+#include <components/interpreter/interpreter.hpp>
+
+#include "../mwscript/compilercontext.hpp"
+#include "../mwscript/interpretercontext.hpp"
+
+#include "referenceinterface.hpp"
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface
+ {
+ private:
+
+ Compiler::Extensions mExtensions;
+ MWScript::CompilerContext mCompilerContext;
+ std::vector<std::string> mNames;
+ bool mConsoleOnlyScripts;
+
+ bool compile (const std::string& cmd, Compiler::Output& output);
+
+ /// Report error to the user.
+ virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type);
+
+ /// Report a file related error
+ virtual void report (const std::string& message, Type type);
+
+ void listNames();
+ ///< Write all valid identifiers and keywords into mNames and sort them.
+ /// \note If mNames is not empty, this function is a no-op.
+ /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same
+ /// time).
+
+ public:
+
+ void setSelectedObject(const MWWorld::Ptr& object);
+ ///< Set the implicit object for script execution
+
+ protected:
+
+ virtual void onReferenceUnavailable();
+
+
+ public:
+ MyGUI::EditBox* mCommandLine;
+ MyGUI::EditBox* mHistory;
+
+ typedef std::list<std::string> StringList;
+
+ // History of previous entered commands
+ StringList mCommandHistory;
+ StringList::iterator mCurrent;
+ std::string mEditString;
+
+ Console(int w, int h, bool consoleOnlyScripts);
+
+ virtual void open();
+ virtual void close();
+
+ void setFont(const std::string &fntName);
+
+ void onResChange(int width, int height);
+
+ void clearHistory();
+
+ // Print a message to the console. Messages may contain color
+ // code, eg. "#FFFFFF this is white".
+ void print(const std::string &msg);
+
+ // These are pre-colored versions that you should use.
+
+ /// Output from successful console command
+ void printOK(const std::string &msg);
+
+ /// Error message
+ void printError(const std::string &msg);
+
+ void execute (const std::string& command);
+
+ void executeFile (const std::string& path);
+
+ private:
+
+ void keyPress(MyGUI::Widget* _sender,
+ MyGUI::KeyCode key,
+ MyGUI::Char _char);
+
+ void acceptCommand(MyGUI::EditBox* _sender);
+
+ std::string complete( std::string input, std::vector<std::string> &matches );
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp
new file mode 100644
index 0000000000..bc869e5fef
--- /dev/null
+++ b/apps/openmw/mwgui/container.cpp
@@ -0,0 +1,280 @@
+#include "container.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+#include "countdialog.hpp"
+#include "tradewindow.hpp"
+#include "inventorywindow.hpp"
+
+#include "itemview.hpp"
+#include "inventoryitemmodel.hpp"
+#include "sortfilteritemmodel.hpp"
+#include "pickpocketitemmodel.hpp"
+
+namespace
+{
+ std::string getCountString(const int count)
+ {
+ if (count == 1)
+ return "";
+ if (count > 9999)
+ return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
+ else
+ return boost::lexical_cast<std::string>(count);
+ }
+}
+
+namespace MWGui
+{
+
+ void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count)
+ {
+ mItem = sourceModel->getItem(index);
+ mDraggedCount = count;
+ mSourceModel = sourceModel;
+ mSourceView = sourceView;
+ mSourceSortModel = sortModel;
+ mIsOnDragAndDrop = true;
+ mDragAndDropWidget->setVisible(true);
+
+ std::string sound = MWWorld::Class::get(mItem.mBase).getUpSoundId(mItem.mBase);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+
+ if (mSourceSortModel)
+ {
+ mSourceSortModel->clearDragItems();
+ mSourceSortModel->addDragItem(mItem.mBase, count);
+ }
+
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(mItem.mBase).getInventoryIcon(mItem.mBase);
+ MyGUI::ImageBox* baseWidget = mDragAndDropWidget->createWidget<MyGUI::ImageBox>
+ ("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default);
+ 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);
+ path.append(".dds");
+ image->setImageTexture(path);
+ image->setNeedMouseFocus(false);
+
+ // text widget that shows item count
+ MyGUI::TextBox* text = image->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label"));
+ text->setTextAlign(MyGUI::Align::Right);
+ text->setNeedMouseFocus(false);
+ text->setTextShadow(true);
+ text->setTextShadowColour(MyGUI::Colour(0,0,0));
+ text->setCaption(getCountString(count));
+
+ sourceView->update();
+
+ MWBase::Environment::get().getWindowManager()->setDragDrop(true);
+ }
+
+ void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView)
+ {
+ std::string sound = MWWorld::Class::get(mItem.mBase).getDownSoundId(mItem.mBase);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+
+ mDragAndDropWidget->setVisible(false);
+
+ // If item is dropped where it was taken from, we don't need to do anything -
+ // otherwise, do the transfer
+ if (targetModel != mSourceModel)
+ {
+ targetModel->copyItem(mItem, mDraggedCount);
+ mSourceModel->removeItem(mItem, mDraggedCount);
+ }
+
+ mSourceModel->update();
+
+ finish();
+ targetView->update();
+ }
+
+ void DragAndDrop::finish()
+ {
+ mIsOnDragAndDrop = false;
+ mSourceSortModel->clearDragItems();
+
+ MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget);
+ mDraggedWidget = 0;
+ MWBase::Environment::get().getWindowManager()->setDragDrop(false);
+ }
+
+
+ ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop)
+ : WindowBase("openmw_container_window.layout")
+ , mDragAndDrop(dragAndDrop)
+ , mSelectedItem(-1)
+ , mModel(NULL)
+ , mSortModel(NULL)
+ {
+ getWidget(mDisposeCorpseButton, "DisposeCorpseButton");
+ getWidget(mTakeButton, "TakeButton");
+ getWidget(mCloseButton, "CloseButton");
+
+ getWidget(mItemView, "ItemView");
+ mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &ContainerWindow::onBackgroundSelected);
+ mItemView->eventItemClicked += MyGUI::newDelegate(this, &ContainerWindow::onItemSelected);
+
+ mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked);
+ mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked);
+ mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked);
+
+ setCoord(200,0,600,300);
+ }
+
+ void ContainerWindow::onItemSelected(int index)
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ if (!dynamic_cast<PickpocketItemModel*>(mModel))
+ dropItem();
+ return;
+ }
+
+ const ItemStack& item = mSortModel->getItem(index);
+
+ MWWorld::Ptr object = item.mBase;
+ int count = item.mCount;
+ bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
+ if (MyGUI::InputManager::getInstance().isControlPressed())
+ count = 1;
+
+ mSelectedItem = mSortModel->mapToSource(index);
+
+ if (count > 1 && !shift)
+ {
+ CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
+ dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count);
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem);
+ }
+ else
+ dragItem (NULL, count);
+ }
+
+ void ContainerWindow::dragItem(MyGUI::Widget* sender, int count)
+ {
+ mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count);
+ }
+
+ void ContainerWindow::dropItem()
+ {
+ if (mPtr.getTypeName() == typeid(ESM::Container).name())
+ {
+ // check that we don't exceed container capacity
+ MWWorld::Ptr item = mDragAndDrop->mItem.mBase;
+ float weight = MWWorld::Class::get(item).getWeight(item) * mDragAndDrop->mDraggedCount;
+ if (MWWorld::Class::get(mPtr).getCapacity(mPtr) < MWWorld::Class::get(mPtr).getEncumbrance(mPtr) + weight)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}");
+ return;
+ }
+
+ // check container organic flag
+ MWWorld::LiveCellRef<ESM::Container>* ref = mPtr.get<ESM::Container>();
+ if (ref->mBase->mFlags & ESM::Container::Organic)
+ {
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sContentsMessage2}");
+ return;
+ }
+ }
+
+ mDragAndDrop->drop(mModel, mItemView);
+ }
+
+ void ContainerWindow::onBackgroundSelected()
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop && !dynamic_cast<PickpocketItemModel*>(mModel))
+ dropItem();
+ }
+
+ void ContainerWindow::open(const MWWorld::Ptr& container, bool loot)
+ {
+ mPtr = container;
+
+ if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot)
+ {
+ // we are stealing stuff
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ mModel = new PickpocketItemModel(player, new InventoryItemModel(container));
+ }
+ else
+ mModel = new InventoryItemModel(container);
+
+ mDisposeCorpseButton->setVisible(loot);
+
+ setTitle(MWWorld::Class::get(container).getName(container));
+
+ mSortModel = new SortFilterItemModel(mModel);
+
+ mItemView->setModel (mSortModel);
+ }
+
+ void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
+ {
+ if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
+ }
+ }
+
+ void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender)
+ {
+ if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop)
+ {
+ // transfer everything into the player's inventory
+ ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel();
+ mModel->update();
+ for (size_t i=0; i<mModel->getItemCount(); ++i)
+ {
+ if (i==0)
+ {
+ // play the sound of the first object
+ MWWorld::Ptr item = mModel->getItem(i).mBase;
+ std::string sound = MWWorld::Class::get(item).getUpSoundId(item);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+ }
+
+ playerModel->copyItem(mModel->getItem(i), mModel->getItem(i).mCount);
+ mModel->removeItem(mModel->getItem(i), mModel->getItem(i).mCount);
+ }
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
+ }
+ }
+
+ void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender)
+ {
+ if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop)
+ {
+ onTakeAllButtonClicked(mTakeButton);
+
+ if (MWWorld::Class::get(mPtr).isPersistent(mPtr))
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}");
+ else
+ MWBase::Environment::get().getWorld()->deleteObject(mPtr);
+
+ mPtr = MWWorld::Ptr();
+ }
+ }
+
+ void ContainerWindow::onReferenceUnavailable()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
+ }
+
+}
diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp
new file mode 100644
index 0000000000..243f77aa56
--- /dev/null
+++ b/apps/openmw/mwgui/container.hpp
@@ -0,0 +1,79 @@
+#ifndef MGUI_CONTAINER_H
+#define MGUI_CONTAINER_H
+
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+
+#include "itemmodel.hpp"
+
+namespace MWWorld
+{
+ class Environment;
+}
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+ class ContainerWindow;
+ class ItemView;
+ class SortFilterItemModel;
+}
+
+
+namespace MWGui
+{
+ class DragAndDrop
+ {
+ public:
+ bool mIsOnDragAndDrop;
+ MyGUI::Widget* mDraggedWidget;
+ MyGUI::Widget* mDragAndDropWidget;
+ ItemModel* mSourceModel;
+ ItemView* mSourceView;
+ SortFilterItemModel* mSourceSortModel;
+ ItemStack mItem;
+ int mDraggedCount;
+
+ void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count);
+ void drop (ItemModel* targetModel, ItemView* targetView);
+
+ void finish();
+ };
+
+ class ContainerWindow : public WindowBase, public ReferenceInterface
+ {
+ public:
+ ContainerWindow(DragAndDrop* dragAndDrop);
+
+ void open(const MWWorld::Ptr& container, bool loot=false);
+
+ private:
+ DragAndDrop* mDragAndDrop;
+
+ MWGui::ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+ ItemModel* mModel;
+ size_t mSelectedItem;
+
+ MyGUI::Button* mDisposeCorpseButton;
+ MyGUI::Button* mTakeButton;
+ MyGUI::Button* mCloseButton;
+
+ void onItemSelected(int index);
+ void onBackgroundSelected();
+ void dragItem(MyGUI::Widget* sender, int count);
+ void dropItem();
+ void onCloseButtonClicked(MyGUI::Widget* _sender);
+ void onTakeAllButtonClicked(MyGUI::Widget* _sender);
+ void onDisposeCorpseButtonClicked(MyGUI::Widget* sender);
+
+ virtual void onReferenceUnavailable();
+ };
+}
+#endif // CONTAINER_H
diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp
new file mode 100644
index 0000000000..eff8fbcc1b
--- /dev/null
+++ b/apps/openmw/mwgui/containeritemmodel.cpp
@@ -0,0 +1,173 @@
+#include "containeritemmodel.hpp"
+
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+
+namespace
+{
+
+ bool stacks (const MWWorld::Ptr& left, const MWWorld::Ptr& right)
+ {
+ if (left == right)
+ return true;
+
+ // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure
+ if (left.getContainerStore() && right.getContainerStore())
+ return left.getContainerStore()->stacks(left, right)
+ && right.getContainerStore()->stacks(left, right);
+
+ if (left.getContainerStore())
+ return left.getContainerStore()->stacks(left, right);
+ if (right.getContainerStore())
+ return right.getContainerStore()->stacks(left, right);
+
+ MWWorld::ContainerStore store;
+ return store.stacks(left, right);
+ }
+
+}
+
+namespace MWGui
+{
+
+ContainerItemModel::ContainerItemModel(const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& worldItems)
+ : mItemSources(itemSources)
+ , mWorldItems(worldItems)
+{
+ assert (mItemSources.size());
+}
+
+ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source)
+{
+ mItemSources.push_back(source);
+}
+
+ItemStack ContainerItemModel::getItem (ModelIndex index)
+{
+ if (index < 0)
+ throw std::runtime_error("Invalid index supplied");
+ if (mItems.size() <= static_cast<size_t>(index))
+ throw std::runtime_error("Item index out of range");
+ return mItems[index];
+}
+
+size_t ContainerItemModel::getItemCount()
+{
+ return mItems.size();
+}
+
+ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
+{
+ size_t i = 0;
+ for (std::vector<ItemStack>::iterator it = mItems.begin(); it != mItems.end(); ++it)
+ {
+ if (*it == item)
+ return i;
+ ++i;
+ }
+ return -1;
+}
+
+void ContainerItemModel::copyItem (const ItemStack& item, size_t count)
+{
+ const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1];
+ if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source))
+ throw std::runtime_error("Item to copy needs to be from a different container!");
+ int origCount = item.mBase.getRefData().getCount();
+ item.mBase.getRefData().setCount(count);
+ source.getClass().getContainerStore(source).add(item.mBase, source);
+ item.mBase.getRefData().setCount(origCount);
+}
+
+void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
+{
+ int toRemove = count;
+
+ for (std::vector<MWWorld::Ptr>::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source)
+ {
+ MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source);
+
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ if (stacks(*it, item.mBase))
+ {
+ int refCount = it->getRefData().getCount();
+ it->getRefData().setCount(std::max(0, refCount - toRemove));
+ toRemove -= refCount;
+ if (toRemove <= 0)
+ return;
+ }
+ }
+ }
+ for (std::vector<MWWorld::Ptr>::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source)
+ {
+ if (stacks(*source, item.mBase))
+ {
+ int refCount = source->getRefData().getCount();
+ if (refCount - toRemove <= 0)
+ MWBase::Environment::get().getWorld()->deleteObject(*source);
+ else
+ source->getRefData().setCount(std::max(0, refCount - toRemove));
+ toRemove -= refCount;
+ if (toRemove <= 0)
+ return;
+ }
+ }
+
+ throw std::runtime_error("Not enough items to remove could be found");
+}
+
+void ContainerItemModel::update()
+{
+ mItems.clear();
+ for (std::vector<MWWorld::Ptr>::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source)
+ {
+ MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source);
+
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ std::vector<ItemStack>::iterator itemStack = mItems.begin();
+ for (; itemStack != mItems.end(); ++itemStack)
+ {
+ if (stacks(*it, itemStack->mBase))
+ {
+ // we already have an item stack of this kind, add to it
+ itemStack->mCount += it->getRefData().getCount();
+ break;
+ }
+ }
+
+ if (itemStack == mItems.end())
+ {
+ // no stack yet, create one
+ ItemStack newItem (*it, this, it->getRefData().getCount());
+ mItems.push_back(newItem);
+ }
+ }
+ }
+ for (std::vector<MWWorld::Ptr>::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source)
+ {
+ std::vector<ItemStack>::iterator itemStack = mItems.begin();
+ for (; itemStack != mItems.end(); ++itemStack)
+ {
+ if (stacks(*source, itemStack->mBase))
+ {
+ // we already have an item stack of this kind, add to it
+ itemStack->mCount += source->getRefData().getCount();
+ break;
+ }
+ }
+
+ if (itemStack == mItems.end())
+ {
+ // no stack yet, create one
+ ItemStack newItem (*source, this, source->getRefData().getCount());
+ mItems.push_back(newItem);
+ }
+ }
+}
+
+}
diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp
new file mode 100644
index 0000000000..22595582ef
--- /dev/null
+++ b/apps/openmw/mwgui/containeritemmodel.hpp
@@ -0,0 +1,38 @@
+#ifndef MWGUI_CONTAINER_ITEM_MODEL_H
+#define MWGUI_CONTAINER_ITEM_MODEL_H
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ /// @brief The container item model supports multiple item sources, which are needed for
+ /// making NPCs sell items from containers owned by them
+ class ContainerItemModel : public ItemModel
+ {
+ public:
+ ContainerItemModel (const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& worldItems);
+ ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for removal,
+ /// while the last element will be used to add new items to.
+
+ ContainerItemModel (const MWWorld::Ptr& source);
+
+ virtual ItemStack getItem (ModelIndex index);
+ virtual ModelIndex getIndex (ItemStack item);
+ virtual size_t getItemCount();
+
+ virtual void copyItem (const ItemStack& item, size_t count);
+ virtual void removeItem (const ItemStack& item, size_t count);
+
+ virtual void update();
+
+ private:
+ std::vector<MWWorld::Ptr> mItemSources;
+ std::vector<MWWorld::Ptr> mWorldItems;
+
+ std::vector<ItemStack> mItems;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp
new file mode 100644
index 0000000000..e62fb3fcec
--- /dev/null
+++ b/apps/openmw/mwgui/controllers.cpp
@@ -0,0 +1,54 @@
+#include "controllers.hpp"
+
+namespace MWGui
+{
+ namespace Controllers
+ {
+
+ ControllerRepeatClick::ControllerRepeatClick() :
+ mInit(0.5),
+ mStep(0.1),
+ mEnabled(true),
+ mTimeLeft(0)
+ {
+ }
+
+ ControllerRepeatClick::~ControllerRepeatClick()
+ {
+ }
+
+ bool ControllerRepeatClick::addTime(MyGUI::Widget* _widget, float _time)
+ {
+ if(mTimeLeft == 0)
+ mTimeLeft = mInit;
+
+ mTimeLeft -= _time;
+ while (mTimeLeft <= 0)
+ {
+ mTimeLeft += mStep;
+ eventRepeatClick(_widget, this);
+ }
+ return true;
+ }
+
+ void ControllerRepeatClick::setRepeat(float init, float step)
+ {
+ mInit = init;
+ mStep = step;
+ }
+
+ void ControllerRepeatClick::setEnabled(bool enable)
+ {
+ mEnabled = enable;
+ }
+
+ void ControllerRepeatClick::setProperty(const std::string& _key, const std::string& _value)
+ {
+ }
+
+ void ControllerRepeatClick::prepareItem(MyGUI::Widget* _widget)
+ {
+ }
+
+ }
+}
diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp
new file mode 100644
index 0000000000..798acde622
--- /dev/null
+++ b/apps/openmw/mwgui/controllers.hpp
@@ -0,0 +1,46 @@
+#ifndef MWGUI_CONTROLLERS_H
+#define MWGUI_CONTROLLERS_H
+
+#include <MyGUI_Widget.h>
+#include <MyGUI_ControllerItem.h>
+
+
+namespace MWGui
+{
+ namespace Controllers
+ {
+ class ControllerRepeatClick :
+ public MyGUI::ControllerItem
+ {
+ MYGUI_RTTI_DERIVED( ControllerRepeatClick )
+
+ public:
+ ControllerRepeatClick();
+ virtual ~ControllerRepeatClick();
+
+ void setRepeat(float init, float step);
+ void setEnabled(bool enable);
+ virtual void setProperty(const std::string& _key, const std::string& _value);
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate2<MyGUI::Widget*, MyGUI::ControllerItem*> EventHandle_RepeatClickVoid;
+
+ /** Event : Repeat Click.\n
+ signature : void method(MyGUI::Widget* _sender, MyGUI::ControllerItem *_controller)\n
+ */
+ EventHandle_RepeatClickVoid eventRepeatClick;
+
+ private:
+ bool addTime(MyGUI::Widget* _widget, float _time);
+ void prepareItem(MyGUI::Widget* _widget);
+
+ private:
+ float mInit;
+ float mStep;
+ bool mEnabled;
+ float mTimeLeft;
+ };
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp
new file mode 100644
index 0000000000..02ccbbc053
--- /dev/null
+++ b/apps/openmw/mwgui/countdialog.cpp
@@ -0,0 +1,104 @@
+#include "countdialog.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWGui
+{
+ CountDialog::CountDialog() :
+ WindowModal("openmw_count_window.layout")
+ {
+ getWidget(mSlider, "CountSlider");
+ getWidget(mItemEdit, "ItemEdit");
+ getWidget(mItemText, "ItemText");
+ getWidget(mLabelText, "LabelText");
+ getWidget(mOkButton, "OkButton");
+ getWidget(mCancelButton, "CancelButton");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onCancelButtonClicked);
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onOkButtonClicked);
+ mItemEdit->eventEditTextChange += MyGUI::newDelegate(this, &CountDialog::onEditTextChange);
+ mSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &CountDialog::onSliderMoved);
+ // make sure we read the enter key being pressed to accept multiple items
+ mItemEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &CountDialog::onEnterKeyPressed);
+ }
+
+ void CountDialog::open(const std::string& item, const std::string& message, const int maxCount)
+ {
+ setVisible(true);
+
+ mLabelText->setCaptionWithReplacing(message);
+
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ mSlider->setScrollRange(maxCount);
+ mItemText->setCaption(item);
+
+ int width = std::max(mItemText->getTextSize().width + 128, 320);
+ setCoord(viewSize.width/2 - width/2,
+ viewSize.height/2 - mMainWidget->getHeight()/2,
+ width,
+ mMainWidget->getHeight());
+
+ // by default, the text edit field has the focus of the keyboard
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mItemEdit);
+
+ mSlider->setScrollPosition(maxCount-1);
+ mItemEdit->setCaption(boost::lexical_cast<std::string>(maxCount));
+ }
+
+ void CountDialog::cancel()
+ {
+ setVisible(false);
+ }
+
+ void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ cancel();
+ }
+
+ void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender)
+ {
+ eventOkClicked(NULL, mSlider->getScrollPosition()+1);
+
+ setVisible(false);
+ }
+
+ // essentially duplicating what the OK button does if user presses
+ // Enter key
+ void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender)
+ {
+ eventOkClicked(NULL, mSlider->getScrollPosition()+1);
+
+ setVisible(false);
+ }
+
+ void CountDialog::onEditTextChange(MyGUI::EditBox* _sender)
+ {
+ if (_sender->getCaption() == "")
+ return;
+
+ unsigned int count;
+ try
+ {
+ count = boost::lexical_cast<unsigned int>(_sender->getCaption());
+ }
+ catch (std::bad_cast&)
+ {
+ count = 1;
+ }
+ if (count > mSlider->getScrollRange())
+ {
+ count = mSlider->getScrollRange();
+ }
+ mSlider->setScrollPosition(count-1);
+ onSliderMoved(mSlider, count-1);
+ }
+
+ void CountDialog::onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position)
+ {
+ mItemEdit->setCaption(boost::lexical_cast<std::string>(_position+1));
+ }
+}
diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp
new file mode 100644
index 0000000000..06de3eb887
--- /dev/null
+++ b/apps/openmw/mwgui/countdialog.hpp
@@ -0,0 +1,39 @@
+#ifndef MWGUI_COUNTDIALOG_H
+#define MWGUI_COUNTDIALOG_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class CountDialog : public WindowModal
+ {
+ public:
+ CountDialog();
+ void open(const std::string& item, const std::string& message, const int maxCount);
+ void cancel();
+
+ typedef MyGUI::delegates::CMultiDelegate2<MyGUI::Widget*, int> EventHandle_WidgetInt;
+
+ /** Event : Ok button was clicked.\n
+ signature : void method(MyGUI::Widget* _sender, int _count)\n
+ */
+ EventHandle_WidgetInt eventOkClicked;
+
+ private:
+ MyGUI::ScrollBar* mSlider;
+ MyGUI::EditBox* mItemEdit;
+ MyGUI::TextBox* mItemText;
+ MyGUI::TextBox* mLabelText;
+ MyGUI::Button* mOkButton;
+ MyGUI::Button* mCancelButton;
+
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onOkButtonClicked(MyGUI::Widget* _sender);
+ void onEditTextChange(MyGUI::EditBox* _sender);
+ void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position);
+ void onEnterKeyPressed(MyGUI::EditBox* _sender);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp
new file mode 100644
index 0000000000..c069eca15a
--- /dev/null
+++ b/apps/openmw/mwgui/cursor.cpp
@@ -0,0 +1,129 @@
+#include "cursor.hpp"
+
+#include <MyGUI_PointerManager.h>
+#include <MyGUI_InputManager.h>
+#include <MyGUI_RotatingSkin.h>
+#include <MyGUI_Gui.h>
+
+#include <OgreMath.h>
+
+namespace MWGui
+{
+
+
+ ResourceImageSetPointerFix::ResourceImageSetPointerFix()
+ : mImageSet(NULL)
+ , mRotation(0)
+ {
+ }
+
+ ResourceImageSetPointerFix::~ResourceImageSetPointerFix()
+ {
+ }
+
+ void ResourceImageSetPointerFix::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version)
+ {
+ Base::deserialization(_node, _version);
+
+ MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator();
+ while (info.next("Property"))
+ {
+ const std::string& key = info->findAttribute("key");
+ const std::string& value = info->findAttribute("value");
+
+ if (key == "Point")
+ mPoint = MyGUI::IntPoint::parse(value);
+ else if (key == "Size")
+ mSize = MyGUI::IntSize::parse(value);
+ else if (key == "Rotation")
+ mRotation = MyGUI::utility::parseInt(value);
+ else if (key == "Resource")
+ mImageSet = MyGUI::ResourceManager::getInstance().getByName(value)->castType<MyGUI::ResourceImageSet>();
+ }
+ }
+
+ int ResourceImageSetPointerFix::getRotation()
+ {
+ return mRotation;
+ }
+
+ void ResourceImageSetPointerFix::setImage(MyGUI::ImageBox* _image)
+ {
+ if (mImageSet != NULL)
+ _image->setItemResourceInfo(mImageSet->getIndexInfo(0, 0));
+ }
+
+ void ResourceImageSetPointerFix::setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point)
+ {
+ _image->setCoord(_point.left - mPoint.left, _point.top - mPoint.top, mSize.width, mSize.height);
+ }
+
+ MyGUI::ResourceImageSetPtr ResourceImageSetPointerFix:: getImageSet()
+ {
+ return mImageSet;
+ }
+
+ MyGUI::IntPoint ResourceImageSetPointerFix::getHotSpot()
+ {
+ return mPoint;
+ }
+
+ MyGUI::IntSize ResourceImageSetPointerFix::getSize()
+ {
+ 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
new file mode 100644
index 0000000000..badf82262b
--- /dev/null
+++ b/apps/openmw/mwgui/cursor.hpp
@@ -0,0 +1,61 @@
+#ifndef MWGUI_CURSOR_H
+#define MWGUI_CURSOR_H
+
+#include <MyGUI_IPointer.h>
+#include <MyGUI_ResourceImageSet.h>
+
+namespace MWGui
+{
+
+ /// \brief Allows us to get the members of
+ /// ResourceImageSetPointer that we need.
+ /// \example MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer");
+ /// MyGUI::ResourceManager::getInstance().load("core.xml");
+ class ResourceImageSetPointerFix :
+ public MyGUI::IPointer
+ {
+ MYGUI_RTTI_DERIVED( ResourceImageSetPointerFix )
+
+ public:
+ ResourceImageSetPointerFix();
+ virtual ~ResourceImageSetPointerFix();
+
+ virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version);
+
+ virtual void setImage(MyGUI::ImageBox* _image);
+ virtual void setPosition(MyGUI::ImageBox* _image, const MyGUI::IntPoint& _point);
+
+ //and now for the whole point of this class, allow us to get
+ //the hot spot, the image and the size of the cursor.
+ virtual MyGUI::ResourceImageSetPtr getImageSet();
+ virtual MyGUI::IntPoint getHotSpot();
+ virtual MyGUI::IntSize getSize();
+ virtual int getRotation();
+
+ private:
+ MyGUI::IntPoint mPoint;
+ MyGUI::IntSize mSize;
+ MyGUI::ResourceImageSetPtr mImageSet;
+ 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
new file mode 100644
index 0000000000..c9a7806918
--- /dev/null
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -0,0 +1,615 @@
+#include "dialogue.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwworld/class.hpp"
+
+#include "../mwdialogue/dialoguemanagerimp.hpp"
+
+#include "widgets.hpp"
+#include "list.hpp"
+#include "tradewindow.hpp"
+#include "spellbuyingwindow.hpp"
+#include "inventorywindow.hpp"
+#include "travelwindow.hpp"
+#include "bookpage.hpp"
+
+
+namespace
+{
+ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
+ {
+ typedef MWGui::BookTypesetter::Utf8Point point;
+
+ point begin = reinterpret_cast <point> (text);
+
+ return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
+ }
+}
+
+namespace MWGui
+{
+
+ PersuasionDialog::PersuasionDialog()
+ : WindowModal("openmw_persuasion_dialog.layout")
+ {
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mAdmireButton, "AdmireButton");
+ getWidget(mIntimidateButton, "IntimidateButton");
+ getWidget(mTauntButton, "TauntButton");
+ getWidget(mBribe10Button, "Bribe10Button");
+ getWidget(mBribe100Button, "Bribe100Button");
+ getWidget(mBribe1000Button, "Bribe1000Button");
+ getWidget(mGoldLabel, "GoldLabel");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onCancel);
+ mAdmireButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ mIntimidateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ mTauntButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ mBribe10Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ mBribe100Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+ }
+
+ void PersuasionDialog::onCancel(MyGUI::Widget *sender)
+ {
+ setVisible(false);
+ }
+
+ void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
+ {
+ 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);
+ type = MWBase::MechanicsManager::PT_Bribe10;
+ }
+ else if (sender == mBribe100Button)
+ {
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100);
+ type = MWBase::MechanicsManager::PT_Bribe100;
+ }
+ else /*if (sender == mBribe1000Button)*/
+ {
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000);
+ type = MWBase::MechanicsManager::PT_Bribe1000;
+ }
+
+ MWBase::Environment::get().getDialogueManager()->persuade(type);
+
+ setVisible(false);
+ }
+
+ void PersuasionDialog::open()
+ {
+ WindowModal::open();
+ center();
+
+ int playerGold = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold();
+
+ mBribe10Button->setEnabled (playerGold >= 10);
+ mBribe100Button->setEnabled (playerGold >= 100);
+ mBribe1000Button->setEnabled (playerGold >= 1000);
+
+ mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(playerGold));
+ }
+
+ // --------------------------------------------------------------------------------------------------
+
+ Response::Response(const std::string &text, const std::string &title)
+ : mTitle(title)
+ {
+ mText = text;
+ }
+
+ void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const
+ {
+ BookTypesetter::Style* title = typesetter->createStyle("", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f));
+ typesetter->sectionBreak(9);
+ if (mTitle != "")
+ typesetter->write(title, to_utf8_span(mTitle.c_str()));
+ typesetter->sectionBreak(9);
+
+ typedef std::pair<size_t, size_t> Range;
+ std::map<Range, intptr_t> hyperLinks;
+
+ // We need this copy for when @# hyperlinks are replaced
+ std::string text = mText;
+
+ size_t pos_begin, pos_end;
+ for(;;)
+ {
+ pos_begin = text.find('@');
+ if (pos_begin != std::string::npos)
+ pos_end = text.find('#', pos_begin);
+
+ if (pos_begin != std::string::npos && pos_end != std::string::npos)
+ {
+ std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1);
+ const char specialPseudoAsteriskCharacter = 127;
+ std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*');
+ std::string topicName = MWBase::Environment::get().getWindowManager()->
+ getTranslationDataStorage().topicStandardForm(link);
+
+ std::string displayName = link;
+ while (displayName[displayName.size()-1] == '*')
+ displayName.erase(displayName.size()-1, 1);
+
+ text.replace(pos_begin, pos_end+1-pos_begin, displayName);
+
+ if (topicLinks.find(Misc::StringUtils::lowerCase(topicName)) != topicLinks.end())
+ hyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = intptr_t(topicLinks[Misc::StringUtils::lowerCase(topicName)]);
+ }
+ else
+ break;
+ }
+
+ typesetter->addContent(to_utf8_span(text.c_str()));
+
+ if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation())
+ {
+ BookTypesetter::Style* style = typesetter->createStyle("", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f));
+ size_t formatted = 0; // points to the first character that is not laid out yet
+ for (std::map<Range, intptr_t>::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it)
+ {
+ intptr_t topicId = it->second;
+ const MyGUI::Colour linkHot (143/255.f, 155/255.f, 218/255.f);
+ const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f);
+ const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f);
+ BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId);
+ if (formatted < it->first.first)
+ typesetter->write(style, formatted, it->first.first);
+ typesetter->write(hotStyle, it->first.first, it->first.second);
+ formatted = it->first.second;
+ }
+ if (formatted < text.size())
+ typesetter->write(style, formatted, text.size());
+ }
+ else
+ {
+ std::string::const_iterator i = text.begin ();
+ KeywordSearchT::Match match;
+ while (i != text.end () && keywordSearch->search (i, text.end (), match))
+ {
+ if (i != match.mBeg)
+ addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ());
+
+ addTopicLink (typesetter, match.mValue, match.mBeg - text.begin (), match.mEnd - text.begin ());
+
+ i = match.mEnd;
+ }
+
+ if (i != text.end ())
+ addTopicLink (typesetter, 0, i - text.begin (), text.size ());
+ }
+ }
+
+ void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const
+ {
+ BookTypesetter::Style* style = typesetter->createStyle("", MyGUI::Colour(202/255.f, 165/255.f, 96/255.f));
+
+ const MyGUI::Colour linkHot (143/255.f, 155/255.f, 218/255.f);
+ const MyGUI::Colour linkNormal (112/255.f, 126/255.f, 207/255.f);
+ const MyGUI::Colour linkActive (175/255.f, 184/255.f, 228/255.f);
+
+ if (topicId)
+ style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId);
+ typesetter->write (style, begin, end);
+ }
+
+ Message::Message(const std::string& text)
+ {
+ mText = text;
+ }
+
+ void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const
+ {
+ BookTypesetter::Style* title = typesetter->createStyle("", MyGUI::Colour(223/255.f, 201/255.f, 159/255.f));
+ typesetter->sectionBreak(9);
+ typesetter->write(title, to_utf8_span(mText.c_str()));
+ }
+
+ // --------------------------------------------------------------------------------------------------
+
+ void Choice::activated()
+ {
+
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.0, 1.0);
+ MWBase::Environment::get().getDialogueManager()->questionAnswered(mChoiceId);
+ }
+
+ void Topic::activated()
+ {
+
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(mTopicId));
+ }
+
+ void Goodbye::activated()
+ {
+
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
+ }
+
+ // --------------------------------------------------------------------------------------------------
+
+ DialogueWindow::DialogueWindow()
+ : WindowBase("openmw_dialogue_window.layout")
+ , mPersuasionDialog()
+ , mEnabled(false)
+ , mServices(0)
+ , mGoodbye(false)
+ {
+ // Centre dialog
+ center();
+
+ mPersuasionDialog.setVisible(false);
+
+ //History view
+ getWidget(mHistory, "History");
+
+ //Topics list
+ getWidget(mTopicsList, "TopicsList");
+ mTopicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic);
+
+ MyGUI::Button* byeButton;
+ getWidget(byeButton, "ByeButton");
+ byeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onByeClicked);
+
+ getWidget(mDispositionBar, "Disposition");
+ getWidget(mDispositionText,"DispositionText");
+ getWidget(mScrollBar, "VScroll");
+
+ mScrollBar->eventScrollChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onScrollbarMoved);
+ mHistory->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel);
+
+ BookPage::ClickCallback callback = boost::bind (&DialogueWindow::notifyLinkClicked, this, _1);
+ mHistory->adviseLinkClicked(callback);
+
+ static_cast<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize);
+ }
+
+ void DialogueWindow::onWindowResize(MyGUI::Window* _sender)
+ {
+ mTopicsList->adjustSize();
+ updateHistory();
+ }
+
+ void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (!mScrollBar->getVisible())
+ return;
+ mScrollBar->setScrollPosition(std::min(static_cast<int>(mScrollBar->getScrollRange()-1),
+ std::max(0, static_cast<int>(mScrollBar->getScrollPosition() - _rel*0.3))));
+ onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition());
+ }
+
+ void DialogueWindow::onByeClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
+ }
+
+ void DialogueWindow::onSelectTopic(const std::string& topic, int id)
+ {
+ if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice())
+ return;
+
+ int separatorPos = 0;
+ for (unsigned int i=0; i<mTopicsList->getItemCount(); ++i)
+ {
+ if (mTopicsList->getItemNameAt(i) == "")
+ separatorPos = i;
+ }
+
+ if (id >= separatorPos)
+ MWBase::Environment::get().getDialogueManager()->keywordSelected(Misc::StringUtils::lowerCase(topic));
+ else
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ if (topic == gmst.find("sPersuasion")->getString())
+ {
+ mPersuasionDialog.setVisible(true);
+ }
+ else if (topic == gmst.find("sCompanionShare")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion);
+ MWBase::Environment::get().getWindowManager()->showCompanionWindow(mPtr);
+ }
+ else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused())
+ {
+ if (topic == gmst.find("sBarter")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter);
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->startTrade(mPtr);
+ }
+ else if (topic == gmst.find("sSpells")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying);
+ MWBase::Environment::get().getWindowManager()->getSpellBuyingWindow()->startSpellBuying(mPtr);
+ }
+ else if (topic == gmst.find("sTravel")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel);
+ MWBase::Environment::get().getWindowManager()->getTravelWindow()->startTravel(mPtr);
+ }
+ else if (topic == gmst.find("sSpellMakingMenuTitle")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation);
+ MWBase::Environment::get().getWindowManager()->startSpellMaking (mPtr);
+ }
+ else if (topic == gmst.find("sEnchanting")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting);
+ MWBase::Environment::get().getWindowManager()->startEnchanting (mPtr);
+ }
+ else if (topic == gmst.find("sServiceTrainingTitle")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training);
+ MWBase::Environment::get().getWindowManager()->startTraining (mPtr);
+ }
+ else if (topic == gmst.find("sRepair")->getString())
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair);
+ MWBase::Environment::get().getWindowManager()->startRepair (mPtr);
+ }
+ }
+ }
+ }
+
+ void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName)
+ {
+ mGoodbye = false;
+ mEnabled = true;
+ mPtr = actor;
+ mTopicsList->setEnabled(true);
+ setTitle(npcName);
+
+ mTopicsList->clear();
+
+ for (std::vector<DialogueText*>::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it)
+ delete (*it);
+ mHistoryContents.clear();
+
+ for (std::vector<Link*>::iterator it = mLinks.begin(); it != mLinks.end(); ++it)
+ delete (*it);
+ mLinks.clear();
+
+ updateOptions();
+ }
+
+ void DialogueWindow::setKeywords(std::list<std::string> keyWords)
+ {
+ mTopicsList->clear();
+ for (std::map<std::string, Link*>::iterator it = mTopicLinks.begin(); it != mTopicLinks.end(); ++it)
+ delete it->second;
+ mTopicLinks.clear();
+ mKeywordSearch.clear();
+
+ bool isCompanion = !MWWorld::Class::get(mPtr).getScript(mPtr).empty()
+ && mPtr.getRefData().getLocals().getIntVar(MWWorld::Class::get(mPtr).getScript(mPtr), "companion");
+
+ bool anyService = mServices > 0 || isCompanion || mPtr.getTypeName() == typeid(ESM::NPC).name();
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ if (mPtr.getTypeName() == typeid(ESM::NPC).name())
+ mTopicsList->addItem(gmst.find("sPersuasion")->getString());
+
+ if (mServices & Service_Trade)
+ mTopicsList->addItem(gmst.find("sBarter")->getString());
+
+ if (mServices & Service_BuySpells)
+ mTopicsList->addItem(gmst.find("sSpells")->getString());
+
+ if (mServices & Service_Travel)
+ mTopicsList->addItem(gmst.find("sTravel")->getString());
+
+ if (mServices & Service_CreateSpells)
+ mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->getString());
+
+ if (mServices & Service_Enchant)
+ mTopicsList->addItem(gmst.find("sEnchanting")->getString());
+
+ if (mServices & Service_Training)
+ mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->getString());
+
+ if (mServices & Service_Repair)
+ mTopicsList->addItem(gmst.find("sRepair")->getString());
+
+ if (isCompanion)
+ mTopicsList->addItem(gmst.find("sCompanionShare")->getString());
+
+ if (anyService)
+ mTopicsList->addSeparator();
+
+
+ for(std::list<std::string>::iterator it = keyWords.begin(); it != keyWords.end(); ++it)
+ {
+ mTopicsList->addItem(*it);
+
+ Topic* t = new Topic(*it);
+ mTopicLinks[Misc::StringUtils::lowerCase(*it)] = t;
+
+ mKeywordSearch.seed(Misc::StringUtils::lowerCase(*it), intptr_t(t));
+ }
+ mTopicsList->adjustSize();
+
+ updateHistory();
+ }
+
+ void DialogueWindow::updateHistory(bool scrollbar)
+ {
+ if (!scrollbar && mScrollBar->getVisible())
+ {
+ mHistory->setSize(mHistory->getSize()+MyGUI::IntSize(mScrollBar->getWidth(),0));
+ mScrollBar->setVisible(false);
+ }
+ if (scrollbar && !mScrollBar->getVisible())
+ {
+ mHistory->setSize(mHistory->getSize()-MyGUI::IntSize(mScrollBar->getWidth(),0));
+ mScrollBar->setVisible(true);
+ }
+
+ BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits<int>().max());
+
+ for (std::vector<DialogueText*>::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it)
+ (*it)->write(typesetter, &mKeywordSearch, mTopicLinks);
+
+
+ BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White);
+
+ typesetter->sectionBreak(9);
+ // choices
+ const MyGUI::Colour linkHot (223/255.f, 201/255.f, 159/255.f);
+ const MyGUI::Colour linkNormal (150/255.f, 50/255.f, 30/255.f);
+ const MyGUI::Colour linkActive (243/255.f, 237/255.f, 221/255.f);
+ for (std::map<std::string, int>::reverse_iterator it = mChoices.rbegin(); it != mChoices.rend(); ++it)
+ {
+ Choice* link = new Choice(it->second);
+ mLinks.push_back(link);
+
+ typesetter->lineBreak();
+ BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive,
+ TypesetBook::InteractiveId(link));
+ typesetter->write(questionStyle, to_utf8_span(it->first.c_str()));
+ }
+
+ if (mGoodbye)
+ {
+ std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sGoodbye")->getString();
+ BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive,
+ TypesetBook::InteractiveId(mLinks.back()));
+ typesetter->lineBreak();
+ typesetter->write(questionStyle, to_utf8_span(goodbye.c_str()));
+ }
+
+ TypesetBook::Ptr book = typesetter->complete();
+ mHistory->showPage(book, 0);
+ size_t viewHeight = mHistory->getParent()->getHeight();
+ if (!scrollbar && book->getSize().second > viewHeight)
+ updateHistory(true);
+ else if (scrollbar)
+ {
+ mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second));
+ size_t range = book->getSize().second - viewHeight;
+ mScrollBar->setScrollRange(range);
+ mScrollBar->setScrollPosition(range-1);
+ mScrollBar->setTrackSize(viewHeight / static_cast<float>(book->getSize().second) * mScrollBar->getLineSize());
+ onScrollbarMoved(mScrollBar, range-1);
+ }
+ else
+ {
+ // no scrollbar
+ onScrollbarMoved(mScrollBar, 0);
+ }
+ }
+
+ void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link)
+ {
+ reinterpret_cast<Link*>(link)->activated();
+ }
+
+ void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos)
+ {
+ mHistory->setPosition(0,-pos);
+ }
+
+ void DialogueWindow::addResponse(const std::string &text, const std::string &title)
+ {
+ // This is called from the dialogue manager, so text is
+ // case-smashed - thus we have to retrieve the correct case
+ // of the title through the topic list.
+ std::string realTitle = title;
+ if (realTitle != "")
+ {
+ for (size_t i=0; i<mTopicsList->getItemCount(); ++i)
+ {
+ std::string item = mTopicsList->getItemNameAt(i);
+ if (Misc::StringUtils::lowerCase(item) == title)
+ {
+ realTitle = item;
+ break;
+ }
+ }
+ }
+
+ mHistoryContents.push_back(new Response(text, realTitle));
+ updateHistory();
+ }
+
+ void DialogueWindow::addMessageBox(const std::string& text)
+ {
+ mHistoryContents.push_back(new Message(text));
+ updateHistory();
+ }
+
+ void DialogueWindow::addChoice(const std::string& choice, int id)
+ {
+ mChoices[choice] = id;
+ updateHistory();
+ }
+
+ void DialogueWindow::clearChoices()
+ {
+ mChoices.clear();
+ updateHistory();
+ }
+
+ void DialogueWindow::updateOptions()
+ {
+ //Clear the list of topics
+ mTopicsList->clear();
+
+ if (mPtr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ mDispositionBar->setProgressRange(100);
+ mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr));
+ mDispositionText->eraseText(0, mDispositionText->getTextLength());
+ mDispositionText->addText("#B29154"+boost::lexical_cast<std::string>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154");
+ }
+ }
+
+ void DialogueWindow::goodbye()
+ {
+ mLinks.push_back(new Goodbye());
+ mGoodbye = true;
+ mTopicsList->setEnabled(false);
+ mEnabled = false;
+ updateHistory();
+ }
+
+ void DialogueWindow::onReferenceUnavailable()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ }
+
+ void DialogueWindow::onFrame()
+ {
+ if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ int disp = std::max(0, std::min(100,
+ MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
+ + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()));
+ mDispositionBar->setProgressRange(100);
+ mDispositionBar->setProgressPosition(disp);
+ mDispositionText->eraseText(0, mDispositionText->getTextLength());
+ mDispositionText->addText("#B29154"+boost::lexical_cast<std::string>(disp)+std::string("/100")+"#B29154");
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp
new file mode 100644
index 0000000000..befbd6eeeb
--- /dev/null
+++ b/apps/openmw/mwgui/dialogue.hpp
@@ -0,0 +1,176 @@
+#ifndef MWGUI_DIALOGE_H
+#define MWGUI_DIALOGE_H
+
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+
+#include "bookpage.hpp"
+
+#include "keywordsearch.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+
+ namespace Widgets
+ {
+ class MWList;
+ }
+}
+
+/*
+ This file contains the dialouge window
+ Layout is defined by resources/mygui/openmw_dialogue_window.layout.
+ */
+
+namespace MWGui
+{
+ class DialogueHistoryViewModel;
+ class BookPage;
+
+ class PersuasionDialog : public WindowModal
+ {
+ public:
+ PersuasionDialog();
+
+ virtual void open();
+
+ private:
+ MyGUI::Button* mCancelButton;
+ MyGUI::Button* mAdmireButton;
+ MyGUI::Button* mIntimidateButton;
+ MyGUI::Button* mTauntButton;
+ MyGUI::Button* mBribe10Button;
+ MyGUI::Button* mBribe100Button;
+ MyGUI::Button* mBribe1000Button;
+ MyGUI::TextBox* mGoldLabel;
+
+ void onCancel (MyGUI::Widget* sender);
+ void onPersuade (MyGUI::Widget* sender);
+ };
+
+
+ struct Link
+ {
+ virtual ~Link() {}
+ virtual void activated () = 0;
+ };
+
+ struct Topic : Link
+ {
+ Topic(const std::string& id) : mTopicId(id) {}
+ std::string mTopicId;
+ virtual void activated ();
+ };
+
+ struct Choice : Link
+ {
+ Choice(int id) : mChoiceId(id) {}
+ int mChoiceId;
+ virtual void activated ();
+ };
+
+ struct Goodbye : Link
+ {
+ virtual void activated ();
+ };
+
+ typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
+
+ struct DialogueText
+ {
+ virtual ~DialogueText() {}
+ virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const = 0;
+ std::string mText;
+ };
+
+ struct Response : DialogueText
+ {
+ Response(const std::string& text, const std::string& title = "");
+ virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const;
+ void addTopicLink (BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const;
+ std::string mTitle;
+ };
+
+ struct Message : DialogueText
+ {
+ Message(const std::string& text);
+ virtual void write (BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map<std::string, Link*>& topicLinks) const;
+ };
+
+ class DialogueWindow: public WindowBase, public ReferenceInterface
+ {
+ public:
+ DialogueWindow();
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ void notifyLinkClicked (TypesetBook::InteractiveId link);
+
+ void startDialogue(MWWorld::Ptr actor, std::string npcName);
+ void setKeywords(std::list<std::string> keyWord);
+
+ void addResponse (const std::string& text, const std::string& title="");
+
+ void addMessageBox(const std::string& text);
+
+ void addChoice(const std::string& choice, int id);
+ void clearChoices();
+
+ void goodbye();
+ void onFrame();
+
+ // make sure to call these before setKeywords()
+ void setServices(int services) { mServices = services; }
+
+ enum Services
+ {
+ Service_Trade = 0x01,
+ Service_BuySpells = 0x02,
+ Service_CreateSpells = 0x04,
+ Service_Enchant = 0x08,
+ Service_Training = 0x10,
+ Service_Travel = 0x20,
+ Service_Repair = 0x40
+ };
+
+ protected:
+ void onSelectTopic(const std::string& topic, int id);
+ void onByeClicked(MyGUI::Widget* _sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onWindowResize(MyGUI::Window* _sender);
+
+ void onScrollbarMoved (MyGUI::ScrollBar* sender, size_t pos);
+
+ void updateHistory(bool scrollbar=false);
+
+ virtual void onReferenceUnavailable();
+
+ private:
+ void updateOptions();
+
+ int mServices;
+
+ bool mEnabled;
+
+ bool mGoodbye;
+
+ std::vector<DialogueText*> mHistoryContents;
+ std::map<std::string, int> mChoices;
+
+ std::vector<Link*> mLinks;
+ std::map<std::string, Link*> mTopicLinks;
+
+ KeywordSearchT mKeywordSearch;
+
+ BookPage* mHistory;
+ Widgets::MWList* mTopicsList;
+ MyGUI::ScrollBar* mScrollBar;
+ MyGUI::ProgressPtr mDispositionBar;
+ MyGUI::EditBox* mDispositionText;
+
+ PersuasionDialog mPersuasionDialog;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp
new file mode 100644
index 0000000000..98ba8ec2f2
--- /dev/null
+++ b/apps/openmw/mwgui/enchantingdialog.cpp
@@ -0,0 +1,307 @@
+#include "enchantingdialog.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/class.hpp"
+
+#include "itemselection.hpp"
+#include "container.hpp"
+#include "inventorywindow.hpp"
+
+#include "sortfilteritemmodel.hpp"
+
+namespace MWGui
+{
+
+
+ EnchantingDialog::EnchantingDialog()
+ : WindowBase("openmw_enchanting_dialog.layout")
+ , EffectEditorBase()
+ , mItemSelectionDialog(NULL)
+ {
+ getWidget(mName, "NameEdit");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mAvailableEffectsList, "AvailableEffects");
+ getWidget(mUsedEffectsView, "UsedEffects");
+ getWidget(mItemBox, "ItemBox");
+ getWidget(mSoulBox, "SoulBox");
+ getWidget(mEnchantmentPoints, "Enchantment");
+ getWidget(mCastCost, "CastCost");
+ getWidget(mCharge, "Charge");
+ getWidget(mTypeButton, "TypeButton");
+ getWidget(mBuyButton, "BuyButton");
+ getWidget(mPrice, "PriceLabel");
+ getWidget(mPriceText, "PriceTextLabel");
+
+ setWidgets(mAvailableEffectsList, mUsedEffectsView);
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onCancelButtonClicked);
+ mItemBox->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onSelectItem);
+ mSoulBox->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onSelectSoul);
+ mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onBuyButtonClicked);
+ mTypeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onTypeButtonClicked);
+ }
+
+ EnchantingDialog::~EnchantingDialog()
+ {
+ delete mItemSelectionDialog;
+ }
+
+ void EnchantingDialog::open()
+ {
+ center();
+ onRemoveItem(NULL);
+ onRemoveSoul(NULL);
+ }
+
+ void EnchantingDialog::updateLabels()
+ {
+ std::stringstream enchantCost;
+ enchantCost << std::setprecision(1) << std::fixed << mEnchanting.getEnchantPoints();
+ mEnchantmentPoints->setCaption(enchantCost.str() + " / " + boost::lexical_cast<std::string>(mEnchanting.getMaxEnchantValue()));
+
+ mCharge->setCaption(boost::lexical_cast<std::string>(mEnchanting.getGemCharge()));
+
+ std::stringstream castCost;
+ castCost << std::setprecision(1) << std::fixed << mEnchanting.getCastCost();
+ mCastCost->setCaption(boost::lexical_cast<std::string>(castCost.str()));
+
+ mPrice->setCaption(boost::lexical_cast<std::string>(mEnchanting.getEnchantPrice()));
+
+ switch(mEnchanting.getCastStyle())
+ {
+ case ESM::Enchantment::CastOnce:
+ mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce","Cast Once"));
+ mAddEffectDialog.constantEffect=false;
+ break;
+ case ESM::Enchantment::WhenStrikes:
+ mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenStrikes", "When Strikes"));
+ mAddEffectDialog.constantEffect=false;
+ break;
+ case ESM::Enchantment::WhenUsed:
+ mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenUsed", "When Used"));
+ mAddEffectDialog.constantEffect=false;
+ break;
+ case ESM::Enchantment::ConstantEffect:
+ mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastConstant", "Cast Constant"));
+ mAddEffectDialog.constantEffect=true;
+ break;
+ }
+ }
+
+ void EnchantingDialog::startEnchanting (MWWorld::Ptr actor)
+ {
+ mEnchanting.setSelfEnchanting(false);
+ mEnchanting.setEnchanter(actor);
+
+ mPtr = actor;
+
+ startEditing ();
+ }
+
+ void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ mEnchanting.setSelfEnchanting(true);
+ mEnchanting.setEnchanter(player);
+
+ mPtr = player;
+ startEditing();
+ mEnchanting.setSoulGem(soulgem);
+
+ MyGUI::ImageBox* image = mSoulBox->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(soulgem).getInventoryIcon(soulgem);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture (path);
+ image->setUserString ("ToolTipType", "ItemPtr");
+ image->setUserData(soulgem);
+ image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveSoul);
+
+ mPrice->setVisible(false);
+ mPriceText->setVisible(false);
+ updateLabels();
+ }
+
+ void EnchantingDialog::onReferenceUnavailable ()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
+ }
+
+ void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
+ }
+
+ void EnchantingDialog::onSelectItem(MyGUI::Widget *sender)
+ {
+ delete mItemSelectionDialog;
+ mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}");
+ mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected);
+ mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel);
+ mItemSelectionDialog->setVisible(true);
+ mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable);
+ }
+
+ void EnchantingDialog::onItemSelected(MWWorld::Ptr item)
+ {
+ mItemSelectionDialog->setVisible(false);
+
+ while (mItemBox->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (mItemBox->getChildAt(0));
+
+ MyGUI::ImageBox* image = mItemBox->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture (path);
+ image->setUserString ("ToolTipType", "ItemPtr");
+ image->setUserData(item);
+ image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveItem);
+
+ mEnchanting.setOldItem(item);
+ mEnchanting.nextCastStyle();
+ updateLabels();
+ }
+
+ void EnchantingDialog::onRemoveItem(MyGUI::Widget *sender)
+ {
+ while (mItemBox->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (mItemBox->getChildAt(0));
+ mEnchanting.setOldItem(MWWorld::Ptr());
+ updateLabels();
+ }
+
+ void EnchantingDialog::onItemCancel()
+ {
+ mItemSelectionDialog->setVisible(false);
+ }
+
+ void EnchantingDialog::onSoulSelected(MWWorld::Ptr item)
+ {
+ mItemSelectionDialog->setVisible(false);
+ mEnchanting.setSoulGem(item);
+
+ if(mEnchanting.getGemCharge()==0)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage32}");
+ return;
+ }
+
+ while (mSoulBox->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (mSoulBox->getChildAt(0));
+
+ MyGUI::ImageBox* image = mSoulBox->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture (path);
+ image->setUserString ("ToolTipType", "ItemPtr");
+ image->setUserData(item);
+ image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveSoul);
+ updateLabels();
+ }
+
+ void EnchantingDialog::onRemoveSoul(MyGUI::Widget *sender)
+ {
+ while (mSoulBox->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (mSoulBox->getChildAt(0));
+ mEnchanting.setSoulGem(MWWorld::Ptr());
+ updateLabels();
+ }
+
+ void EnchantingDialog::onSoulCancel()
+ {
+ mItemSelectionDialog->setVisible(false);
+ }
+
+ void EnchantingDialog::onSelectSoul(MyGUI::Widget *sender)
+ {
+ delete mItemSelectionDialog;
+ mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}");
+ mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected);
+ mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel);
+ mItemSelectionDialog->setVisible(true);
+ mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones);
+
+ //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}");
+ }
+
+ void EnchantingDialog::notifyEffectsChanged ()
+ {
+ mEffectList.mList = mEffects;
+ mEnchanting.setEffect(mEffectList);
+ updateLabels();
+ }
+
+ void EnchantingDialog::onTypeButtonClicked(MyGUI::Widget* sender)
+ {
+ mEnchanting.nextCastStyle();
+ updateLabels();
+ }
+
+ void EnchantingDialog::onBuyButtonClicked(MyGUI::Widget* sender)
+ {
+ if (mEffects.size() <= 0)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}");
+ return;
+ }
+
+ if (mName->getCaption ().empty())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}");
+ return;
+ }
+
+ if (mEnchanting.soulEmpty())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage52}");
+ return;
+ }
+
+ if (mEnchanting.itemEmpty())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage11}");
+ return;
+ }
+
+ if (mEnchanting.getEnchantPoints() > mEnchanting.getMaxEnchantValue())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage29}");
+ return;
+ }
+
+ mEnchanting.setNewItemName(mName->getCaption());
+ mEnchanting.setEffect(mEffectList);
+
+ if (mEnchanting.getEnchantPrice() > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
+ return;
+ }
+
+ int result = mEnchanting.create();
+
+ if(result==1)
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}");
+ else
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}");
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
+ }
+}
diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp
new file mode 100644
index 0000000000..8bad60c8e6
--- /dev/null
+++ b/apps/openmw/mwgui/enchantingdialog.hpp
@@ -0,0 +1,65 @@
+#ifndef MWGUI_ENCHANTINGDIALOG_H
+#define MWGUI_ENCHANTINGDIALOG_H
+
+#include "spellcreationdialog.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwmechanics/enchanting.hpp"
+
+namespace MWGui
+{
+
+ class ItemSelectionDialog;
+
+ class EnchantingDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase
+ {
+ public:
+ EnchantingDialog();
+ virtual ~EnchantingDialog();
+
+ virtual void open();
+ void startEnchanting(MWWorld::Ptr actor);
+ void startSelfEnchanting(MWWorld::Ptr soulgem);
+
+ protected:
+ virtual void onReferenceUnavailable();
+ virtual void notifyEffectsChanged ();
+
+ void onCancelButtonClicked(MyGUI::Widget* sender);
+ void onSelectItem (MyGUI::Widget* sender);
+ void onSelectSoul (MyGUI::Widget* sender);
+ void onRemoveItem (MyGUI::Widget* sender);
+ void onRemoveSoul (MyGUI::Widget* sender);
+
+ void onItemSelected(MWWorld::Ptr item);
+ void onItemCancel();
+ void onSoulSelected(MWWorld::Ptr item);
+ void onSoulCancel();
+ void onBuyButtonClicked(MyGUI::Widget* sender);
+ void updateLabels();
+ void onTypeButtonClicked(MyGUI::Widget* sender);
+
+ ItemSelectionDialog* mItemSelectionDialog;
+
+ MyGUI::Button* mCancelButton;
+ MyGUI::ImageBox* mItemBox;
+ MyGUI::ImageBox* mSoulBox;
+
+ MyGUI::Button* mTypeButton;
+ MyGUI::Button* mBuyButton;
+
+ MyGUI::TextBox* mName;
+ MyGUI::TextBox* mEnchantmentPoints;
+ MyGUI::TextBox* mCastCost;
+ MyGUI::TextBox* mCharge;
+ MyGUI::TextBox* mPrice;
+ MyGUI::TextBox* mPriceText;
+
+ MWMechanics::Enchanting mEnchanting;
+ ESM::EffectList mEffectList;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/exposedwindow.cpp b/apps/openmw/mwgui/exposedwindow.cpp
new file mode 100644
index 0000000000..150a8c893a
--- /dev/null
+++ b/apps/openmw/mwgui/exposedwindow.cpp
@@ -0,0 +1,24 @@
+#include "exposedwindow.hpp"
+
+namespace MWGui
+{
+ MyGUI::VectorWidgetPtr ExposedWindow::getSkinWidgetsByName (const std::string &name)
+ {
+ return MyGUI::Widget::getSkinWidgetsByName (name);
+ }
+
+ MyGUI::Widget* ExposedWindow::getSkinWidget(const std::string & _name, bool _throw)
+ {
+ MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName (_name);
+
+ if (widgets.empty())
+ {
+ MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'");
+ return NULL;
+ }
+ else
+ {
+ return widgets[0];
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/exposedwindow.hpp b/apps/openmw/mwgui/exposedwindow.hpp
new file mode 100644
index 0000000000..7df2fcb358
--- /dev/null
+++ b/apps/openmw/mwgui/exposedwindow.hpp
@@ -0,0 +1,26 @@
+#ifndef MWGUI_EXPOSEDWINDOW_H
+#define MWGUI_EXPOSEDWINDOW_H
+
+#include <MyGUI_Window.h>
+
+namespace MWGui
+{
+
+ /**
+ * @brief subclass to provide access to some Widget internals.
+ */
+ class ExposedWindow : public MyGUI::Window
+ {
+ MYGUI_RTTI_DERIVED(ExposedWindow)
+
+ public:
+ MyGUI::VectorWidgetPtr getSkinWidgetsByName (const std::string &name);
+
+ MyGUI::Widget* getSkinWidget(const std::string & _name, bool _throw = true);
+ ///< Get a widget defined in the inner skin of this window.
+ };
+
+}
+
+#endif
+
diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp
new file mode 100644
index 0000000000..ff160105a3
--- /dev/null
+++ b/apps/openmw/mwgui/fontloader.cpp
@@ -0,0 +1,238 @@
+#include "fontloader.hpp"
+
+#include <OgreResourceGroupManager.h>
+#include <OgreTextureManager.h>
+
+#include <MyGUI_ResourceManager.h>
+#include <MyGUI_FontManager.h>
+#include <MyGUI_ResourceManualFont.h>
+#include <MyGUI_XmlDocument.h>
+#include <MyGUI_FactoryManager.h>
+
+#include <components/misc/stringops.hpp>
+
+namespace
+{
+ unsigned long utf8ToUnicode(const std::string& utf8)
+ {
+ size_t i = 0;
+ unsigned long unicode;
+ size_t todo;
+ unsigned char ch = utf8[i++];
+ if (ch <= 0x7F)
+ {
+ unicode = ch;
+ todo = 0;
+ }
+ else if (ch <= 0xBF)
+ {
+ throw std::logic_error("not a UTF-8 string");
+ }
+ else if (ch <= 0xDF)
+ {
+ unicode = ch&0x1F;
+ todo = 1;
+ }
+ else if (ch <= 0xEF)
+ {
+ unicode = ch&0x0F;
+ todo = 2;
+ }
+ else if (ch <= 0xF7)
+ {
+ unicode = ch&0x07;
+ todo = 3;
+ }
+ else
+ {
+ throw std::logic_error("not a UTF-8 string");
+ }
+ for (size_t j = 0; j < todo; ++j)
+ {
+ unsigned char ch = utf8[i++];
+ if (ch < 0x80 || ch > 0xBF)
+ throw std::logic_error("not a UTF-8 string");
+ unicode <<= 6;
+ unicode += ch & 0x3F;
+ }
+ if (unicode >= 0xD800 && unicode <= 0xDFFF)
+ throw std::logic_error("not a UTF-8 string");
+ if (unicode > 0x10FFFF)
+ throw std::logic_error("not a UTF-8 string");
+
+ return unicode;
+ }
+}
+
+namespace MWGui
+{
+
+ FontLoader::FontLoader(ToUTF8::FromType encoding)
+ {
+ if (encoding == ToUTF8::WINDOWS_1252)
+ mEncoding = ToUTF8::CP437;
+ else
+ mEncoding = encoding;
+ }
+
+ void FontLoader::loadAllFonts()
+ {
+ Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups ();
+ for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt");
+ for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource)
+ {
+ loadFont(*resource);
+ }
+ }
+ }
+
+
+ typedef struct
+ {
+ float x;
+ float y;
+ } Point;
+
+ typedef struct
+ {
+ float u1; // appears unused, always 0
+ Point top_left;
+ Point top_right;
+ Point bottom_left;
+ Point bottom_right;
+ float width;
+ float height;
+ float u2; // appears unused, always 0
+ float kerning;
+ float ascent;
+ } GlyphInfo;
+
+ void FontLoader::loadFont(const std::string &fileName)
+ {
+ Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName);
+
+ float fontSize;
+ int one;
+ file->read(&fontSize, sizeof(fontSize));
+
+ file->read(&one, sizeof(int));
+ assert(one == 1);
+ file->read(&one, sizeof(int));
+ assert(one == 1);
+
+ char name_[284];
+ file->read(name_, sizeof(name_));
+ std::string name(name_);
+
+ GlyphInfo data[256];
+ file->read(data, sizeof(data));
+ file->close();
+
+ // Create the font texture
+ std::string bitmapFilename = "Fonts/" + std::string(name) + ".tex";
+ Ogre::DataStreamPtr bitmapFile = Ogre::ResourceGroupManager::getSingleton().openResource(bitmapFilename);
+
+ int width, height;
+ bitmapFile->read(&width, sizeof(int));
+ bitmapFile->read(&height, sizeof(int));
+
+ std::vector<Ogre::uchar> textureData;
+ textureData.resize(width*height*4);
+ bitmapFile->read(&textureData[0], width*height*4);
+ bitmapFile->close();
+
+ std::string textureName = name;
+ Ogre::Image image;
+ image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA);
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName,
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D,
+ width, height, 0, Ogre::PF_BYTE_RGBA);
+ texture->loadImage(image);
+
+ // Register the font with MyGUI
+ MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
+ MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont"));
+ // We need to emulate loading from XML because the data members are private as of mygui 3.2.0
+ MyGUI::xml::Document xmlDocument;
+ MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont");
+
+ if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic"))
+ root->addAttribute("name", "Magic Cards");
+ else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century"))
+ root->addAttribute("name", "Century Gothic");
+ else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric"))
+ root->addAttribute("name", "Daedric");
+ else
+ return; // no point in loading it, since there is no way of using additional fonts
+
+ MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property");
+ defaultHeight->addAttribute("key", "DefaultHeight");
+ defaultHeight->addAttribute("value", fontSize);
+ MyGUI::xml::ElementPtr source = root->createChild("Property");
+ source->addAttribute("key", "Source");
+ source->addAttribute("value", std::string(textureName));
+ MyGUI::xml::ElementPtr codes = root->createChild("Codes");
+
+ for(int i = 0; i < 256; i++)
+ {
+ int x1 = data[i].top_left.x*width;
+ int y1 = data[i].top_left.y*height;
+ int w = data[i].top_right.x*width - x1;
+ int h = data[i].bottom_left.y*height - y1;
+
+ ToUTF8::Utf8Encoder encoder(mEncoding);
+ unsigned long unicodeVal = utf8ToUnicode(encoder.getUtf8(std::string(1, (unsigned char)(i))));
+
+ MyGUI::xml::ElementPtr code = codes->createChild("Code");
+ code->addAttribute("index", unicodeVal);
+ code->addAttribute("coord", MyGUI::utility::toString(x1) + " "
+ + MyGUI::utility::toString(y1) + " "
+ + MyGUI::utility::toString(w) + " "
+ + MyGUI::utility::toString(h));
+ code->addAttribute("advance", data[i].width);
+ code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ + MyGUI::utility::toString((fontSize-data[i].ascent)));
+
+ // ASCII vertical bar, use this as text input cursor
+ if (i == 124)
+ {
+ MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code");
+ cursorCode->addAttribute("index", MyGUI::FontCodeType::Cursor);
+ cursorCode->addAttribute("coord", MyGUI::utility::toString(x1) + " "
+ + MyGUI::utility::toString(y1) + " "
+ + MyGUI::utility::toString(w) + " "
+ + MyGUI::utility::toString(h));
+ cursorCode->addAttribute("advance", data[i].width);
+ cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ + MyGUI::utility::toString((fontSize-data[i].ascent)));
+ }
+ }
+
+ // These are required as well, but the fonts don't provide them
+ for (int i=0; i<3; ++i)
+ {
+ MyGUI::FontCodeType::Enum type;
+ if(i == 0)
+ type = MyGUI::FontCodeType::Selected;
+ else if (i == 1)
+ type = MyGUI::FontCodeType::SelectedBack;
+ else if (i == 2)
+ type = MyGUI::FontCodeType::NotDefined;
+
+ MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code");
+ cursorCode->addAttribute("index", type);
+ cursorCode->addAttribute("coord", "0 0 0 0");
+ cursorCode->addAttribute("advance", "0");
+ cursorCode->addAttribute("bearing", "0 0");
+
+ }
+
+ font->deserialization(root, MyGUI::Version(3,2,0));
+
+ MyGUI::ResourceManager::getInstance().addResource(font);
+ }
+
+}
diff --git a/apps/openmw/mwgui/fontloader.hpp b/apps/openmw/mwgui/fontloader.hpp
new file mode 100644
index 0000000000..7954b0875e
--- /dev/null
+++ b/apps/openmw/mwgui/fontloader.hpp
@@ -0,0 +1,25 @@
+#ifndef MWGUI_FONTLOADER_H
+#define MWGUI_FONTLOADER_H
+
+#include <components/to_utf8/to_utf8.hpp>
+
+namespace MWGui
+{
+
+
+ /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and Ogre
+ class FontLoader
+ {
+ public:
+ FontLoader (ToUTF8::FromType encoding);
+ void loadAllFonts ();
+
+ private:
+ ToUTF8::FromType mEncoding;
+
+ void loadFont (const std::string& fileName);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp
new file mode 100644
index 0000000000..bd75c078c0
--- /dev/null
+++ b/apps/openmw/mwgui/formatting.cpp
@@ -0,0 +1,407 @@
+#include "formatting.hpp"
+
+#include <components/interpreter/defines.hpp>
+
+#include "../mwscript/interpretercontext.hpp"
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/algorithm/copy.hpp>
+#include <OgreUTFString.h>
+
+namespace
+{
+ int convertFromHex(std::string hex)
+ {
+ int value = 0;
+
+ int a = 0;
+ int b = hex.length() - 1;
+ for (; b >= 0; a++, b--)
+ {
+ if (hex[b] >= '0' && hex[b] <= '9')
+ {
+ value += (hex[b] - '0') * (1 << (a * 4));
+ }
+ else
+ {
+ switch (hex[b])
+ {
+ case 'A':
+ case 'a':
+ value += 10 * (1 << (a * 4));
+ break;
+
+ case 'B':
+ case 'b':
+ value += 11 * (1 << (a * 4));
+ break;
+
+ case 'C':
+ case 'c':
+ value += 12 * (1 << (a * 4));
+ break;
+
+ case 'D':
+ case 'd':
+ value += 13 * (1 << (a * 4));
+ break;
+
+ case 'E':
+ case 'e':
+ value += 14 * (1 << (a * 4));
+ break;
+
+ case 'F':
+ case 'f':
+ value += 15 * (1 << (a * 4));
+ break;
+
+ default:
+ throw std::runtime_error("invalid character in hex number");
+ break;
+ }
+ }
+ }
+
+ return value;
+ }
+
+ Ogre::UTFString::unicode_char unicodeCharFromChar(char ch)
+ {
+ std::string s;
+ s += ch;
+ Ogre::UTFString string(s);
+ return string.getChar(0);
+ }
+
+ bool is_not_empty(const std::string& s) {
+ std::string temp = s;
+ boost::algorithm::trim(temp);
+ return !temp.empty();
+ }
+}
+
+namespace MWGui
+{
+
+ std::vector<std::string> BookTextParser::split(std::string utf8Text, const int width, const int height)
+ {
+ using Ogre::UTFString;
+ std::vector<std::string> result;
+
+ MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor
+ utf8Text = Interpreter::fixDefinesBook(utf8Text, interpreterContext);
+
+ boost::algorithm::replace_all(utf8Text, "\n", "");
+ boost::algorithm::replace_all(utf8Text, "\r", "");
+ boost::algorithm::replace_all(utf8Text, "<BR>", "\n");
+ boost::algorithm::replace_all(utf8Text, "<P>", "\n\n");
+
+ UTFString text(utf8Text);
+ const int spacing = 48;
+
+ const UTFString::unicode_char LEFT_ANGLE = unicodeCharFromChar('<');
+ const UTFString::unicode_char NEWLINE = unicodeCharFromChar('\n');
+ const UTFString::unicode_char SPACE = unicodeCharFromChar(' ');
+
+ while (!text.empty())
+ {
+ // read in characters until we have exceeded the size, or run out of text
+ int currentWidth = 0;
+ int currentHeight = 0;
+
+ size_t currentWordStart = 0;
+ size_t index = 0;
+
+ {
+ std::string texToTrim = text.asUTF8();
+ boost::algorithm::trim( texToTrim );
+ text = UTFString(texToTrim);
+ }
+
+
+ while (currentHeight <= height - spacing && index < text.size())
+ {
+ const UTFString::unicode_char ch = text.getChar(index);
+ if (ch == LEFT_ANGLE)
+ {
+ const size_t tagStart = index + 1;
+ const size_t tagEnd = text.find('>', tagStart);
+ if (tagEnd == UTFString::npos)
+ throw std::runtime_error("BookTextParser Error: Tag is not terminated");
+ const std::string tag = text.substr(tagStart, tagEnd - tagStart).asUTF8();
+
+ if (boost::algorithm::starts_with(tag, "IMG"))
+ {
+ const int h = mHeight;
+ parseImage(tag, false);
+ currentHeight += (mHeight - h);
+ currentWidth = 0;
+ }
+ else if (boost::algorithm::starts_with(tag, "FONT"))
+ {
+ parseFont(tag);
+ if (currentWidth != 0) {
+ currentHeight += currentFontHeight();
+ currentWidth = 0;
+ }
+ currentWidth = 0;
+ }
+ else if (boost::algorithm::starts_with(tag, "DIV"))
+ {
+ parseDiv(tag);
+ if (currentWidth != 0) {
+ currentHeight += currentFontHeight();
+ currentWidth = 0;
+ }
+ }
+ index = tagEnd;
+ }
+ else if (ch == NEWLINE)
+ {
+ currentHeight += currentFontHeight();
+ currentWidth = 0;
+ currentWordStart = index;
+ }
+ else if (ch == SPACE)
+ {
+ currentWidth += 3; // keep this in sync with the font's SpaceWidth property
+ currentWordStart = index;
+ }
+ else
+ {
+ currentWidth += widthForCharGlyph(ch);
+ }
+
+ if (currentWidth > width)
+ {
+ currentHeight += currentFontHeight();
+ currentWidth = 0;
+ // add size of the current word
+ UTFString word = text.substr(currentWordStart, index - currentWordStart);
+ for (UTFString::const_iterator it = word.begin(), end = word.end(); it != end; ++it)
+ currentWidth += widthForCharGlyph(it.getCharacter());
+ }
+ index += UTFString::_utf16_char_length(ch);
+ }
+ const size_t pageEnd = (currentHeight > height - spacing && currentWordStart != 0)
+ ? currentWordStart : index;
+
+ result.push_back(text.substr(0, pageEnd).asUTF8());
+ text.erase(0, pageEnd);
+ }
+
+ std::vector<std::string> nonEmptyPages;
+ boost::copy(result | boost::adaptors::filtered(is_not_empty), std::back_inserter(nonEmptyPages));
+ return nonEmptyPages;
+ }
+
+ float BookTextParser::widthForCharGlyph(unsigned unicodeChar) const
+ {
+ std::string fontName(mTextStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont);
+ return MyGUI::FontManager::getInstance().getByName(fontName)
+ ->getGlyphInfo(unicodeChar)->width;
+ }
+
+ float BookTextParser::currentFontHeight() const
+ {
+ std::string fontName(mTextStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont);
+ return MyGUI::FontManager::getInstance().getByName(fontName)->getDefaultHeight();
+ }
+
+ MyGUI::IntSize BookTextParser::parsePage(std::string text, MyGUI::Widget* parent, const int width)
+ {
+ MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor
+ text = Interpreter::fixDefinesBook(text, interpreterContext);
+
+ mParent = parent;
+ mWidth = width;
+ mHeight = 0;
+
+ assert(mParent);
+ while (mParent->getChildCount())
+ {
+ MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0));
+ }
+
+ // remove trailing "
+ if (text[text.size()-1] == '\"')
+ text.erase(text.size()-1);
+
+ parseSubText(text);
+ return MyGUI::IntSize(mWidth, mHeight);
+ }
+
+ MyGUI::IntSize BookTextParser::parseScroll(std::string text, MyGUI::Widget* parent, const int width)
+ {
+ MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor
+ text = Interpreter::fixDefinesBook(text, interpreterContext);
+
+ mParent = parent;
+ mWidth = width;
+ mHeight = 0;
+
+ assert(mParent);
+ while (mParent->getChildCount())
+ {
+ MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0));
+ }
+
+ boost::algorithm::replace_all(text, "<BR>", "\n");
+ boost::algorithm::replace_all(text, "<P>", "\n\n");
+ boost::algorithm::trim_left(text);
+
+ // remove trailing "
+ if (text[text.size()-1] == '\"')
+ text.erase(text.size()-1);
+
+ parseSubText(text);
+ return MyGUI::IntSize(mWidth, mHeight);
+ }
+
+
+ void BookTextParser::parseImage(std::string tag, bool createWidget)
+ {
+ int src_start = tag.find("SRC=")+5;
+ std::string image = tag.substr(src_start, tag.find('"', src_start)-src_start);
+
+ // fix texture extension to .dds
+ if (image.size() > 4)
+ {
+ image[image.size()-3] = 'd';
+ image[image.size()-2] = 'd';
+ image[image.size()-1] = 's';
+ }
+
+ int width_start = tag.find("WIDTH=")+7;
+ int width = boost::lexical_cast<int>(tag.substr(width_start, tag.find('"', width_start)-width_start));
+
+ int height_start = tag.find("HEIGHT=")+8;
+ int height = boost::lexical_cast<int>(tag.substr(height_start, tag.find('"', height_start)-height_start));
+
+ if (createWidget)
+ {
+ MyGUI::ImageBox* box = mParent->createWidget<MyGUI::ImageBox> ("ImageBox",
+ MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top,
+ mParent->getName() + boost::lexical_cast<std::string>(mParent->getChildCount()));
+ box->setImageTexture("bookart\\" + image);
+ box->setProperty("NeedMouse", "false");
+ }
+
+ mWidth = std::max(mWidth, width);
+ mHeight += height;
+ }
+
+ void BookTextParser::parseDiv(std::string tag)
+ {
+ if (tag.find("ALIGN=") == std::string::npos)
+ return;
+
+ int align_start = tag.find("ALIGN=")+7;
+ std::string align = tag.substr(align_start, tag.find('"', align_start)-align_start);
+ if (align == "CENTER")
+ mTextStyle.mTextAlign = MyGUI::Align::HCenter;
+ else if (align == "LEFT")
+ mTextStyle.mTextAlign = MyGUI::Align::Left;
+ }
+
+ void BookTextParser::parseFont(std::string tag)
+ {
+ if (tag.find("COLOR=") != std::string::npos)
+ {
+ int color_start = tag.find("COLOR=")+7;
+ std::string color = tag.substr(color_start, tag.find('"', color_start)-color_start);
+
+ mTextStyle.mColour = MyGUI::Colour(
+ convertFromHex(color.substr(0, 2))/255.0,
+ convertFromHex(color.substr(2, 2))/255.0,
+ convertFromHex(color.substr(4, 2))/255.0);
+ }
+ if (tag.find("FACE=") != std::string::npos)
+ {
+ int face_start = tag.find("FACE=")+6;
+ std::string face = tag.substr(face_start, tag.find('"', face_start)-face_start);
+
+ if (face != "Magic Cards")
+ mTextStyle.mFont = face;
+ }
+ if (tag.find("SIZE=") != std::string::npos)
+ {
+ /// \todo
+ }
+ }
+
+ void BookTextParser::parseSubText(std::string text)
+ {
+ if (text[0] == '<')
+ {
+ const size_t tagStart = 1;
+ const size_t tagEnd = text.find('>', tagStart);
+ if (tagEnd == std::string::npos)
+ throw std::runtime_error("BookTextParser Error: Tag is not terminated");
+ const std::string tag = text.substr(tagStart, tagEnd - tagStart);
+
+ if (boost::algorithm::starts_with(tag, "IMG"))
+ parseImage(tag);
+ if (boost::algorithm::starts_with(tag, "FONT"))
+ parseFont(tag);
+ if (boost::algorithm::starts_with(tag, "DIV"))
+ parseDiv(tag);
+
+ text.erase(0, tagEnd + 1);
+ }
+
+ size_t tagStart = std::string::npos;
+ std::string realText; // real text, without tags
+ for (size_t i = 0; i<text.size(); ++i)
+ {
+ char c = text[i];
+ if (c == '<')
+ {
+ if ((i + 1 < text.size()) && text[i+1] == '/') // ignore closing tags
+ {
+ while (c != '>')
+ {
+ if (i >= text.size())
+ throw std::runtime_error("BookTextParser Error: Tag is not terminated");
+ ++i;
+ c = text[i];
+ }
+ continue;
+ }
+ else
+ {
+ tagStart = i;
+ break;
+ }
+ }
+ else
+ realText += c;
+ }
+
+ MyGUI::EditBox* box = mParent->createWidget<MyGUI::EditBox>("NormalText",
+ MyGUI::IntCoord(0, mHeight, mWidth, 24), MyGUI::Align::Left | MyGUI::Align::Top,
+ mParent->getName() + boost::lexical_cast<std::string>(mParent->getChildCount()));
+ box->setProperty("Static", "true");
+ box->setProperty("MultiLine", "true");
+ box->setProperty("WordWrap", "true");
+ box->setProperty("NeedMouse", "false");
+ box->setMaxTextLength(realText.size());
+ box->setTextAlign(mTextStyle.mTextAlign);
+ box->setTextColour(mTextStyle.mColour);
+ box->setFontName(mTextStyle.mFont);
+ box->setCaption(realText);
+ box->setSize(box->getSize().width, box->getTextSize().height);
+ mHeight += box->getTextSize().height;
+
+ if (tagStart != std::string::npos)
+ {
+ parseSubText(text.substr(tagStart, text.size()));
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp
new file mode 100644
index 0000000000..a32d98fe58
--- /dev/null
+++ b/apps/openmw/mwgui/formatting.hpp
@@ -0,0 +1,67 @@
+#ifndef MWGUI_FORMATTING_H
+#define MWGUI_FORMATTING_H
+
+#include <MyGUI.h>
+
+namespace MWGui
+{
+ struct TextStyle
+ {
+ TextStyle() :
+ mColour(0,0,0)
+ , mFont("Default")
+ , mTextSize(16)
+ , mTextAlign(MyGUI::Align::Left | MyGUI::Align::Top)
+ {
+ }
+
+ MyGUI::Colour mColour;
+ std::string mFont;
+ int mTextSize;
+ MyGUI::Align mTextAlign;
+ };
+
+ /// \brief utilities for parsing book/scroll text as mygui widgets
+ class BookTextParser
+ {
+ public:
+ /**
+ * Parse markup as MyGUI widgets
+ * @param markup to parse
+ * @param parent for the created widgets
+ * @param maximum width
+ * @return size of the created widgets
+ */
+ MyGUI::IntSize parsePage(std::string text, MyGUI::Widget* parent, const int width);
+
+ /**
+ * Parse markup as MyGUI widgets
+ * @param markup to parse
+ * @param parent for the created widgets
+ * @param maximum width
+ * @return size of the created widgets
+ */
+ MyGUI::IntSize parseScroll(std::string text, MyGUI::Widget* parent, const int width);
+
+ /**
+ * Split the specified text into pieces that fit in the area specified by width and height parameters
+ */
+ std::vector<std::string> split(std::string text, const int width, const int height);
+
+ protected:
+ float widthForCharGlyph(unsigned unicodeChar) const;
+ float currentFontHeight() const;
+ void parseSubText(std::string text);
+
+ void parseImage(std::string tag, bool createWidget=true);
+ void parseDiv(std::string tag);
+ void parseFont(std::string tag);
+ private:
+ MyGUI::Widget* mParent;
+ int mWidth; // maximum width
+ int mHeight; // current height
+ TextStyle mTextStyle;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp
new file mode 100644
index 0000000000..f9d31bdcd6
--- /dev/null
+++ b/apps/openmw/mwgui/hud.cpp
@@ -0,0 +1,625 @@
+#include "hud.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "inventorywindow.hpp"
+#include "console.hpp"
+#include "spellicons.hpp"
+#include "itemmodel.hpp"
+#include "container.hpp"
+
+namespace MWGui
+{
+
+ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop)
+ : Layout("openmw_hud.layout")
+ , mHealth(NULL)
+ , mMagicka(NULL)
+ , mStamina(NULL)
+ , mDrowning(NULL)
+ , mDrowningFrame(NULL)
+ , mWeapImage(NULL)
+ , mSpellImage(NULL)
+ , mWeapStatus(NULL)
+ , mSpellStatus(NULL)
+ , mEffectBox(NULL)
+ , mMinimap(NULL)
+ , mCompass(NULL)
+ , mCrosshair(NULL)
+ , mFpsBox(NULL)
+ , mFpsCounter(NULL)
+ , mTriangleCounter(NULL)
+ , mBatchCounter(NULL)
+ , mHealthManaStaminaBaseLeft(0)
+ , mWeapBoxBaseLeft(0)
+ , mSpellBoxBaseLeft(0)
+ , mEffectBoxBaseRight(0)
+ , mMinimapBoxBaseRight(0)
+ , mDragAndDrop(dragAndDrop)
+ , mCellNameTimer(0.0f)
+ , mCellNameBox(NULL)
+ , mMapVisible(true)
+ , mWeaponVisible(true)
+ , mSpellVisible(true)
+ , mWorldMouseOver(false)
+ , mEnemyHealthTimer(0)
+ {
+ setCoord(0,0, width, height);
+
+ // Energy bars
+ getWidget(mHealthFrame, "HealthFrame");
+ getWidget(mHealth, "Health");
+ getWidget(mMagicka, "Magicka");
+ getWidget(mStamina, "Stamina");
+ getWidget(mEnemyHealth, "EnemyHealth");
+ mHealthManaStaminaBaseLeft = mHealthFrame->getLeft();
+
+ MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame;
+ getWidget(healthFrame, "HealthFrame");
+ getWidget(magickaFrame, "MagickaFrame");
+ getWidget(fatigueFrame, "FatigueFrame");
+ healthFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked);
+ magickaFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked);
+ fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked);
+
+ //Drowning bar
+ getWidget(mDrowningFrame, "DrowningFrame");
+ getWidget(mDrowning, "Drowning");
+ mDrowning->setProgressRange(200);
+
+ const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ // Item and spell images and status bars
+ getWidget(mWeapBox, "WeapBox");
+ getWidget(mWeapImage, "WeapImage");
+ getWidget(mWeapStatus, "WeapStatus");
+ mWeapBoxBaseLeft = mWeapBox->getLeft();
+ mWeapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWeaponClicked);
+
+ getWidget(mSpellBox, "SpellBox");
+ getWidget(mSpellImage, "SpellImage");
+ getWidget(mSpellStatus, "SpellStatus");
+ mSpellBoxBaseLeft = mSpellBox->getLeft();
+ mSpellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked);
+
+ getWidget(mSneakBox, "SneakBox");
+ mSneakBoxBaseLeft = mSneakBox->getLeft();
+
+ getWidget(mEffectBox, "EffectBox");
+ mEffectBoxBaseRight = viewSize.width - mEffectBox->getRight();
+
+ getWidget(mMinimapBox, "MiniMapBox");
+ mMinimapBoxBaseRight = viewSize.width - mMinimapBox->getRight();
+ getWidget(mMinimap, "MiniMap");
+ getWidget(mCompass, "Compass");
+ getWidget(mMinimapButton, "MiniMapButton");
+ mMinimapButton->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked);
+
+ getWidget(mCellNameBox, "CellName");
+ getWidget(mWeaponSpellBox, "WeaponSpellName");
+
+ getWidget(mCrosshair, "Crosshair");
+
+ setFpsLevel(fpsLevel);
+
+ getWidget(mTriangleCounter, "TriangleCounter");
+ getWidget(mBatchCounter, "BatchCounter");
+
+ LocalMapBase::init(mMinimap, mCompass, this);
+
+ mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked);
+ mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver);
+ mMainWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &HUD::onWorldMouseLostFocus);
+
+ mSpellIcons = new SpellIcons();
+ }
+
+ HUD::~HUD()
+ {
+ delete mSpellIcons;
+ }
+
+ void HUD::setFpsLevel(int level)
+ {
+ mFpsCounter = 0;
+
+ MyGUI::Widget* fps;
+ getWidget(fps, "FPSBoxAdv");
+ fps->setVisible(false);
+ getWidget(fps, "FPSBox");
+ fps->setVisible(false);
+
+ if (level == 2)
+ {
+ getWidget(mFpsBox, "FPSBoxAdv");
+ mFpsBox->setVisible(true);
+ getWidget(mFpsCounter, "FPSCounterAdv");
+ }
+ else if (level == 1)
+ {
+ getWidget(mFpsBox, "FPSBox");
+ mFpsBox->setVisible(true);
+ getWidget(mFpsCounter, "FPSCounter");
+ }
+ }
+
+ void HUD::setFPS(float fps)
+ {
+ if (mFpsCounter)
+ mFpsCounter->setCaption(boost::lexical_cast<std::string>((int)fps));
+ }
+
+ void HUD::setTriangleCount(unsigned int count)
+ {
+ mTriangleCounter->setCaption(boost::lexical_cast<std::string>(count));
+ }
+
+ void HUD::setBatchCount(unsigned int count)
+ {
+ mBatchCounter->setCaption(boost::lexical_cast<std::string>(count));
+ }
+
+ void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
+ {
+ static const char *ids[] =
+ {
+ "HBar", "MBar", "FBar", 0
+ };
+
+ 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;
+ }
+ }
+ }
+
+ void HUD::setDrowningTimeLeft(float time)
+ {
+ mDrowning->setProgressPosition(time/20.0*200.0);
+ }
+
+ void HUD::setDrowningBarVisible(bool visible)
+ {
+ mDrowningFrame->setVisible(visible);
+ }
+
+ void HUD::onWorldClicked(MyGUI::Widget* _sender)
+ {
+ if (!MWBase::Environment::get().getWindowManager ()->isGuiMode ())
+ return;
+
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ // drop item into the gameworld
+ MWWorld::Ptr object = mDragAndDrop->mItem.mBase;
+
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition();
+ 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);
+ else
+ world->dropObjectOnGround(world->getPlayer().getPlayer(), object);
+
+ 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();
+ }
+ else
+ {
+ GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
+
+ if ( (mode != GM_Console) && (mode != GM_Container) && (mode != GM_Inventory) )
+ return;
+
+ MWWorld::Ptr object = MWBase::Environment::get().getWorld()->getFacedObject();
+
+ if (mode == GM_Console)
+ MWBase::Environment::get().getWindowManager()->getConsole()->setSelectedObject(object);
+ else if ((mode == GM_Container) || (mode == GM_Inventory))
+ {
+ // pick up object
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
+ }
+ }
+ }
+
+ void HUD::onWorldMouseOver(MyGUI::Widget* _sender, int x, int y)
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ mWorldMouseOver = false;
+
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition();
+ float mouseX = cursorPosition.left / float(viewSize.width);
+ float mouseY = cursorPosition.top / float(viewSize.height);
+
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+
+ // if we can't drop the object at the wanted position, show the "drop on ground" cursor.
+ bool canDrop = world->canPlaceObject(mouseX, mouseY);
+
+ if (!canDrop)
+ MWBase::Environment::get().getWindowManager()->changePointer("drop_ground");
+ else
+ MWBase::Environment::get().getWindowManager()->changePointer("arrow");
+
+ }
+ else
+ {
+ MWBase::Environment::get().getWindowManager()->changePointer("arrow");
+ mWorldMouseOver = true;
+ }
+ }
+
+ void HUD::onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new)
+ {
+ MWBase::Environment::get().getWindowManager()->changePointer("arrow");
+ mWorldMouseOver = false;
+ }
+
+ void HUD::onHMSClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats);
+ }
+
+ void HUD::onMapClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map);
+ }
+
+ void HUD::onWeaponClicked(MyGUI::Widget* _sender)
+ {
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}");
+ return;
+ }
+
+ MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
+ }
+
+ void HUD::onMagicClicked(MyGUI::Widget* _sender)
+ {
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}");
+ return;
+ }
+
+ MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic);
+ }
+
+ void HUD::setCellName(const std::string& cellName)
+ {
+ if (mCellName != cellName)
+ {
+ mCellNameTimer = 5.0f;
+ mCellName = cellName;
+
+ mCellNameBox->setCaptionWithReplacing("#{sCell=" + mCellName + "}");
+ mCellNameBox->setVisible(mMapVisible);
+ }
+ }
+
+ void HUD::onFrame(float dt)
+ {
+ mCellNameTimer -= dt;
+ mWeaponSpellTimer -= dt;
+ if (mCellNameTimer < 0)
+ mCellNameBox->setVisible(false);
+ if (mWeaponSpellTimer < 0)
+ mWeaponSpellBox->setVisible(false);
+
+ mEnemyHealthTimer -= dt;
+ if (mEnemyHealth->getVisible() && mEnemyHealthTimer < 0)
+ {
+ mEnemyHealth->setVisible(false);
+ mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20));
+ }
+ }
+
+ void HUD::onResChange(int width, int height)
+ {
+ setCoord(0, 0, width, height);
+ }
+
+ void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent)
+ {
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+
+ std::string spellName = spell->mName;
+ if (spellName != mSpellName && mSpellVisible)
+ {
+ mWeaponSpellTimer = 5.0f;
+ mSpellName = spellName;
+ mWeaponSpellBox->setCaption(mSpellName);
+ mWeaponSpellBox->setVisible(true);
+ }
+
+ mSpellStatus->setProgressRange(100);
+ mSpellStatus->setProgressPosition(successChancePercent);
+
+ if (mSpellImage->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0));
+
+ mSpellBox->setUserString("ToolTipType", "Spell");
+ mSpellBox->setUserString("Spell", spellId);
+
+ // use the icon of the first effect
+ const ESM::MagicEffect* effect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(spell->mEffects.mList.front().mEffectID);
+
+ std::string icon = effect->mIcon;
+ int slashPos = icon.find("\\");
+ icon.insert(slashPos+1, "b_");
+ icon = std::string("icons\\") + icon;
+ Widgets::fixTexturePath(icon);
+ mSpellImage->setImageTexture(icon);
+ }
+
+ void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent)
+ {
+ std::string itemName = MWWorld::Class::get(item).getName(item);
+ if (itemName != mSpellName && mSpellVisible)
+ {
+ mWeaponSpellTimer = 5.0f;
+ mSpellName = itemName;
+ mWeaponSpellBox->setCaption(mSpellName);
+ mWeaponSpellBox->setVisible(true);
+ }
+
+ mSpellStatus->setProgressRange(100);
+ mSpellStatus->setProgressPosition(chargePercent);
+
+ if (mSpellImage->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0));
+
+ mSpellBox->setUserString("ToolTipType", "ItemPtr");
+ mSpellBox->setUserData(item);
+
+ mSpellImage->setImageTexture("textures\\menu_icon_magic_mini.dds");
+ MyGUI::ImageBox* itemBox = mSpellImage->createWidgetReal<MyGUI::ImageBox>("ImageBox", MyGUI::FloatCoord(0,0,1,1)
+ , MyGUI::Align::Stretch);
+
+ std::string path = std::string("icons\\");
+ path+=MWWorld::Class::get(item).getInventoryIcon(item);
+ Widgets::fixTexturePath(path);
+ itemBox->setImageTexture(path);
+ itemBox->setNeedMouseFocus(false);
+ }
+
+ void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent)
+ {
+ std::string itemName = MWWorld::Class::get(item).getName(item);
+ if (itemName != mWeaponName && mWeaponVisible)
+ {
+ mWeaponSpellTimer = 5.0f;
+ mWeaponName = itemName;
+ mWeaponSpellBox->setCaption(mWeaponName);
+ mWeaponSpellBox->setVisible(true);
+ }
+
+ mWeapBox->setUserString("ToolTipType", "ItemPtr");
+ mWeapBox->setUserData(item);
+
+ mWeapStatus->setProgressRange(100);
+ mWeapStatus->setProgressPosition(durabilityPercent);
+
+ if (mWeapImage->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0));
+
+ std::string path = std::string("icons\\");
+ path+=MWWorld::Class::get(item).getInventoryIcon(item);
+ Widgets::fixTexturePath(path);
+
+ if (MWWorld::Class::get(item).getEnchantment(item) != "")
+ {
+ mWeapImage->setImageTexture("textures\\menu_icon_magic_mini.dds");
+ MyGUI::ImageBox* itemBox = mWeapImage->createWidgetReal<MyGUI::ImageBox>("ImageBox", MyGUI::FloatCoord(0,0,1,1)
+ , MyGUI::Align::Stretch);
+ itemBox->setImageTexture(path);
+ itemBox->setNeedMouseFocus(false);
+ }
+ else
+ mWeapImage->setImageTexture(path);
+ }
+
+ void HUD::unsetSelectedSpell()
+ {
+ std::string spellName = "#{sNone}";
+ if (spellName != mSpellName && mSpellVisible)
+ {
+ mWeaponSpellTimer = 5.0f;
+ mSpellName = spellName;
+ mWeaponSpellBox->setCaptionWithReplacing(mSpellName);
+ mWeaponSpellBox->setVisible(true);
+ }
+
+ if (mSpellImage->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0));
+ mSpellStatus->setProgressRange(100);
+ mSpellStatus->setProgressPosition(0);
+ mSpellImage->setImageTexture("");
+ mSpellBox->clearUserStrings();
+ }
+
+ void HUD::unsetSelectedWeapon()
+ {
+ std::string itemName = "#{sSkillHandtohand}";
+ if (itemName != mWeaponName && mWeaponVisible)
+ {
+ mWeaponSpellTimer = 5.0f;
+ mWeaponName = itemName;
+ mWeaponSpellBox->setCaptionWithReplacing(mWeaponName);
+ mWeaponSpellBox->setVisible(true);
+ }
+
+ if (mWeapImage->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0));
+ mWeapStatus->setProgressRange(100);
+ mWeapStatus->setProgressPosition(0);
+
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
+ mWeapImage->setImageTexture("icons\\k\\tx_werewolf_hand.dds");
+ else
+ mWeapImage->setImageTexture("icons\\k\\stealth_handtohand.dds");
+
+ mWeapBox->clearUserStrings();
+ }
+
+ void HUD::setCrosshairVisible(bool visible)
+ {
+ mCrosshair->setVisible (visible);
+ }
+
+ void HUD::setHmsVisible(bool visible)
+ {
+ mHealth->setVisible(visible);
+ mMagicka->setVisible(visible);
+ mStamina->setVisible(visible);
+ updatePositions();
+ }
+
+ void HUD::setWeapVisible(bool visible)
+ {
+ mWeapBox->setVisible(visible);
+ updatePositions();
+ }
+
+ void HUD::setSpellVisible(bool visible)
+ {
+ mSpellBox->setVisible(visible);
+ updatePositions();
+ }
+
+ void HUD::setSneakVisible(bool visible)
+ {
+ mSneakBox->setVisible(visible);
+ updatePositions();
+ }
+
+ void HUD::setEffectVisible(bool visible)
+ {
+ mEffectBox->setVisible (visible);
+ updatePositions();
+ }
+
+ void HUD::setMinimapVisible(bool visible)
+ {
+ mMinimapBox->setVisible (visible);
+ updatePositions();
+ }
+
+ void HUD::updatePositions()
+ {
+ int weapDx = 0, spellDx = 0, sneakDx = 0;
+ if (!mHealth->getVisible())
+ sneakDx = spellDx = weapDx = mWeapBoxBaseLeft - mHealthManaStaminaBaseLeft;
+
+ if (!mWeapBox->getVisible())
+ {
+ spellDx += mSpellBoxBaseLeft - mWeapBoxBaseLeft;
+ sneakDx = spellDx;
+ }
+
+ if (!mSpellBox->getVisible())
+ sneakDx += mSneakBoxBaseLeft - mSpellBoxBaseLeft;
+
+ mWeaponVisible = mWeapBox->getVisible();
+ mSpellVisible = mSpellBox->getVisible();
+ if (!mWeaponVisible && !mSpellVisible)
+ mWeaponSpellBox->setVisible(false);
+
+ mWeapBox->setPosition(mWeapBoxBaseLeft - weapDx, mWeapBox->getTop());
+ mSpellBox->setPosition(mSpellBoxBaseLeft - spellDx, mSpellBox->getTop());
+ mSneakBox->setPosition(mSneakBoxBaseLeft - sneakDx, mSneakBox->getTop());
+
+ const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ // effect box can have variable width -> variable left coordinate
+ int effectsDx = 0;
+ if (!mMinimapBox->getVisible ())
+ effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight);
+
+ mMapVisible = mMinimapBox->getVisible ();
+ mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop());
+ }
+
+ void HUD::update()
+ {
+ mSpellIcons->updateWidgets(mEffectBox, true);
+
+ if (!mEnemy.isEmpty() && mEnemyHealth->getVisible())
+ {
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy);
+ mEnemyHealth->setProgressRange(100);
+ mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100);
+ }
+ }
+
+ void HUD::setEnemy(const MWWorld::Ptr &enemy)
+ {
+ mEnemy = enemy;
+ mEnemyHealthTimer = 5;
+ if (!mEnemyHealth->getVisible())
+ mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20));
+ mEnemyHealth->setVisible(true);
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy);
+ mEnemyHealth->setProgressRange(100);
+ mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100);
+ }
+
+}
diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp
new file mode 100644
index 0000000000..c40742a603
--- /dev/null
+++ b/apps/openmw/mwgui/hud.hpp
@@ -0,0 +1,114 @@
+#include "mapwindow.hpp"
+
+#include "../mwmechanics/stat.hpp"
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+ class DragAndDrop;
+ class SpellIcons;
+
+ class HUD : public OEngine::GUI::Layout, public LocalMapBase
+ {
+ public:
+ HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop);
+ virtual ~HUD();
+ void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
+ void setFPS(float fps);
+ void setTriangleCount(unsigned int count);
+ void setBatchCount(unsigned int count);
+
+ /// Set time left for the player to start drowning
+ /// @param time value from [0,20]
+ void setDrowningTimeLeft(float time);
+ void setDrowningBarVisible(bool visible);
+
+ void setHmsVisible(bool visible);
+ void setWeapVisible(bool visible);
+ void setSpellVisible(bool visible);
+ void setSneakVisible(bool visible);
+
+ void setEffectVisible(bool visible);
+ void setMinimapVisible(bool visible);
+
+ void setFpsLevel(const int level);
+
+ void setSelectedSpell(const std::string& spellId, int successChancePercent);
+ void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent);
+ void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent);
+ void unsetSelectedSpell();
+ void unsetSelectedWeapon();
+
+ void setCrosshairVisible(bool visible);
+
+ void onFrame(float dt);
+ void onResChange(int width, int height);
+
+ void setCellName(const std::string& cellName);
+
+ bool getWorldMouseOver() { return mWorldMouseOver; }
+
+ MyGUI::Widget* getEffectBox() { return mEffectBox; }
+
+ void update();
+
+ void setEnemy(const MWWorld::Ptr& enemy);
+
+ private:
+ MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning;
+ MyGUI::Widget* mHealthFrame;
+ MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox;
+ MyGUI::ImageBox *mWeapImage, *mSpellImage;
+ MyGUI::ProgressBar *mWeapStatus, *mSpellStatus;
+ MyGUI::Widget *mEffectBox, *mMinimapBox;
+ MyGUI::Button* mMinimapButton;
+ MyGUI::ScrollView* mMinimap;
+ MyGUI::ImageBox* mCompass;
+ MyGUI::ImageBox* mCrosshair;
+ MyGUI::TextBox* mCellNameBox;
+ MyGUI::TextBox* mWeaponSpellBox;
+ MyGUI::Widget* mDrowningFrame;
+
+ MyGUI::Widget* mDummy;
+
+ MyGUI::Widget* mFpsBox;
+ MyGUI::TextBox* mFpsCounter;
+ MyGUI::TextBox* mTriangleCounter;
+ MyGUI::TextBox* mBatchCounter;
+
+ // bottom left elements
+ int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft;
+ // bottom right elements
+ int mMinimapBoxBaseRight, mEffectBoxBaseRight;
+
+ DragAndDrop* mDragAndDrop;
+
+ std::string mCellName;
+ float mCellNameTimer;
+
+ std::string mWeaponName;
+ std::string mSpellName;
+ float mWeaponSpellTimer;
+
+ bool mMapVisible;
+ bool mWeaponVisible;
+ bool mSpellVisible;
+
+ bool mWorldMouseOver;
+
+ SpellIcons* mSpellIcons;
+
+ MWWorld::Ptr mEnemy;
+ float mEnemyHealthTimer;
+
+ void onWorldClicked(MyGUI::Widget* _sender);
+ void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y);
+ void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new);
+ void onHMSClicked(MyGUI::Widget* _sender);
+ void onWeaponClicked(MyGUI::Widget* _sender);
+ void onMagicClicked(MyGUI::Widget* _sender);
+ void onMapClicked(MyGUI::Widget* _sender);
+
+ void updatePositions();
+ };
+}
diff --git a/apps/openmw/mwgui/imagebutton.cpp b/apps/openmw/mwgui/imagebutton.cpp
new file mode 100644
index 0000000000..98f05373ba
--- /dev/null
+++ b/apps/openmw/mwgui/imagebutton.cpp
@@ -0,0 +1,63 @@
+#include "imagebutton.hpp"
+
+#include <OgreTextureManager.h>
+
+namespace MWGui
+{
+
+ void ImageButton::setPropertyOverride(const std::string &_key, const std::string &_value)
+ {
+ if (_key == "ImageHighlighted")
+ mImageHighlighted = _value;
+ else if (_key == "ImagePushed")
+ mImagePushed = _value;
+ else if (_key == "ImageNormal")
+ {
+ if (mImageNormal == "")
+ {
+ setImageTexture(_value);
+ }
+ mImageNormal = _value;
+ }
+ else
+ ImageBox::setPropertyOverride(_key, _value);
+ }
+ void ImageButton::onMouseSetFocus(Widget* _old)
+ {
+ setImageTexture(mImageHighlighted);
+ ImageBox::onMouseSetFocus(_old);
+ }
+
+ void ImageButton::onMouseLostFocus(Widget* _new)
+ {
+ setImageTexture(mImageNormal);
+ ImageBox::onMouseLostFocus(_new);
+ }
+
+ void ImageButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id)
+ {
+ if (_id == MyGUI::MouseButton::Left)
+ setImageTexture(mImagePushed);
+
+ ImageBox::onMouseButtonPressed(_left, _top, _id);
+ }
+
+ MyGUI::IntSize ImageButton::getRequestedSize()
+ {
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal);
+ if (texture.isNull())
+ {
+ std::cerr << "ImageButton: can't find " << mImageNormal << std::endl;
+ return MyGUI::IntSize(0,0);
+ }
+ return MyGUI::IntSize (texture->getWidth(), texture->getHeight());
+ }
+
+ void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id)
+ {
+ if (_id == MyGUI::MouseButton::Left)
+ setImageTexture(mImageHighlighted);
+
+ ImageBox::onMouseButtonReleased(_left, _top, _id);
+ }
+}
diff --git a/apps/openmw/mwgui/imagebutton.hpp b/apps/openmw/mwgui/imagebutton.hpp
new file mode 100644
index 0000000000..f531e22469
--- /dev/null
+++ b/apps/openmw/mwgui/imagebutton.hpp
@@ -0,0 +1,33 @@
+#ifndef MWGUI_IMAGEBUTTON_H
+#define MWGUI_IMAGEBUTTON_H
+
+#include <MyGUI_ImageBox.h>
+
+namespace MWGui
+{
+
+ /**
+ * @brief allows using different image textures depending on the button state
+ */
+ class ImageButton : public MyGUI::ImageBox
+ {
+ MYGUI_RTTI_DERIVED(ImageButton)
+
+ public:
+ MyGUI::IntSize getRequestedSize();
+
+ protected:
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+ virtual void onMouseLostFocus(MyGUI::Widget* _new);
+ virtual void onMouseSetFocus(MyGUI::Widget* _old);
+ virtual void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id);
+ virtual void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id);
+
+ std::string mImageHighlighted;
+ std::string mImageNormal;
+ std::string mImagePushed;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp
new file mode 100644
index 0000000000..62a5a75f06
--- /dev/null
+++ b/apps/openmw/mwgui/inventoryitemmodel.cpp
@@ -0,0 +1,103 @@
+#include "inventoryitemmodel.hpp"
+
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+namespace MWGui
+{
+
+InventoryItemModel::InventoryItemModel(const MWWorld::Ptr &actor)
+ : mActor(actor)
+{
+}
+
+ItemStack InventoryItemModel::getItem (ModelIndex index)
+{
+ if (index < 0)
+ throw std::runtime_error("Invalid index supplied");
+ if (mItems.size() <= static_cast<size_t>(index))
+ throw std::runtime_error("Item index out of range");
+ return mItems[index];
+}
+
+size_t InventoryItemModel::getItemCount()
+{
+ return mItems.size();
+}
+
+ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item)
+{
+ size_t i = 0;
+ for (std::vector<ItemStack>::iterator it = mItems.begin(); it != mItems.end(); ++it)
+ {
+ if (*it == item)
+ return i;
+ ++i;
+ }
+ return -1;
+}
+
+void InventoryItemModel::copyItem (const ItemStack& item, size_t count)
+{
+ if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor))
+ throw std::runtime_error("Item to copy needs to be from a different container!");
+ int origCount = item.mBase.getRefData().getCount();
+ item.mBase.getRefData().setCount(count);
+ mActor.getClass().getContainerStore(mActor).add(item.mBase, mActor);
+ item.mBase.getRefData().setCount(origCount);
+}
+
+
+void InventoryItemModel::removeItem (const ItemStack& item, size_t count)
+{
+ MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(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");
+}
+
+void InventoryItemModel::update()
+{
+ MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor);
+
+ mItems.clear();
+
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ MWWorld::Ptr item = *it;
+ // NOTE: Don't show WerewolfRobe objects in the inventory, or allow them to be taken.
+ // Vanilla likely uses a hack like this since there's no other way to prevent it from
+ // being shown or taken.
+ if(item.getCellRef().mRefID == "WerewolfRobe")
+ continue;
+
+ ItemStack newItem (item, this, item.getRefData().getCount());
+
+ if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ {
+ MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor);
+ for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
+ {
+ MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
+ if (equipped == store.end())
+ continue;
+ if (*equipped == newItem.mBase)
+ newItem.mType = ItemStack::Type_Equipped;
+ }
+ }
+
+ mItems.push_back(newItem);
+ }
+}
+
+}
diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp
new file mode 100644
index 0000000000..23856395e8
--- /dev/null
+++ b/apps/openmw/mwgui/inventoryitemmodel.hpp
@@ -0,0 +1,31 @@
+#ifndef MWGUI_INVENTORY_ITEM_MODEL_H
+#define MWGUI_INVENTORY_ITEM_MODEL_H
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ class InventoryItemModel : public ItemModel
+ {
+ public:
+ InventoryItemModel (const MWWorld::Ptr& actor);
+
+ virtual ItemStack getItem (ModelIndex index);
+ virtual ModelIndex getIndex (ItemStack item);
+ virtual size_t getItemCount();
+
+ virtual void copyItem (const ItemStack& item, size_t count);
+ virtual void removeItem (const ItemStack& item, size_t count);
+
+ virtual void update();
+
+ protected:
+ MWWorld::Ptr mActor;
+ private:
+ std::vector<ItemStack> mItems;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp
new file mode 100644
index 0000000000..4f616b3126
--- /dev/null
+++ b/apps/openmw/mwgui/inventorywindow.cpp
@@ -0,0 +1,516 @@
+#include "inventorywindow.hpp"
+
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/action.hpp"
+
+#include "bookwindow.hpp"
+#include "scrollwindow.hpp"
+#include "spellwindow.hpp"
+#include "itemview.hpp"
+#include "inventoryitemmodel.hpp"
+#include "sortfilteritemmodel.hpp"
+#include "tradeitemmodel.hpp"
+#include "countdialog.hpp"
+#include "tradewindow.hpp"
+#include "container.hpp"
+
+namespace MWGui
+{
+
+ InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop)
+ : WindowPinnableBase("openmw_inventory_window.layout")
+ , mTrading(false)
+ , mLastXSize(0)
+ , mLastYSize(0)
+ , mPreview(MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ())
+ , 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);
+
+ getWidget(mAvatar, "Avatar");
+ getWidget(mAvatarImage, "AvatarImage");
+ getWidget(mEncumbranceBar, "EncumbranceBar");
+ getWidget(mFilterAll, "AllButton");
+ getWidget(mFilterWeapon, "WeaponButton");
+ getWidget(mFilterApparel, "ApparelButton");
+ getWidget(mFilterMagic, "MagicButton");
+ getWidget(mFilterMisc, "MiscButton");
+ getWidget(mLeftPane, "LeftPane");
+ getWidget(mRightPane, "RightPane");
+ getWidget(mArmorRating, "ArmorRating");
+
+ mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
+
+ getWidget(mItemView, "ItemView");
+ 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);
+ mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
+ mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
+
+ mFilterAll->setStateSelected(true);
+
+ setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height);
+ onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
+
+ mPreview.setup();
+ }
+
+ void InventoryWindow::updatePlayer()
+ {
+ mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr());
+ mSortModel = new SortFilterItemModel(mTradeModel);
+ mItemView->setModel(mSortModel);
+ mPreview = MWRender::InventoryPreview(mPtr);
+ mPreview.setup();
+ }
+
+ void InventoryWindow::setGuiMode(GuiMode mode)
+ {
+ mGuiMode = mode;
+ switch(mode) {
+ case GM_Container:
+ setPinButtonVisible(false);
+ mMainWidget->setCoord(mPositionContainer);
+ break;
+ case GM_Companion:
+ setPinButtonVisible(false);
+ mMainWidget->setCoord(mPositionCompanion);
+ break;
+ case GM_Barter:
+ setPinButtonVisible(false);
+ mMainWidget->setCoord(mPositionBarter);
+ break;
+ case GM_Inventory:
+ default:
+ setPinButtonVisible(true);
+ mMainWidget->setCoord(mPositionInventory);
+ break;
+ }
+ onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
+ }
+
+ TradeItemModel* InventoryWindow::getTradeModel()
+ {
+ return mTradeModel;
+ }
+
+ ItemModel* InventoryWindow::getModel()
+ {
+ return mTradeModel;
+ }
+
+ void InventoryWindow::onBackgroundSelected()
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ mDragAndDrop->drop(mTradeModel, mItemView);
+ }
+
+ void InventoryWindow::onItemSelected (int index)
+ {
+ onItemSelectedFromSourceModel (mSortModel->mapToSource(index));
+ }
+
+ void InventoryWindow::onItemSelectedFromSourceModel (int index)
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ mDragAndDrop->drop(mTradeModel, mItemView);
+ return;
+ }
+
+ const ItemStack& item = mTradeModel->getItem(index);
+
+ unequipItem(item.mBase);
+
+ MWWorld::Ptr object = item.mBase;
+ int count = item.mCount;
+ bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
+ if (MyGUI::InputManager::getInstance().isControlPressed())
+ count = 1;
+
+ if (mTrading)
+ {
+ // check if merchant accepts item
+ int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices();
+ if (!MWWorld::Class::get(object).canSell(object, services))
+ {
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sBarterDialog4}");
+ return;
+ }
+ }
+
+ if (count > 1 && !shift)
+ {
+ CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
+ std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}";
+ dialog->open(MWWorld::Class::get(object).getName(object), message, count);
+ dialog->eventOkClicked.clear();
+ if (mTrading)
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem);
+ else
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem);
+ mSelectedItem = index;
+ }
+ else
+ {
+ mSelectedItem = index;
+ if (mTrading)
+ sellItem (NULL, count);
+ else
+ dragItem (NULL, count);
+ }
+
+ // item might have been unequipped
+ notifyContentChanged();
+ }
+
+ void InventoryWindow::dragItem(MyGUI::Widget* sender, int count)
+ {
+ mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count);
+ }
+
+ void InventoryWindow::sellItem(MyGUI::Widget* sender, int count)
+ {
+ const ItemStack& item = mTradeModel->getItem(mSelectedItem);
+ std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+
+ if (item.mType == ItemStack::Type_Barter)
+ {
+ // this was an item borrowed to us by the merchant
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count);
+ mTradeModel->returnItemBorrowedToUs(mSelectedItem, count);
+ }
+ else
+ {
+ // borrow item to the merchant
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count);
+ mTradeModel->borrowItemFromUs(mSelectedItem, count);
+ }
+
+ mItemView->update();
+ }
+
+ void InventoryWindow::updateItemView()
+ {
+ mItemView->update();
+ mPreviewDirty = true;
+ }
+
+ void InventoryWindow::open()
+ {
+ updateEncumbranceBar();
+
+ mItemView->update();
+
+ notifyContentChanged();
+ }
+
+ 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 );
+
+ switch(mGuiMode) {
+ case GM_Container:
+ mPositionContainer = _sender->getCoord();
+ break;
+ case GM_Companion:
+ mPositionCompanion = _sender->getCoord();
+ break;
+ case GM_Barter:
+ mPositionBarter = _sender->getCoord();
+ break;
+ case GM_Inventory:
+ default:
+ mPositionInventory = _sender->getCoord();
+ }
+
+ if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize)
+ {
+ mLastXSize = mMainWidget->getSize().width;
+ mLastYSize = mMainWidget->getSize().height;
+ mPreviewDirty = true;
+ }
+ }
+
+ void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender)
+ {
+ if (_sender == mFilterAll)
+ mSortModel->setCategory(SortFilterItemModel::Category_All);
+ else if (_sender == mFilterWeapon)
+ mSortModel->setCategory(SortFilterItemModel::Category_Weapon);
+ else if (_sender == mFilterApparel)
+ mSortModel->setCategory(SortFilterItemModel::Category_Apparel);
+ else if (_sender == mFilterMagic)
+ mSortModel->setCategory(SortFilterItemModel::Category_Magic);
+ else if (_sender == mFilterMisc)
+ mSortModel->setCategory(SortFilterItemModel::Category_Misc);
+
+ mFilterAll->setStateSelected(false);
+ mFilterWeapon->setStateSelected(false);
+ mFilterApparel->setStateSelected(false);
+ mFilterMagic->setStateSelected(false);
+ mFilterMisc->setStateSelected(false);
+
+ mItemView->update();
+
+ static_cast<MyGUI::Button*>(_sender)->setStateSelected(true);
+ }
+
+ void InventoryWindow::onPinToggled()
+ {
+ MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned);
+ }
+
+ void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender)
+ {
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase;
+ mDragAndDrop->finish();
+
+ if (mDragAndDrop->mSourceModel != mTradeModel)
+ {
+ // add item to the player's inventory
+ MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr);
+ MWWorld::ContainerStoreIterator it = invStore.begin();
+
+ int origCount = ptr.getRefData().getCount();
+ ptr.getRefData().setCount(mDragAndDrop->mDraggedCount);
+ it = invStore.add(ptr, mPtr);
+ ptr.getRefData().setCount(origCount);
+
+ mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount);
+ ptr = *it;
+ }
+
+ boost::shared_ptr<MWWorld::Action> action = MWWorld::Class::get(ptr).use(ptr);
+
+ action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ // this is necessary for books/scrolls: if they are already in the player's inventory,
+ // the "Take" button should not be visible.
+ // NOTE: the take button is "reset" when the window opens, so we can safely do the following
+ // without screwing up future book windows
+ MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false);
+ MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false);
+
+ mItemView->update();
+
+ notifyContentChanged();
+ }
+ else
+ {
+ MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left);
+ MyGUI::IntPoint relPos = mousePos - mAvatar->getAbsolutePosition ();
+ int realX = int(float(relPos.left) / float(mAvatar->getSize().width) * 512.f );
+ int realY = int(float(relPos.top) / float(mAvatar->getSize().height) * 1024.f );
+
+ MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY);
+ if (itemSelected.isEmpty ())
+ return;
+
+ for (size_t i=0; i < mTradeModel->getItemCount (); ++i)
+ {
+ if (mTradeModel->getItem(i).mBase == itemSelected)
+ {
+ onItemSelectedFromSourceModel(i);
+ return;
+ }
+ }
+ throw std::runtime_error("Can't find clicked item");
+ }
+ }
+
+ MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y)
+ {
+ int slot = mPreview.getSlotSelected (x, y);
+
+ if (slot == -1)
+ return MWWorld::Ptr();
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
+ if(invStore.getSlot(slot) != invStore.end())
+ {
+ MWWorld::Ptr item = *invStore.getSlot(slot);
+ // NOTE: Don't allow users to select WerewolfRobe objects in the inventory. Vanilla
+ // likely uses a hack like this since there's no other way to prevent it from being
+ // taken.
+ if(item.getCellRef().mRefID == "WerewolfRobe")
+ return MWWorld::Ptr();
+ return item;
+ }
+
+ 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();
+
+ float capacity = MWWorld::Class::get(player).getCapacity(player);
+ float encumbrance = MWWorld::Class::get(player).getEncumbrance(player);
+ mEncumbranceBar->setValue(encumbrance, capacity);
+ }
+
+ void InventoryWindow::onFrame()
+ {
+ if (!mMainWidget->getVisible())
+ return;
+
+ updateEncumbranceBar();
+ }
+
+ int InventoryWindow::getPlayerGold()
+ {
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
+
+ for (MWWorld::ContainerStoreIterator it = invStore.begin();
+ it != invStore.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
+ return it->getRefData().getCount();
+ }
+ return 0;
+ }
+
+ void InventoryWindow::setTrading(bool trading)
+ {
+ mTrading = trading;
+ }
+
+ void InventoryWindow::doRenderUpdate ()
+ {
+ if (mPreviewDirty)
+ {
+ mPreviewDirty = false;
+ MyGUI::IntSize size = mAvatar->getSize();
+
+ mPreview.update (size.width, size.height);
+ mAvatarImage->setSize(MyGUI::IntSize(std::max(mAvatar->getSize().width, 512), std::max(mAvatar->getSize().height, 1024)));
+ mAvatarImage->setImageTexture("CharacterPreview");
+ }
+ }
+
+ void InventoryWindow::notifyContentChanged()
+ {
+ // update the spell window just in case new enchanted items were added to inventory
+ 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}: "
+ + boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
+ }
+
+ void InventoryWindow::pickUpObject (MWWorld::Ptr object)
+ {
+ // make sure the object is of a type that can be picked up
+ std::string type = object.getTypeName();
+ if ( (type != typeid(ESM::Apparatus).name())
+ && (type != typeid(ESM::Armor).name())
+ && (type != typeid(ESM::Book).name())
+ && (type != typeid(ESM::Clothing).name())
+ && (type != typeid(ESM::Ingredient).name())
+ && (type != typeid(ESM::Light).name())
+ && (type != typeid(ESM::Miscellaneous).name())
+ && (type != typeid(ESM::Lockpick).name())
+ && (type != typeid(ESM::Probe).name())
+ && (type != typeid(ESM::Repair).name())
+ && (type != typeid(ESM::Weapon).name())
+ && (type != typeid(ESM::Potion).name()))
+ return;
+
+ if (MWWorld::Class::get(object).getName(object) == "") // objects without name presented to user can never be picked up
+ return;
+
+ int count = object.getRefData().getCount();
+ if (object.getCellRef().mGoldValue > 1)
+ count = object.getCellRef().mGoldValue;
+
+ // add to player inventory
+ // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr newObject = *MWWorld::Class::get (player).getContainerStore (player).add (object, player);
+ // remove from world
+ MWBase::Environment::get().getWorld()->deleteObject (object);
+
+ // get ModelIndex to the item
+ mTradeModel->update();
+ size_t i=0;
+ for (; i<mTradeModel->getItemCount(); ++i)
+ {
+ if (mTradeModel->getItem(i).mBase == newObject)
+ break;
+ }
+ if (i == mTradeModel->getItemCount())
+ throw std::runtime_error("Added item not found");
+ mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count);
+ }
+
+ MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord ()
+ {
+ return mAvatar->getAbsoluteCoord ();
+ }
+}
diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp
new file mode 100644
index 0000000000..35140437d1
--- /dev/null
+++ b/apps/openmw/mwgui/inventorywindow.hpp
@@ -0,0 +1,112 @@
+#ifndef MGUI_Inventory_H
+#define MGUI_Inventory_H
+
+#include "../mwrender/characterpreview.hpp"
+
+#include "windowpinnablebase.hpp"
+#include "widgets.hpp"
+#include "mode.hpp"
+
+namespace MWGui
+{
+ class ItemView;
+ class SortFilterItemModel;
+ class TradeItemModel;
+ class DragAndDrop;
+ class ItemModel;
+
+ class InventoryWindow : public WindowPinnableBase
+ {
+ public:
+ InventoryWindow(DragAndDrop* dragAndDrop);
+
+ virtual void open();
+
+ void doRenderUpdate();
+
+ /// start trading, disables item drag&drop
+ void setTrading(bool trading);
+
+ void onFrame();
+
+ void pickUpObject (MWWorld::Ptr object);
+
+ int getPlayerGold();
+
+ MyGUI::IntCoord getAvatarScreenCoord();
+
+ MWWorld::Ptr getAvatarSelectedItem(int x, int y);
+
+ void rebuildAvatar() {
+ mPreview.rebuild();
+ }
+
+ TradeItemModel* getTradeModel();
+ ItemModel* getModel();
+
+ void updateItemView();
+
+ void updatePlayer();
+
+ void setGuiMode(GuiMode mode);
+
+ private:
+ DragAndDrop* mDragAndDrop;
+
+ bool mPreviewDirty;
+ size_t mSelectedItem;
+
+ MWWorld::Ptr mPtr;
+
+ MWGui::ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+ TradeItemModel* mTradeModel;
+
+ MyGUI::Widget* mAvatar;
+ MyGUI::ImageBox* mAvatarImage;
+ MyGUI::TextBox* mArmorRating;
+ Widgets::MWDynamicStat* mEncumbranceBar;
+
+ MyGUI::Widget* mLeftPane;
+ MyGUI::Widget* mRightPane;
+
+ MyGUI::Button* mFilterAll;
+ MyGUI::Button* mFilterWeapon;
+ MyGUI::Button* mFilterApparel;
+ MyGUI::Button* mFilterMagic;
+ MyGUI::Button* mFilterMisc;
+
+ MyGUI::IntCoord mPositionInventory;
+ MyGUI::IntCoord mPositionContainer;
+ MyGUI::IntCoord mPositionCompanion;
+ MyGUI::IntCoord mPositionBarter;
+
+ GuiMode mGuiMode;
+
+ int mLastXSize;
+ int mLastYSize;
+
+ MWRender::InventoryPreview mPreview;
+
+ bool mTrading;
+
+ void onItemSelected(int index);
+ void onItemSelectedFromSourceModel(int index);
+
+ void onBackgroundSelected();
+
+ void sellItem(MyGUI::Widget* sender, int count);
+ void dragItem(MyGUI::Widget* sender, int count);
+
+ void onWindowResize(MyGUI::Window* _sender);
+ void onFilterChanged(MyGUI::Widget* _sender);
+ void onAvatarClicked(MyGUI::Widget* _sender);
+ void onPinToggled();
+
+ void unequipItem(const MWWorld::Ptr& item);
+ void updateEncumbranceBar();
+ void notifyContentChanged();
+ };
+}
+
+#endif // Inventory_H
diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp
new file mode 100644
index 0000000000..58e89c4fc4
--- /dev/null
+++ b/apps/openmw/mwgui/itemmodel.cpp
@@ -0,0 +1,120 @@
+#include "itemmodel.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+
+namespace MWGui
+{
+
+ ItemStack::ItemStack(const MWWorld::Ptr &base, ItemModel *creator, size_t count)
+ : mCreator(creator)
+ , mCount(count)
+ , mFlags(0)
+ , mType(Type_Normal)
+ , mBase(base)
+ {
+ if (MWWorld::Class::get(base).getEnchantment(base) != "")
+ mFlags |= Flag_Enchanted;
+ }
+
+ ItemStack::ItemStack()
+ : mCreator(NULL)
+ , mCount(0)
+ , mFlags(0)
+ , mType(Type_Normal)
+ {
+ }
+
+ bool ItemStack::stacks(const ItemStack &other)
+ {
+ if(mBase == other.mBase)
+ return true;
+
+ // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure
+ if (mBase.getContainerStore() && other.mBase.getContainerStore())
+ return mBase.getContainerStore()->stacks(mBase, other.mBase)
+ && other.mBase.getContainerStore()->stacks(mBase, other.mBase);
+
+ if (mBase.getContainerStore())
+ return mBase.getContainerStore()->stacks(mBase, other.mBase);
+ if (other.mBase.getContainerStore())
+ return other.mBase.getContainerStore()->stacks(mBase, other.mBase);
+
+ MWWorld::ContainerStore store;
+ return store.stacks(mBase, other.mBase);
+
+ }
+
+ bool operator == (const ItemStack& left, const ItemStack& right)
+ {
+ if (left.mType != right.mType)
+ return false;
+
+ if(left.mBase == right.mBase)
+ return true;
+
+ // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure
+ if (left.mBase.getContainerStore() && right.mBase.getContainerStore())
+ return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase)
+ && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase);
+
+ if (left.mBase.getContainerStore())
+ return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase);
+ if (right.mBase.getContainerStore())
+ return right.mBase.getContainerStore()->stacks(left.mBase, right.mBase);
+
+ MWWorld::ContainerStore store;
+ return store.stacks(left.mBase, right.mBase);
+ }
+
+ ItemModel::ItemModel()
+ {
+ }
+
+
+ ProxyItemModel::~ProxyItemModel()
+ {
+ delete mSourceModel;
+ }
+
+ void ProxyItemModel::copyItem (const ItemStack& item, size_t count)
+ {
+ // no need to use mapToSource since itemIndex refers to an index in the sourceModel
+ mSourceModel->copyItem (item, count);
+ }
+
+ void ProxyItemModel::removeItem (const ItemStack& item, size_t count)
+ {
+ mSourceModel->removeItem (item, count);
+ }
+
+ ItemModel::ModelIndex ProxyItemModel::mapToSource (ModelIndex index)
+ {
+ const ItemStack& itemToSearch = getItem(index);
+ for (size_t i=0; i<mSourceModel->getItemCount(); ++i)
+ {
+ const ItemStack& item = mSourceModel->getItem(i);
+ if (item == itemToSearch)
+ return i;
+ }
+ return -1;
+ }
+
+ ItemModel::ModelIndex ProxyItemModel::mapFromSource (ModelIndex index)
+ {
+ const ItemStack& itemToSearch = mSourceModel->getItem(index);
+ for (size_t i=0; i<getItemCount(); ++i)
+ {
+ const ItemStack& item = getItem(i);
+ if (item == itemToSearch)
+ return i;
+ }
+ return -1;
+ }
+
+ ItemModel::ModelIndex ProxyItemModel::getIndex (ItemStack item)
+ {
+ return mSourceModel->getIndex(item);
+ }
+
+}
diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp
new file mode 100644
index 0000000000..47aaf79e85
--- /dev/null
+++ b/apps/openmw/mwgui/itemmodel.hpp
@@ -0,0 +1,84 @@
+#ifndef MWGUI_ITEM_MODEL_H
+#define MWGUI_ITEM_MODEL_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+
+ class ItemModel;
+
+ /// @brief A single item stack managed by an item model
+ struct ItemStack
+ {
+ ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count);
+ ItemStack();
+ bool stacks (const ItemStack& other);
+ ///< like operator==, only without checking mType
+
+ enum Type
+ {
+ Type_Barter,
+ Type_Equipped,
+ Type_Normal
+ };
+ Type mType;
+
+ enum Flags
+ {
+ Flag_Enchanted = (1<<0)
+ };
+ int mFlags;
+
+ ItemModel* mCreator;
+ size_t mCount;
+ MWWorld::Ptr mBase;
+ };
+
+ bool operator == (const ItemStack& left, const ItemStack& right);
+
+
+ /// @brief The base class that all item models should derive from.
+ class ItemModel
+ {
+ public:
+ ItemModel();
+ virtual ~ItemModel() {}
+
+ typedef int ModelIndex;
+
+ virtual ItemStack getItem (ModelIndex index) = 0;
+ ///< throws for invalid index
+ virtual size_t getItemCount() = 0;
+
+ virtual ModelIndex getIndex (ItemStack item) = 0;
+
+ virtual void update() = 0;
+
+ virtual void copyItem (const ItemStack& item, size_t count) = 0;
+ virtual void removeItem (const ItemStack& item, size_t count) = 0;
+
+ private:
+ ItemModel(const ItemModel&);
+ ItemModel& operator=(const ItemModel&);
+ };
+
+ /// @brief A proxy item model can be used to filter or rearrange items from a source model (or even add new items to it).
+ /// The neat thing is that this does not actually alter the source model.
+ class ProxyItemModel : public ItemModel
+ {
+ public:
+ virtual ~ProxyItemModel();
+ virtual void copyItem (const ItemStack& item, size_t count);
+ virtual void removeItem (const ItemStack& item, size_t count);
+ virtual ModelIndex getIndex (ItemStack item);
+
+ ModelIndex mapToSource (ModelIndex index);
+ ModelIndex mapFromSource (ModelIndex index);
+ protected:
+ ItemModel* mSourceModel;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp
new file mode 100644
index 0000000000..01ea3429c3
--- /dev/null
+++ b/apps/openmw/mwgui/itemselection.cpp
@@ -0,0 +1,59 @@
+#include "itemselection.hpp"
+
+#include "itemview.hpp"
+#include "inventoryitemmodel.hpp"
+#include "sortfilteritemmodel.hpp"
+
+namespace MWGui
+{
+
+ ItemSelectionDialog::ItemSelectionDialog(const std::string &label)
+ : WindowModal("openmw_itemselection_dialog.layout")
+ , mSortModel(NULL)
+ , mModel(NULL)
+ {
+ getWidget(mItemView, "ItemView");
+ mItemView->eventItemClicked += MyGUI::newDelegate(this, &ItemSelectionDialog::onSelectedItem);
+
+ MyGUI::TextBox* l;
+ getWidget(l, "Label");
+ l->setCaptionWithReplacing (label);
+
+ MyGUI::Button* cancelButton;
+ getWidget(cancelButton, "CancelButton");
+ cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemSelectionDialog::onCancelButtonClicked);
+
+ center();
+ }
+
+ void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container)
+ {
+ mModel = new InventoryItemModel(container);
+ mSortModel = new SortFilterItemModel(mModel);
+ mItemView->setModel(mSortModel);
+ }
+
+ void ItemSelectionDialog::setCategory(int category)
+ {
+ mSortModel->setCategory(category);
+ mItemView->update();
+ }
+
+ void ItemSelectionDialog::setFilter(int filter)
+ {
+ mSortModel->setFilter(filter);
+ mItemView->update();
+ }
+
+ void ItemSelectionDialog::onSelectedItem(int index)
+ {
+ ItemStack item = mSortModel->getItem(index);
+ eventItemSelected(item.mBase);
+ }
+
+ void ItemSelectionDialog::onCancelButtonClicked(MyGUI::Widget* sender)
+ {
+ eventDialogCanceled();
+ }
+
+}
diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp
new file mode 100644
index 0000000000..d6d19d9e1a
--- /dev/null
+++ b/apps/openmw/mwgui/itemselection.hpp
@@ -0,0 +1,34 @@
+#include "container.hpp"
+
+namespace MWGui
+{
+ class ItemView;
+ class SortFilterItemModel;
+ class InventoryItemModel;
+
+ class ItemSelectionDialog : public WindowModal
+ {
+ public:
+ ItemSelectionDialog(const std::string& label);
+
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+ typedef MyGUI::delegates::CMultiDelegate1<MWWorld::Ptr> EventHandle_Item;
+
+ EventHandle_Item eventItemSelected;
+ EventHandle_Void eventDialogCanceled;
+
+ void openContainer (const MWWorld::Ptr& container);
+ void setCategory(int category);
+ void setFilter(int filter);
+
+ private:
+ ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+ InventoryItemModel* mModel;
+
+ void onSelectedItem(int index);
+
+ void onCancelButtonClicked(MyGUI::Widget* sender);
+ };
+
+}
diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp
new file mode 100644
index 0000000000..f9a900ebac
--- /dev/null
+++ b/apps/openmw/mwgui/itemview.cpp
@@ -0,0 +1,202 @@
+#include "itemview.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <MyGUI_FactoryManager.h>
+#include <MyGUI_Gui.h>
+#include <MyGUI_ImageBox.h>
+#include <MyGUI_TextBox.h>
+#include <MyGUI_ScrollView.h>
+#include <MyGUI_Button.h>
+
+#include "../mwworld/class.hpp"
+
+#include "itemmodel.hpp"
+
+namespace
+{
+ std::string getCountString(const int count)
+ {
+ if (count == 1)
+ return "";
+ if (count > 9999)
+ return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
+ else
+ return boost::lexical_cast<std::string>(count);
+ }
+}
+
+
+namespace MWGui
+{
+
+ItemView::ItemView()
+ : mModel(NULL)
+ , mScrollView(NULL)
+{
+}
+
+ItemView::~ItemView()
+{
+ delete mModel;
+}
+
+void ItemView::setModel(ItemModel *model)
+{
+ delete mModel;
+ mModel = model;
+ update();
+}
+
+void ItemView::initialiseOverride()
+{
+ Base::initialiseOverride();
+
+ assignWidget(mScrollView, "ScrollView");
+ if (mScrollView == NULL)
+ throw std::runtime_error("Item view needs a scroll view");
+
+ mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top);
+}
+
+void ItemView::update()
+{
+ while (mScrollView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0));
+
+ if (!mModel)
+ return;
+
+ int x = 0;
+ int y = 0;
+ int maxHeight = mScrollView->getSize().height - 58;
+
+ mModel->update();
+
+ MyGUI::Widget* dragArea = mScrollView->createWidget<MyGUI::Widget>("",0,0,mScrollView->getWidth(),mScrollView->getHeight(),
+ MyGUI::Align::Stretch);
+ dragArea->setNeedMouseFocus(true);
+ dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground);
+ dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel);
+
+ for (ItemModel::ModelIndex i=0; i<static_cast<int>(mModel->getItemCount()); ++i)
+ {
+ const ItemStack& item = mModel->getItem(i);
+
+ /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item.mBase).getInventoryIcon(item.mBase);
+
+ // background widget (for the "equipped" frame and magic item background image)
+ bool isMagic = (item.mFlags & ItemStack::Flag_Enchanted);
+ MyGUI::ImageBox* backgroundWidget = dragArea->createWidget<MyGUI::ImageBox>("ImageBox",
+ MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default);
+ backgroundWidget->setUserString("ToolTipType", "ItemModelIndex");
+ backgroundWidget->setUserData(std::make_pair(i, mModel));
+
+ std::string backgroundTex = "textures\\menu_icon";
+ if (isMagic)
+ backgroundTex += "_magic";
+ if (item.mType == ItemStack::Type_Normal)
+ {
+ if (!isMagic)
+ backgroundTex = "";
+ }
+ else if (item.mType == ItemStack::Type_Equipped)
+ backgroundTex += "_equip";
+ else if (item.mType == ItemStack::Type_Barter)
+ backgroundTex += "_barter";
+
+ if (backgroundTex != "")
+ backgroundTex += ".dds";
+
+ backgroundWidget->setImageTexture(backgroundTex);
+ if ((item.mType == ItemStack::Type_Barter) && !isMagic)
+ backgroundWidget->setProperty("ImageCoord", "2 2 42 42");
+ else
+ backgroundWidget->setProperty("ImageCoord", "0 0 42 42");
+ backgroundWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem);
+ backgroundWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel);
+
+ // image
+ MyGUI::ImageBox* image = backgroundWidget->createWidget<MyGUI::ImageBox>("ImageBox",
+ MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
+ std::string::size_type pos = path.rfind(".");
+ if(pos != std::string::npos)
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture(path);
+ image->setNeedMouseFocus(false);
+
+ // text widget that shows item count
+ MyGUI::TextBox* text = image->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label"));
+ text->setTextAlign(MyGUI::Align::Right);
+ text->setNeedMouseFocus(false);
+ text->setTextShadow(true);
+ text->setTextShadowColour(MyGUI::Colour(0,0,0));
+ text->setCaption(getCountString(item.mCount));
+
+ y += 42;
+ if (y > maxHeight)
+ {
+ x += 42;
+ y = 0;
+ }
+
+ }
+ x += 42;
+ MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height);
+ mScrollView->setCanvasSize(size);
+ dragArea->setSize(size);
+}
+
+void ItemView::onSelectedItem(MyGUI::Widget *sender)
+{
+ ItemModel::ModelIndex index = (*sender->getUserData<std::pair<ItemModel::ModelIndex, ItemModel*> >()).first;
+ eventItemClicked(index);
+}
+
+void ItemView::onSelectedBackground(MyGUI::Widget *sender)
+{
+ eventBackgroundClicked();
+}
+
+void ItemView::onMouseWheel(MyGUI::Widget *_sender, int _rel)
+{
+ if (mScrollView->getViewOffset().left + _rel*0.3 > 0)
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mScrollView->setViewOffset(MyGUI::IntPoint(mScrollView->getViewOffset().left + _rel*0.3, 0));
+}
+
+void ItemView::setSize(const MyGUI::IntSize &_value)
+{
+ Base::setSize(_value);
+ update();
+}
+
+void ItemView::setSize(int _width, int _height)
+{
+ Base::setSize(_width, _height);
+ update();
+}
+
+void ItemView::setCoord(const MyGUI::IntCoord &_value)
+{
+ Base::setCoord(_value);
+ update();
+}
+
+void ItemView::setCoord(int _left, int _top, int _width, int _height)
+{
+ Base::setCoord(_left, _top, _width, _height);
+ update();
+}
+
+void ItemView::registerComponents()
+{
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::ItemView>("Widget");
+}
+
+}
diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp
new file mode 100644
index 0000000000..17f609f2b8
--- /dev/null
+++ b/apps/openmw/mwgui/itemview.hpp
@@ -0,0 +1,52 @@
+#ifndef MWGUI_ITEMVIEW_H
+#define MWGUI_ITEMVIEW_H
+
+#include <MyGUI_Widget.h>
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ class ItemView : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED(ItemView)
+ public:
+ ItemView();
+ virtual ~ItemView();
+
+ /// Register needed components with MyGUI's factory manager
+ static void registerComponents ();
+
+ /// Takes ownership of \a model
+ void setModel (ItemModel* model);
+
+ typedef MyGUI::delegates::CMultiDelegate1<ItemModel::ModelIndex> EventHandle_ModelIndex;
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+ /// Fired when an item was clicked
+ EventHandle_ModelIndex eventItemClicked;
+ /// Fired when the background was clicked (useful for drag and drop)
+ EventHandle_Void eventBackgroundClicked;
+
+ void update();
+
+ private:
+ virtual void initialiseOverride();
+
+ virtual void setSize(const MyGUI::IntSize& _value);
+ virtual void setCoord(const MyGUI::IntCoord& _value);
+ void setSize(int _width, int _height);
+ void setCoord(int _left, int _top, int _width, int _height);
+
+ void onSelectedItem (MyGUI::Widget* sender);
+ void onSelectedBackground (MyGUI::Widget* sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+ ItemModel* mModel;
+ MyGUI::ScrollView* mScrollView;
+
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp
new file mode 100644
index 0000000000..dbea10e775
--- /dev/null
+++ b/apps/openmw/mwgui/journalbooks.cpp
@@ -0,0 +1,326 @@
+#include "journalbooks.hpp"
+
+namespace
+{
+ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
+ {
+ typedef MWGui::BookTypesetter::Utf8Point point;
+
+ point begin = reinterpret_cast <point> (text);
+
+ return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
+ }
+
+ const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f);
+ const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f);
+ const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f);
+
+ struct AddContent
+ {
+ MWGui::BookTypesetter::Ptr mTypesetter;
+ MWGui::BookTypesetter::Style* mBodyStyle;
+
+ AddContent (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) :
+ mTypesetter (typesetter), mBodyStyle (body_style)
+ {
+ }
+ };
+
+ struct AddSpan : AddContent
+ {
+ AddSpan (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) :
+ AddContent (typesetter, body_style)
+ {
+ }
+
+ void operator () (intptr_t topicId, size_t begin, size_t end)
+ {
+ MWGui::BookTypesetter::Style* style = mBodyStyle;
+
+ if (topicId)
+ style = mTypesetter->createHotStyle (mBodyStyle, linkNormal, linkHot, linkActive, topicId);
+
+ mTypesetter->write (style, begin, end);
+ }
+ };
+
+ struct AddEntry
+ {
+ MWGui::BookTypesetter::Ptr mTypesetter;
+ MWGui::BookTypesetter::Style* mBodyStyle;
+
+ AddEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style) :
+ mTypesetter (typesetter), mBodyStyle (body_style)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::Entry const & entry)
+ {
+ mTypesetter->addContent (entry.body ());
+
+ entry.visitSpans (AddSpan (mTypesetter, mBodyStyle));
+ }
+ };
+
+ struct AddJournalEntry : AddEntry
+ {
+ bool mAddHeader;
+ MWGui::BookTypesetter::Style* mHeaderStyle;
+
+ AddJournalEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style,
+ MWGui::BookTypesetter::Style* header_style, bool add_header) :
+ AddEntry (typesetter, body_style),
+ mHeaderStyle (header_style),
+ mAddHeader (add_header)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::JournalEntry const & entry)
+ {
+ if (mAddHeader)
+ {
+ mTypesetter->write (mHeaderStyle, entry.timestamp ());
+ mTypesetter->lineBreak ();
+ }
+
+ AddEntry::operator () (entry);
+
+ mTypesetter->sectionBreak (10);
+ }
+ };
+
+ struct AddTopicEntry : AddEntry
+ {
+ intptr_t mContentId;
+ MWGui::BookTypesetter::Style* mHeaderStyle;
+
+ AddTopicEntry (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* body_style,
+ MWGui::BookTypesetter::Style* header_style, intptr_t contentId) :
+ AddEntry (typesetter, body_style), mHeaderStyle (header_style), mContentId (contentId)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::TopicEntry const & entry)
+ {
+ mTypesetter->write (mBodyStyle, entry.source ());
+ mTypesetter->write (mBodyStyle, 0, 3);// begin
+
+ AddEntry::operator() (entry);
+
+ mTypesetter->selectContent (mContentId);
+ mTypesetter->write (mBodyStyle, 2, 3);// end quote
+
+ mTypesetter->sectionBreak (10);
+ }
+ };
+
+ struct AddTopicName : AddContent
+ {
+ AddTopicName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) :
+ AddContent (typesetter, style)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::Utf8Span topicName)
+ {
+ mTypesetter->write (mBodyStyle, topicName);
+ mTypesetter->sectionBreak (10);
+ }
+ };
+
+ struct AddQuestName : AddContent
+ {
+ AddQuestName (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) :
+ AddContent (typesetter, style)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::Utf8Span topicName)
+ {
+ mTypesetter->write (mBodyStyle, topicName);
+ mTypesetter->sectionBreak (10);
+ }
+ };
+
+ struct AddTopicLink : AddContent
+ {
+ AddTopicLink (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) :
+ AddContent (typesetter, style)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::TopicId topicId, MWGui::JournalViewModel::Utf8Span name)
+ {
+ MWGui::BookTypesetter::Style* link = mTypesetter->createHotStyle (mBodyStyle, MyGUI::Colour::Black, linkHot, linkActive, topicId);
+
+ mTypesetter->write (link, name);
+ mTypesetter->lineBreak ();
+ }
+ };
+
+ struct AddQuestLink : AddContent
+ {
+ AddQuestLink (MWGui::BookTypesetter::Ptr typesetter, MWGui::BookTypesetter::Style* style) :
+ AddContent (typesetter, style)
+ {
+ }
+
+ void operator () (MWGui::JournalViewModel::QuestId id, MWGui::JournalViewModel::Utf8Span name)
+ {
+ MWGui::BookTypesetter::Style* style = mTypesetter->createHotStyle (mBodyStyle, MyGUI::Colour::Black, linkHot, linkActive, id);
+
+ mTypesetter->write (style, name);
+ mTypesetter->lineBreak ();
+ }
+ };
+}
+
+namespace MWGui
+{
+
+typedef TypesetBook::Ptr book;
+
+JournalBooks::JournalBooks (JournalViewModel::Ptr model) :
+ mModel (model)
+{
+}
+
+book JournalBooks::createEmptyJournalBook ()
+{
+ BookTypesetter::Ptr typesetter = createTypesetter ();
+
+ BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ typesetter->write (header, to_utf8_span ("You have no journal entries!"));
+ typesetter->lineBreak ();
+ typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest."));
+
+ BookTypesetter::Style* big = typesetter->createStyle ("", MyGUI::Colour::Black);
+ BookTypesetter::Style* test = typesetter->createStyle ("MonoFont", MyGUI::Colour::Blue);
+
+ typesetter->sectionBreak (20);
+ typesetter->write (body, to_utf8_span (
+ "The layout engine doesn't currently support aligning fonts to "
+ "their baseline within a single line so the following text looks "
+ "funny. In order to properly implement it, a stupidly simple "
+ "change is needed in MyGUI to report the where the baseline is for "
+ "a particular font"
+ ));
+
+ typesetter->sectionBreak (20);
+ typesetter->write (big, to_utf8_span ("big text g"));
+ typesetter->write (body, to_utf8_span (" проверÑем только в дебаге"));
+ typesetter->write (body, to_utf8_span (" normal g"));
+ typesetter->write (big, to_utf8_span (" done g"));
+
+ typesetter->sectionBreak (20);
+ typesetter->write (test, to_utf8_span (
+ "int main (int argc,\n"
+ " char ** argv)\n"
+ "{\n"
+ " print (\"hello world!\\n\");\n"
+ " return 0;\n"
+ "}\n"
+ ));
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createJournalBook ()
+{
+ BookTypesetter::Ptr typesetter = createTypesetter ();
+
+ BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ mModel->visitJournalEntries (0, AddJournalEntry (typesetter, body, header, true));
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createTopicBook (uintptr_t topicId)
+{
+ BookTypesetter::Ptr typesetter = createTypesetter ();
+
+ BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ mModel->visitTopicName (topicId, AddTopicName (typesetter, header));
+
+ intptr_t contentId = typesetter->addContent (to_utf8_span (", \""));
+
+ mModel->visitTopicEntries (topicId, AddTopicEntry (typesetter, body, header, contentId));
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createQuestBook (uintptr_t questId)
+{
+ BookTypesetter::Ptr typesetter = createTypesetter ();
+
+ BookTypesetter::Style* header = typesetter->createStyle ("", MyGUI::Colour (0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ mModel->visitQuestName (questId, AddQuestName (typesetter, header));
+
+ mModel->visitJournalEntries (questId, AddJournalEntry (typesetter, body, header, false));
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createTopicIndexBook ()
+{
+ BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250);
+
+ typesetter->setSectionAlignment (BookTypesetter::AlignCenter);
+
+ BookTypesetter::Style* body = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ for (int i = 0; i < 26; ++i)
+ {
+ char ch = 'A' + i;
+
+ char buffer [32];
+
+ sprintf (buffer, "( %c )", ch);
+
+ BookTypesetter::Style* style = typesetter->createHotStyle (body, MyGUI::Colour::Black, linkHot, linkActive, ch);
+
+ if (i == 13)
+ typesetter->sectionBreak ();
+
+ typesetter->write (style, to_utf8_span (buffer));
+ typesetter->lineBreak ();
+ }
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createTopicIndexBook (char character)
+{
+ BookTypesetter::Ptr typesetter = BookTypesetter::create (0x7FFFFFFF, 0x7FFFFFFF);
+ BookTypesetter::Style* style = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ mModel->visitTopicNamesStartingWith (character, AddTopicLink (typesetter, style));
+
+ return typesetter->complete ();
+}
+
+book JournalBooks::createQuestIndexBook (bool activeOnly)
+{
+ BookTypesetter::Ptr typesetter = BookTypesetter::create (0x7FFFFFFF, 0x7FFFFFFF);
+ BookTypesetter::Style* base = typesetter->createStyle ("", MyGUI::Colour::Black);
+
+ mModel->visitQuestNames (activeOnly, AddQuestLink (typesetter, base));
+
+ return typesetter->complete ();
+}
+
+BookTypesetter::Ptr JournalBooks::createTypesetter ()
+{
+ //TODO: determine page size from layout...
+ return BookTypesetter::create (240, 300);
+}
+
+}
diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp
new file mode 100644
index 0000000000..09d3cf1a85
--- /dev/null
+++ b/apps/openmw/mwgui/journalbooks.hpp
@@ -0,0 +1,29 @@
+#ifndef MWGUI_JOURNALBOOKS_HPP
+#define MWGUI_JOURNALBOOKS_HPP
+
+#include "bookpage.hpp"
+#include "journalviewmodel.hpp"
+
+namespace MWGui
+{
+ struct JournalBooks
+ {
+ typedef TypesetBook::Ptr Book;
+ JournalViewModel::Ptr mModel;
+
+ JournalBooks (JournalViewModel::Ptr model);
+
+ Book createEmptyJournalBook ();
+ Book createJournalBook ();
+ Book createTopicBook (uintptr_t topicId);
+ Book createQuestBook (uintptr_t questId);
+ Book createTopicIndexBook ();
+ Book createTopicIndexBook (char character);
+ Book createQuestIndexBook (bool showAll);
+
+ private:
+ BookTypesetter::Ptr createTypesetter ();
+ };
+}
+
+#endif // MWGUI_JOURNALBOOKS_HPP
diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp
new file mode 100644
index 0000000000..79a77070ae
--- /dev/null
+++ b/apps/openmw/mwgui/journalviewmodel.cpp
@@ -0,0 +1,389 @@
+#include "journalviewmodel.hpp"
+
+#include <map>
+#include <sstream>
+#include <boost/make_shared.hpp>
+
+#include <MyGUI_LanguageManager.h>
+
+#include <components/misc/utf8stream.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/journal.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwdialogue/journalentry.hpp"
+
+#include "keywordsearch.hpp"
+
+namespace MWGui {
+
+struct JournalViewModelImpl;
+
+static void injectMonthName (std::ostream & os, int month);
+
+struct JournalViewModelImpl : JournalViewModel
+{
+ typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
+
+ mutable bool mKeywordSearchLoaded;
+ mutable KeywordSearchT mKeywordSearch;
+
+ std::locale mLocale;
+
+ JournalViewModelImpl ()
+ {
+ mKeywordSearchLoaded = false;
+ }
+
+ virtual ~JournalViewModelImpl ()
+ {
+ }
+
+ /// \todo replace this nasty BS
+ static Utf8Span toUtf8Span (std::string const & str)
+ {
+ if (str.size () == 0)
+ return Utf8Span (Utf8Point (NULL), Utf8Point (NULL));
+
+ Utf8Point point = reinterpret_cast <Utf8Point> (str.c_str ());
+
+ return Utf8Span (point, point + str.size ());
+ }
+
+ void load ()
+ {
+ }
+
+ void unload ()
+ {
+ mKeywordSearch.clear ();
+ mKeywordSearchLoaded = false;
+ }
+
+ void ensureKeyWordSearchLoaded () const
+ {
+ if (!mKeywordSearchLoaded)
+ {
+ MWBase::Journal * journal = MWBase::Environment::get().getJournal();
+
+ for(MWBase::Journal::TTopicIter i = journal->topicBegin(); i != journal->topicEnd (); ++i)
+ mKeywordSearch.seed (i->first, intptr_t (&i->second));
+
+ mKeywordSearchLoaded = true;
+ }
+ }
+
+ wchar_t tolower (wchar_t ch) const { return std::tolower (ch, mLocale); }
+
+ bool isEmpty () const
+ {
+ MWBase::Journal * journal = MWBase::Environment::get().getJournal();
+
+ return journal->begin () == journal->end ();
+ }
+
+ template <typename t_iterator, typename Interface>
+ struct BaseEntry : Interface
+ {
+ typedef t_iterator iterator_t;
+
+ iterator_t itr;
+ JournalViewModelImpl const * mModel;
+
+ BaseEntry (JournalViewModelImpl const * model, iterator_t itr) :
+ mModel (model), itr (itr), loaded (false)
+ {}
+
+ virtual ~BaseEntry () {}
+
+ mutable bool loaded;
+ mutable std::string utf8text;
+
+ typedef std::pair<size_t, size_t> Range;
+
+ // hyperlinks in @link# notation
+ mutable std::map<Range, intptr_t> mHyperLinks;
+
+ virtual std::string getText () const = 0;
+
+ void ensureLoaded () const
+ {
+ if (!loaded)
+ {
+ mModel->ensureKeyWordSearchLoaded ();
+
+ utf8text = getText ();
+
+ size_t pos_begin, pos_end;
+ for(;;)
+ {
+ pos_begin = utf8text.find('@');
+ if (pos_begin != std::string::npos)
+ pos_end = utf8text.find('#', pos_begin);
+
+ if (pos_begin != std::string::npos && pos_end != std::string::npos)
+ {
+ std::string link = utf8text.substr(pos_begin + 1, pos_end - pos_begin - 1);
+ const char specialPseudoAsteriskCharacter = 127;
+ std::replace(link.begin(), link.end(), specialPseudoAsteriskCharacter, '*');
+ std::string topicName = MWBase::Environment::get().getWindowManager()->
+ getTranslationDataStorage().topicStandardForm(link);
+
+ std::string displayName = link;
+ while (displayName[displayName.size()-1] == '*')
+ displayName.erase(displayName.size()-1, 1);
+
+ utf8text.replace(pos_begin, pos_end+1-pos_begin, displayName);
+
+ intptr_t value;
+ if (mModel->mKeywordSearch.containsKeyword(topicName, value))
+ mHyperLinks[std::make_pair(pos_begin, pos_begin+displayName.size())] = value;
+ }
+ else
+ break;
+ }
+
+ loaded = true;
+ }
+ }
+
+ Utf8Span body () const
+ {
+ ensureLoaded ();
+
+ return toUtf8Span (utf8text);
+ }
+
+ void visitSpans (boost::function < void (TopicId, size_t, size_t)> visitor) const
+ {
+ ensureLoaded ();
+ mModel->ensureKeyWordSearchLoaded ();
+
+ if (mHyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation())
+ {
+ size_t formatted = 0; // points to the first character that is not laid out yet
+ for (std::map<Range, intptr_t>::const_iterator it = mHyperLinks.begin(); it != mHyperLinks.end(); ++it)
+ {
+ intptr_t topicId = it->second;
+ if (formatted < it->first.first)
+ visitor (0, formatted, it->first.first);
+ visitor (topicId, it->first.first, it->first.second);
+ formatted = it->first.second;
+ }
+ if (formatted < utf8text.size())
+ visitor (0, formatted, utf8text.size());
+ }
+ else
+ {
+ std::string::const_iterator i = utf8text.begin ();
+
+ KeywordSearchT::Match match;
+
+ while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match))
+ {
+ if (i != match.mBeg)
+ visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ());
+
+ visitor (match.mValue, match.mBeg - utf8text.begin (), match.mEnd - utf8text.begin ());
+
+ i = match.mEnd;
+ }
+
+ if (i != utf8text.end ())
+ visitor (0, i - utf8text.begin (), utf8text.size ());
+ }
+ }
+
+ };
+
+ void visitQuestNames (bool active_only, boost::function <void (QuestId, Utf8Span)> visitor) const
+ {
+ MWBase::Journal * journal = MWBase::Environment::get ().getJournal ();
+
+ for (MWBase::Journal::TQuestIter i = journal->questBegin (); i != journal->questEnd (); ++i)
+ {
+ if (active_only && i->second.isFinished ())
+ continue;
+
+ /// \todo quest.getName() is broken? returns empty string
+ //const MWDialogue::Quest& quest = i->second;
+
+ visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
+ }
+ }
+
+ void visitQuestName (QuestId questId, boost::function <void (Utf8Span)> visitor) const
+ {
+ MWDialogue::Quest const * quest = reinterpret_cast <MWDialogue::Quest const *> (questId);
+
+ std::string name = quest->getName ();
+
+ visitor (toUtf8Span (name));
+ }
+
+ template <typename iterator_t>
+ struct JournalEntryImpl : BaseEntry <iterator_t, JournalEntry>
+ {
+ using BaseEntry <iterator_t, JournalEntry>::itr;
+
+ mutable std::string timestamp_buffer;
+
+ JournalEntryImpl (JournalViewModelImpl const * model, iterator_t itr) :
+ BaseEntry <iterator_t, JournalEntry> (model, itr)
+ {}
+
+ std::string getText () const
+ {
+ return itr->getText(MWBase::Environment::get().getWorld()->getStore());
+ }
+
+ Utf8Span timestamp () const
+ {
+ if (timestamp_buffer.empty ())
+ {
+ std::ostringstream os;
+
+ os << itr->mDayOfMonth << ' ';
+
+ injectMonthName (os, itr->mMonth);
+
+ const std::string& dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}");
+ os << " (" << dayStr << " " << (itr->mDay + 1) << ')';
+
+ timestamp_buffer = os.str ();
+ }
+
+ return toUtf8Span (timestamp_buffer);
+ }
+ };
+
+ void visitJournalEntries (QuestId questId, boost::function <void (JournalEntry const &)> visitor) const
+ {
+ MWBase::Journal * journal = MWBase::Environment::get().getJournal();
+
+ if (questId != 0)
+ {
+ MWDialogue::Quest const * quest = reinterpret_cast <MWDialogue::Quest const *> (questId);
+
+ for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i)
+ {
+ for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j)
+ {
+ if (i->mInfoId == *j)
+ visitor (JournalEntryImpl <MWBase::Journal::TEntryIter> (this, i));
+ }
+ }
+ }
+ else
+ {
+ for(MWBase::Journal::TEntryIter i = journal->begin(); i != journal->end (); ++i)
+ visitor (JournalEntryImpl <MWBase::Journal::TEntryIter> (this, i));
+ }
+ }
+
+ void visitTopics (boost::function <void (TopicId, Utf8Span)> visitor) const
+ {
+ throw std::runtime_error ("not implemented");
+ }
+
+ void visitTopicName (TopicId topicId, boost::function <void (Utf8Span)> visitor) const
+ {
+ MWDialogue::Topic const & topic = * reinterpret_cast <MWDialogue::Topic const *> (topicId);
+ // This is to get the correct case for the topic
+ const std::string& name = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find(topic.getName())->mId;
+ visitor (toUtf8Span (name));
+ }
+
+ void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const
+ {
+ MWBase::Journal * journal = MWBase::Environment::get().getJournal();
+
+ for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
+ {
+ if (i->first [0] != std::tolower (character, mLocale))
+ continue;
+
+ // This is to get the correct case for the topic
+ const std::string& name = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find(i->first)->mId;
+
+ visitor (TopicId (&i->second), toUtf8Span (name));
+ }
+
+ }
+
+ struct TopicEntryImpl : BaseEntry <MWDialogue::Topic::TEntryIter, TopicEntry>
+ {
+ MWDialogue::Topic const & mTopic;
+
+ mutable std::string source_buffer;
+
+ TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) :
+ BaseEntry (model, itr), mTopic (topic)
+ {}
+
+ std::string getText () const
+ {
+ /// \todo defines are not replaced (%PCName etc). should probably be done elsewhere though since we need the actor
+ return mTopic.getEntry (*itr).getText(MWBase::Environment::get().getWorld()->getStore());
+
+ }
+
+ Utf8Span source () const
+ {
+ if (source_buffer.empty ())
+ source_buffer = "someone";
+ return toUtf8Span (source_buffer);
+ }
+
+ };
+
+ void visitTopicEntries (TopicId topicId, boost::function <void (TopicEntry const &)> visitor) const
+ {
+ typedef MWDialogue::Topic::TEntryIter iterator_t;
+
+ MWDialogue::Topic const & topic = * reinterpret_cast <MWDialogue::Topic const *> (topicId);
+
+ for (iterator_t i = topic.begin (); i != topic.end (); ++i)
+ visitor (TopicEntryImpl (this, topic, i));
+ }
+};
+
+static void injectMonthName (std::ostream & os, int month)
+{
+ MyGUI::LanguageManager& lm = MyGUI::LanguageManager::getInstance();
+
+ if (month == 0)
+ os << lm.replaceTags ("#{sMonthMorningstar}");
+ else if (month == 1)
+ os << lm.replaceTags ("#{sMonthSunsdawn}");
+ else if (month == 2)
+ os << lm.replaceTags ("#{sMonthFirstseed}");
+ else if (month == 3)
+ os << lm.replaceTags ("#{sMonthRainshand}");
+ else if (month == 4)
+ os << lm.replaceTags ("#{sMonthSecondseed}");
+ else if (month == 5)
+ os << lm.replaceTags ("#{sMonthMidyear}");
+ else if (month == 6)
+ os << lm.replaceTags ("#{sMonthSunsheight}");
+ else if (month == 7)
+ os << lm.replaceTags ("#{sMonthLastseed}");
+ else if (month == 8)
+ os << lm.replaceTags ("#{sMonthHeartfire}");
+ else if (month == 9)
+ os << lm.replaceTags ("#{sMonthFrostfall}");
+ else if (month == 10)
+ os << lm.replaceTags ("#{sMonthSunsdusk}");
+ else if (month == 11)
+ os << lm.replaceTags ("#{sMonthEveningstar}");
+ else
+ os << month;
+}
+
+JournalViewModel::Ptr JournalViewModel::create ()
+{
+ return boost::make_shared <JournalViewModelImpl> ();
+}
+
+}
diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp
new file mode 100644
index 0000000000..21c3a11785
--- /dev/null
+++ b/apps/openmw/mwgui/journalviewmodel.hpp
@@ -0,0 +1,93 @@
+#ifndef MWGUI_JOURNALVIEWMODEL_HPP
+#define MWGUI_JOURNALVIEWMODEL_HPP
+
+#include <string>
+#include <memory>
+#include <functional>
+#include <platform/stdint.h>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace MWGui
+{
+ /// View-Model for the journal GUI
+ ///
+ /// This interface defines an abstract data model suited
+ /// specifically to the needs of the journal GUI. It isolates
+ /// the journal GUI from the implementation details of the
+ /// game data store.
+ struct JournalViewModel
+ {
+ typedef boost::shared_ptr <JournalViewModel> Ptr;
+
+ typedef intptr_t QuestId;
+ typedef intptr_t TopicId;
+ typedef uint8_t const * Utf8Point;
+ typedef std::pair <Utf8Point, Utf8Point> Utf8Span;
+
+ /// The base interface for both journal entries and topics.
+ struct Entry
+ {
+ /// returns the body text for the journal entry
+ ///
+ /// This function returns a borrowed reference to the body of the
+ /// journal entry. The returned reference becomes invalid when the
+ /// entry is destroyed.
+ virtual Utf8Span body () const = 0;
+
+ /// Visits each subset of text in the body, delivering the beginning
+ /// and end of the span relative to the body, and a valid topic ID if
+ /// the span represents a keyword, or zero if not.
+ virtual void visitSpans (boost::function <void (TopicId, size_t, size_t)> visitor) const = 0;
+ };
+
+ /// An interface to topic data.
+ struct TopicEntry : Entry
+ {
+ /// Returns a pre-formatted span of UTF8 encoded text representing
+ /// the name of the NPC this portion of dialog was heard from.
+ virtual Utf8Span source () const = 0;
+ };
+
+ /// An interface to journal data.
+ struct JournalEntry : Entry
+ {
+ /// Returns a pre-formatted span of UTF8 encoded text representing
+ /// the in-game date this entry was added to the journal.
+ virtual Utf8Span timestamp () const = 0;
+ };
+
+
+ /// called prior to journal opening
+ virtual void load () = 0;
+
+ /// called prior to journal closing
+ virtual void unload () = 0;
+
+ /// returns true if their are no journal entries to display
+ virtual bool isEmpty () const = 0;
+
+ /// provides access to the name of the quest with the specified identifier
+ virtual void visitQuestName (TopicId topicId, boost::function <void (Utf8Span)> visitor) const = 0;
+
+ /// walks the active and optionally completed, quests providing the quest id and name
+ virtual void visitQuestNames (bool active_only, boost::function <void (QuestId, Utf8Span)> visitor) const = 0;
+
+ /// walks over the journal entries related to the specified quest identified by its id
+ virtual void visitJournalEntries (QuestId questId, boost::function <void (JournalEntry const &)> visitor) const = 0;
+
+ /// provides the name of the topic specified by its id
+ virtual void visitTopicName (TopicId topicId, boost::function <void (Utf8Span)> visitor) const = 0;
+
+ /// walks over the topics whose names start with the specified character providing the topics id and name
+ virtual void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const = 0;
+
+ /// walks over the topic entries for the topic specified by its identifier
+ virtual void visitTopicEntries (TopicId topicId, boost::function <void (TopicEntry const &)> visitor) const = 0;
+
+ // create an instance of the default journal view model implementation
+ static Ptr create ();
+ };
+}
+
+#endif // MWGUI_JOURNALVIEWMODEL_HPP
diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp
new file mode 100644
index 0000000000..ab8dc1584a
--- /dev/null
+++ b/apps/openmw/mwgui/journalwindow.cpp
@@ -0,0 +1,474 @@
+#include "journalwindow.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "list.hpp"
+
+#include <sstream>
+#include <set>
+#include <stack>
+#include <string>
+#include <utility>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include "boost/lexical_cast.hpp"
+
+#include "bookpage.hpp"
+#include "windowbase.hpp"
+#include "imagebutton.hpp"
+#include "journalviewmodel.hpp"
+#include "journalbooks.hpp"
+
+namespace
+{
+ static char const OptionsOverlay [] = "OptionsOverlay";
+ static char const OptionsBTN [] = "OptionsBTN";
+ static char const PrevPageBTN [] = "PrevPageBTN";
+ static char const NextPageBTN [] = "NextPageBTN";
+ static char const CloseBTN [] = "CloseBTN";
+ static char const JournalBTN [] = "JournalBTN";
+ static char const TopicsBTN [] = "TopicsBTN";
+ static char const QuestsBTN [] = "QuestsBTN";
+ static char const CancelBTN [] = "CancelBTN";
+ static char const ShowAllBTN [] = "ShowAllBTN";
+ static char const ShowActiveBTN [] = "ShowActiveBTN";
+ static char const PageOneNum [] = "PageOneNum";
+ static char const PageTwoNum [] = "PageTwoNum";
+ static char const TopicsList [] = "TopicsList";
+ static char const TopicsPage [] = "TopicsPage";
+ static char const QuestsList [] = "QuestsList";
+ static char const QuestsPage [] = "QuestsPage";
+ static char const LeftBookPage [] = "LeftBookPage";
+ static char const RightBookPage [] = "RightBookPage";
+ static char const LeftTopicIndex [] = "LeftTopicIndex";
+ static char const RightTopicIndex [] = "RightTopicIndex";
+
+ struct JournalWindowImpl : MWGui::WindowBase, MWGui::JournalBooks, MWGui::JournalWindow
+ {
+ struct DisplayState
+ {
+ unsigned int mPage;
+ Book mBook;
+ };
+
+ typedef std::stack <DisplayState> DisplayStateStack;
+
+ DisplayStateStack mStates;
+ Book mTopicIndexBook;
+ bool mQuestMode;
+ bool mAllQuests;
+
+ template <typename T>
+ T * getWidget (char const * name)
+ {
+ T * widget;
+ WindowBase::getWidget (widget, name);
+ return widget;
+ }
+
+ template <typename value_type>
+ void setText (char const * name, value_type const & value)
+ {
+ getWidget <MyGUI::TextBox> (name) ->
+ setCaption (boost::lexical_cast <std::string> (value));
+ }
+
+ void setVisible (char const * name, bool visible)
+ {
+ getWidget <MyGUI::Widget> (name) ->
+ setVisible (visible);
+ }
+
+ void adviseButtonClick (char const * name, void (JournalWindowImpl::*Handler) (MyGUI::Widget* _sender))
+ {
+ getWidget <MWGui::ImageButton> (name) ->
+ eventMouseButtonClick += newDelegate(this, Handler);
+ }
+
+ MWGui::BookPage* getPage (char const * name)
+ {
+ return getWidget <MWGui::BookPage> (name);
+ }
+
+ JournalWindowImpl (MWGui::JournalViewModel::Ptr Model)
+ : WindowBase("openmw_journal.layout"), JournalBooks (Model)
+ {
+ mMainWidget->setVisible(false);
+ center();
+
+ adviseButtonClick (OptionsBTN, &JournalWindowImpl::notifyOptions );
+ adviseButtonClick (PrevPageBTN, &JournalWindowImpl::notifyPrevPage );
+ adviseButtonClick (NextPageBTN, &JournalWindowImpl::notifyNextPage );
+ adviseButtonClick (CloseBTN, &JournalWindowImpl::notifyClose );
+ adviseButtonClick (JournalBTN, &JournalWindowImpl::notifyJournal );
+
+ adviseButtonClick (TopicsBTN, &JournalWindowImpl::notifyTopics );
+ adviseButtonClick (QuestsBTN, &JournalWindowImpl::notifyQuests );
+ adviseButtonClick (CancelBTN, &JournalWindowImpl::notifyCancel );
+
+ adviseButtonClick (ShowAllBTN, &JournalWindowImpl::notifyShowAll );
+ adviseButtonClick (ShowActiveBTN, &JournalWindowImpl::notifyShowActive);
+
+ {
+ MWGui::BookPage::ClickCallback callback;
+
+ callback = boost::bind (&JournalWindowImpl::notifyTopicClicked, this, _1);
+
+ getPage (TopicsPage)->adviseLinkClicked (callback);
+ getPage (LeftBookPage)->adviseLinkClicked (callback);
+ getPage (RightBookPage)->adviseLinkClicked (callback);
+ }
+
+ {
+ MWGui::BookPage::ClickCallback callback;
+
+ callback = boost::bind (&JournalWindowImpl::notifyIndexLinkClicked, this, _1);
+
+ getPage (LeftTopicIndex)->adviseLinkClicked (callback);
+ getPage (RightTopicIndex)->adviseLinkClicked (callback);
+ }
+
+ {
+ MWGui::BookPage::ClickCallback callback;
+
+ callback = boost::bind (&JournalWindowImpl::notifyQuestClicked, this, _1);
+
+ getPage (QuestsPage)->adviseLinkClicked (callback);
+ }
+
+ adjustButton(OptionsBTN);
+ adjustButton(PrevPageBTN);
+ adjustButton(NextPageBTN);
+ adjustButton(CloseBTN);
+ adjustButton(CancelBTN);
+ adjustButton(ShowAllBTN);
+ adjustButton(ShowActiveBTN);
+ adjustButton(JournalBTN);
+
+ MWGui::ImageButton* nextButton = getWidget<MWGui::ImageButton>(NextPageBTN);
+ if (nextButton->getSize().width == 64)
+ {
+ // english button has a 7 pixel wide strip of garbage on its right edge
+ nextButton->setSize(64-7, nextButton->getSize().height);
+ nextButton->setImageCoord(MyGUI::IntCoord(0,0,64-7,nextButton->getSize().height));
+ }
+
+ adjustButton(TopicsBTN);
+ adjustButton(QuestsBTN);
+ 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;
+
+ getWidget<MyGUI::Widget>(TopicsBTN)->setPosition((pageWidth - width)/2, getWidget<MyGUI::Widget>(TopicsBTN)->getPosition().top);
+ getWidget<MyGUI::Widget>(QuestsBTN)->setPosition((pageWidth - width)/2 + topicsWidth, getWidget<MyGUI::Widget>(QuestsBTN)->getPosition().top);
+
+ mQuestMode = false;
+ mAllQuests = false;
+ }
+
+ void adjustButton (char const * name)
+ {
+ MWGui::ImageButton* button = getWidget<MWGui::ImageButton>(name);
+
+ MyGUI::IntSize diff = button->getSize() - button->getRequestedSize();
+ button->setSize(button->getRequestedSize());
+
+ if (button->getAlign().isRight())
+ button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0));
+ }
+
+ void open()
+ {
+ if (!MWBase::Environment::get().getWindowManager ()->getJournalAllowed ())
+ {
+ MWBase::Environment::get().getWindowManager()->popGuiMode ();
+ }
+ mModel->load ();
+
+ setBookMode ();
+
+ /// \todo Wiping the whole book layout each time the journal is opened is probably too costly for a large journal (eg 300+ pages).
+ /// There should be a way to keep the existing layout and append new entries to the end of it.
+ /// However, that still leaves the problem of having to add links to previously unknown, but now known topics, so
+ /// we maybe need to find another way to speed things up.
+ Book journalBook;
+ if (mModel->isEmpty ())
+ journalBook = createEmptyJournalBook ();
+ else
+ journalBook = createJournalBook ();
+
+ pushBook (journalBook, 0);
+
+ // fast forward to the last page
+ if (!mStates.empty ())
+ {
+ unsigned int & page = mStates.top ().mPage;
+ page = mStates.top().mBook->pageCount()-1;
+ if (page%2)
+ --page;
+ }
+ updateShowingPages();
+ }
+
+ void close()
+ {
+ mModel->unload ();
+
+ getPage (LeftBookPage)->showPage (Book (), 0);
+ getPage (RightBookPage)->showPage (Book (), 0);
+
+ while (!mStates.empty ())
+ mStates.pop ();
+
+ mTopicIndexBook.reset ();
+ }
+
+ void setVisible (bool newValue)
+ {
+ WindowBase::setVisible (newValue);
+ }
+
+ void setBookMode ()
+ {
+ setVisible (OptionsBTN, true);
+ setVisible (OptionsOverlay, false);
+
+ updateShowingPages ();
+ updateCloseJournalButton ();
+ }
+
+ void setOptionsMode ()
+ {
+ setVisible (OptionsBTN, false);
+ setVisible (OptionsOverlay, true);
+
+ setVisible (PrevPageBTN, false);
+ setVisible (NextPageBTN, false);
+ setVisible (CloseBTN, false);
+ setVisible (JournalBTN, false);
+
+ setVisible (TopicsList, false);
+ setVisible (QuestsList, mQuestMode);
+ setVisible (LeftTopicIndex, !mQuestMode);
+ setVisible (RightTopicIndex, !mQuestMode);
+ setVisible (ShowAllBTN, mQuestMode && !mAllQuests);
+ setVisible (ShowActiveBTN, mQuestMode && mAllQuests);
+
+ //TODO: figure out how to make "options" page overlay book page
+ // correctly, so that text may show underneath
+ getPage (RightBookPage)->showPage (Book (), 0);
+ }
+
+ void pushBook (Book book, unsigned int page)
+ {
+ DisplayState bs;
+ bs.mPage = page;
+ bs.mBook = book;
+ mStates.push (bs);
+ updateShowingPages ();
+ updateCloseJournalButton ();
+ }
+
+ void replaceBook (Book book, unsigned int page)
+ {
+ assert (!mStates.empty ());
+ mStates.top ().mBook = book;
+ mStates.top ().mPage = page;
+ updateShowingPages ();
+ }
+
+ void popBook ()
+ {
+ mStates.pop ();
+ updateShowingPages ();
+ updateCloseJournalButton ();
+ }
+
+ void updateCloseJournalButton ()
+ {
+ setVisible (CloseBTN, mStates.size () < 2);
+ setVisible (JournalBTN, mStates.size () >= 2);
+ }
+
+ void updateShowingPages ()
+ {
+ Book book;
+ unsigned int page;
+ unsigned int relPages;
+
+ if (!mStates.empty ())
+ {
+ book = mStates.top ().mBook;
+ page = mStates.top ().mPage;
+ relPages = book->pageCount () - page;
+ }
+ else
+ {
+ page = 0;
+ relPages = 0;
+ }
+
+ setVisible (PrevPageBTN, page > 0);
+ setVisible (NextPageBTN, relPages > 2);
+
+ setVisible (PageOneNum, relPages > 0);
+ setVisible (PageTwoNum, relPages > 1);
+
+ getPage (LeftBookPage)->showPage ((relPages > 0) ? book : Book (), page+0);
+ getPage (RightBookPage)->showPage ((relPages > 0) ? book : Book (), page+1);
+
+ setText (PageOneNum, page + 1);
+ setText (PageTwoNum, page + 2);
+ }
+
+ void notifyTopicClicked (intptr_t linkId)
+ {
+ Book topicBook = createTopicBook (linkId);
+
+ if (mStates.size () > 1)
+ replaceBook (topicBook, 0);
+ else
+ pushBook (topicBook, 0);
+
+ setVisible (OptionsOverlay, false);
+ setVisible (OptionsBTN, true);
+ setVisible (JournalBTN, true);
+ }
+
+ void notifyQuestClicked (intptr_t questId)
+ {
+ Book book = createQuestBook (questId);
+
+ if (mStates.size () > 1)
+ replaceBook (book, 0);
+ else
+ pushBook (book, 0);
+
+ setVisible (OptionsOverlay, false);
+ setVisible (OptionsBTN, true);
+ setVisible (JournalBTN, true);
+ }
+
+ void notifyOptions(MyGUI::Widget* _sender)
+ {
+ setOptionsMode ();
+
+ if (!mTopicIndexBook)
+ mTopicIndexBook = createTopicIndexBook ();
+
+ getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0);
+ getPage (RightTopicIndex)->showPage (mTopicIndexBook, 1);
+ }
+
+ void notifyJournal(MyGUI::Widget* _sender)
+ {
+ assert (mStates.size () > 1);
+ popBook ();
+ }
+
+ void showList (char const * listId, char const * pageId, Book book)
+ {
+ std::pair <int, int> size = book->getSize ();
+
+ getPage (pageId)->showPage (book, 0);
+
+ getWidget <MyGUI::ScrollView> (listId)->setCanvasSize (size.first, size.second);
+ }
+
+ void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character)
+ {
+ setVisible (LeftTopicIndex, false);
+ setVisible (RightTopicIndex, false);
+ setVisible (TopicsList, true);
+
+ showList (TopicsList, TopicsPage, createTopicIndexBook ((char)character));
+ }
+
+ void notifyTopics(MyGUI::Widget* _sender)
+ {
+ mQuestMode = false;
+ setVisible (LeftTopicIndex, true);
+ setVisible (RightTopicIndex, true);
+ setVisible (TopicsList, false);
+ setVisible (QuestsList, false);
+ setVisible (ShowAllBTN, false);
+ setVisible (ShowActiveBTN, false);
+ }
+
+ void notifyQuests(MyGUI::Widget* _sender)
+ {
+ mQuestMode = true;
+ setVisible (LeftTopicIndex, false);
+ setVisible (RightTopicIndex, false);
+ setVisible (TopicsList, false);
+ setVisible (QuestsList, true);
+ setVisible (ShowAllBTN, !mAllQuests);
+ setVisible (ShowActiveBTN, mAllQuests);
+
+ showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests));
+ }
+
+ void notifyShowAll(MyGUI::Widget* _sender)
+ {
+ mAllQuests = true;
+ setVisible (ShowAllBTN, !mAllQuests);
+ setVisible (ShowActiveBTN, mAllQuests);
+ showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests));
+ }
+
+ void notifyShowActive(MyGUI::Widget* _sender)
+ {
+ mAllQuests = false;
+ setVisible (ShowAllBTN, !mAllQuests);
+ setVisible (ShowActiveBTN, mAllQuests);
+ showList (QuestsList, QuestsPage, createQuestIndexBook (!mAllQuests));
+ }
+
+ void notifyCancel(MyGUI::Widget* _sender)
+ {
+ setBookMode ();
+ }
+
+ void notifyClose(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0);
+ MWBase::Environment::get().getWindowManager ()->popGuiMode ();
+ }
+
+ void notifyNextPage(MyGUI::Widget* _sender)
+ {
+ if (!mStates.empty ())
+ {
+ unsigned int & page = mStates.top ().mPage;
+ Book book = mStates.top ().mBook;
+
+ if (page < book->pageCount () - 2)
+ {
+ page += 2;
+ updateShowingPages ();
+ }
+ }
+ }
+
+ void notifyPrevPage(MyGUI::Widget* _sender)
+ {
+ if (!mStates.empty ())
+ {
+ unsigned int & page = mStates.top ().mPage;
+
+ if(page > 0)
+ {
+ page -= 2;
+ updateShowingPages ();
+ }
+ }
+ }
+ };
+}
+
+// glue the implementation to the interface
+MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model)
+{
+ return new JournalWindowImpl (Model);
+}
diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp
new file mode 100644
index 0000000000..63770ec1aa
--- /dev/null
+++ b/apps/openmw/mwgui/journalwindow.hpp
@@ -0,0 +1,26 @@
+#ifndef MWGUI_JOURNAL_H
+#define MWGUI_JOURNAL_H
+
+#include <memory>
+#include <boost/shared_ptr.hpp>
+
+namespace MWBase { class WindowManager; }
+
+namespace MWGui
+{
+ struct JournalViewModel;
+
+ struct JournalWindow
+ {
+ /// construct a new instance of the one JournalWindow implementation
+ static JournalWindow * create (boost::shared_ptr <JournalViewModel> Model);
+
+ /// destroy this instance of the JournalWindow implementation
+ virtual ~JournalWindow () {};
+
+ /// show/hide the journal window
+ virtual void setVisible (bool newValue) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/keywordsearch.cpp b/apps/openmw/mwgui/keywordsearch.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/apps/openmw/mwgui/keywordsearch.cpp
diff --git a/apps/openmw/mwgui/keywordsearch.hpp b/apps/openmw/mwgui/keywordsearch.hpp
new file mode 100644
index 0000000000..a9fb6daaba
--- /dev/null
+++ b/apps/openmw/mwgui/keywordsearch.hpp
@@ -0,0 +1,199 @@
+#ifndef MWGUI_KEYWORDSEARCH_H
+#define MWGUI_KEYWORDSEARCH_H
+
+#include <map>
+#include <locale>
+#include <stdexcept>
+#include <vector>
+#include <algorithm> // std::reverse
+
+#include <components/misc/stringops.hpp>
+
+namespace MWGui
+{
+
+template <typename string_t, typename value_t>
+class KeywordSearch
+{
+public:
+
+ typedef typename string_t::const_iterator Point;
+
+ struct Match
+ {
+ Point mBeg;
+ Point mEnd;
+ value_t mValue;
+ };
+
+ void seed (string_t keyword, value_t value)
+ {
+ seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot);
+ }
+
+ void clear ()
+ {
+ mRoot.mChildren.clear ();
+ mRoot.mKeyword.clear ();
+ }
+
+ bool containsKeyword (string_t keyword, value_t& value)
+ {
+ typename Entry::childen_t::iterator current;
+ typename Entry::childen_t::iterator next;
+
+ current = mRoot.mChildren.find (std::tolower (*keyword.begin(), mLocale));
+ if (current == mRoot.mChildren.end())
+ return false;
+ else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword))
+ {
+ value = current->second.mValue;
+ return true;
+ }
+
+ for (Point i = ++keyword.begin(); i != keyword.end(); ++i)
+ {
+ next = current->second.mChildren.find(std::tolower (*i, mLocale));
+ if (next == current->second.mChildren.end())
+ return false;
+ if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword))
+ {
+ value = next->second.mValue;
+ return true;
+ }
+ current = next;
+ }
+ return false;
+ }
+
+ bool search (Point beg, Point end, Match & match)
+ {
+ for (Point i = beg; i != end; ++i)
+ {
+ // check first character
+ typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale));
+
+ // no match, on to next character
+ if (candidate == mRoot.mChildren.end ())
+ continue;
+
+ // see how far the match goes
+ Point j = i;
+
+ // some keywords might be longer variations of other keywords, so we definitely need a list of candidates
+ // the first element in the pair is length of the match, i.e. depth from the first character on
+ std::vector< typename std::pair<int, typename Entry::childen_t::iterator> > candidates;
+
+ while ((j + 1) != end)
+ {
+ typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale));
+
+ if (next == candidate->second.mChildren.end ())
+ {
+ if (candidate->second.mKeyword.size() > 0)
+ candidates.push_back(std::make_pair((j-i), candidate));
+ break;
+ }
+
+ candidate = next;
+
+ if (candidate->second.mKeyword.size() > 0)
+ candidates.push_back(std::make_pair((j-i), candidate));
+ }
+
+ if (candidates.empty())
+ continue; // didn't match enough to disambiguate, on to next character
+
+ // shorter candidates will be added to the vector first. however, we want to check against longer candidates first
+ std::reverse(candidates.begin(), candidates.end());
+
+ for (typename std::vector< std::pair<int, typename Entry::childen_t::iterator> >::iterator it = candidates.begin();
+ it != candidates.end(); ++it)
+ {
+ candidate = it->second;
+ // try to match the rest of the keyword
+ Point k = i + it->first;
+ typename string_t::const_iterator t = candidate->second.mKeyword.begin () + (k - i);
+
+
+ while (k != end && t != candidate->second.mKeyword.end ())
+ {
+ if (std::tolower (*k, mLocale) != std::tolower (*t, mLocale))
+ break;
+
+ ++k, ++t;
+ }
+
+ // didn't match full keyword, try the next candidate
+ if (t != candidate->second.mKeyword.end ())
+ continue;
+
+ // we did it, report the good news
+ match.mValue = candidate->second.mValue;
+ match.mBeg = i;
+ match.mEnd = k;
+
+ return true;
+ }
+ }
+
+ // no match in range, report the bad news
+ return false;
+ }
+
+private:
+
+ struct Entry
+ {
+ typedef std::map <wchar_t, Entry> childen_t;
+
+ string_t mKeyword;
+ value_t mValue;
+ childen_t mChildren;
+ };
+
+ void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry)
+ {
+ int ch = tolower (keyword.at (depth), mLocale);
+
+ typename Entry::childen_t::iterator j = entry.mChildren.find (ch);
+
+ if (j == entry.mChildren.end ())
+ {
+ entry.mChildren [ch].mValue = /*std::move*/ (value);
+ entry.mChildren [ch].mKeyword = /*std::move*/ (keyword);
+ }
+ else
+ {
+ if (j->second.mKeyword.size () > 0)
+ {
+ if (keyword == j->second.mKeyword)
+ throw std::runtime_error ("duplicate keyword inserted");
+
+ value_t pushValue = /*std::move*/ (j->second.mValue);
+ string_t pushKeyword = /*std::move*/ (j->second.mKeyword);
+
+ if (depth >= pushKeyword.size ())
+ throw std::runtime_error ("unexpected");
+
+ if (depth+1 < pushKeyword.size())
+ {
+ seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second);
+ j->second.mKeyword.clear ();
+ }
+ }
+ if (depth+1 == keyword.size())
+ j->second.mKeyword = value;
+ else // depth+1 < keyword.size()
+ seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second);
+ }
+
+ }
+
+ Entry mRoot;
+ std::locale mLocale;
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
new file mode 100644
index 0000000000..7c0191d495
--- /dev/null
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -0,0 +1,200 @@
+#include "levelupdialog.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/fallback.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+namespace MWGui
+{
+
+ LevelupDialog::LevelupDialog()
+ : WindowBase("openmw_levelup_dialog.layout")
+ {
+ getWidget(mOkButton, "OkButton");
+ getWidget(mClassImage, "ClassImage");
+ getWidget(mLevelText, "LevelText");
+ getWidget(mLevelDescription, "LevelDescription");
+ getWidget(mCoinBox, "Coins");
+
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onOkButtonClicked);
+
+ for (int i=1; i<9; ++i)
+ {
+ MyGUI::TextBox* t;
+ getWidget(t, "AttribVal" + boost::lexical_cast<std::string>(i));
+
+ MyGUI::Button* b;
+ getWidget(b, "Attrib" + boost::lexical_cast<std::string>(i));
+ b->setUserData (i-1);
+ b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked);
+ mAttributes.push_back(b);
+
+ mAttributeValues.push_back(t);
+
+ getWidget(t, "AttribMultiplier" + boost::lexical_cast<std::string>(i));
+
+ mAttributeMultipliers.push_back(t);
+ }
+
+ int curX = mMainWidget->getWidth()/2 - (16 + 2) * 1.5;
+ for (int i=0; i<3; ++i)
+ {
+ MyGUI::ImageBox* image = mMainWidget->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(curX,250,16,16), MyGUI::Align::Default);
+ image->setImageTexture ("icons\\tx_goldicon.dds");
+ curX += 24+2;
+ mCoins.push_back(image);
+ }
+
+ center();
+ }
+
+ void LevelupDialog::setAttributeValues()
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
+ MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ for (int i=0; i<8; ++i)
+ {
+ int val = creatureStats.getAttribute (i).getBase ();
+ if (std::find(mSpentAttributes.begin(), mSpentAttributes.end(), i) != mSpentAttributes.end())
+ {
+ val += pcStats.getLevelupAttributeMultiplier (i);
+ }
+
+ if (val >= 100)
+ val = 100;
+
+ mAttributeValues[i]->setCaption(boost::lexical_cast<std::string>(val));
+ }
+ }
+
+
+ void LevelupDialog::resetCoins ()
+ {
+ int curX = 0;
+ for (int i=0; i<3; ++i)
+ {
+ MyGUI::ImageBox* image = mCoins[i];
+ image->detachFromWidget();
+ image->attachToWidget(mCoinBox);
+ image->setCoord(MyGUI::IntCoord(curX,0,16,16));
+ curX += 24+2;
+ }
+ }
+
+ void LevelupDialog::assignCoins ()
+ {
+ resetCoins();
+ for (unsigned int i=0; i<mSpentAttributes.size(); ++i)
+ {
+ MyGUI::ImageBox* image = mCoins[i];
+ image->detachFromWidget();
+ image->attachToWidget(mMainWidget);
+
+ int attribute = mSpentAttributes[i];
+
+ int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 30;
+
+ MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(24+xdiff,-4);
+ image->setPosition(pos);
+ }
+
+ setAttributeValues();
+ }
+
+ void LevelupDialog::open()
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
+ MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ mSpentAttributes.clear();
+ resetCoins();
+
+ setAttributeValues();
+
+ const ESM::NPC *playerData = player.get<ESM::NPC>()->mBase;
+
+ // set class image
+ const ESM::Class *cls =
+ world->getStore().get<ESM::Class>().find(playerData->mClass);
+
+ mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds");
+
+ int level = creatureStats.getLevel ()+1;
+ mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level));
+
+ std::string levelupdescription;
+ if(level>20)
+ levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default");
+ else
+ levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+boost::lexical_cast<std::string>(level));
+
+ mLevelDescription->setCaption (levelupdescription);
+
+ for (int i=0; i<8; ++i)
+ {
+ MyGUI::TextBox* text = mAttributeMultipliers[i];
+ int mult = pcStats.getLevelupAttributeMultiplier (i);
+ text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast<std::string>(mult));
+ }
+
+ center();
+ }
+
+ void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
+ MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ if (mSpentAttributes.size() < 3)
+ MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage36}");
+ else
+ {
+ // increase attributes
+ for (int i=0; i<3; ++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.levelUp();
+ pcStats.levelUp ();
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Levelup);
+ }
+
+ }
+
+ void LevelupDialog::onAttributeClicked (MyGUI::Widget *sender)
+ {
+ int attribute = *sender->getUserData<int>();
+
+ std::vector<int>::iterator found = std::find(mSpentAttributes.begin(), mSpentAttributes.end(), attribute);
+ if (found != mSpentAttributes.end())
+ mSpentAttributes.erase (found);
+ else
+ {
+ if (mSpentAttributes.size() == 3)
+ mSpentAttributes[2] = attribute;
+ else
+ mSpentAttributes.push_back(attribute);
+ }
+ assignCoins();
+ }
+}
diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp
new file mode 100644
index 0000000000..69afbf0897
--- /dev/null
+++ b/apps/openmw/mwgui/levelupdialog.hpp
@@ -0,0 +1,42 @@
+#ifndef MWGUI_LEVELUPDIALOG_H
+#define MWGUI_LEVELUPDIALOG_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+
+ class LevelupDialog : public WindowBase
+ {
+ public:
+ LevelupDialog();
+
+ virtual void open();
+
+ private:
+ MyGUI::Button* mOkButton;
+ MyGUI::ImageBox* mClassImage;
+ MyGUI::TextBox* mLevelText;
+ MyGUI::EditBox* mLevelDescription;
+
+ MyGUI::Widget* mCoinBox;
+
+ std::vector<MyGUI::Button*> mAttributes;
+ std::vector<MyGUI::TextBox*> mAttributeValues;
+ std::vector<MyGUI::TextBox*> mAttributeMultipliers;
+ std::vector<MyGUI::ImageBox*> mCoins;
+
+ std::vector<int> mSpentAttributes;
+
+ void onOkButtonClicked (MyGUI::Widget* sender);
+ void onAttributeClicked (MyGUI::Widget* sender);
+
+ void assignCoins();
+ void resetCoins();
+
+ void setAttributeValues();
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp
new file mode 100644
index 0000000000..8dda041ca6
--- /dev/null
+++ b/apps/openmw/mwgui/list.cpp
@@ -0,0 +1,169 @@
+#include "list.hpp"
+
+#include <MyGUI_Gui.h>
+#include <MyGUI_Button.h>
+#include <MyGUI_ImageBox.h>
+#include <MyGUI_ScrollBar.h>
+
+namespace MWGui
+{
+
+ namespace Widgets
+ {
+
+ MWList::MWList() :
+ mClient(0)
+ , mScrollView(0)
+ , mItemHeight(0)
+ {
+ }
+
+ void MWList::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mClient, "Client");
+ if (mClient == 0)
+ mClient = this;
+
+ mScrollView = mClient->createWidgetReal<MWGui::Widgets::MWScrollView>(
+ "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0),
+ MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView");
+ }
+
+ void MWList::addItem(const std::string& name)
+ {
+ mItems.push_back(name);
+ }
+
+ void MWList::addSeparator()
+ {
+ mItems.push_back("");
+ }
+
+ void MWList::adjustSize()
+ {
+ redraw();
+ }
+
+ void MWList::redraw(bool scrollbarShown)
+ {
+ const int _scrollBarWidth = 24; // fetch this from skin?
+ const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0;
+ const int spacing = 3;
+ size_t scrollbarPosition = mScrollView->getScrollPosition();
+
+ while (mScrollView->getChildCount())
+ {
+ MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0));
+ }
+
+ mItemHeight = 0;
+ int i=0;
+ for (std::vector<std::string>::const_iterator it=mItems.begin();
+ it!=mItems.end(); ++it)
+ {
+ if (*it != "")
+ {
+ MyGUI::Button* button = mScrollView->createWidget<MyGUI::Button>(
+ "MW_ListLine", MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24),
+ MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + (*it));
+ button->setCaption((*it));
+ button->getSubWidgetText()->setWordWrap(true);
+ button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left);
+ button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheel);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected);
+
+ int height = button->getTextSize().height;
+ button->setSize(MyGUI::IntSize(button->getSize().width, height));
+ button->setUserData(i);
+
+ mItemHeight += height + spacing;
+ }
+ else
+ {
+ MyGUI::ImageBox* separator = mScrollView->createWidget<MyGUI::ImageBox>("MW_HLine",
+ MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth()-4, 18),
+ MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ separator->setNeedMouseFocus(false);
+
+ mItemHeight += 18 + spacing;
+ }
+ ++i;
+ }
+ mScrollView->setCanvasSize(mClient->getSize().width + (_scrollBarWidth-scrollBarWidth), std::max(mItemHeight, mClient->getSize().height));
+
+ if (!scrollbarShown && mItemHeight > mClient->getSize().height)
+ redraw(true);
+
+ size_t scrollbarRange = mScrollView->getScrollRange();
+ if(scrollbarPosition > scrollbarRange)
+ scrollbarPosition = scrollbarRange;
+ mScrollView->setScrollPosition(scrollbarPosition);
+ }
+
+ bool MWList::hasItem(const std::string& name)
+ {
+ return (std::find(mItems.begin(), mItems.end(), name) != mItems.end());
+ }
+
+ unsigned int MWList::getItemCount()
+ {
+ return mItems.size();
+ }
+
+ std::string MWList::getItemNameAt(unsigned int at)
+ {
+ assert(at < mItems.size() && "List item out of bounds");
+ return mItems[at];
+ }
+
+ void MWList::removeItem(const std::string& name)
+ {
+ assert( std::find(mItems.begin(), mItems.end(), name) != mItems.end() );
+ mItems.erase( std::find(mItems.begin(), mItems.end(), name) );
+ }
+
+ void MWList::clear()
+ {
+ mItems.clear();
+ }
+
+ void MWList::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ //NB view offset is negative
+ if (mScrollView->getViewOffset().top + _rel*0.3 > 0)
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3));
+ }
+
+ void MWList::onItemSelected(MyGUI::Widget* _sender)
+ {
+ std::string name = static_cast<MyGUI::Button*>(_sender)->getCaption();
+ int id = *_sender->getUserData<int>();
+ eventItemSelected(name, id);
+ eventWidgetSelected(_sender);
+ }
+
+ MyGUI::Widget* MWList::getItemWidget(const std::string& name)
+ {
+ return mScrollView->findWidget (getName() + "_item_" + name);
+ }
+
+ size_t MWScrollView::getScrollPosition()
+ {
+ return getVScroll()->getScrollPosition();
+ }
+
+ void MWScrollView::setScrollPosition(size_t position)
+ {
+ getVScroll()->setScrollPosition(position);
+ }
+ size_t MWScrollView::getScrollRange()
+ {
+ return getVScroll()->getScrollRange();
+ }
+
+ }
+}
diff --git a/apps/openmw/mwgui/list.hpp b/apps/openmw/mwgui/list.hpp
new file mode 100644
index 0000000000..956523c0dc
--- /dev/null
+++ b/apps/openmw/mwgui/list.hpp
@@ -0,0 +1,83 @@
+#ifndef MWGUI_LIST_HPP
+#define MWGUI_LIST_HPP
+
+#include <MyGUI_ScrollView.h>
+
+namespace MWGui
+{
+ namespace Widgets
+ {
+ /**
+ * \brief a custom ScrollView which has access to scrollbar properties
+ */
+ class MWScrollView : public MyGUI::ScrollView
+ {
+ MYGUI_RTTI_DERIVED(MWScrollView)
+ public:
+ size_t getScrollPosition();
+ void setScrollPosition(size_t);
+ size_t getScrollRange();
+ };
+
+ /**
+ * \brief a very simple list widget that supports word-wrapping entries
+ * \note if the width or height of the list changes, you must call adjustSize() method
+ */
+ class MWList : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED(MWList)
+ public:
+ MWList();
+
+ typedef MyGUI::delegates::CMultiDelegate2<const std::string&, int> EventHandle_StringInt;
+ typedef MyGUI::delegates::CMultiDelegate1<MyGUI::Widget*> EventHandle_Widget;
+
+ /**
+ * Event: Item selected with the mouse.
+ * signature: void method(std::string itemName)
+ */
+ EventHandle_StringInt eventItemSelected;
+
+ /**
+ * Event: Item selected with the mouse.
+ * signature: void method(MyGUI::Widget* sender)
+ */
+ EventHandle_Widget eventWidgetSelected;
+
+
+ /**
+ * Call after the size of the list changed, or items were inserted/removed
+ */
+ void adjustSize();
+
+ void addItem(const std::string& name);
+ void addSeparator(); ///< add a seperator between the current and the next item.
+ void removeItem(const std::string& name);
+ bool hasItem(const std::string& name);
+ unsigned int getItemCount();
+ std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is
+ void clear();
+
+ MyGUI::Widget* getItemWidget(const std::string& name);
+ ///< get widget for an item name, useful to set up tooltip
+
+ protected:
+ void initialiseOverride();
+
+ void redraw(bool scrollbarShown = false);
+
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onItemSelected(MyGUI::Widget* _sender);
+
+ private:
+ MWGui::Widgets::MWScrollView* mScrollView;
+ MyGUI::Widget* mClient;
+
+ std::vector<std::string> mItems;
+
+ int mItemHeight; // height of all items
+ };
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp
new file mode 100644
index 0000000000..9b63dfa76d
--- /dev/null
+++ b/apps/openmw/mwgui/loadingscreen.cpp
@@ -0,0 +1,235 @@
+#include "loadingscreen.hpp"
+
+#include <OgreRenderWindow.h>
+#include <OgreCompositorManager.h>
+#include <OgreCompositorChain.h>
+
+#include <openengine/ogre/fader.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/inputmanager.hpp"
+
+namespace MWGui
+{
+
+ LoadingScreen::LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw)
+ : mSceneMgr(sceneMgr)
+ , mWindow(rw)
+ , WindowBase("openmw_loading_screen.layout")
+ , mLastRenderTime(0.f)
+ , mLastWallpaperChangeTime(0.f)
+ , mFirstLoad(true)
+ , mProgress(0)
+ {
+ getWidget(mLoadingText, "LoadingText");
+ getWidget(mProgressBar, "ProgressBar");
+ getWidget(mBackgroundImage, "BackgroundImage");
+
+ mProgressBar->setScrollViewPage(1);
+
+ mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+ mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ mBackgroundMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
+ mBackgroundMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("");
+
+ mRectangle = new Ogre::Rectangle2D(true);
+ mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
+ mRectangle->setMaterial("BackgroundMaterial");
+ // Render the background before everything else
+ mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1);
+ // Use infinite AAB to always stay visible
+ Ogre::AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+ mRectangle->setBoundingBox(aabInf);
+ // Attach background to the scene
+ Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ node->attachObject(mRectangle);
+ mRectangle->setVisible(false);
+ }
+
+ void LoadingScreen::setLabel(const std::string &label)
+ {
+ mLoadingText->setCaptionWithReplacing(label);
+ }
+
+ LoadingScreen::~LoadingScreen()
+ {
+ delete mRectangle;
+ }
+
+ void LoadingScreen::onResChange(int w, int h)
+ {
+ setCoord(0,0,w,h);
+ }
+
+ void LoadingScreen::loadingOn()
+ {
+ setVisible(true);
+
+ if (mFirstLoad)
+ {
+ changeWallpaper();
+ }
+ else
+ {
+ mBackgroundImage->setImageTexture("");
+ }
+
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(mFirstLoad ? GM_LoadingWallpaper : GM_Loading);
+ }
+
+ void LoadingScreen::loadingOff()
+ {
+ setVisible(false);
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper);
+ }
+
+ void LoadingScreen::changeWallpaper ()
+ {
+ if (mResources.empty())
+ {
+ Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups ();
+ for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga");
+ mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end());
+ }
+ }
+
+ if (!mResources.empty())
+ {
+ std::string const & randomSplash = mResources.at (rand() % mResources.size());
+
+ Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);
+
+ mBackgroundImage->setImageTexture (randomSplash);
+ }
+ else
+ std::cerr << "No loading screens found!" << std::endl;
+ }
+
+ void LoadingScreen::setProgressRange (size_t range)
+ {
+ mProgressBar->setScrollRange(range+1);
+ mProgressBar->setScrollPosition(0);
+ mProgressBar->setTrackSize(0);
+ mProgress = 0;
+ }
+
+ void LoadingScreen::setProgress (size_t value)
+ {
+ assert(value < mProgressBar->getScrollRange());
+ if (value - mProgress < mProgressBar->getScrollRange()/100.f)
+ return;
+ mProgress = value;
+ mProgressBar->setScrollPosition(0);
+ mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize());
+ draw();
+ }
+
+ void LoadingScreen::increaseProgress (size_t increase)
+ {
+ mProgressBar->setScrollPosition(0);
+ size_t value = mProgress + increase;
+ mProgress = value;
+ assert(mProgress < mProgressBar->getScrollRange());
+ mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize());
+ draw();
+ }
+
+ void LoadingScreen::indicateProgress()
+ {
+ float time = (mTimer.getMilliseconds() % 2001) / 1000.f;
+ if (time > 1)
+ time = (time-2)*-1;
+
+ mProgressBar->setTrackSize(50);
+ mProgressBar->setScrollPosition(time * (mProgressBar->getScrollRange()-1));
+ draw();
+ }
+
+ void LoadingScreen::removeWallpaper()
+ {
+ mFirstLoad = false;
+ }
+
+ void LoadingScreen::draw()
+ {
+ const float loadingScreenFps = 20.f;
+
+ if (mTimer.getMilliseconds () > mLastRenderTime + (1.f/loadingScreenFps) * 1000.f)
+ {
+ mLastRenderTime = mTimer.getMilliseconds ();
+
+ if (mFirstLoad && mTimer.getMilliseconds () > mLastWallpaperChangeTime + 5000*1)
+ {
+ mLastWallpaperChangeTime = mTimer.getMilliseconds ();
+ changeWallpaper();
+ }
+
+ // Turn off rendering except the GUI
+ mSceneMgr->clearSpecialCaseRenderQueues();
+ // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work.
+ for (int i = 0; i < Ogre::RENDER_QUEUE_MAX; ++i)
+ {
+ if (i > 0 && i < 96)
+ mSceneMgr->addSpecialCaseRenderQueue(i);
+ }
+ mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);
+
+ 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);
+ }
+ }
+
+ // First, swap buffers from last draw, then, queue an update of the
+ // window contents, but don't swap buffers (which would have
+ // caused a sync / flush and would be expensive).
+ // We're doing this so we can do some actual loading while the GPU is busy with the render.
+ // This means the render is lagging a frame behind, but this is hardly noticable.
+ mWindow->swapBuffers(false); // never Vsync, makes no sense here
+ 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);
+ }
+ }
+
+ mRectangle->setVisible(false);
+
+ // resume 3d rendering
+ mSceneMgr->clearSpecialCaseRenderQueues();
+ mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp
new file mode 100644
index 0000000000..dde8ff63aa
--- /dev/null
+++ b/apps/openmw/mwgui/loadingscreen.hpp
@@ -0,0 +1,68 @@
+#ifndef MWGUI_LOADINGSCREEN_H
+#define MWGUI_LOADINGSCREEN_H
+
+#include <OgreSceneManager.h>
+
+#include "windowbase.hpp"
+
+#include <components/loadinglistener/loadinglistener.hpp>
+
+namespace MWGui
+{
+ class LoadingScreen : public WindowBase, public Loading::Listener
+ {
+ public:
+ virtual void setLabel (const std::string& label);
+
+ /// Indicate that some progress has been made, without specifying how much
+ virtual void indicateProgress ();
+
+ virtual void loadingOn();
+ virtual void loadingOff();
+
+ virtual void setProgressRange (size_t range);
+ virtual void setProgress (size_t value);
+ virtual void increaseProgress (size_t increase);
+
+ virtual void removeWallpaper();
+
+ LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw);
+ virtual ~LoadingScreen();
+
+ void setLoadingProgress (const std::string& stage, int depth, int current, int total);
+ void loadingDone();
+
+ void onResChange(int w, int h);
+
+ void updateWindow(Ogre::RenderWindow* rw) { mWindow = rw; }
+
+ private:
+ bool mFirstLoad;
+
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::RenderWindow* mWindow;
+
+ unsigned long mLastWallpaperChangeTime;
+ unsigned long mLastRenderTime;
+ Ogre::Timer mTimer;
+
+ size_t mProgress;
+
+ MyGUI::TextBox* mLoadingText;
+ MyGUI::ScrollBar* mProgressBar;
+ MyGUI::ImageBox* mBackgroundImage;
+
+ Ogre::Rectangle2D* mRectangle;
+ Ogre::MaterialPtr mBackgroundMaterial;
+
+ Ogre::StringVector mResources;
+
+ void changeWallpaper();
+
+ void draw();
+ };
+
+}
+
+
+#endif
diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp
new file mode 100644
index 0000000000..88227c7512
--- /dev/null
+++ b/apps/openmw/mwgui/mainmenu.cpp
@@ -0,0 +1,87 @@
+#include "mainmenu.hpp"
+
+#include <OgreRoot.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/journal.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+
+namespace MWGui
+{
+
+ MainMenu::MainMenu(int w, int h)
+ : OEngine::GUI::Layout("openmw_mainmenu.layout")
+ , mButtonBox(0)
+ {
+ onResChange(w,h);
+ }
+
+ void MainMenu::onResChange(int w, int h)
+ {
+ setCoord(0,0,w,h);
+
+
+ if (mButtonBox)
+ MyGUI::Gui::getInstance ().destroyWidget(mButtonBox);
+
+ mButtonBox = mMainWidget->createWidget<MyGUI::Widget>("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
+ int curH = 0;
+
+ std::vector<std::string> buttons;
+ buttons.push_back("return");
+ buttons.push_back("newgame");
+ //buttons.push_back("loadgame");
+ //buttons.push_back("savegame");
+ buttons.push_back("options");
+ //buttons.push_back("credits");
+ buttons.push_back("exitgame");
+
+ int maxwidth = 0;
+
+ mButtons.clear();
+ for (std::vector<std::string>::iterator it = buttons.begin(); it != buttons.end(); ++it)
+ {
+ MWGui::ImageButton* button = mButtonBox->createWidget<MWGui::ImageButton>
+ ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default);
+ button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds");
+ button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds");
+ button->setProperty("ImagePushed", "textures\\menu_" + *it + "_pressed.dds");
+ MyGUI::IntSize requested = button->getRequestedSize();
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked);
+ mButtons[*it] = button;
+ curH += requested.height;
+
+ if (requested.width > maxwidth)
+ maxwidth = requested.width;
+ }
+ for (std::map<std::string, MWGui::ImageButton*>::iterator it = mButtons.begin(); it != mButtons.end(); ++it)
+ {
+ MyGUI::IntSize requested = it->second->getRequestedSize();
+ it->second->setCoord((maxwidth-requested.width) / 2, it->second->getTop(), requested.width, requested.height);
+ }
+
+ mButtonBox->setCoord (w/2 - maxwidth/2, h/2 - curH/2, maxwidth, curH);
+ }
+
+ void MainMenu::onButtonClicked(MyGUI::Widget *sender)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ if (sender == mButtons["return"])
+ MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu);
+ else if (sender == mButtons["options"])
+ MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings);
+ else if (sender == mButtons["exitgame"])
+ Ogre::Root::getSingleton ().queueEndRendering ();
+ else if (sender == mButtons["newgame"])
+ {
+ MWBase::Environment::get().getWorld()->startNewGame();
+ MWBase::Environment::get().getWindowManager()->setNewGame(true);
+ MWBase::Environment::get().getDialogueManager()->clear();
+ MWBase::Environment::get().getJournal()->clear();
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp
new file mode 100644
index 0000000000..4e76a64df7
--- /dev/null
+++ b/apps/openmw/mwgui/mainmenu.hpp
@@ -0,0 +1,23 @@
+#include <openengine/gui/layout.hpp>
+
+#include "imagebutton.hpp"
+
+namespace MWGui
+{
+
+ class MainMenu : public OEngine::GUI::Layout
+ {
+ public:
+ MainMenu(int w, int h);
+
+ void onResChange(int w, int h);
+
+ private:
+ MyGUI::Widget* mButtonBox;
+
+ std::map<std::string, MWGui::ImageButton*> mButtons;
+
+ void onButtonClicked (MyGUI::Widget* sender);
+ };
+
+}
diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp
new file mode 100644
index 0000000000..5ed002d7b3
--- /dev/null
+++ b/apps/openmw/mwgui/mapwindow.cpp
@@ -0,0 +1,446 @@
+#include "mapwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <OgreSceneNode.h>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwrender/globalmap.hpp"
+
+#include "widgets.hpp"
+
+namespace MWGui
+{
+
+ LocalMapBase::LocalMapBase()
+ : mCurX(0)
+ , mCurY(0)
+ , mInterior(false)
+ , mFogOfWar(true)
+ , mLocalMap(NULL)
+ , mMapDragAndDrop(false)
+ , mPrefix()
+ , mChanged(true)
+ , mLayout(NULL)
+ , mLastPositionX(0.0f)
+ , mLastPositionY(0.0f)
+ , mLastDirectionX(0.0f)
+ , mLastDirectionY(0.0f)
+ , mCompass(NULL)
+ {
+ }
+
+ LocalMapBase::~LocalMapBase()
+ {
+ }
+
+ void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop)
+ {
+ mLocalMap = widget;
+ mLayout = layout;
+ mMapDragAndDrop = mapDragAndDrop;
+ mCompass = compass;
+
+ // create 3x3 map widgets, 512x512 each, holding a 1024x1024 texture each
+ const int widgetSize = 512;
+ for (int mx=0; mx<3; ++mx)
+ {
+ for (int my=0; my<3; ++my)
+ {
+ MyGUI::ImageBox* map = mLocalMap->createWidget<MyGUI::ImageBox>("ImageBox",
+ MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize),
+ MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast<std::string>(mx) + "_" + boost::lexical_cast<std::string>(my));
+
+ MyGUI::ImageBox* fog = map->createWidget<MyGUI::ImageBox>("ImageBox",
+ MyGUI::IntCoord(0, 0, widgetSize, widgetSize),
+ MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast<std::string>(mx) + "_" + boost::lexical_cast<std::string>(my) + "_fog");
+
+ if (!mMapDragAndDrop)
+ {
+ map->setNeedMouseFocus(false);
+ fog->setNeedMouseFocus(false);
+ }
+
+ mMapWidgets.push_back(map);
+ mFogWidgets.push_back(fog);
+ }
+ }
+ }
+
+ void LocalMapBase::setCellPrefix(const std::string& prefix)
+ {
+ mPrefix = prefix;
+ mChanged = true;
+ }
+
+ void LocalMapBase::toggleFogOfWar()
+ {
+ mFogOfWar = !mFogOfWar;
+ applyFogOfWar();
+ }
+
+ void LocalMapBase::applyFogOfWar()
+ {
+ for (int mx=0; mx<3; ++mx)
+ {
+ for (int my=0; my<3; ++my)
+ {
+ std::string image = mPrefix+"_"+ boost::lexical_cast<std::string>(mCurX + (mx-1)) + "_"
+ + boost::lexical_cast<std::string>(mCurY + (-1*(my-1)));
+ MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx];
+ fog->setImageTexture(mFogOfWar ?
+ ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog"
+ : "black.png" )
+ : "");
+ }
+ }
+ notifyMapChanged ();
+ }
+
+ void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2)
+ {
+ applyFogOfWar ();
+ }
+
+ void LocalMapBase::onMarkerUnfocused (MyGUI::Widget* w1, MyGUI::Widget* w2)
+ {
+ applyFogOfWar ();
+ }
+
+ 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
+
+ // 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));
+ }
+ }
+
+ for (int mx=0; mx<3; ++mx)
+ {
+ for (int my=0; my<3; ++my)
+ {
+ // map
+ std::string image = mPrefix+"_"+ boost::lexical_cast<std::string>(x + (mx-1)) + "_"
+ + boost::lexical_cast<std::string>(y + (-1*(my-1)));
+
+ MyGUI::ImageBox* box = mMapWidgets[my + 3*mx];
+
+ if (MyGUI::RenderManager::getInstance().getTexture(image) != 0)
+ box->setImageTexture(image);
+ else
+ box->setImageTexture("black.png");
+
+
+ // 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)
+ {
+ 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);
+ }
+
+
+ }
+ }
+ mInterior = interior;
+ mCurX = x;
+ mCurY = y;
+ mChanged = false;
+
+ // fog of war
+ applyFogOfWar();
+
+ // set the compass texture again, because MyGUI determines sorting of ImageBox widgets
+ // based on the last setImageTexture call
+ std::string tex = "textures\\compass.dds";
+ mCompass->setImageTexture("");
+ mCompass->setImageTexture(tex);
+ }
+
+
+ void LocalMapBase::setPlayerPos(const float x, const float y)
+ {
+ if (x == mLastPositionX && y == mLastPositionY)
+ return;
+
+ notifyPlayerUpdate ();
+
+ MyGUI::IntSize size = mLocalMap->getCanvasSize();
+ MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height);
+ MyGUI::IntCoord viewsize = mLocalMap->getCoord();
+ MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top);
+ mLocalMap->setViewOffset(pos);
+
+ mCompass->setPosition(MyGUI::IntPoint(512+x*512-16, 512+y*512-16));
+ mLastPositionX = x;
+ mLastPositionY = y;
+ }
+
+ void LocalMapBase::setPlayerDir(const float x, const float y)
+ {
+ if (x == mLastDirectionX && y == mLastDirectionY)
+ return;
+
+ notifyPlayerUpdate ();
+
+ MyGUI::ISubWidget* main = mCompass->getSubWidgetMain();
+ MyGUI::RotatingSkin* rotatingSubskin = main->castType<MyGUI::RotatingSkin>();
+ rotatingSubskin->setCenter(MyGUI::IntPoint(16,16));
+ float angle = std::atan2(x,y);
+ rotatingSubskin->setAngle(angle);
+
+ mLastDirectionX = x;
+ mLastDirectionY = y;
+ }
+
+ // ------------------------------------------------------------------------------------------
+
+ MapWindow::MapWindow(const std::string& cacheDir)
+ : MWGui::WindowPinnableBase("openmw_map_window.layout")
+ , mGlobal(false)
+ , mGlobalMap(0)
+ {
+ setCoord(500,0,320,300);
+
+ getWidget(mLocalMap, "LocalMap");
+ getWidget(mGlobalMap, "GlobalMap");
+ getWidget(mGlobalMapImage, "GlobalMapImage");
+ getWidget(mGlobalMapOverlay, "GlobalMapOverlay");
+ getWidget(mPlayerArrowLocal, "CompassLocal");
+ getWidget(mPlayerArrowGlobal, "CompassGlobal");
+
+ mGlobalMap->setVisible (false);
+
+ getWidget(mButton, "WorldButton");
+ mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked);
+ mButton->setCaptionWithReplacing("#{sWorld}");
+
+ getWidget(mEventBoxGlobal, "EventBoxGlobal");
+ mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
+ mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
+ getWidget(mEventBoxLocal, "EventBoxLocal");
+ mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
+ mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
+
+ LocalMapBase::init(mLocalMap, mPlayerArrowLocal, this);
+ }
+
+ void MapWindow::renderGlobalMap(Loading::Listener* loadingListener)
+ {
+ mGlobalMapRender = new MWRender::GlobalMap("");
+ mGlobalMapRender->render(loadingListener);
+ mGlobalMapImage->setImageTexture("GlobalMap.png");
+ mGlobalMapOverlay->setImageTexture("GlobalMapOverlay");
+ }
+
+ MapWindow::~MapWindow()
+ {
+ delete mGlobalMapRender;
+ }
+
+ void MapWindow::setCellName(const std::string& cellName)
+ {
+ setTitle("#{sCell=" + cellName + "}");
+ }
+
+ void MapWindow::addVisitedLocation(const std::string& name, int x, int y)
+ {
+ float worldX, worldY;
+ mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY);
+
+ MyGUI::IntCoord widgetCoord(
+ worldX * mGlobalMapRender->getWidth()+6,
+ worldY * mGlobalMapRender->getHeight()+6,
+ 12, 12);
+
+
+ static int _counter=0;
+ MyGUI::Button* markerWidget = mGlobalMapImage->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", name);
+ ++_counter;
+
+ markerWidget = mEventBoxGlobal->createWidget<MyGUI::Button>("",
+ widgetCoord, MyGUI::Align::Default);
+ markerWidget->setNeedMouseFocus (true);
+ markerWidget->setUserString("ToolTipType", "Layout");
+ markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine");
+ markerWidget->setUserString("Caption_TextOneLine", name);
+ }
+
+ void MapWindow::cellExplored(int x, int y)
+ {
+ mGlobalMapRender->exploreCell(x,y);
+ }
+
+ void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ if (_id!=MyGUI::MouseButton::Left) return;
+ mLastDragPos = MyGUI::IntPoint(_left, _top);
+ }
+
+ void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ if (_id!=MyGUI::MouseButton::Left) return;
+
+ MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos;
+
+ if (!mGlobal)
+ mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff );
+ else
+ mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff );
+
+
+ mLastDragPos = MyGUI::IntPoint(_left, _top);
+ }
+
+ void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender)
+ {
+ mGlobal = !mGlobal;
+ mGlobalMap->setVisible(mGlobal);
+ mLocalMap->setVisible(!mGlobal);
+
+ mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" :
+ "#{sWorld}");
+
+ if (mGlobal)
+ globalMapUpdatePlayer ();
+ }
+
+ void MapWindow::onPinToggled()
+ {
+ MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned);
+ }
+
+ void MapWindow::open()
+ {
+ mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight());
+ mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight());
+
+ for (unsigned int i=0; i<mGlobalMapImage->getChildCount (); ++i)
+ {
+ if (mGlobalMapImage->getChildAt (i)->getName().substr(0,6) == "Marker")
+ mGlobalMapImage->getChildAt (i)->castType<MyGUI::Button>()->setImageResource("DoorMarker");
+ }
+
+ globalMapUpdatePlayer();
+
+ mPlayerArrowGlobal->setImageTexture ("textures\\compass.dds");
+ }
+
+ 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?
+ if (MWBase::Environment::get().getWorld ()->isCellExterior ())
+ {
+ mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(worldX - 16, worldY - 16));
+
+ MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain();
+ MyGUI::RotatingSkin* rotatingSubskin = main->castType<MyGUI::RotatingSkin>();
+ rotatingSubskin->setCenter(MyGUI::IntPoint(16,16));
+ float angle = std::atan2(dir.x, dir.y);
+ rotatingSubskin->setAngle(angle);
+
+ // set the view offset so that player is in the center
+ MyGUI::IntSize viewsize = mGlobalMap->getSize();
+ MyGUI::IntPoint viewoffs(0.5*viewsize.width - worldX, 0.5*viewsize.height - worldY);
+ mGlobalMap->setViewOffset(viewoffs);
+ }
+ }
+
+ void MapWindow::notifyPlayerUpdate ()
+ {
+ globalMapUpdatePlayer ();
+ }
+
+ void MapWindow::notifyMapChanged ()
+ {
+ // workaround to prevent the map from drawing on top of the button
+ MyGUI::IntCoord oldCoord = mButton->getCoord ();
+ MyGUI::Gui::getInstance().destroyWidget (mButton);
+ mButton = mMainWidget->createWidget<MWGui::Widgets::AutoSizedButton>("MW_Button",
+ oldCoord, MyGUI::Align::Bottom | MyGUI::Align::Right);
+ mButton->setProperty ("ExpandDirection", "Left");
+
+ mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked);
+ mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" :
+ "#{sWorld}");
+ }
+
+}
diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp
new file mode 100644
index 0000000000..5518ab4a8f
--- /dev/null
+++ b/apps/openmw/mwgui/mapwindow.hpp
@@ -0,0 +1,115 @@
+#ifndef MWGUI_MAPWINDOW_H
+#define MWGUI_MAPWINDOW_H
+
+#include "windowpinnablebase.hpp"
+
+namespace MWRender
+{
+ class GlobalMap;
+}
+
+namespace Loading
+{
+ class Listener;
+}
+
+namespace MWGui
+{
+ class LocalMapBase
+ {
+ public:
+ LocalMapBase();
+ virtual ~LocalMapBase();
+ void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop=false);
+
+ void setCellPrefix(const std::string& prefix);
+ void setActiveCell(const int x, const int y, bool interior=false);
+ void setPlayerDir(const float x, const float y);
+ void setPlayerPos(const float x, const float y);
+
+ void toggleFogOfWar();
+
+ struct MarkerPosition
+ {
+ bool interior;
+ int cellX;
+ int cellY;
+ float nX;
+ float nY;
+ };
+
+ protected:
+ int mCurX, mCurY;
+ bool mInterior;
+ MyGUI::ScrollView* mLocalMap;
+ MyGUI::ImageBox* mCompass;
+ std::string mPrefix;
+ bool mChanged;
+ bool mFogOfWar;
+
+ std::vector<MyGUI::ImageBox*> mMapWidgets;
+ std::vector<MyGUI::ImageBox*> mFogWidgets;
+
+ void applyFogOfWar();
+
+ void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2);
+ void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2);
+
+ virtual void notifyPlayerUpdate() {}
+ virtual void notifyMapChanged() {}
+
+ OEngine::GUI::Layout* mLayout;
+
+ bool mMapDragAndDrop;
+
+ float mLastPositionX;
+ float mLastPositionY;
+ float mLastDirectionX;
+ float mLastDirectionY;
+ };
+
+ class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase
+ {
+ public:
+ MapWindow(const std::string& cacheDir);
+ virtual ~MapWindow();
+
+ void setCellName(const std::string& cellName);
+
+ void renderGlobalMap(Loading::Listener* loadingListener);
+
+ void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map
+ void cellExplored(int x, int y);
+
+ virtual void open();
+
+ private:
+ void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onWorldButtonClicked(MyGUI::Widget* _sender);
+
+ void globalMapUpdatePlayer();
+
+ MyGUI::ScrollView* mGlobalMap;
+ MyGUI::ImageBox* mGlobalMapImage;
+ MyGUI::ImageBox* mGlobalMapOverlay;
+ MyGUI::ImageBox* mPlayerArrowLocal;
+ MyGUI::ImageBox* mPlayerArrowGlobal;
+ MyGUI::Button* mButton;
+ MyGUI::IntPoint mLastDragPos;
+ bool mGlobal;
+
+ MyGUI::Button* mEventBoxGlobal;
+ MyGUI::Button* mEventBoxLocal;
+
+ MWRender::GlobalMap* mGlobalMapRender;
+
+ protected:
+ virtual void onPinToggled();
+
+ virtual void notifyPlayerUpdate();
+ virtual void notifyMapChanged();
+
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp
new file mode 100644
index 0000000000..530594ddaa
--- /dev/null
+++ b/apps/openmw/mwgui/merchantrepair.cpp
@@ -0,0 +1,132 @@
+#include "merchantrepair.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+
+#include "inventorywindow.hpp"
+#include "tradewindow.hpp"
+
+namespace MWGui
+{
+
+MerchantRepair::MerchantRepair()
+ : WindowBase("openmw_merchantrepair.layout")
+{
+ getWidget(mList, "RepairView");
+ getWidget(mOkButton, "OkButton");
+ getWidget(mGoldLabel, "PlayerGold");
+
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick);
+}
+
+void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
+{
+ mActor = actor;
+
+ while (mList->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0));
+
+ int currentY = 0;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
+ int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor;
+ for (MWWorld::ContainerStoreIterator iter (store.begin(categories));
+ iter!=store.end(); ++iter)
+ {
+ if (MWWorld::Class::get(*iter).hasItemHealth(*iter))
+ {
+ int maxDurability = MWWorld::Class::get(*iter).getItemMaxHealth(*iter);
+ int durability = (iter->getCellRef().mCharge == -1) ? maxDurability : iter->getCellRef().mCharge;
+ if (maxDurability == durability)
+ continue;
+
+ int basePrice = MWWorld::Class::get(*iter).getValue(*iter);
+ float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("fRepairMult")->getFloat();
+
+ float p = std::max(1, basePrice);
+ float r = std::max(1, static_cast<int>(maxDurability / p));
+
+ int x = ((maxDurability - durability) / r);
+ x = (fRepairMult * x);
+
+ int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true);
+
+
+ std::string name = MWWorld::Class::get(*iter).getName(*iter)
+ + " - " + boost::lexical_cast<std::string>(price)
+ + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("sgp")->getString();;
+
+
+ MyGUI::Button* button =
+ mList->createWidget<MyGUI::Button>(
+ (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton",
+ 0,
+ currentY,
+ 0,
+ 18,
+ MyGUI::Align::Default
+ );
+
+ currentY += 18;
+
+ button->setEnabled(price<=MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold());
+ button->setUserString("Price", boost::lexical_cast<std::string>(price));
+ button->setUserData(*iter);
+ button->setCaptionWithReplacing(name);
+ button->setSize(button->getTextSize().width,18);
+ button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel);
+ button->setUserString("ToolTipType", "ItemPtr");
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick);
+ }
+ }
+ mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY)));
+
+ mGoldLabel->setCaptionWithReplacing("#{sGold}: "
+ + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+}
+
+void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+{
+ if (mList->getViewOffset().top + _rel*0.3 > 0)
+ mList->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mList->setViewOffset(MyGUI::IntPoint(0, mList->getViewOffset().top + _rel*0.3));
+}
+
+void MerchantRepair::open()
+{
+ center();
+}
+
+void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender)
+{
+ // repair
+ MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
+ item.getCellRef().mCharge = MWWorld::Class::get(item).getItemMaxHealth(item);
+
+ MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);
+
+ int price = boost::lexical_cast<int>(sender->getUserString("Price"));
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+
+ startRepair(mActor);
+}
+
+void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender)
+{
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair);
+}
+
+}
diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp
new file mode 100644
index 0000000000..4cb39fe012
--- /dev/null
+++ b/apps/openmw/mwgui/merchantrepair.hpp
@@ -0,0 +1,37 @@
+#ifndef OPENMW_MWGUI_MERCHANTREPAIR_H
+#define OPENMW_MWGUI_MERCHANTREPAIR_H
+
+#include "windowbase.hpp"
+#include "../mwworld/ptr.hpp"
+
+
+
+namespace MWGui
+{
+
+class MerchantRepair : public WindowBase
+{
+public:
+ MerchantRepair();
+
+ virtual void open();
+
+ void startRepair(const MWWorld::Ptr& actor);
+
+private:
+ MyGUI::ScrollView* mList;
+ MyGUI::Button* mOkButton;
+ MyGUI::TextBox* mGoldLabel;
+
+ MWWorld::Ptr mActor;
+
+protected:
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onRepairButtonClick(MyGUI::Widget* sender);
+ void onOkButtonClick(MyGUI::Widget* sender);
+
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp
new file mode 100644
index 0000000000..45da1bf17e
--- /dev/null
+++ b/apps/openmw/mwgui/messagebox.cpp
@@ -0,0 +1,429 @@
+#include <components/misc/stringops.hpp>
+
+#include "messagebox.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/inputmanager.hpp"
+
+namespace MWGui
+{
+
+ MessageBoxManager::MessageBoxManager ()
+ {
+ // defines
+ mMessageBoxSpeed = 0.1;
+ mInterMessageBoxe = NULL;
+ mStaticMessageBox = NULL;
+ }
+
+ void MessageBoxManager::onFrame (float frameDuration)
+ {
+ std::vector<MessageBoxManagerTimer>::iterator it;
+ for(it = mTimers.begin(); it != mTimers.end();)
+ {
+ // 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;
+ }
+
+ it->current += frameDuration;
+ if(it->current >= it->max)
+ {
+ 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);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) {
+ delete mInterMessageBoxe;
+ mInterMessageBoxe = NULL;
+ MWBase::Environment::get().getInputManager()->changeInputMode(
+ MWBase::Environment::get().getWindowManager()->isGuiMode());
+ }
+ }
+
+ void MessageBoxManager::createMessageBox (const std::string& message, bool stat)
+ {
+ MessageBox *box = new MessageBox(*this, message);
+
+ 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();
+ mMessageBoxes.erase(mMessageBoxes.begin());
+ }
+
+ int height = 0;
+ for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
+ {
+ (*it)->update(height);
+ height += (*it)->getHeight();
+ }
+ }
+
+ void MessageBoxManager::removeStaticMessageBox ()
+ {
+ removeMessageBox(mStaticMessageBox);
+ mStaticMessageBox = NULL;
+ }
+
+ bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons)
+ {
+ if(mInterMessageBoxe != NULL) {
+ throw std::runtime_error("There is a message box already");
+ }
+
+ mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons);
+
+ return true;
+ }
+
+ bool MessageBoxManager::isInteractiveMessageBox ()
+ {
+ 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)
+ {
+ std::vector<MessageBox*>::iterator it;
+ for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
+ {
+ if((*it) == msgbox)
+ {
+ delete (*it);
+ mMessageBoxes.erase(it);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void MessageBoxManager::setMessageBoxSpeed (int speed)
+ {
+ mMessageBoxSpeed = speed;
+ }
+
+ void MessageBoxManager::okayPressed ()
+ {
+ if(mInterMessageBoxe != NULL)
+ mInterMessageBoxe->okayPressed();
+ }
+
+ int MessageBoxManager::readPressedButton ()
+ {
+ if(mInterMessageBoxe != NULL)
+ {
+ return mInterMessageBoxe->readPressedButton();
+ }
+ return -1;
+ }
+
+
+
+
+ MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message)
+ : Layout("openmw_messagebox.layout")
+ , mMessageBoxManager(parMessageBoxManager)
+ , mMessage(message)
+ {
+ // 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;
+
+ mMainWidget->setCoord(coord);
+ mMainWidget->setSize(size);
+ mMainWidget->setVisible(true);
+ }
+
+ int MessageBox::getHeight ()
+ {
+ return mHeight+mNextBoxPadding; // 20 is the padding between this and the next MessageBox
+ }
+
+
+
+ InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons)
+ : WindowModal("openmw_interactive_messagebox.layout")
+ , mMessageBoxManager(parMessageBoxManager)
+ , mButtonPressed(-1)
+ , mTextButtonPadding(0)
+ {
+ WindowModal::open();
+
+ int fixedWidth = 500;
+ int textPadding = 10; // padding between text-widget and main-widget
+ int textButtonPadding = 20; // padding between the text-widget und the button-widget
+ int buttonLeftPadding = 10; // padding between the buttons if horizontal
+ int buttonTopPadding = 5; // ^-- if vertical
+ int buttonPadding = 5; // padding between button label and button itself
+ int buttonMainPadding = 10; // padding between buttons and bottom of the main widget
+
+ mMarkedToDelete = false;
+
+
+ getWidget(mMessageWidget, "message");
+ getWidget(mButtonsWidget, "buttons");
+
+ mMessageWidget->setOverflowToTheLeft(true);
+ mMessageWidget->setCaptionWithReplacing(message);
+
+ MyGUI::IntSize textSize = mMessageWidget->getTextSize();
+
+ MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ int biggestButtonWidth = 0;
+ int buttonWidth = 0;
+ int buttonsWidth = 0;
+ int buttonHeight = 0;
+ MyGUI::IntCoord dummyCoord(0, 0, 0, 0);
+
+ std::vector<std::string>::const_iterator it;
+ for(it = buttons.begin(); it != buttons.end(); ++it)
+ {
+ MyGUI::Button* button = mButtonsWidget->createWidget<MyGUI::Button>(
+ MyGUI::WidgetStyle::Child,
+ std::string("MW_Button"),
+ dummyCoord,
+ MyGUI::Align::Default);
+ button->setCaptionWithReplacing(*it);
+
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &InteractiveMessageBox::mousePressed);
+
+ mButtons.push_back(button);
+
+ buttonWidth = button->getTextSize().width + 2*buttonPadding + buttonLeftPadding;
+ buttonsWidth += buttonWidth;
+ buttonHeight = button->getTextSize().height + 2*buttonPadding + buttonTopPadding;
+
+ if(buttonWidth > biggestButtonWidth)
+ {
+ biggestButtonWidth = buttonWidth;
+ }
+ }
+ buttonsWidth += buttonLeftPadding;
+
+ MyGUI::IntSize mainWidgetSize;
+ if(buttonsWidth < fixedWidth)
+ {
+ // on one line
+ if(textSize.width + 2*textPadding < buttonsWidth)
+ {
+ mainWidgetSize.width = buttonsWidth;
+ }
+ else
+ {
+ mainWidgetSize.width = textSize.width + 3*textPadding;
+ }
+ mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding;
+
+ MyGUI::IntCoord absCoord;
+ absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
+ absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
+
+ mMainWidget->setCoord(absCoord);
+ mMainWidget->setSize(mainWidgetSize);
+
+ MyGUI::IntCoord messageWidgetCoord;
+ messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
+ messageWidgetCoord.top = textPadding;
+ mMessageWidget->setCoord(messageWidgetCoord);
+
+ mMessageWidget->setSize(textSize);
+
+ MyGUI::IntCoord buttonCord;
+ MyGUI::IntSize buttonSize(0, buttonHeight);
+ int left = (mainWidgetSize.width - buttonsWidth)/2 + buttonPadding;
+
+ std::vector<MyGUI::Button*>::const_iterator button;
+ for(button = mButtons.begin(); button != mButtons.end(); ++button)
+ {
+ buttonCord.left = left;
+ buttonCord.top = textSize.height + textButtonPadding;
+
+ buttonSize.width = (*button)->getTextSize().width + 2*buttonPadding;
+ buttonSize.height = (*button)->getTextSize().height + 2*buttonPadding;
+
+ (*button)->setCoord(buttonCord);
+ (*button)->setSize(buttonSize);
+
+ left += buttonSize.width + buttonLeftPadding;
+ }
+ }
+ else
+ {
+ // among each other
+ if(biggestButtonWidth > textSize.width) {
+ mainWidgetSize.width = biggestButtonWidth + buttonTopPadding;
+ }
+ else {
+ mainWidgetSize.width = textSize.width + 3*textPadding;
+ }
+ mainWidgetSize.height = textSize.height + 2*textPadding + textButtonPadding + buttonHeight * buttons.size() + buttonMainPadding;
+
+ mMainWidget->setSize(mainWidgetSize);
+
+ MyGUI::IntCoord absCoord;
+ absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
+ absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
+
+ mMainWidget->setCoord(absCoord);
+ mMainWidget->setSize(mainWidgetSize);
+
+
+ MyGUI::IntCoord messageWidgetCoord;
+ messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
+ messageWidgetCoord.top = textPadding;
+ mMessageWidget->setCoord(messageWidgetCoord);
+
+ mMessageWidget->setSize(textSize);
+
+ MyGUI::IntCoord buttonCord;
+ MyGUI::IntSize buttonSize(0, buttonHeight);
+
+ int top = textButtonPadding + buttonTopPadding + textSize.height;
+
+ std::vector<MyGUI::Button*>::const_iterator button;
+ for(button = mButtons.begin(); button != mButtons.end(); ++button)
+ {
+ buttonSize.width = (*button)->getTextSize().width + buttonPadding*2;
+ buttonSize.height = (*button)->getTextSize().height + buttonPadding*2;
+
+ buttonCord.top = top;
+ buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2 - 5; // FIXME: -5 is not so nice :/
+
+ (*button)->setCoord(buttonCord);
+ (*button)->setSize(buttonSize);
+
+ top += buttonSize.height + 2*buttonTopPadding;
+ }
+
+ }
+ }
+
+ void InteractiveMessageBox::okayPressed()
+ {
+
+ std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}"));
+ std::vector<MyGUI::Button*>::const_iterator button;
+ for(button = mButtons.begin(); button != mButtons.end(); ++button)
+ {
+ if(Misc::StringUtils::lowerCase((*button)->getCaption()) == ok)
+ {
+ buttonActivated(*button);
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ break;
+ }
+ }
+
+ }
+
+ void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed)
+ {
+ buttonActivated (pressed);
+ }
+
+ void InteractiveMessageBox::buttonActivated (MyGUI::Widget* pressed)
+ {
+ mMarkedToDelete = true;
+ int index = 0;
+ std::vector<MyGUI::Button*>::const_iterator button;
+ for(button = mButtons.begin(); button != mButtons.end(); ++button)
+ {
+ if(*button == pressed)
+ {
+ mButtonPressed = index;
+ mMessageBoxManager.onButtonPressed(mButtonPressed);
+ return;
+ }
+ index++;
+ }
+ }
+
+ int InteractiveMessageBox::readPressedButton ()
+ {
+ int pressed = mButtonPressed;
+ mButtonPressed = -1;
+ return pressed;
+ }
+
+}
diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp
new file mode 100644
index 0000000000..4ef645f5e6
--- /dev/null
+++ b/apps/openmw/mwgui/messagebox.hpp
@@ -0,0 +1,105 @@
+#ifndef MWGUI_MESSAGE_BOX_H
+#define MWGUI_MESSAGE_BOX_H
+
+#include "windowbase.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+
+#undef MessageBox
+
+namespace MyGUI
+{
+ class Widget;
+ class Button;
+ class EditBox;
+}
+
+namespace MWGui
+{
+ class InteractiveMessageBox;
+ class MessageBoxManager;
+ class MessageBox;
+
+ struct MessageBoxManagerTimer {
+ float current;
+ float max;
+ MessageBox *messageBox;
+ };
+
+ class MessageBoxManager
+ {
+ public:
+ 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);
+
+ void okayPressed();
+ int readPressedButton ();
+
+ typedef MyGUI::delegates::CMultiDelegate1<int> EventHandle_Int;
+
+ // Note: this delegate unassigns itself after it was fired, i.e. works once.
+ EventHandle_Int eventButtonPressed;
+
+ void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); }
+
+ private:
+ std::vector<MessageBox*> mMessageBoxes;
+ InteractiveMessageBox* mInterMessageBoxe;
+ MessageBox* mStaticMessageBox;
+ std::vector<MessageBoxManagerTimer> mTimers;
+ float mMessageBoxSpeed;
+ };
+
+ class MessageBox : public OEngine::GUI::Layout
+ {
+ public:
+ MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message);
+ void setMessage (const std::string& message);
+ int getHeight ();
+ void update (int height);
+
+ bool mMarkedToDelete;
+
+ protected:
+ MessageBoxManager& mMessageBoxManager;
+ int mHeight;
+ const std::string& mMessage;
+ MyGUI::EditBox* mMessageWidget;
+ int mFixedWidth;
+ int mBottomPadding;
+ int mNextBoxPadding;
+ };
+
+ class InteractiveMessageBox : public WindowModal
+ {
+ public:
+ InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons);
+ void okayPressed ();
+ void mousePressed (MyGUI::Widget* _widget);
+ int readPressedButton ();
+
+ bool mMarkedToDelete;
+
+ private:
+ void buttonActivated (MyGUI::Widget* _widget);
+
+ MessageBoxManager& mMessageBoxManager;
+ MyGUI::EditBox* mMessageWidget;
+ MyGUI::Widget* mButtonsWidget;
+ std::vector<MyGUI::Button*> mButtons;
+
+ int mTextButtonPadding;
+ int mButtonPressed;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp
new file mode 100644
index 0000000000..879fcb483f
--- /dev/null
+++ b/apps/openmw/mwgui/mode.hpp
@@ -0,0 +1,68 @@
+#ifndef MWGUI_MODE_H
+#define MWGUI_MODE_H
+
+namespace MWGui
+{
+ enum GuiMode
+ {
+ GM_None,
+ GM_Settings, // Settings window
+ GM_Inventory, // Inventory mode
+ GM_Container,
+ GM_Companion,
+ GM_MainMenu, // Main menu mode
+
+ GM_Console, // Console mode
+ GM_Journal, // Journal mode
+
+ GM_Scroll, // Read scroll
+ GM_Book, // Read book
+ GM_Alchemy, // Make potions
+ GM_Repair,
+
+ GM_Dialogue, // NPC interaction
+ GM_Barter,
+ GM_Rest,
+ GM_RestBed,
+ GM_SpellBuying,
+ GM_Travel,
+ GM_SpellCreation,
+ GM_Enchanting,
+ GM_Training,
+ GM_MerchantRepair,
+
+ GM_Levelup,
+
+ // Startup character creation dialogs
+ GM_Name,
+ GM_Race,
+ GM_Birth,
+ GM_Class,
+ GM_ClassGenerate,
+ GM_ClassPick,
+ GM_ClassCreate,
+ GM_Review,
+
+ GM_Loading,
+ GM_LoadingWallpaper,
+
+ GM_QuickKeysMenu,
+
+ GM_Video
+ };
+
+ // Windows shown in inventory mode
+ enum GuiWindow
+ {
+ GW_None = 0,
+
+ GW_Map = 0x01,
+ GW_Inventory = 0x02,
+ GW_Magic = 0x04,
+ GW_Stats = 0x08,
+
+ GW_ALL = 0xFF
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp
new file mode 100644
index 0000000000..16be5f6cca
--- /dev/null
+++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp
@@ -0,0 +1,55 @@
+#include "pickpocketitemmodel.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwworld/class.hpp"
+
+namespace MWGui
+{
+
+ PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel)
+ {
+ mSourceModel = sourceModel;
+ int chance = MWWorld::Class::get(thief).getNpcStats(thief).getSkill(ESM::Skill::Sneak).getModified();
+
+ mSourceModel->update();
+ for (size_t i = 0; i<mSourceModel->getItemCount(); ++i)
+ {
+ if (std::rand() / static_cast<float>(RAND_MAX) * 100 > chance)
+ mHiddenItems.push_back(mSourceModel->getItem(i));
+ }
+ }
+
+ ItemStack PickpocketItemModel::getItem (ModelIndex index)
+ {
+ if (index < 0)
+ throw std::runtime_error("Invalid index supplied");
+ if (mItems.size() <= static_cast<size_t>(index))
+ throw std::runtime_error("Item index out of range");
+ return mItems[index];
+ }
+
+ size_t PickpocketItemModel::getItemCount()
+ {
+ return mItems.size();
+ }
+
+ void PickpocketItemModel::update()
+ {
+ mSourceModel->update();
+ mItems.clear();
+ for (size_t i = 0; i<mSourceModel->getItemCount(); ++i)
+ {
+ const ItemStack& item = mSourceModel->getItem(i);
+ if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end()
+ && item.mType != ItemStack::Type_Equipped)
+ mItems.push_back(item);
+ }
+ }
+
+ void PickpocketItemModel::removeItem (const ItemStack &item, size_t count)
+ {
+ ProxyItemModel::removeItem(item, count);
+ /// \todo check if player is detected
+ }
+
+}
diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp
new file mode 100644
index 0000000000..090d48d0e7
--- /dev/null
+++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp
@@ -0,0 +1,26 @@
+#ifndef MWGUI_PICKPOCKET_ITEM_MODEL_H
+#define MWGUI_PICKPOCKET_ITEM_MODEL_H
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ /// @brief The pickpocket item model randomly hides item stacks based on a specified chance. Equipped items are always hidden.
+ class PickpocketItemModel : public ProxyItemModel
+ {
+ public:
+ PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel);
+ virtual ItemStack getItem (ModelIndex index);
+ virtual size_t getItemCount();
+ virtual void update();
+ virtual void removeItem (const ItemStack& item, size_t count);
+
+ private:
+ std::vector<ItemStack> mHiddenItems;
+ std::vector<ItemStack> mItems;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp
new file mode 100644
index 0000000000..5f749d3d3d
--- /dev/null
+++ b/apps/openmw/mwgui/quickkeysmenu.cpp
@@ -0,0 +1,626 @@
+#include "quickkeysmenu.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwmechanics/spellsuccess.hpp"
+#include "../mwgui/inventorywindow.hpp"
+#include "../mwgui/bookwindow.hpp"
+#include "../mwgui/scrollwindow.hpp"
+
+#include "windowmanagerimp.hpp"
+#include "itemselection.hpp"
+
+
+namespace
+{
+ bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
+ {
+ int cmp = MWWorld::Class::get(left).getName(left).compare(
+ MWWorld::Class::get(right).getName(right));
+ return cmp < 0;
+ }
+
+ bool sortSpells(const std::string& left, const std::string& right)
+ {
+ const MWWorld::Store<ESM::Spell> &spells =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>();
+
+ const ESM::Spell* a = spells.find(left);
+ const ESM::Spell* b = spells.find(right);
+
+ int cmp = a->mName.compare(b->mName);
+ return cmp < 0;
+ }
+}
+
+namespace MWGui
+{
+
+ QuickKeysMenu::QuickKeysMenu()
+ : WindowBase("openmw_quickkeys_menu.layout")
+ , mAssignDialog(0)
+ , mItemSelectionDialog(0)
+ , mMagicSelectionDialog(0)
+ , mSelectedIndex(-1)
+ {
+ getWidget(mOkButton, "OKButton");
+ getWidget(mInstructionLabel, "InstructionLabel");
+
+ mMainWidget->setSize(mMainWidget->getWidth(),
+ mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight()));
+
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked);
+ center();
+
+
+ for (int i = 0; i < 10; ++i)
+ {
+ MyGUI::Button* button;
+ getWidget(button, "QuickKey" + boost::lexical_cast<std::string>(i+1));
+
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
+
+ unassign(button, i);
+
+ mQuickKeyButtons.push_back(button);
+ }
+ }
+
+ QuickKeysMenu::~QuickKeysMenu()
+ {
+ delete mAssignDialog;
+ delete mItemSelectionDialog;
+ delete mMagicSelectionDialog;
+ }
+
+ void QuickKeysMenu::unassign(MyGUI::Widget* key, int index)
+ {
+ while (key->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (key->getChildAt(0));
+
+ key->setUserData(Type_Unassigned);
+
+ MyGUI::TextBox* textBox = key->createWidgetReal<MyGUI::TextBox>("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default);
+ textBox->setTextAlign (MyGUI::Align::Center);
+ textBox->setCaption (boost::lexical_cast<std::string>(index+1));
+ textBox->setNeedMouseFocus (false);
+ }
+
+ void QuickKeysMenu::onQuickKeyButtonClicked(MyGUI::Widget* sender)
+ {
+ int index = -1;
+ for (int i = 0; i < 10; ++i)
+ {
+ if (sender == mQuickKeyButtons[i] || sender->getParent () == mQuickKeyButtons[i])
+ {
+ index = i;
+ break;
+ }
+ }
+ assert(index != -1);
+ mSelectedIndex = index;
+
+ {
+ // open assign dialog
+ if (!mAssignDialog)
+ mAssignDialog = new QuickKeysMenuAssign(this);
+ mAssignDialog->setVisible (true);
+ }
+ }
+
+ void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu);
+ }
+
+
+ void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender)
+ {
+ if (!mItemSelectionDialog )
+ {
+ mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}");
+ mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem);
+ mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel);
+ }
+ mItemSelectionDialog->setVisible(true);
+ mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ mAssignDialog->setVisible (false);
+ }
+
+ void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender)
+ {
+ if (!mMagicSelectionDialog )
+ {
+ mMagicSelectionDialog = new MagicSelectionDialog(this);
+ }
+ mMagicSelectionDialog->setVisible(true);
+
+ mAssignDialog->setVisible (false);
+ }
+
+ void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender)
+ {
+ unassign(mQuickKeyButtons[mSelectedIndex], mSelectedIndex);
+ mAssignDialog->setVisible (false);
+ }
+
+ void QuickKeysMenu::onCancelButtonClicked(MyGUI::Widget* sender)
+ {
+ mAssignDialog->setVisible (false);
+ }
+
+ void QuickKeysMenu::onAssignItem(MWWorld::Ptr item)
+ {
+ MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex];
+ while (button->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0));
+
+ button->setUserData(Type_Item);
+
+ MyGUI::ImageBox* frame = button->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default);
+ std::string backgroundTex = "textures\\menu_icon_barter.dds";
+ frame->setImageTexture (backgroundTex);
+ frame->setImageCoord (MyGUI::IntCoord(4, 4, 40, 40));
+ frame->setUserString ("ToolTipType", "ItemPtr");
+ frame->setUserData(item);
+ frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
+
+
+ MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture (path);
+ image->setNeedMouseFocus (false);
+
+ mItemSelectionDialog->setVisible(false);
+ }
+
+ void QuickKeysMenu::onAssignItemCancel()
+ {
+ mItemSelectionDialog->setVisible(false);
+ }
+
+ void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item)
+ {
+ MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex];
+ while (button->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0));
+
+ button->setUserData(Type_MagicItem);
+
+ MyGUI::ImageBox* frame = button->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default);
+ std::string backgroundTex = "textures\\menu_icon_select_magic_magic.dds";
+ frame->setImageTexture (backgroundTex);
+ frame->setImageCoord (MyGUI::IntCoord(2, 2, 40, 40));
+ frame->setUserString ("ToolTipType", "ItemPtr");
+ frame->setUserData(item);
+ frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
+
+ MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ image->setImageTexture (path);
+ image->setNeedMouseFocus (false);
+
+ mMagicSelectionDialog->setVisible(false);
+ }
+
+ void QuickKeysMenu::onAssignMagic (const std::string& spellId)
+ {
+ MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex];
+ while (button->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0));
+
+ button->setUserData(Type_Magic);
+
+ MyGUI::ImageBox* frame = button->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default);
+ std::string backgroundTex = "textures\\menu_icon_select_magic.dds";
+ frame->setImageTexture (backgroundTex);
+ frame->setImageCoord (MyGUI::IntCoord(2, 2, 40, 40));
+ frame->setUserString ("ToolTipType", "Spell");
+ frame->setUserString ("Spell", spellId);
+ frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked);
+
+ MyGUI::ImageBox* image = frame->createWidget<MyGUI::ImageBox>("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default);
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ // use the icon of the first effect
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(spellId);
+
+ const ESM::MagicEffect* effect =
+ esmStore.get<ESM::MagicEffect>().find(spell->mEffects.mList.front().mEffectID);
+
+ std::string path = effect->mIcon;
+ int slashPos = path.find("\\");
+ path.insert(slashPos+1, "b_");
+ path = std::string("icons\\") + path;
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+
+ image->setImageTexture (path);
+ image->setNeedMouseFocus (false);
+
+ mMagicSelectionDialog->setVisible(false);
+ }
+
+ void QuickKeysMenu::onAssignMagicCancel ()
+ {
+ mMagicSelectionDialog->setVisible(false);
+ }
+
+ void QuickKeysMenu::activateQuickKey(int index)
+ {
+ MyGUI::Button* button = mQuickKeyButtons[index-1];
+
+ QuickKeyType type = *button->getUserData<QuickKeyType>();
+
+ 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_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)));
+ }
+ else if (type == Type_Item)
+ {
+ 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());
+
+ // this is necessary for books/scrolls: if they are already in the player's inventory,
+ // the "Take" button should not be visible.
+ // NOTE: the take button is "reset" when the window opens, so we can safely do the following
+ // without screwing up future book windows
+ MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false);
+ MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false);
+
+ // since we changed equipping status, update the inventory window
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
+ }
+ else if (type == Type_MagicItem)
+ {
+ 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)
+ {
+ if (*it == item)
+ {
+ break;
+ }
+ }
+ assert(it != store.end());
+
+ // equip, if it can be equipped
+ if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
+ {
+ // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
+
+ 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);
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------------------
+
+ QuickKeysMenuAssign::QuickKeysMenuAssign (QuickKeysMenu* parent)
+ : WindowModal("openmw_quickkeys_menu_assign.layout")
+ , mParent(parent)
+ {
+ getWidget(mLabel, "Label");
+ getWidget(mItemButton, "ItemButton");
+ getWidget(mMagicButton, "MagicButton");
+ getWidget(mUnassignButton, "UnassignButton");
+ getWidget(mCancelButton, "CancelButton");
+
+ mItemButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onItemButtonClicked);
+ mMagicButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onMagicButtonClicked);
+ mUnassignButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onUnassignButtonClicked);
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(mParent, &QuickKeysMenu::onCancelButtonClicked);
+
+
+ int maxWidth = mItemButton->getTextSize ().width + 24;
+ maxWidth = std::max(maxWidth, mMagicButton->getTextSize ().width + 24);
+ maxWidth = std::max(maxWidth, mUnassignButton->getTextSize ().width + 24);
+ maxWidth = std::max(maxWidth, mCancelButton->getTextSize ().width + 24);
+
+ mMainWidget->setSize(maxWidth + 24, mMainWidget->getHeight());
+ mLabel->setSize(maxWidth, mLabel->getHeight());
+
+ mItemButton->setCoord((maxWidth - mItemButton->getTextSize().width-24)/2 + 8,
+ mItemButton->getTop(),
+ mItemButton->getTextSize().width + 24,
+ mItemButton->getHeight());
+ mMagicButton->setCoord((maxWidth - mMagicButton->getTextSize().width-24)/2 + 8,
+ mMagicButton->getTop(),
+ mMagicButton->getTextSize().width + 24,
+ mMagicButton->getHeight());
+ mUnassignButton->setCoord((maxWidth - mUnassignButton->getTextSize().width-24)/2 + 8,
+ mUnassignButton->getTop(),
+ mUnassignButton->getTextSize().width + 24,
+ mUnassignButton->getHeight());
+ mCancelButton->setCoord((maxWidth - mCancelButton->getTextSize().width-24)/2 + 8,
+ mCancelButton->getTop(),
+ mCancelButton->getTextSize().width + 24,
+ mCancelButton->getHeight());
+
+ center();
+ }
+
+
+ // ---------------------------------------------------------------------------------------------------------
+
+ MagicSelectionDialog::MagicSelectionDialog(QuickKeysMenu* parent)
+ : WindowModal("openmw_magicselection_dialog.layout")
+ , mParent(parent)
+ , mWidth(0)
+ , mHeight(0)
+ {
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mMagicList, "MagicList");
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onCancelButtonClicked);
+
+ center();
+ }
+
+ void MagicSelectionDialog::onCancelButtonClicked (MyGUI::Widget *sender)
+ {
+ mParent->onAssignMagicCancel ();
+ }
+
+ void MagicSelectionDialog::open ()
+ {
+ WindowModal::open();
+
+ while (mMagicList->getChildCount ())
+ MyGUI::Gui::getInstance ().destroyWidget (mMagicList->getChildAt (0));
+
+ mHeight = 0;
+
+ const int spellHeight = 18;
+
+ 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();
+
+ /// \todo lots of copy&pasted code from SpellWindow
+
+ // retrieve powers & spells, sort by name
+ std::vector<std::string> spellList;
+
+ 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();
+
+ std::vector<std::string> powers;
+ std::vector<std::string>::iterator it = spellList.begin();
+ while (it != spellList.end())
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+ if (spell->mData.mType == ESM::Spell::ST_Power)
+ {
+ powers.push_back(*it);
+ it = spellList.erase(it);
+ }
+ else if (spell->mData.mType == ESM::Spell::ST_Ability
+ || spell->mData.mType == ESM::Spell::ST_Blight
+ || spell->mData.mType == ESM::Spell::ST_Curse
+ || spell->mData.mType == ESM::Spell::ST_Disease)
+ {
+ it = spellList.erase(it);
+ }
+ else
+ ++it;
+ }
+ std::sort(powers.begin(), powers.end(), sortSpells);
+ std::sort(spellList.begin(), spellList.end(), sortSpells);
+
+ // retrieve usable magic items & sort
+ std::vector<MWWorld::Ptr> items;
+ for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
+ {
+ std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it);
+ if (enchantId != "")
+ {
+ // only add items with "Cast once" or "Cast on use"
+ const ESM::Enchantment* enchant =
+ esmStore.get<ESM::Enchantment>().find(enchantId);
+
+ int type = enchant->mData.mType;
+ if (type != ESM::Enchantment::CastOnce
+ && type != ESM::Enchantment::WhenUsed)
+ continue;
+
+ items.push_back(*it);
+ }
+ }
+ std::sort(items.begin(), items.end(), sortItems);
+
+
+ int height = estimateHeight(items.size() + powers.size() + spellList.size());
+ bool scrollVisible = height > mMagicList->getHeight();
+ mWidth = mMagicList->getWidth() - scrollVisible * 18;
+
+
+ // powers
+ addGroup("#{sPowers}", "");
+
+ for (std::vector<std::string>::const_iterator it = powers.begin(); it != powers.end(); ++it)
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+ MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>("SpellText",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(spell->mName);
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserString("ToolTipType", "Spell");
+ t->setUserString("Spell", *it);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected);
+
+ mHeight += spellHeight;
+ }
+
+ // other spells
+ addGroup("#{sSpells}", "");
+ for (std::vector<std::string>::const_iterator it = spellList.begin(); it != spellList.end(); ++it)
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+ MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>("SpellText",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(spell->mName);
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserString("ToolTipType", "Spell");
+ t->setUserString("Spell", *it);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected);
+
+ mHeight += spellHeight;
+ }
+
+
+ // enchanted items
+ addGroup("#{sMagicItem}", "");
+
+ for (std::vector<MWWorld::Ptr>::const_iterator it = items.begin(); it != items.end(); ++it)
+ {
+ MWWorld::Ptr item = *it;
+
+ // check if the item is currently equipped (will display in a different color)
+ bool equipped = false;
+ for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
+ {
+ if (store.getSlot(i) != store.end() && *store.getSlot(i) == item)
+ {
+ equipped = true;
+ break;
+ }
+ }
+
+ MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(MWWorld::Class::get(item).getName(item));
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserData(item);
+ t->setUserString("ToolTipType", "ItemPtr");
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onEnchantedItemSelected);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
+
+ mHeight += spellHeight;
+ }
+
+
+ mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight));
+
+ }
+
+ void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2)
+ {
+ if (mMagicList->getChildCount() > 0)
+ {
+ MyGUI::ImageBox* separator = mMagicList->createWidget<MyGUI::ImageBox>("MW_HLine",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, 18),
+ MyGUI::Align::Left | MyGUI::Align::Top);
+ separator->setNeedMouseFocus(false);
+ mHeight += 18;
+ }
+
+ MyGUI::TextBox* groupWidget = mMagicList->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, mHeight, mWidth, 24),
+ MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ groupWidget->setCaptionWithReplacing(label);
+ groupWidget->setTextAlign(MyGUI::Align::Left);
+ groupWidget->setNeedMouseFocus(false);
+
+ if (label2 != "")
+ {
+ MyGUI::TextBox* groupWidget2 = mMagicList->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, mHeight, mWidth-4, 24),
+ MyGUI::Align::Left | MyGUI::Align::Top);
+ groupWidget2->setCaptionWithReplacing(label2);
+ groupWidget2->setTextAlign(MyGUI::Align::Right);
+ groupWidget2->setNeedMouseFocus(false);
+ }
+
+ mHeight += 24;
+ }
+
+
+ void MagicSelectionDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mMagicList->getViewOffset().top + _rel*0.3 > 0)
+ mMagicList->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mMagicList->setViewOffset(MyGUI::IntPoint(0, mMagicList->getViewOffset().top + _rel*0.3));
+ }
+
+ void MagicSelectionDialog::onEnchantedItemSelected(MyGUI::Widget* _sender)
+ {
+ MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
+
+ mParent->onAssignMagicItem (item);
+ }
+
+ void MagicSelectionDialog::onSpellSelected(MyGUI::Widget* _sender)
+ {
+ mParent->onAssignMagic (_sender->getUserString("Spell"));
+ }
+
+ int MagicSelectionDialog::estimateHeight(int numSpells) const
+ {
+ int height = 0;
+ height += 24 * 3 + 18 * 2; // group headings
+ height += numSpells * 18;
+ return height;
+ }
+
+}
diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp
new file mode 100644
index 0000000000..058519ece4
--- /dev/null
+++ b/apps/openmw/mwgui/quickkeysmenu.hpp
@@ -0,0 +1,106 @@
+#ifndef MWGUI_QUICKKEYS_H
+#define MWGUI_QUICKKEYS_H
+
+#include "../mwworld/ptr.hpp"
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+
+ class QuickKeysMenuAssign;
+ class ItemSelectionDialog;
+ class MagicSelectionDialog;
+
+ class QuickKeysMenu : public WindowBase
+ {
+ public:
+ QuickKeysMenu();
+ ~QuickKeysMenu();
+
+
+ void onItemButtonClicked(MyGUI::Widget* sender);
+ void onMagicButtonClicked(MyGUI::Widget* sender);
+ void onUnassignButtonClicked(MyGUI::Widget* sender);
+ void onCancelButtonClicked(MyGUI::Widget* sender);
+
+ void onAssignItem (MWWorld::Ptr item);
+ void onAssignItemCancel ();
+ void onAssignMagicItem (MWWorld::Ptr item);
+ void onAssignMagic (const std::string& spellId);
+ void onAssignMagicCancel ();
+
+ void activateQuickKey(int index);
+
+ enum QuickKeyType
+ {
+ Type_Item,
+ Type_Magic,
+ Type_MagicItem,
+ Type_Unassigned
+ };
+
+
+ private:
+ MyGUI::EditBox* mInstructionLabel;
+ MyGUI::Button* mOkButton;
+
+ std::vector<MyGUI::Button*> mQuickKeyButtons;
+
+ QuickKeysMenuAssign* mAssignDialog;
+ ItemSelectionDialog* mItemSelectionDialog;
+ MagicSelectionDialog* mMagicSelectionDialog;
+
+ int mSelectedIndex;
+
+
+ void onQuickKeyButtonClicked(MyGUI::Widget* sender);
+ void onOkButtonClicked(MyGUI::Widget* sender);
+
+ void unassign(MyGUI::Widget* key, int index);
+ };
+
+ class QuickKeysMenuAssign : public WindowModal
+ {
+ public:
+ QuickKeysMenuAssign(QuickKeysMenu* parent);
+
+ private:
+ MyGUI::TextBox* mLabel;
+ MyGUI::Button* mItemButton;
+ MyGUI::Button* mMagicButton;
+ MyGUI::Button* mUnassignButton;
+ MyGUI::Button* mCancelButton;
+
+ QuickKeysMenu* mParent;
+ };
+
+ class MagicSelectionDialog : public WindowModal
+ {
+ public:
+ MagicSelectionDialog(QuickKeysMenu* parent);
+
+ virtual void open();
+
+ private:
+ MyGUI::Button* mCancelButton;
+ MyGUI::ScrollView* mMagicList;
+
+ int mWidth;
+ int mHeight;
+
+ QuickKeysMenu* mParent;
+
+ void onCancelButtonClicked (MyGUI::Widget* sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onEnchantedItemSelected(MyGUI::Widget* _sender);
+ void onSpellSelected(MyGUI::Widget* _sender);
+ int estimateHeight(int numSpells) const;
+
+
+ void addGroup(const std::string& label, const std::string& label2);
+ };
+}
+
+
+#endif
diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp
new file mode 100644
index 0000000000..2c73226e3d
--- /dev/null
+++ b/apps/openmw/mwgui/race.cpp
@@ -0,0 +1,400 @@
+#include "race.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "tooltips.hpp"
+
+namespace
+{
+ int wrap(int index, int max)
+ {
+ if (index < 0)
+ return max - 1;
+ else if (index >= max)
+ return 0;
+ else
+ return index;
+ }
+}
+
+namespace MWGui
+{
+
+ RaceDialog::RaceDialog()
+ : WindowModal("openmw_chargen_race.layout")
+ , mGenderIndex(0)
+ , mFaceIndex(0)
+ , mHairIndex(0)
+ , mCurrentAngle(0)
+ , mPreviewDirty(true)
+ , mPreview(NULL)
+ {
+ // Centre dialog
+ center();
+
+ setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance"));
+ getWidget(mPreviewImage, "PreviewImage");
+
+ getWidget(mHeadRotate, "HeadRotate");
+ mHeadRotate->setScrollRange(50);
+ mHeadRotate->setScrollPosition(25);
+ mHeadRotate->setScrollViewPage(10);
+ mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate);
+
+ // Set up next/previous buttons
+ MyGUI::Button *prevButton, *nextButton;
+
+ setText("GenderChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu2", "Change Sex"));
+ getWidget(prevButton, "PrevGenderButton");
+ getWidget(nextButton, "NextGenderButton");
+ prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousGender);
+ nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextGender);
+
+ setText("FaceChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu3", "Change Face"));
+ getWidget(prevButton, "PrevFaceButton");
+ getWidget(nextButton, "NextFaceButton");
+ prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousFace);
+ nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextFace);
+
+ setText("HairChoiceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu4", "Change Hair"));
+ getWidget(prevButton, "PrevHairButton");
+ getWidget(nextButton, "NextHairButton");
+ prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair);
+ nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextHair);
+
+ setText("RaceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu5", "Race"));
+ getWidget(mRaceList, "RaceList");
+ mRaceList->setScrollVisible(true);
+ mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace);
+ mRaceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace);
+ mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace);
+
+ setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus"));
+ getWidget(mSkillList, "SkillList");
+ setText("SpellPowerT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials"));
+ getWidget(mSpellPowerList, "SpellPowerList");
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked);
+
+ updateRaces();
+ updateSkills();
+ updateSpellPowers();
+ }
+
+ void RaceDialog::setNextButtonShow(bool shown)
+ {
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ if (shown)
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
+ else
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ }
+
+ void RaceDialog::open()
+ {
+ WindowModal::open();
+
+ updateRaces();
+ updateSkills();
+ updateSpellPowers();
+
+ mPreview = new MWRender::RaceSelectionPreview();
+ mPreview->setup();
+ mPreview->update (0);
+
+ const ESM::NPC proto = mPreview->getPrototype();
+ setRaceId(proto.mRace);
+ recountParts();
+
+ std::string index = proto.mHead.substr(proto.mHead.size() - 2, 2);
+ mFaceIndex = boost::lexical_cast<int>(index) - 1;
+
+ index = proto.mHair.substr(proto.mHair.size() - 2, 2);
+ mHairIndex = boost::lexical_cast<int>(index) - 1;
+
+ mPreviewImage->setImageTexture ("CharacterHeadPreview");
+
+ mPreviewDirty = true;
+ }
+
+
+ void RaceDialog::setRaceId(const std::string &raceId)
+ {
+ mCurrentRaceId = raceId;
+ mRaceList->setIndexSelected(MyGUI::ITEM_NONE);
+ size_t count = mRaceList->getItemCount();
+ for (size_t i = 0; i < count; ++i)
+ {
+ if (boost::iequals(*mRaceList->getItemDataAt<std::string>(i), raceId))
+ {
+ mRaceList->setIndexSelected(i);
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ break;
+ }
+ }
+
+ updateSkills();
+ updateSpellPowers();
+ }
+
+ void RaceDialog::close()
+ {
+ delete mPreview;
+ mPreview = 0;
+ }
+
+ // widget controls
+
+ void RaceDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
+ void RaceDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position)
+ {
+ float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2;
+ float diff = angle - mCurrentAngle;
+ mPreview->update (diff);
+ mPreviewDirty = true;
+ mCurrentAngle += diff;
+ }
+
+ void RaceDialog::onSelectPreviousGender(MyGUI::Widget*)
+ {
+ mGenderIndex = wrap(mGenderIndex - 1, 2);
+
+ recountParts();
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectNextGender(MyGUI::Widget*)
+ {
+ mGenderIndex = wrap(mGenderIndex + 1, 2);
+
+ recountParts();
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectPreviousFace(MyGUI::Widget*)
+ {
+ mFaceIndex = wrap(mFaceIndex - 1, mAvailableHeads.size());
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectNextFace(MyGUI::Widget*)
+ {
+ mFaceIndex = wrap(mFaceIndex + 1, mAvailableHeads.size());
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectPreviousHair(MyGUI::Widget*)
+ {
+ mHairIndex = wrap(mHairIndex - 1, mAvailableHairs.size());
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectNextHair(MyGUI::Widget*)
+ {
+ mHairIndex = wrap(mHairIndex + 1, mAvailableHairs.size());
+ updatePreview();
+ }
+
+ void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index)
+ {
+ if (_index == MyGUI::ITEM_NONE)
+ return;
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ const std::string *raceId = mRaceList->getItemDataAt<std::string>(_index);
+ if (boost::iequals(mCurrentRaceId, *raceId))
+ return;
+
+ mCurrentRaceId = *raceId;
+
+ recountParts();
+
+ updatePreview();
+ updateSkills();
+ updateSpellPowers();
+ }
+
+ void RaceDialog::getBodyParts (int part, std::vector<std::string>& out)
+ {
+ out.clear();
+ const MWWorld::Store<ESM::BodyPart> &store =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>();
+
+ for (MWWorld::Store<ESM::BodyPart>::iterator it = store.begin(); it != store.end(); ++it)
+ {
+ const ESM::BodyPart& bodypart = *it;
+ if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
+ continue;
+ if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
+ continue;
+ if (bodypart.mData.mPart != static_cast<ESM::BodyPart::MeshPart>(part))
+ continue;
+ if (mGenderIndex != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
+ continue;
+ bool firstPerson = (bodypart.mId.size() >= 3)
+ && bodypart.mId[bodypart.mId.size()-3] == '1'
+ && bodypart.mId[bodypart.mId.size()-2] == 's'
+ && bodypart.mId[bodypart.mId.size()-1] == 't';
+ if (firstPerson)
+ continue;
+ if (Misc::StringUtils::ciEqual(bodypart.mRace, mCurrentRaceId))
+ out.push_back(bodypart.mId);
+ }
+ }
+
+ void RaceDialog::recountParts()
+ {
+ getBodyParts(ESM::BodyPart::MP_Hair, mAvailableHairs);
+ getBodyParts(ESM::BodyPart::MP_Head, mAvailableHeads);
+
+ mFaceIndex = 0;
+ mHairIndex = 0;
+ }
+
+ // update widget content
+
+ void RaceDialog::updatePreview()
+ {
+ ESM::NPC record = mPreview->getPrototype();
+ record.mRace = mCurrentRaceId;
+ record.setIsMale(mGenderIndex == 0);
+
+ record.mHead = mAvailableHeads[mFaceIndex];
+ record.mHair = mAvailableHairs[mHairIndex];
+
+ mPreview->setPrototype(record);
+ mPreviewDirty = true;
+ }
+
+ void RaceDialog::doRenderUpdate()
+ {
+ if (mPreviewDirty)
+ {
+ mPreview->render();
+ mPreviewDirty = false;
+ }
+ }
+
+ void RaceDialog::updateRaces()
+ {
+ mRaceList->removeAllItems();
+
+ const MWWorld::Store<ESM::Race> &races =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>();
+
+
+ int index = 0;
+ MWWorld::Store<ESM::Race>::iterator it = races.begin();
+ for (; it != races.end(); ++it)
+ {
+ bool playable = it->mData.mFlags & ESM::Race::Playable;
+ if (!playable) // Only display playable races
+ continue;
+
+ mRaceList->addItem(it->mName, it->mId);
+ if (boost::iequals(it->mId, mCurrentRaceId))
+ mRaceList->setIndexSelected(index);
+ ++index;
+ }
+ }
+
+ void RaceDialog::updateSkills()
+ {
+ for (std::vector<MyGUI::Widget*>::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ mSkillItems.clear();
+
+ if (mCurrentRaceId.empty())
+ return;
+
+ Widgets::MWSkillPtr skillWidget;
+ const int lineHeight = 18;
+ MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18);
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Race *race = store.get<ESM::Race>().find(mCurrentRaceId);
+ int count = sizeof(race->mData.mBonus)/sizeof(race->mData.mBonus[0]); // TODO: Find a portable macro for this ARRAYSIZE?
+ for (int i = 0; i < count; ++i)
+ {
+ int skillId = race->mData.mBonus[i].mSkill;
+ if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes
+ continue;
+
+ skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
+ std::string("Skill") + boost::lexical_cast<std::string>(i));
+ skillWidget->setSkillNumber(skillId);
+ skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(race->mData.mBonus[i].mBonus));
+ ToolTips::createSkillToolTip(skillWidget, skillId);
+
+
+ mSkillItems.push_back(skillWidget);
+
+ coord1.top += lineHeight;
+ }
+ }
+
+ void RaceDialog::updateSpellPowers()
+ {
+ for (std::vector<MyGUI::Widget*>::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ mSpellPowerItems.clear();
+
+ if (mCurrentRaceId.empty())
+ return;
+
+ Widgets::MWSpellPtr spellPowerWidget;
+ const int lineHeight = 18;
+ MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18);
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Race *race = store.get<ESM::Race>().find(mCurrentRaceId);
+
+ std::vector<std::string>::const_iterator it = race->mPowers.mList.begin();
+ std::vector<std::string>::const_iterator end = race->mPowers.mList.end();
+ for (int i = 0; it != end; ++it)
+ {
+ const std::string &spellpower = *it;
+ spellPowerWidget = mSpellPowerList->createWidget<Widgets::MWSpell>("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast<std::string>(i));
+ spellPowerWidget->setSpellId(spellpower);
+ spellPowerWidget->setUserString("ToolTipType", "Spell");
+ spellPowerWidget->setUserString("Spell", spellpower);
+
+ mSpellPowerItems.push_back(spellPowerWidget);
+
+ coord.top += lineHeight;
+ ++i;
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp
new file mode 100644
index 0000000000..914ae80964
--- /dev/null
+++ b/apps/openmw/mwgui/race.hpp
@@ -0,0 +1,107 @@
+#ifndef MWGUI_RACE_H
+#define MWGUI_RACE_H
+
+#include "../mwrender/characterpreview.hpp"
+
+#include "windowbase.hpp"
+
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+/*
+ This file contains the dialog for choosing a race.
+ Layout is defined by resources/mygui/openmw_chargen_race.layout.
+ */
+
+namespace MWGui
+{
+ class RaceDialog : public WindowModal
+ {
+ public:
+ RaceDialog();
+
+ enum Gender
+ {
+ GM_Male,
+ GM_Female
+ };
+
+ const ESM::NPC &getResult() const { return mPreview->getPrototype(); }
+ const std::string &getRaceId() const { return mCurrentRaceId; }
+ Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; }
+ // getFace()
+ // getHair()
+
+ void setRaceId(const std::string &raceId);
+ void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; }
+ // setFace()
+ // setHair()
+
+ void setNextButtonShow(bool shown);
+ virtual void open();
+ virtual void close();
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ void doRenderUpdate();
+
+ protected:
+ void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position);
+
+ void onSelectPreviousGender(MyGUI::Widget* _sender);
+ void onSelectNextGender(MyGUI::Widget* _sender);
+
+ void onSelectPreviousFace(MyGUI::Widget* _sender);
+ void onSelectNextFace(MyGUI::Widget* _sender);
+
+ void onSelectPreviousHair(MyGUI::Widget* _sender);
+ void onSelectNextHair(MyGUI::Widget* _sender);
+
+ void onSelectRace(MyGUI::ListBox* _sender, size_t _index);
+
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ private:
+ void updateRaces();
+ void updateSkills();
+ void updateSpellPowers();
+ void updatePreview();
+ void recountParts();
+
+ void getBodyParts (int part, std::vector<std::string>& out);
+
+ std::vector<std::string> mAvailableHeads;
+ std::vector<std::string> mAvailableHairs;
+
+ MyGUI::ImageBox* mPreviewImage;
+ MyGUI::ListBox* mRaceList;
+ MyGUI::ScrollBar* mHeadRotate;
+
+ MyGUI::Widget* mSkillList;
+ std::vector<MyGUI::Widget*> mSkillItems;
+
+ MyGUI::Widget* mSpellPowerList;
+ std::vector<MyGUI::Widget*> mSpellPowerItems;
+
+ int mGenderIndex, mFaceIndex, mHairIndex;
+
+ std::string mCurrentRaceId;
+
+ float mCurrentAngle;
+
+ MWRender::RaceSelectionPreview* mPreview;
+
+ bool mPreviewDirty;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp
new file mode 100644
index 0000000000..86a85be18e
--- /dev/null
+++ b/apps/openmw/mwgui/referenceinterface.cpp
@@ -0,0 +1,36 @@
+#include "referenceinterface.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+
+#include "../mwworld/player.hpp"
+
+namespace MWGui
+{
+ ReferenceInterface::ReferenceInterface()
+ : mCurrentPlayerCell(NULL)
+ {
+ }
+
+ ReferenceInterface::~ReferenceInterface()
+ {
+ }
+
+ void ReferenceInterface::checkReferenceAvailable()
+ {
+ MWWorld::Ptr::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+
+ // check if player has changed cell, or count of the reference has become 0
+ if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL)
+ || (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0))
+ {
+ if (!mPtr.isEmpty())
+ {
+ mPtr = MWWorld::Ptr();
+ onReferenceUnavailable();
+ }
+ }
+
+ mCurrentPlayerCell = playerCell;
+ }
+}
diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp
new file mode 100644
index 0000000000..39574d0f7a
--- /dev/null
+++ b/apps/openmw/mwgui/referenceinterface.hpp
@@ -0,0 +1,30 @@
+#ifndef MWGUI_REFERENCEINTERFACE_H
+#define MWGUI_REFERENCEINTERFACE_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+ /// \brief this class is intended for GUI interfaces that access an MW-Reference
+ /// for example dialogue window accesses an NPC, or Container window accesses a Container
+ /// these classes have to be automatically closed if the reference becomes unavailable
+ /// make sure that checkReferenceAvailable() is called every frame and that onReferenceUnavailable() has been overridden
+ class ReferenceInterface
+ {
+ public:
+ ReferenceInterface();
+ virtual ~ReferenceInterface();
+
+ void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable
+
+ protected:
+ virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable
+
+ MWWorld::Ptr mPtr;
+
+ private:
+ MWWorld::CellStore* mCurrentPlayerCell;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp
new file mode 100644
index 0000000000..0bd4b0995f
--- /dev/null
+++ b/apps/openmw/mwgui/repair.cpp
@@ -0,0 +1,157 @@
+#include "repair.hpp"
+
+#include <boost/lexical_cast.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 "widgets.hpp"
+
+namespace MWGui
+{
+
+Repair::Repair()
+ : WindowBase("openmw_repair.layout")
+{
+ getWidget(mRepairBox, "RepairBox");
+ getWidget(mRepairView, "RepairView");
+ getWidget(mToolBox, "ToolBox");
+ getWidget(mToolIcon, "ToolIcon");
+ getWidget(mUsesLabel, "UsesLabel");
+ getWidget(mQualityLabel, "QualityLabel");
+ getWidget(mCancelButton, "CancelButton");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onCancel);
+}
+
+void Repair::open()
+{
+ center();
+}
+
+void Repair::startRepairItem(const MWWorld::Ptr &item)
+{
+ mRepair.setTool(item);
+
+ std::string path = std::string("icons\\");
+ path += MWWorld::Class::get(item).getInventoryIcon(item);
+ int pos = path.rfind(".");
+ path.erase(pos);
+ path.append(".dds");
+ mToolIcon->setImageTexture (path);
+ mToolIcon->setUserString("ToolTipType", "ItemPtr");
+ mToolIcon->setUserData(item);
+
+ updateRepairView();
+}
+
+void Repair::updateRepairView()
+{
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ mRepair.getTool().get<ESM::Repair>();
+
+ int uses = (mRepair.getTool().getCellRef().mCharge != -1) ? mRepair.getTool().getCellRef().mCharge : ref->mBase->mData.mUses;
+
+ float quality = ref->mBase->mData.mQuality;
+
+ std::stringstream qualityStr;
+ qualityStr << std::setprecision(3) << quality;
+
+ mUsesLabel->setCaptionWithReplacing("#{sUses} " + boost::lexical_cast<std::string>(uses));
+ mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str());
+
+ bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0);
+ mToolBox->setVisible(toolBoxVisible);
+
+ bool toolBoxWasVisible = (mRepairBox->getPosition().top != mToolBox->getPosition().top);
+
+ if (toolBoxVisible && !toolBoxWasVisible)
+ {
+ // shrink
+ mRepairBox->setPosition(mRepairBox->getPosition() + MyGUI::IntPoint(0,mToolBox->getSize().height));
+ mRepairBox->setSize(mRepairBox->getSize() - MyGUI::IntSize(0,mToolBox->getSize().height));
+ }
+ else if (!toolBoxVisible && toolBoxWasVisible)
+ {
+ // expand
+ mRepairBox->setPosition(MyGUI::IntPoint (mRepairBox->getPosition().left, mToolBox->getPosition().top));
+ mRepairBox->setSize(mRepairBox->getSize() + MyGUI::IntSize(0,mToolBox->getSize().height));
+ }
+
+ while (mRepairView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mRepairView->getChildAt(0));
+
+ int currentY = 0;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
+ int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor;
+ for (MWWorld::ContainerStoreIterator iter (store.begin(categories));
+ iter!=store.end(); ++iter)
+ {
+ if (MWWorld::Class::get(*iter).hasItemHealth(*iter))
+ {
+ int maxDurability = MWWorld::Class::get(*iter).getItemMaxHealth(*iter);
+ int durability = (iter->getCellRef().mCharge == -1) ? maxDurability : iter->getCellRef().mCharge;
+ if (maxDurability == durability)
+ continue;
+
+ MyGUI::TextBox* text = mRepairView->createWidget<MyGUI::TextBox> (
+ "SandText", MyGUI::IntCoord(8, currentY, mRepairView->getWidth()-8, 18), MyGUI::Align::Default);
+ text->setCaption(MWWorld::Class::get(*iter).getName(*iter));
+ text->setNeedMouseFocus(false);
+ currentY += 19;
+
+ MyGUI::ImageBox* icon = mRepairView->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, &Repair::onRepairItem);
+ icon->eventMouseWheel += MyGUI::newDelegate(this, &Repair::onMouseWheel);
+
+ Widgets::MWDynamicStatPtr chargeWidget = mRepairView->createWidget<Widgets::MWDynamicStat>
+ ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default);
+ chargeWidget->setValue(durability, maxDurability);
+ chargeWidget->setNeedMouseFocus(false);
+
+ currentY += 32 + 4;
+ }
+ }
+ mRepairView->setCanvasSize (MyGUI::IntSize(mRepairView->getWidth(), std::max(mRepairView->getHeight(), currentY)));
+}
+
+void Repair::onCancel(MyGUI::Widget *sender)
+{
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair);
+}
+
+void Repair::onRepairItem(MyGUI::Widget *sender)
+{
+ if (!mRepair.getTool().getRefData().getCount())
+ return;
+
+ mRepair.repair(*sender->getUserData<MWWorld::Ptr>());
+
+ updateRepairView();
+}
+
+void Repair::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+{
+ if (mRepairView->getViewOffset().top + _rel*0.3 > 0)
+ mRepairView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mRepairView->setViewOffset(MyGUI::IntPoint(0, mRepairView->getViewOffset().top + _rel*0.3));
+}
+
+}
diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp
new file mode 100644
index 0000000000..d0f5c54c4b
--- /dev/null
+++ b/apps/openmw/mwgui/repair.hpp
@@ -0,0 +1,45 @@
+#ifndef OPENMW_MWGUI_REPAIR_H
+#define OPENMW_MWGUI_REPAIR_H
+
+#include "windowbase.hpp"
+
+#include "../mwmechanics/repair.hpp"
+
+namespace MWGui
+{
+
+class Repair : public WindowBase
+{
+public:
+ Repair();
+
+ virtual void open();
+
+ void startRepairItem (const MWWorld::Ptr& item);
+
+protected:
+ MyGUI::Widget* mRepairBox;
+ MyGUI::ScrollView* mRepairView;
+
+ MyGUI::Widget* mToolBox;
+
+ MyGUI::ImageBox* mToolIcon;
+
+ MyGUI::TextBox* mUsesLabel;
+ MyGUI::TextBox* mQualityLabel;
+
+ MyGUI::Button* mCancelButton;
+
+ MWMechanics::Repair mRepair;
+
+ void updateRepairView();
+
+ void onRepairItem (MyGUI::Widget* sender);
+ void onCancel (MyGUI::Widget* sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp
new file mode 100644
index 0000000000..dfc86a547b
--- /dev/null
+++ b/apps/openmw/mwgui/review.cpp
@@ -0,0 +1,366 @@
+#include "review.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "tooltips.hpp"
+
+#undef min
+#undef max
+
+namespace MWGui
+{
+
+ const int ReviewDialog::sLineHeight = 18;
+
+ ReviewDialog::ReviewDialog()
+ : WindowModal("openmw_chargen_review.layout")
+ {
+ // Centre dialog
+ center();
+
+ // Setup static stats
+ MyGUI::Button* button;
+ getWidget(mNameWidget, "NameText");
+ getWidget(button, "NameButton");
+ adjustButtonSize(button);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);;
+
+ getWidget(mRaceWidget, "RaceText");
+ getWidget(button, "RaceButton");
+ adjustButtonSize(button);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);;
+
+ getWidget(mClassWidget, "ClassText");
+ getWidget(button, "ClassButton");
+ adjustButtonSize(button);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);;
+
+ getWidget(mBirthSignWidget, "SignText");
+ getWidget(button, "SignButton");
+ adjustButtonSize(button);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);;
+
+ // Setup dynamic stats
+ getWidget(mHealth, "Health");
+ mHealth->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sHealth", ""));
+ mHealth->setValue(45, 45);
+
+ getWidget(mMagicka, "Magicka");
+ mMagicka->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sMagic", ""));
+ mMagicka->setValue(50, 50);
+
+ getWidget(mFatigue, "Fatigue");
+ mFatigue->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFatigue", ""));
+ mFatigue->setValue(160, 160);
+
+ // Setup attributes
+
+ Widgets::MWAttributePtr attribute;
+ for (int idx = 0; idx < ESM::Attribute::Length; ++idx)
+ {
+ getWidget(attribute, std::string("Attribute") + boost::lexical_cast<std::string>(idx));
+ mAttributeWidgets.insert(std::make_pair(static_cast<int>(ESM::Attribute::sAttributeIds[idx]), attribute));
+ attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]);
+ attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue(0, 0));
+ }
+
+ // Setup skills
+ getWidget(mSkillView, "SkillView");
+ mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+
+ for (int i = 0; i < ESM::Skill::Length; ++i)
+ {
+ mSkillValues.insert(std::make_pair(i, MWMechanics::Stat<float>()));
+ mSkillWidgetMap.insert(std::make_pair(i, static_cast<MyGUI::TextBox*> (0)));
+ }
+
+ MyGUI::Button* backButton;
+ getWidget(backButton, "BackButton");
+ backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked);
+ }
+
+ void ReviewDialog::open()
+ {
+ WindowModal::open();
+ updateSkillArea();
+ }
+
+ void ReviewDialog::setPlayerName(const std::string &name)
+ {
+ mNameWidget->setCaption(name);
+ }
+
+ void ReviewDialog::setRace(const std::string &raceId)
+ {
+ mRaceId = raceId;
+
+ const ESM::Race *race =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(mRaceId);
+ if (race)
+ {
+ ToolTips::createRaceToolTip(mRaceWidget, race);
+ mRaceWidget->setCaption(race->mName);
+ }
+ }
+
+ void ReviewDialog::setClass(const ESM::Class& class_)
+ {
+ mKlass = class_;
+ mClassWidget->setCaption(mKlass.mName);
+ ToolTips::createClassToolTip(mClassWidget, mKlass);
+ }
+
+ void ReviewDialog::setBirthSign(const std::string& signId)
+ {
+ mBirthSignId = signId;
+
+ const ESM::BirthSign *sign =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::BirthSign>().search(mBirthSignId);
+ if (sign)
+ {
+ mBirthSignWidget->setCaption(sign->mName);
+ ToolTips::createBirthsignToolTip(mBirthSignWidget, mBirthSignId);
+ }
+ }
+
+ void ReviewDialog::setHealth(const MWMechanics::DynamicStat<float>& value)
+ {
+ mHealth->setValue(value.getCurrent(), value.getModified());
+ std::string valStr = boost::lexical_cast<std::string>(value.getCurrent()) + "/" + boost::lexical_cast<std::string>(value.getModified());
+ mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
+ }
+
+ void ReviewDialog::setMagicka(const MWMechanics::DynamicStat<float>& value)
+ {
+ mMagicka->setValue(value.getCurrent(), value.getModified());
+ std::string valStr = boost::lexical_cast<std::string>(value.getCurrent()) + "/" + boost::lexical_cast<std::string>(value.getModified());
+ mMagicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr);
+ }
+
+ void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value)
+ {
+ mFatigue->setValue(value.getCurrent(), value.getModified());
+ std::string valStr = boost::lexical_cast<std::string>(value.getCurrent()) + "/" + boost::lexical_cast<std::string>(value.getModified());
+ mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
+ }
+
+ void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat<int>& value)
+ {
+ std::map<int, Widgets::MWAttributePtr>::iterator attr = mAttributeWidgets.find(static_cast<int>(attributeId));
+ if (attr == mAttributeWidgets.end())
+ return;
+
+ attr->second->setAttributeValue(value);
+ }
+
+ void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat<float>& value)
+ {
+ mSkillValues[skillId] = value;
+ MyGUI::TextBox* widget = mSkillWidgetMap[skillId];
+ if (widget)
+ {
+ float modified = value.getModified(), base = value.getBase();
+ std::string text = boost::lexical_cast<std::string>(std::floor(modified));
+ std::string state = "normal";
+ if (modified > base)
+ state = "increased";
+ else if (modified < base)
+ state = "decreased";
+
+ widget->setCaption(text);
+ widget->_setWidgetState(state);
+ }
+
+ }
+
+ void ReviewDialog::configureSkills(const std::vector<int>& major, const std::vector<int>& minor)
+ {
+ mMajorSkills = major;
+ mMinorSkills = minor;
+
+ // Update misc skills with the remaining skills not in major or minor
+ std::set<int> skillSet;
+ std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin()));
+ std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
+ boost::array<ESM::Skill::SkillEnum, ESM::Skill::Length>::const_iterator end = ESM::Skill::sSkillIds.end();
+ mMiscSkills.clear();
+ for (boost::array<ESM::Skill::SkillEnum, ESM::Skill::Length>::const_iterator it = ESM::Skill::sSkillIds.begin(); it != end; ++it)
+ {
+ int skill = *it;
+ if (skillSet.find(skill) == skillSet.end())
+ mMiscSkills.push_back(skill);
+ }
+
+ updateSkillArea();
+ }
+
+ void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::ImageBox* separator = mSkillView->createWidget<MyGUI::ImageBox>("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default);
+ separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+
+ mSkillWidgets.push_back(separator);
+
+ coord1.top += separator->getHeight();
+ coord2.top += separator->getHeight();
+ }
+
+ void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox* groupWidget = mSkillView->createWidget<MyGUI::TextBox>("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default);
+ groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+ groupWidget->setCaption(label);
+ mSkillWidgets.push_back(groupWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+ }
+
+ MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox* skillNameWidget;
+ MyGUI::TextBox* skillValueWidget;
+
+ skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1, MyGUI::Align::Default);
+ skillNameWidget->setCaption(text);
+ skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+
+ skillValueWidget = mSkillView->createWidget<MyGUI::TextBox>("SandTextRight", coord2, MyGUI::Align::Top | MyGUI::Align::Right);
+ skillValueWidget->setCaption(value);
+ skillValueWidget->_setWidgetState(state);
+ skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+
+ mSkillWidgets.push_back(skillNameWidget);
+ mSkillWidgets.push_back(skillValueWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+
+ return skillValueWidget;
+ }
+
+ void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox* skillNameWidget;
+
+ skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default);
+ skillNameWidget->setCaption(text);
+ skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
+
+ mSkillWidgets.push_back(skillNameWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+ }
+
+ void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ // Add a line separator if there are items above
+ if (!mSkillWidgets.empty())
+ {
+ addSeparator(coord1, coord2);
+ }
+
+ addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
+
+ SkillList::const_iterator end = skills.end();
+ for (SkillList::const_iterator it = skills.begin(); it != end; ++it)
+ {
+ int skillId = *it;
+ if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes
+ continue;
+ assert(skillId >= 0 && skillId < ESM::Skill::Length);
+ const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId];
+ const MWMechanics::Stat<float> &stat = mSkillValues.find(skillId)->second;
+ float base = stat.getBase();
+ float modified = stat.getModified();
+
+ std::string state = "normal";
+ if (modified > base)
+ state = "increased";
+ else if (modified < base)
+ state = "decreased";
+ MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), boost::lexical_cast<std::string>(static_cast<int>(modified)), state, coord1, coord2);
+
+ for (int i=0; i<2; ++i)
+ {
+ ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size()-1-i], skillId);
+ }
+
+ mSkillWidgetMap[skillId] = widget;
+ }
+ }
+
+ void ReviewDialog::updateSkillArea()
+ {
+ for (std::vector<MyGUI::Widget*>::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ mSkillWidgets.clear();
+
+ 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);
+
+ if (!mMajorSkills.empty())
+ addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2);
+
+ if (!mMinorSkills.empty())
+ addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2);
+
+ if (!mMiscSkills.empty())
+ addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2);
+
+ mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top));
+ }
+
+ // widget controls
+
+ void ReviewDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ eventDone(this);
+ }
+
+ void ReviewDialog::onBackClicked(MyGUI::Widget* _sender)
+ {
+ eventBack();
+ }
+
+ void ReviewDialog::onNameClicked(MyGUI::Widget* _sender)
+ {
+ eventActivateDialog(NAME_DIALOG);
+ }
+
+ void ReviewDialog::onRaceClicked(MyGUI::Widget* _sender)
+ {
+ eventActivateDialog(RACE_DIALOG);
+ }
+
+ void ReviewDialog::onClassClicked(MyGUI::Widget* _sender)
+ {
+ eventActivateDialog(CLASS_DIALOG);
+ }
+
+ void ReviewDialog::onBirthSignClicked(MyGUI::Widget* _sender)
+ {
+ eventActivateDialog(BIRTHSIGN_DIALOG);
+ }
+
+ void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mSkillView->getViewOffset().top + _rel*0.3 > 0)
+ mSkillView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mSkillView->setViewOffset(MyGUI::IntPoint(0, mSkillView->getViewOffset().top + _rel*0.3));
+ }
+
+}
diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp
new file mode 100644
index 0000000000..1c24fec745
--- /dev/null
+++ b/apps/openmw/mwgui/review.hpp
@@ -0,0 +1,95 @@
+#ifndef MWGUI_REVIEW_H
+#define MWGUI_REVIEW_H
+
+#include "windowbase.hpp"
+#include "widgets.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+/*
+This file contains the dialog for reviewing the generated character.
+Layout is defined by resources/mygui/openmw_chargen_review.layout.
+*/
+
+namespace MWGui
+{
+ class ReviewDialog : public WindowModal
+ {
+ public:
+ enum Dialogs {
+ NAME_DIALOG,
+ RACE_DIALOG,
+ CLASS_DIALOG,
+ BIRTHSIGN_DIALOG
+ };
+ typedef std::vector<int> SkillList;
+
+ ReviewDialog();
+
+ void setPlayerName(const std::string &name);
+ void setRace(const std::string &raceId);
+ void setClass(const ESM::Class& class_);
+ void setBirthSign (const std::string &signId);
+
+ void setHealth(const MWMechanics::DynamicStat<float>& value);
+ void setMagicka(const MWMechanics::DynamicStat<float>& value);
+ void setFatigue(const MWMechanics::DynamicStat<float>& value);
+
+ void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat<int>& value);
+
+ void configureSkills(const SkillList& major, const SkillList& minor);
+ void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat<float>& value);
+
+ virtual void open();
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
+ typedef MyGUI::delegates::CMultiDelegate1<int> EventHandle_Int;
+
+ /** Event : Back button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_Void eventBack;
+
+ EventHandle_Int eventActivateDialog;
+
+ protected:
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onBackClicked(MyGUI::Widget* _sender);
+
+ void onNameClicked(MyGUI::Widget* _sender);
+ void onRaceClicked(MyGUI::Widget* _sender);
+ void onClassClicked(MyGUI::Widget* _sender);
+ void onBirthSignClicked(MyGUI::Widget* _sender);
+
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+ 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);
+ void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+ MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+ void addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+ void updateSkillArea();
+
+ static const int sLineHeight;
+
+ MyGUI::TextBox *mNameWidget, *mRaceWidget, *mClassWidget, *mBirthSignWidget;
+ MyGUI::ScrollView* mSkillView;
+
+ Widgets::MWDynamicStatPtr mHealth, mMagicka, mFatigue;
+
+ std::map<int, Widgets::MWAttributePtr> mAttributeWidgets;
+
+ SkillList mMajorSkills, mMinorSkills, mMiscSkills;
+ std::map<int, MWMechanics::Stat<float> > mSkillValues;
+ std::map<int, MyGUI::TextBox*> mSkillWidgetMap;
+ std::string mName, mRaceId, mBirthSignId;
+ ESM::Class mKlass;
+ std::vector<MyGUI::Widget*> mSkillWidgets; //< Skills and other information
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp
new file mode 100644
index 0000000000..48931b18e7
--- /dev/null
+++ b/apps/openmw/mwgui/scrollwindow.cpp
@@ -0,0 +1,97 @@
+#include "scrollwindow.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/actiontake.hpp"
+#include "../mwworld/player.hpp"
+
+#include "formatting.hpp"
+
+namespace
+{
+ void adjustButton (MWGui::ImageButton* button)
+ {
+ MyGUI::IntSize diff = button->getSize() - button->getRequestedSize();
+ button->setSize(button->getRequestedSize());
+
+ if (button->getAlign().isRight())
+ button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0));
+ }
+}
+
+namespace MWGui
+{
+
+ ScrollWindow::ScrollWindow ()
+ : WindowBase("openmw_scroll.layout")
+ , mTakeButtonShow(true)
+ , mTakeButtonAllowed(true)
+ {
+ getWidget(mTextView, "TextView");
+
+ getWidget(mCloseButton, "CloseButton");
+ mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onCloseButtonClicked);
+
+ getWidget(mTakeButton, "TakeButton");
+ mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onTakeButtonClicked);
+
+ adjustButton(mCloseButton);
+ adjustButton(mTakeButton);
+
+ center();
+ }
+
+ void ScrollWindow::open (MWWorld::Ptr scroll)
+ {
+ // no 3d sounds because the object could be in a container.
+ MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0);
+
+ mScroll = scroll;
+
+ MWWorld::LiveCellRef<ESM::Book> *ref = mScroll.get<ESM::Book>();
+
+ BookTextParser parser;
+ MyGUI::IntSize size = parser.parseScroll(ref->mBase->mText, mTextView, 390);
+
+ if (size.height > mTextView->getSize().height)
+ mTextView->setCanvasSize(MyGUI::IntSize(410, size.height));
+ else
+ mTextView->setCanvasSize(410, mTextView->getSize().height);
+
+ mTextView->setViewOffset(MyGUI::IntPoint(0,0));
+
+ setTakeButtonShow(true);
+ }
+
+ void ScrollWindow::setTakeButtonShow(bool show)
+ {
+ mTakeButtonShow = show;
+ mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed);
+ }
+
+ void ScrollWindow::setInventoryAllowed(bool allowed)
+ {
+ mTakeButtonAllowed = allowed;
+ mTakeButton->setVisible(mTakeButtonShow && mTakeButtonAllowed);
+ }
+
+ void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0);
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll);
+ }
+
+ void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0);
+
+ MWWorld::ActionTake take(mScroll);
+ take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll);
+ }
+}
diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp
new file mode 100644
index 0000000000..5feaff7bf8
--- /dev/null
+++ b/apps/openmw/mwgui/scrollwindow.hpp
@@ -0,0 +1,38 @@
+#ifndef MWGUI_SCROLLWINDOW_H
+#define MWGUI_SCROLLWINDOW_H
+
+#include "windowbase.hpp"
+#include "imagebutton.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+ class ScrollWindow : public WindowBase
+ {
+ public:
+ ScrollWindow ();
+
+ void open (MWWorld::Ptr scroll);
+ void setTakeButtonShow(bool show);
+ void setInventoryAllowed(bool allowed);
+
+ protected:
+ void onCloseButtonClicked (MyGUI::Widget* _sender);
+ void onTakeButtonClicked (MyGUI::Widget* _sender);
+
+ private:
+ MWGui::ImageButton* mCloseButton;
+ MWGui::ImageButton* mTakeButton;
+ MyGUI::ScrollView* mTextView;
+
+ MWWorld::Ptr mScroll;
+
+ bool mTakeButtonShow;
+ bool mTakeButtonAllowed;
+
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp
new file mode 100644
index 0000000000..3dfa17badc
--- /dev/null
+++ b/apps/openmw/mwgui/settingswindow.cpp
@@ -0,0 +1,621 @@
+#include "settingswindow.hpp"
+
+#include <OgreRoot.h>
+#include <OgrePlugin.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/math/common_factor_rt.hpp>
+
+#include <SDL_video.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "confirmationdialog.hpp"
+
+namespace
+{
+ std::string fpsLevelToStr(int level)
+ {
+ if (level == 0)
+ return "#{sOff}";
+ else if (level == 1)
+ return "Basic";
+ else
+ return "Detailed";
+ }
+
+ std::string textureFilteringToStr(const std::string& val)
+ {
+ if (val == "none")
+ return "None";
+ else if (val == "anisotropic")
+ return "Anisotropic";
+ else if (val == "bilinear")
+ return "Bilinear";
+ else
+ return "Trilinear";
+ }
+
+ void parseResolution (int &x, int &y, const std::string& str)
+ {
+ std::vector<std::string> split;
+ boost::algorithm::split (split, str, boost::is_any_of("@(x"));
+ assert (split.size() >= 2);
+ boost::trim(split[0]);
+ boost::trim(split[1]);
+ x = boost::lexical_cast<int> (split[0]);
+ y = boost::lexical_cast<int> (split[1]);
+ }
+
+ bool sortResolutions (std::pair<int, int> left, std::pair<int, int> right)
+ {
+ if (left.first == right.first)
+ return left.second > right.second;
+ return left.first > right.first;
+ }
+
+ std::string getAspect (int x, int y)
+ {
+ int gcd = boost::math::gcd (x, y);
+ int xaspect = x / gcd;
+ int yaspect = y / gcd;
+ // special case: 8 : 5 is usually referred to as 16:10
+ if (xaspect == 8 && yaspect == 5)
+ return "16 : 10";
+ return boost::lexical_cast<std::string>(xaspect) + " : " + boost::lexical_cast<std::string>(yaspect);
+ }
+
+ std::string hlslGlsl ()
+ {
+ return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos) ? "glsl" : "hlsl";
+ }
+
+ bool cgAvailable ()
+ {
+ Ogre::Root::PluginInstanceList list = Ogre::Root::getSingleton ().getInstalledPlugins ();
+ for (Ogre::Root::PluginInstanceList::const_iterator it = list.begin(); it != list.end(); ++it)
+ {
+ if ((*it)->getName() == "Cg Program Manager")
+ return true;
+ }
+ return false;
+ }
+}
+
+namespace MWGui
+{
+ SettingsWindow::SettingsWindow() :
+ WindowBase("openmw_settings_window.layout")
+ {
+ getWidget(mOkButton, "OkButton");
+ getWidget(mBestAttackButton, "BestAttackButton");
+ getWidget(mSubtitlesButton, "SubtitlesButton");
+ getWidget(mCrosshairButton, "CrosshairButton");
+ getWidget(mResolutionList, "ResolutionList");
+ getWidget(mMenuTransparencySlider, "MenuTransparencySlider");
+ getWidget(mToolTipDelaySlider, "ToolTipDelaySlider");
+ getWidget(mViewDistanceSlider, "ViewDistanceSlider");
+ getWidget(mFullscreenButton, "FullscreenButton");
+ getWidget(mVSyncButton, "VSyncButton");
+ getWidget(mFPSButton, "FPSButton");
+ getWidget(mFOVSlider, "FOVSlider");
+ getWidget(mMasterVolumeSlider, "MasterVolume");
+ getWidget(mVoiceVolumeSlider, "VoiceVolume");
+ getWidget(mEffectsVolumeSlider, "EffectsVolume");
+ getWidget(mFootstepsVolumeSlider, "FootstepsVolume");
+ getWidget(mMusicVolumeSlider, "MusicVolume");
+ getWidget(mAnisotropySlider, "AnisotropySlider");
+ getWidget(mTextureFilteringButton, "TextureFilteringButton");
+ getWidget(mAnisotropyLabel, "AnisotropyLabel");
+ getWidget(mAnisotropyBox, "AnisotropyBox");
+ getWidget(mWaterShaderButton, "WaterShaderButton");
+ getWidget(mReflectObjectsButton, "ReflectObjectsButton");
+ getWidget(mReflectActorsButton, "ReflectActorsButton");
+ getWidget(mReflectTerrainButton, "ReflectTerrainButton");
+ getWidget(mShadersButton, "ShadersButton");
+ getWidget(mShaderModeButton, "ShaderModeButton");
+ getWidget(mShadowsEnabledButton, "ShadowsEnabledButton");
+ getWidget(mShadowsLargeDistance, "ShadowsLargeDistance");
+ getWidget(mShadowsTextureSize, "ShadowsTextureSize");
+ getWidget(mActorShadows, "ActorShadows");
+ getWidget(mStaticsShadows, "StaticsShadows");
+ getWidget(mMiscShadows, "MiscShadows");
+ getWidget(mTerrainShadows, "TerrainShadows");
+ getWidget(mControlsBox, "ControlsBox");
+ getWidget(mResetControlsButton, "ResetControlsButton");
+ getWidget(mInvertYButton, "InvertYButton");
+ getWidget(mCameraSensitivitySlider, "CameraSensitivitySlider");
+ getWidget(mRefractionButton, "RefractionButton");
+
+ mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mBestAttackButton->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);
+ mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled);
+ mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mRefractionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ 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);
+ mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled);
+ mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mFOVSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mToolTipDelaySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mViewDistanceSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected);
+ mAnisotropySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+
+ mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mShadowsTextureSize->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSize);
+ mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+ mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
+
+ mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mEffectsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mFootstepsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+ mMusicVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+
+ center();
+
+ mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings);
+
+ // fill resolution list
+ int screen = Settings::Manager::getInt("screen", "Video");
+ int numDisplayModes = SDL_GetNumDisplayModes(screen);
+ std::vector < std::pair<int, int> > resolutions;
+ for (int i = 0; i < numDisplayModes; i++)
+ {
+ SDL_DisplayMode mode;
+ SDL_GetDisplayMode(screen, i, &mode);
+ resolutions.push_back(std::make_pair(mode.w, mode.h));
+ }
+ std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
+ for (std::vector < std::pair<int, int> >::const_iterator it=resolutions.begin();
+ it!=resolutions.end(); ++it)
+ {
+ std::string str = boost::lexical_cast<std::string>(it->first) + " x " + boost::lexical_cast<std::string>(it->second)
+ + " (" + getAspect(it->first,it->second) + ")";
+
+ if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
+ mResolutionList->addItem(str);
+ }
+
+ // read settings
+ int menu_transparency = (mMenuTransparencySlider->getScrollRange()-1) * Settings::Manager::getFloat("menu transparency", "GUI");
+ mMenuTransparencySlider->setScrollPosition(menu_transparency);
+ int tooltip_delay = (mToolTipDelaySlider->getScrollRange()-1) * Settings::Manager::getFloat("tooltip delay", "GUI");
+ mToolTipDelaySlider->setScrollPosition(tooltip_delay);
+
+ 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}");
+
+ float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin);
+ mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1));
+ MyGUI::TextBox* fovText;
+ getWidget(fovText, "FovText");
+ fovText->setCaption("Field of View (" + boost::lexical_cast<std::string>(int(Settings::Manager::getFloat("field of view", "General"))) + ")");
+
+ float anisotropyVal = Settings::Manager::getInt("anisotropy", "General") / 16.0;
+ mAnisotropySlider->setScrollPosition(anisotropyVal * (mAnisotropySlider->getScrollRange()-1));
+ std::string tf = Settings::Manager::getString("texture filtering", "General");
+ mTextureFilteringButton->setCaption(textureFilteringToStr(tf));
+ mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast<std::string>(Settings::Manager::getInt("anisotropy", "General")) + ")");
+
+ float val = (Settings::Manager::getFloat("max viewing distance", "Viewing distance")-sViewDistMin)/(sViewDistMax-sViewDistMin);
+ int viewdist = (mViewDistanceSlider->getScrollRange()-1) * val;
+ mViewDistanceSlider->setScrollPosition(viewdist);
+
+ mMasterVolumeSlider->setScrollPosition(Settings::Manager::getFloat("master volume", "Sound") * (mMasterVolumeSlider->getScrollRange()-1));
+ mMusicVolumeSlider->setScrollPosition(Settings::Manager::getFloat("music volume", "Sound") * (mMusicVolumeSlider->getScrollRange()-1));
+ mEffectsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("sfx volume", "Sound") * (mEffectsVolumeSlider->getScrollRange()-1));
+ mFootstepsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("footsteps volume", "Sound") * (mFootstepsVolumeSlider->getScrollRange()-1));
+ mVoiceVolumeSlider->setScrollPosition(Settings::Manager::getFloat("voice volume", "Sound") * (mVoiceVolumeSlider->getScrollRange()-1));
+
+ mWaterShaderButton->setCaptionWithReplacing(Settings::Manager::getBool("shader", "Water") ? "#{sOn}" : "#{sOff}");
+ mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect statics", "Water") ? "#{sOn}" : "#{sOff}");
+ mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}");
+ mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}");
+
+ mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows"));
+ mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}");
+
+ mShadowsEnabledButton->setCaptionWithReplacing(Settings::Manager::getBool("enabled", "Shadows") ? "#{sOn}" : "#{sOff}");
+ mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
+ mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
+ mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
+ mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
+
+ float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2);
+ mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1));
+ mCameraSensitivitySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
+
+ mInvertYButton->setCaptionWithReplacing(Settings::Manager::getBool("invert y axis", "Input") ? "#{sOn}" : "#{sOff}");
+
+ mShadersButton->setCaptionWithReplacing (Settings::Manager::getBool("shaders", "Objects") ? "#{sOn}" : "#{sOff}");
+ mShaderModeButton->setCaption (Settings::Manager::getString("shader mode", "General"));
+
+ mRefractionButton->setCaptionWithReplacing (Settings::Manager::getBool("refraction", "Water") ? "#{sOn}" : "#{sOff}");
+
+ if (!Settings::Manager::getBool("shaders", "Objects"))
+ {
+ mRefractionButton->setEnabled(false);
+ mShadowsEnabledButton->setEnabled(false);
+ }
+
+ mFullscreenButton->setCaptionWithReplacing(Settings::Manager::getBool("fullscreen", "Video") ? "#{sOn}" : "#{sOff}");
+ mVSyncButton->setCaptionWithReplacing(Settings::Manager::getBool("vsync", "Video") ? "#{sOn}": "#{sOff}");
+ mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD")));
+ }
+
+ void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings);
+ }
+
+ void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index)
+ {
+ if (index == MyGUI::ITEM_NONE)
+ return;
+
+ ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
+ dialog->open("#{sNotifyMessage67}");
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept);
+ dialog->eventCancelClicked.clear();
+ dialog->eventCancelClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionCancel);
+ }
+
+ void SettingsWindow::onResolutionAccept()
+ {
+ std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected());
+ int resX, resY;
+ parseResolution (resX, resY, resStr);
+
+ Settings::Manager::setInt("resolution x", "Video", resX);
+ Settings::Manager::setInt("resolution y", "Video", resY);
+
+ apply();
+ }
+
+ void SettingsWindow::onResolutionCancel()
+ {
+ mResolutionList->setIndexSelected(MyGUI::ITEM_NONE);
+ }
+
+ void SettingsWindow::onShadowTextureSize(MyGUI::Widget* _sender)
+ {
+ 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);
+ apply();
+ }
+
+ void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender)
+ {
+ std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On");
+ std::string off = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "On");
+ bool newState;
+ if (_sender->castType<MyGUI::Button>()->getCaption() == on)
+ {
+ _sender->castType<MyGUI::Button>()->setCaption(off);
+ newState = false;
+ }
+ else
+ {
+ _sender->castType<MyGUI::Button>()->setCaption(on);
+ newState = true;
+ }
+
+ if (_sender == mFullscreenButton)
+ {
+ // check if this resolution is supported in fullscreen
+ bool supported = false;
+ for (unsigned int i=0; i<mResolutionList->getItemCount(); ++i)
+ {
+ std::string resStr = mResolutionList->getItemNameAt(i);
+ int resX, resY;
+ parseResolution (resX, resY, resStr);
+
+ if (resX == Settings::Manager::getInt("resolution x", "Video")
+ && resY == Settings::Manager::getInt("resolution y", "Video"))
+ supported = true;
+ }
+
+ if (!supported)
+ {
+ std::string msg = "This resolution is not supported in Fullscreen mode. Please select a resolution from the list.";
+ MWBase::Environment::get().getWindowManager()->
+ messageBox(msg);
+ _sender->castType<MyGUI::Button>()->setCaption(off);
+ }
+ else
+ {
+ Settings::Manager::setBool("fullscreen", "Video", newState);
+ apply();
+ }
+ }
+ else if (_sender == mVSyncButton)
+ {
+ Settings::Manager::setBool("vsync", "Video", newState);
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("VSync will be applied after a restart", std::vector<std::string>());
+ }
+ else
+ {
+ if (_sender == mVSyncButton)
+ Settings::Manager::setBool("vsync", "Video", newState);
+ if (_sender == mWaterShaderButton)
+ Settings::Manager::setBool("shader", "Water", newState);
+ else if (_sender == mRefractionButton)
+ Settings::Manager::setBool("refraction", "Water", newState);
+ else if (_sender == mReflectObjectsButton)
+ {
+ Settings::Manager::setBool("reflect misc", "Water", newState);
+ Settings::Manager::setBool("reflect statics", "Water", newState);
+ Settings::Manager::setBool("reflect statics small", "Water", newState);
+ }
+ else if (_sender == mReflectActorsButton)
+ Settings::Manager::setBool("reflect actors", "Water", newState);
+ else if (_sender == mReflectTerrainButton)
+ Settings::Manager::setBool("reflect terrain", "Water", newState);
+ else if (_sender == mShadowsEnabledButton)
+ Settings::Manager::setBool("enabled", "Shadows", newState);
+ else if (_sender == mShadowsLargeDistance)
+ Settings::Manager::setBool("split", "Shadows", newState);
+ else if (_sender == mActorShadows)
+ Settings::Manager::setBool("actor shadows", "Shadows", newState);
+ else if (_sender == mStaticsShadows)
+ Settings::Manager::setBool("statics shadows", "Shadows", newState);
+ else if (_sender == mMiscShadows)
+ Settings::Manager::setBool("misc shadows", "Shadows", newState);
+ else if (_sender == mTerrainShadows)
+ Settings::Manager::setBool("terrain shadows", "Shadows", newState);
+ else if (_sender == mInvertYButton)
+ Settings::Manager::setBool("invert y axis", "Input", newState);
+ else if (_sender == mCrosshairButton)
+ Settings::Manager::setBool("crosshair", "HUD", newState);
+ else if (_sender == mSubtitlesButton)
+ Settings::Manager::setBool("subtitles", "GUI", newState);
+ else if (_sender == mBestAttackButton)
+ Settings::Manager::setBool("best attack", "Game", newState);
+
+ apply();
+ }
+ }
+
+ void SettingsWindow::onShaderModeToggled(MyGUI::Widget* _sender)
+ {
+ std::string val = static_cast<MyGUI::Button*>(_sender)->getCaption();
+ if (val == "cg")
+ {
+ val = hlslGlsl();
+ }
+ else if (cgAvailable ())
+ val = "cg";
+
+ static_cast<MyGUI::Button*>(_sender)->setCaption(val);
+
+ Settings::Manager::setString("shader mode", "General", val);
+
+ apply();
+ }
+
+ void SettingsWindow::onShadersToggled(MyGUI::Widget* _sender)
+ {
+ std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On");
+ std::string off = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "On");
+
+ std::string val = static_cast<MyGUI::Button*>(_sender)->getCaption();
+ if (val == off)
+ val = on;
+ else
+ val = off;
+ static_cast<MyGUI::Button*>(_sender)->setCaptionWithReplacing (val);
+
+ if (val == off)
+ {
+ Settings::Manager::setBool("shaders", "Objects", false);
+
+ // refraction needs shaders to display underwater fog
+ mRefractionButton->setCaptionWithReplacing("#{sOff}");
+ mRefractionButton->setEnabled(false);
+
+ Settings::Manager::setBool("refraction", "Water", false);
+ Settings::Manager::setBool("underwater effect", "Water", false);
+
+ // shadows not supported
+ mShadowsEnabledButton->setEnabled(false);
+ mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}");
+ Settings::Manager::setBool("enabled", "Shadows", false);
+ }
+ else
+ {
+ Settings::Manager::setBool("shaders", "Objects", true);
+
+ // re-enable
+ mReflectObjectsButton->setEnabled(true);
+ mReflectActorsButton->setEnabled(true);
+ mReflectTerrainButton->setEnabled(true);
+ mRefractionButton->setEnabled(true);
+
+ mShadowsEnabledButton->setEnabled(true);
+ }
+
+ apply();
+ }
+
+ void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender)
+ {
+ int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 3;
+ Settings::Manager::setInt("fps", "HUD", newLevel);
+ mFPSButton->setCaptionWithReplacing(fpsLevelToStr(newLevel));
+ apply();
+ }
+
+ void SettingsWindow::onTextureFilteringToggled(MyGUI::Widget* _sender)
+ {
+ 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);
+ apply();
+ }
+
+ void SettingsWindow::onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos)
+ {
+ float val = pos / float(scroller->getScrollRange()-1);
+ if (scroller == mMenuTransparencySlider)
+ Settings::Manager::setFloat("menu transparency", "GUI", val);
+ else if (scroller == mToolTipDelaySlider)
+ Settings::Manager::setFloat("tooltip delay", "GUI", val);
+ else if (scroller == mViewDistanceSlider)
+ Settings::Manager::setFloat("max viewing distance", "Viewing distance", (1-val) * sViewDistMin + val * sViewDistMax);
+ else if (scroller == mFOVSlider)
+ {
+ MyGUI::TextBox* fovText;
+ getWidget(fovText, "FovText");
+ fovText->setCaption("Field of View (" + boost::lexical_cast<std::string>(int((1-val) * sFovMin + val * sFovMax)) + ")");
+ Settings::Manager::setFloat("field of view", "General", (1-val) * sFovMin + val * sFovMax);
+ }
+ else if (scroller == mAnisotropySlider)
+ {
+ mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast<std::string>(int(val*16)) + ")");
+ Settings::Manager::setInt("anisotropy", "General", val * 16);
+ }
+ else if (scroller == mMasterVolumeSlider)
+ Settings::Manager::setFloat("master volume", "Sound", val);
+ else if (scroller == mVoiceVolumeSlider)
+ Settings::Manager::setFloat("voice volume", "Sound", val);
+ else if (scroller == mEffectsVolumeSlider)
+ Settings::Manager::setFloat("sfx volume", "Sound", val);
+ else if (scroller == mFootstepsVolumeSlider)
+ Settings::Manager::setFloat("footsteps volume", "Sound", val);
+ else if (scroller == mMusicVolumeSlider)
+ Settings::Manager::setFloat("music volume", "Sound", val);
+ else if (scroller == mCameraSensitivitySlider)
+ Settings::Manager::setFloat("camera sensitivity", "Input", (1-val) * 0.2 + val * 5.f);
+
+ apply();
+ }
+
+ void SettingsWindow::apply()
+ {
+ const Settings::CategorySettingVector changed = Settings::Manager::apply();
+ MWBase::Environment::get().getWorld()->processChangedSettings(changed);
+ MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
+ MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
+ MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
+ }
+
+ void SettingsWindow::updateControlsBox()
+ {
+ while (mControlsBox->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mControlsBox->getChildAt(0));
+
+ MWBase::Environment::get().getWindowManager ()->removeStaticMessageBox();
+
+ std::vector<int> actions = MWBase::Environment::get().getInputManager()->getActionSorting ();
+
+ const int h = 18;
+ const int w = mControlsBox->getWidth() - 28;
+ int curH = 0;
+ for (std::vector<int>::const_iterator it = actions.begin(); it != actions.end(); ++it)
+ {
+ std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (*it);
+ if (desc == "")
+ continue;
+
+ std::string binding = MWBase::Environment::get().getInputManager()->getActionBindingName (*it);
+
+ MyGUI::TextBox* leftText = mControlsBox->createWidget<MyGUI::TextBox>("SandText", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default);
+ leftText->setCaptionWithReplacing(desc);
+
+ MyGUI::Button* rightText = mControlsBox->createWidget<MyGUI::Button>("SandTextButton", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default);
+ rightText->setCaptionWithReplacing(binding);
+ rightText->setTextAlign (MyGUI::Align::Right);
+ rightText->setUserData(*it); // save the action id for callbacks
+ rightText->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onRebindAction);
+ rightText->eventMouseWheel += MyGUI::newDelegate(this, &SettingsWindow::onInputTabMouseWheel);
+ curH += h;
+ }
+
+ mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(curH, mControlsBox->getHeight()));
+ }
+
+ void SettingsWindow::onRebindAction(MyGUI::Widget* _sender)
+ {
+ int actionId = *_sender->getUserData<int>();
+
+ static_cast<MyGUI::Button*>(_sender)->setCaptionWithReplacing("#{sNone}");
+
+ MWBase::Environment::get().getWindowManager ()->staticMessageBox ("#{sControlsMenu3}");
+ MWBase::Environment::get().getWindowManager ()->disallowMouse();
+
+ MWBase::Environment::get().getInputManager ()->enableDetectingBindingMode (actionId);
+
+ }
+
+ void SettingsWindow::onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mControlsBox->getViewOffset().top + _rel*0.3 > 0)
+ mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mControlsBox->setViewOffset(MyGUI::IntPoint(0, mControlsBox->getViewOffset().top + _rel*0.3));
+ }
+
+ void SettingsWindow::onResetDefaultBindings(MyGUI::Widget* _sender)
+ {
+ ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
+ dialog->open("#{sNotifyMessage66}");
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindingsAccept);
+ dialog->eventCancelClicked.clear();
+ }
+
+ void SettingsWindow::onResetDefaultBindingsAccept()
+ {
+ MWBase::Environment::get().getInputManager ()->resetToDefaultBindings ();
+ updateControlsBox ();
+ }
+
+ void SettingsWindow::open()
+ {
+ updateControlsBox ();
+ }
+}
diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp
new file mode 100644
index 0000000000..a585bda7e1
--- /dev/null
+++ b/apps/openmw/mwgui/settingswindow.hpp
@@ -0,0 +1,99 @@
+#ifndef MWGUI_SETTINGS_H
+#define MWGUI_SETTINGS_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+namespace MWGui
+{
+ class SettingsWindow : public WindowBase
+ {
+ public:
+ SettingsWindow();
+
+ virtual void open();
+
+ void updateControlsBox();
+
+ private:
+ static int const sFovMin = 30;
+ static int const sFovMax = 140;
+ static int const sViewDistMin = 2000;
+ static int const sViewDistMax = 5600;
+
+ protected:
+ MyGUI::Button* mOkButton;
+
+ MyGUI::ScrollBar* mMenuTransparencySlider;
+ MyGUI::ScrollBar* mToolTipDelaySlider;
+ MyGUI::Button* mSubtitlesButton;
+ MyGUI::Button* mCrosshairButton;
+ MyGUI::Button* mBestAttackButton;
+
+ // graphics
+ MyGUI::ListBox* mResolutionList;
+ MyGUI::Button* mFullscreenButton;
+ MyGUI::Button* mVSyncButton;
+ MyGUI::Button* mFPSButton;
+ MyGUI::ScrollBar* mViewDistanceSlider;
+ MyGUI::ScrollBar* mFOVSlider;
+ MyGUI::ScrollBar* mAnisotropySlider;
+ MyGUI::Button* mTextureFilteringButton;
+ MyGUI::TextBox* mAnisotropyLabel;
+ MyGUI::Widget* mAnisotropyBox;
+ MyGUI::Button* mWaterShaderButton;
+ MyGUI::Button* mReflectObjectsButton;
+ MyGUI::Button* mReflectActorsButton;
+ MyGUI::Button* mReflectTerrainButton;
+ MyGUI::Button* mShadersButton;
+ MyGUI::Button* mShaderModeButton;
+ MyGUI::Button* mRefractionButton;
+
+ MyGUI::Button* mShadowsEnabledButton;
+ MyGUI::Button* mShadowsLargeDistance;
+ MyGUI::Button* mShadowsTextureSize;
+ MyGUI::Button* mActorShadows;
+ MyGUI::Button* mStaticsShadows;
+ MyGUI::Button* mMiscShadows;
+ MyGUI::Button* mTerrainShadows;
+
+ // audio
+ MyGUI::ScrollBar* mMasterVolumeSlider;
+ MyGUI::ScrollBar* mVoiceVolumeSlider;
+ MyGUI::ScrollBar* mEffectsVolumeSlider;
+ MyGUI::ScrollBar* mFootstepsVolumeSlider;
+ MyGUI::ScrollBar* mMusicVolumeSlider;
+
+ // controls
+ MyGUI::ScrollView* mControlsBox;
+ MyGUI::Button* mResetControlsButton;
+ MyGUI::Button* mInvertYButton;
+ MyGUI::ScrollBar* mCameraSensitivitySlider;
+
+ void onOkButtonClicked(MyGUI::Widget* _sender);
+ void onFpsToggled(MyGUI::Widget* _sender);
+ void onTextureFilteringToggled(MyGUI::Widget* _sender);
+ void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos);
+ void onButtonToggled(MyGUI::Widget* _sender);
+ void onResolutionSelected(MyGUI::ListBox* _sender, size_t index);
+ void onResolutionAccept();
+ void onResolutionCancel();
+
+ void onShadersToggled(MyGUI::Widget* _sender);
+ void onShaderModeToggled(MyGUI::Widget* _sender);
+ void onShadowTextureSize(MyGUI::Widget* _sender);
+
+ void onRebindAction(MyGUI::Widget* _sender);
+ void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onResetDefaultBindings(MyGUI::Widget* _sender);
+ void onResetDefaultBindingsAccept ();
+
+ void apply();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp
new file mode 100644
index 0000000000..3cf514dc5c
--- /dev/null
+++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp
@@ -0,0 +1,171 @@
+#include "sortfilteritemmodel.hpp"
+
+#include "../mwworld/class.hpp"
+
+namespace
+{
+ bool compareType(const std::string& type1, const std::string& type2)
+ {
+ // this defines the sorting order of types. types that are first in the vector appear before other types.
+ std::vector<std::string> mapping;
+ mapping.push_back( typeid(ESM::Weapon).name() );
+ mapping.push_back( typeid(ESM::Armor).name() );
+ mapping.push_back( typeid(ESM::Clothing).name() );
+ mapping.push_back( typeid(ESM::Potion).name() );
+ mapping.push_back( typeid(ESM::Ingredient).name() );
+ mapping.push_back( typeid(ESM::Apparatus).name() );
+ mapping.push_back( typeid(ESM::Book).name() );
+ mapping.push_back( typeid(ESM::Light).name() );
+ mapping.push_back( typeid(ESM::Miscellaneous).name() );
+ mapping.push_back( typeid(ESM::Lockpick).name() );
+ mapping.push_back( typeid(ESM::Repair).name() );
+ mapping.push_back( typeid(ESM::Probe).name() );
+
+ assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() );
+ assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() );
+
+ return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2);
+ }
+
+ bool compare (const MWGui::ItemStack& left, const MWGui::ItemStack& right)
+ {
+ if (left.mType != right.mType)
+ return left.mType < right.mType;
+
+ if (left.mBase.getTypeName() == right.mBase.getTypeName())
+ {
+ int cmp = MWWorld::Class::get(left.mBase).getName(left.mBase).compare(
+ MWWorld::Class::get(right.mBase).getName(right.mBase));
+ return cmp < 0;
+ }
+ else
+ return compareType(left.mBase.getTypeName(), right.mBase.getTypeName());
+ }
+}
+
+namespace MWGui
+{
+
+ SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel)
+ : mCategory(Category_All)
+ , mShowEquipped(true)
+ , mFilter(0)
+ {
+ mSourceModel = sourceModel;
+ }
+
+ void SortFilterItemModel::addDragItem (const MWWorld::Ptr& dragItem, size_t count)
+ {
+ mDragItems.push_back(std::make_pair(dragItem, count));
+ }
+
+ void SortFilterItemModel::clearDragItems()
+ {
+ mDragItems.clear();
+ }
+
+ bool SortFilterItemModel::filterAccepts (const ItemStack& item)
+ {
+ MWWorld::Ptr base = item.mBase;
+
+ if (item.mType == ItemStack::Type_Equipped && !mShowEquipped)
+ return false;
+
+ int category = 0;
+ if (base.getTypeName() == typeid(ESM::Armor).name()
+ || base.getTypeName() == typeid(ESM::Clothing).name())
+ category = Category_Apparel;
+ else if (base.getTypeName() == typeid(ESM::Weapon).name())
+ category = Category_Weapon;
+ else if (base.getTypeName() == typeid(ESM::Ingredient).name()
+ || base.getTypeName() == typeid(ESM::Potion).name())
+ category = Category_Magic;
+ else if (base.getTypeName() == typeid(ESM::Miscellaneous).name()
+ || base.getTypeName() == typeid(ESM::Ingredient).name()
+ || base.getTypeName() == typeid(ESM::Repair).name()
+ || base.getTypeName() == typeid(ESM::Lockpick).name()
+ || base.getTypeName() == typeid(ESM::Light).name()
+ || base.getTypeName() == typeid(ESM::Apparatus).name()
+ || base.getTypeName() == typeid(ESM::Book).name()
+ || base.getTypeName() == typeid(ESM::Probe).name())
+ category = Category_Misc;
+
+ if (item.mFlags & ItemStack::Flag_Enchanted)
+ category |= Category_Magic;
+
+ if (!(category & mCategory))
+ return false;
+
+ if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name())
+ return false;
+ if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
+ return false;
+ if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
+ || base.getCellRef().mSoul == ""))
+ return false;
+ if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted
+ || (base.getTypeName() != typeid(ESM::Armor).name()
+ && base.getTypeName() != typeid(ESM::Clothing).name()
+ && base.getTypeName() != typeid(ESM::Weapon).name()
+ && base.getTypeName() != typeid(ESM::Book).name())))
+ return false;
+ if ((mFilter & Filter_OnlyEnchantable) && base.getTypeName() == typeid(ESM::Book).name()
+ && !base.get<ESM::Book>()->mBase->mData.mIsScroll)
+ return false;
+
+ return true;
+ }
+
+ ItemStack SortFilterItemModel::getItem (ModelIndex index)
+ {
+ if (index < 0)
+ throw std::runtime_error("Invalid index supplied");
+ if (mItems.size() <= static_cast<size_t>(index))
+ throw std::runtime_error("Item index out of range");
+ return mItems[index];
+ }
+
+ size_t SortFilterItemModel::getItemCount()
+ {
+ return mItems.size();
+ }
+
+ void SortFilterItemModel::setCategory (int category)
+ {
+ mCategory = category;
+ }
+
+ void SortFilterItemModel::setFilter (int filter)
+ {
+ mFilter = filter;
+ }
+
+ void SortFilterItemModel::update()
+ {
+ mSourceModel->update();
+
+ size_t count = mSourceModel->getItemCount();
+
+ mItems.clear();
+ for (size_t i=0; i<count; ++i)
+ {
+ ItemStack item = mSourceModel->getItem(i);
+
+ for (std::vector<std::pair<MWWorld::Ptr, size_t> >::iterator it = mDragItems.begin(); it != mDragItems.end(); ++it)
+ {
+ if (item.mBase == it->first)
+ {
+ if (item.mCount < it->second)
+ throw std::runtime_error("Dragging more than present in the model");
+ item.mCount -= it->second;
+ }
+ }
+
+ if (item.mCount > 0 && filterAccepts(item))
+ mItems.push_back(item);
+ }
+
+ std::sort(mItems.begin(), mItems.end(), compare);
+ }
+
+}
diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp
new file mode 100644
index 0000000000..c7feaa3b92
--- /dev/null
+++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp
@@ -0,0 +1,53 @@
+#ifndef MWGUI_SORT_FILTER_ITEM_MODEL_H
+#define MWGUI_SORT_FILTER_ITEM_MODEL_H
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ class SortFilterItemModel : public ProxyItemModel
+ {
+ public:
+ SortFilterItemModel (ItemModel* sourceModel);
+
+ virtual void update();
+
+ bool filterAccepts (const ItemStack& item);
+
+ virtual ItemStack getItem (ModelIndex index);
+ virtual size_t getItemCount();
+
+ /// Dragged items are not displayed.
+ void addDragItem (const MWWorld::Ptr& dragItem, size_t count);
+ void clearDragItems();
+
+ void setCategory (int category);
+ void setFilter (int filter);
+ void setShowEquipped (bool show) { mShowEquipped = show; }
+
+ static const int Category_Weapon = (1<<1);
+ static const int Category_Apparel = (1<<2);
+ static const int Category_Misc = (1<<3);
+ static const int Category_Magic = (1<<4);
+ static const int Category_All = 255;
+
+ static const int Filter_OnlyIngredients = (1<<0);
+ static const int Filter_OnlyEnchanted = (1<<1);
+ static const int Filter_OnlyEnchantable = (1<<2);
+ static const int Filter_OnlyChargedSoulstones = (1<<3);
+
+
+ private:
+ std::vector<ItemStack> mItems;
+
+ std::vector<std::pair<MWWorld::Ptr, size_t> > mDragItems;
+
+ int mCategory;
+ int mFilter;
+ bool mShowEquipped;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp
new file mode 100644
index 0000000000..b95eec0b67
--- /dev/null
+++ b/apps/openmw/mwgui/soulgemdialog.cpp
@@ -0,0 +1,33 @@
+#include "soulgemdialog.hpp"
+
+#include "../mwbase/environment.hpp"
+
+#include "messagebox.hpp"
+
+namespace MWGui
+{
+
+ void SoulgemDialog::show(const MWWorld::Ptr &soulgem)
+ {
+ mSoulgem = soulgem;
+ std::vector<std::string> buttons;
+ buttons.push_back("#{sRechargeEnchantment}");
+ buttons.push_back("#{sMake Enchantment}");
+ mManager->createInteractiveMessageBox("#{sDoYouWantTo}", buttons);
+ mManager->eventButtonPressed += MyGUI::newDelegate(this, &SoulgemDialog::onButtonPressed);
+ }
+
+ void SoulgemDialog::onButtonPressed(int button)
+ {
+ if (button == 0)
+ {
+ /// \todo show recharge enchanted item dialog here
+ }
+ else
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting);
+ MWBase::Environment::get().getWindowManager()->startSelfEnchanting(mSoulgem);
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/soulgemdialog.hpp b/apps/openmw/mwgui/soulgemdialog.hpp
new file mode 100644
index 0000000000..9aea1f3393
--- /dev/null
+++ b/apps/openmw/mwgui/soulgemdialog.hpp
@@ -0,0 +1,28 @@
+#ifndef OPENMW_MWGUI_SOULGEMDIALOG_H
+#define OPENMW_MWGUI_SOULGEMDIALOG_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWGui
+{
+
+ class MessageBoxManager;
+
+ class SoulgemDialog
+ {
+ public:
+ SoulgemDialog (MessageBoxManager* manager)
+ : mManager(manager) {}
+
+ void show (const MWWorld::Ptr& soulgem);
+
+ void onButtonPressed(int button);
+
+ private:
+ MessageBoxManager* mManager;
+ MWWorld::Ptr mSoulgem;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp
new file mode 100644
index 0000000000..a7fcfdd021
--- /dev/null
+++ b/apps/openmw/mwgui/spellbuyingwindow.cpp
@@ -0,0 +1,162 @@
+#include "spellbuyingwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "inventorywindow.hpp"
+#include "tradewindow.hpp"
+
+namespace MWGui
+{
+ const int SpellBuyingWindow::sLineHeight = 18;
+
+ SpellBuyingWindow::SpellBuyingWindow() :
+ WindowBase("openmw_spell_buying_window.layout")
+ , mCurrentY(0)
+ , mLastPos(0)
+ {
+ setCoord(0, 0, 450, 300);
+
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mPlayerGold, "PlayerGold");
+ getWidget(mSpellsView, "SpellsView");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked);
+ }
+
+ void SpellBuyingWindow::addSpell(const std::string& spellId)
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+ int price = spell->mData.mCost*store.get<ESM::GameSetting>().find("fSpellValueMult")->getFloat();
+ price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true);
+
+ MyGUI::Button* toAdd =
+ mSpellsView->createWidget<MyGUI::Button>(
+ (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton",
+ 0,
+ mCurrentY,
+ 200,
+ sLineHeight,
+ MyGUI::Align::Default
+ );
+
+ mCurrentY += sLineHeight;
+
+ toAdd->setUserData(price);
+ toAdd->setCaptionWithReplacing(spell->mName+" - "+boost::lexical_cast<std::string>(price)+"#{sgp}");
+ toAdd->setSize(toAdd->getTextSize().width,sLineHeight);
+ toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel);
+ toAdd->setUserString("ToolTipType", "Spell");
+ toAdd->setUserString("Spell", spellId);
+ toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick);
+ mSpellsWidgetMap.insert(std::make_pair (toAdd, spellId));
+ }
+
+ void SpellBuyingWindow::clearSpells()
+ {
+ mSpellsView->setViewOffset(MyGUI::IntPoint(0,0));
+ mCurrentY = 0;
+ while (mSpellsView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mSpellsView->getChildAt(0));
+ mSpellsWidgetMap.clear();
+ }
+
+ void SpellBuyingWindow::startSpellBuying(const MWWorld::Ptr& actor)
+ {
+ center();
+ mPtr = actor;
+ clearSpells();
+
+ MWMechanics::Spells& merchantSpells = MWWorld::Class::get (actor).getCreatureStats (actor).getSpells();
+
+ for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter)
+ {
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType!=ESM::Spell::ST_Spell)
+ continue; // don't try to sell diseases, curses or powers
+
+ if (playerHasSpell(iter->first))
+ continue;
+
+ addSpell (iter->first);
+ }
+
+ updateLabels();
+
+ mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY)));
+ }
+
+ bool SpellBuyingWindow::playerHasSpell(const std::string &id)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells();
+ for (MWMechanics::Spells::TIterator it = playerSpells.begin(); it != playerSpells.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(id, it->first))
+ return true;
+ }
+ return false;
+ }
+
+ void SpellBuyingWindow::onSpellButtonClick(MyGUI::Widget* _sender)
+ {
+ int price = *_sender->getUserData<int>();
+
+ if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()>=price)
+ {
+ 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 (mSpellsWidgetMap.find(_sender)->second);
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+ startSpellBuying(mPtr);
+
+ MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
+ }
+ }
+
+ void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying);
+ }
+
+ void SpellBuyingWindow::updateLabels()
+ {
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ mPlayerGold->setCoord(8,
+ mPlayerGold->getTop(),
+ mPlayerGold->getTextSize().width,
+ mPlayerGold->getHeight());
+ }
+
+ void SpellBuyingWindow::onReferenceUnavailable()
+ {
+ // remove both Spells and Dialogue (since you always trade with the NPC/creature that you have previously talked to)
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ }
+
+ void SpellBuyingWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mSpellsView->getViewOffset().top + _rel*0.3 > 0)
+ mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mSpellsView->setViewOffset(MyGUI::IntPoint(0, mSpellsView->getViewOffset().top + _rel*0.3));
+ }
+}
+
diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp
new file mode 100644
index 0000000000..f7ea54c89c
--- /dev/null
+++ b/apps/openmw/mwgui/spellbuyingwindow.hpp
@@ -0,0 +1,53 @@
+#ifndef MWGUI_SpellBuyingWINDOW_H
+#define MWGUI_SpellBuyingWINDOW_H
+
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+
+namespace MWGui
+{
+ class SpellBuyingWindow : public ReferenceInterface, public WindowBase
+ {
+ public:
+ SpellBuyingWindow();
+
+ void startSpellBuying(const MWWorld::Ptr& actor);
+
+ protected:
+ MyGUI::Button* mCancelButton;
+ MyGUI::TextBox* mPlayerGold;
+
+ MyGUI::ScrollView* mSpellsView;
+
+ std::map<MyGUI::Widget*, std::string> mSpellsWidgetMap;
+
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onSpellButtonClick(MyGUI::Widget* _sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void addSpell(const std::string& spellID);
+ void clearSpells();
+ int mLastPos,mCurrentY;
+
+ static const int sLineHeight;
+
+ void updateLabels();
+
+ virtual void onReferenceUnavailable();
+
+ bool playerHasSpell (const std::string& id);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp
new file mode 100644
index 0000000000..dc86fd825f
--- /dev/null
+++ b/apps/openmw/mwgui/spellcreationdialog.cpp
@@ -0,0 +1,646 @@
+#include "spellcreationdialog.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/player.hpp"
+
+#include "../mwmechanics/spellsuccess.hpp"
+
+#include "tooltips.hpp"
+#include "class.hpp"
+#include "inventorywindow.hpp"
+#include "tradewindow.hpp"
+
+namespace
+{
+
+ bool sortMagicEffects (short id1, short id2)
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ return gmst.find(ESM::MagicEffect::effectIdToString (id1))->getString()
+ < gmst.find(ESM::MagicEffect::effectIdToString (id2))->getString();
+ }
+}
+
+namespace MWGui
+{
+
+ EditEffectDialog::EditEffectDialog()
+ : WindowModal("openmw_edit_effect.layout")
+ , mEditing(false)
+ {
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mOkButton, "OkButton");
+ getWidget(mDeleteButton, "DeleteButton");
+ getWidget(mRangeButton, "RangeButton");
+ getWidget(mMagnitudeMinValue, "MagnitudeMinValue");
+ getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue");
+ getWidget(mDurationValue, "DurationValue");
+ getWidget(mAreaValue, "AreaValue");
+ getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider");
+ getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider");
+ getWidget(mDurationSlider, "DurationSlider");
+ getWidget(mAreaSlider, "AreaSlider");
+ getWidget(mEffectImage, "EffectImage");
+ getWidget(mEffectName, "EffectName");
+ getWidget(mAreaText, "AreaText");
+ getWidget(mDurationBox, "DurationBox");
+ getWidget(mAreaBox, "AreaBox");
+ getWidget(mMagnitudeBox, "MagnitudeBox");
+
+ mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked);
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked);
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked);
+ mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked);
+
+ mMagnitudeMinSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged);
+ mMagnitudeMaxSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged);
+ mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged);
+ mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged);
+ constantEffect=false;
+ }
+
+ void EditEffectDialog::open()
+ {
+ WindowModal::open();
+ center();
+ }
+
+ void EditEffectDialog::newEffect (const ESM::MagicEffect *effect)
+ {
+ setMagicEffect(effect);
+ mEditing = false;
+
+ mDeleteButton->setVisible (false);
+
+ mEffect.mRange = ESM::RT_Self;
+ if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf))
+ mEffect.mRange = ESM::RT_Touch;
+ if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch))
+ mEffect.mRange = ESM::RT_Target;
+ mEffect.mMagnMin = 1;
+ mEffect.mMagnMax = 1;
+ mEffect.mDuration = 1;
+ mEffect.mArea = 0;
+ eventEffectAdded(mEffect);
+
+ onRangeButtonClicked(mRangeButton);
+
+ mMagnitudeMinSlider->setScrollPosition (0);
+ mMagnitudeMaxSlider->setScrollPosition (0);
+ mAreaSlider->setScrollPosition (0);
+ mDurationSlider->setScrollPosition (0);
+
+ mDurationValue->setCaption("1");
+ mMagnitudeMinValue->setCaption("1");
+ mMagnitudeMaxValue->setCaption("- 1");
+ mAreaValue->setCaption("0");
+ }
+
+ void EditEffectDialog::editEffect (ESM::ENAMstruct effect)
+ {
+ const ESM::MagicEffect* magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
+
+ setMagicEffect(magicEffect);
+ mOldEffect = effect;
+ mEffect = effect;
+ mEditing = true;
+
+ mDeleteButton->setVisible (true);
+
+ mMagnitudeMinSlider->setScrollPosition (effect.mMagnMin-1);
+ mMagnitudeMaxSlider->setScrollPosition (effect.mMagnMax-1);
+ mAreaSlider->setScrollPosition (effect.mArea);
+ mDurationSlider->setScrollPosition (effect.mDuration-1);
+
+ onMagnitudeMinChanged (mMagnitudeMinSlider, effect.mMagnMin-1);
+ onMagnitudeMaxChanged (mMagnitudeMinSlider, effect.mMagnMax-1);
+ onAreaChanged (mAreaSlider, effect.mArea);
+ onDurationChanged (mDurationSlider, effect.mDuration-1);
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect)
+ {
+ std::string icon = effect->mIcon;
+ icon[icon.size()-3] = 'd';
+ icon[icon.size()-2] = 'd';
+ icon[icon.size()-1] = 's';
+ icon = "icons\\" + icon;
+
+ mEffectImage->setImageTexture (icon);
+
+ mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}");
+
+ mEffect.mEffectID = effect->mIndex;
+
+ mMagicEffect = effect;
+
+ updateBoxes();
+ }
+
+ void EditEffectDialog::updateBoxes()
+ {
+ static int startY = mMagnitudeBox->getPosition().top;
+ int curY = startY;
+
+ mMagnitudeBox->setVisible (false);
+ mDurationBox->setVisible (false);
+ mAreaBox->setVisible (false);
+
+ if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
+ {
+ mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY);
+ mMagnitudeBox->setVisible (true);
+ curY += mMagnitudeBox->getSize().height;
+ }
+ if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&constantEffect==false)
+ {
+ mDurationBox->setPosition(mDurationBox->getPosition().left, curY);
+ mDurationBox->setVisible (true);
+ curY += mDurationBox->getSize().height;
+ }
+ if (mEffect.mRange != ESM::RT_Self)
+ {
+ mAreaBox->setPosition(mAreaBox->getPosition().left, curY);
+ mAreaBox->setVisible (true);
+ curY += mAreaBox->getSize().height;
+ }
+ }
+
+ void EditEffectDialog::onRangeButtonClicked (MyGUI::Widget* sender)
+ {
+ mEffect.mRange = (mEffect.mRange+1)%3;
+
+ if (mEffect.mRange == ESM::RT_Self)
+ mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}");
+ else if (mEffect.mRange == ESM::RT_Target)
+ mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}");
+ else if (mEffect.mRange == ESM::RT_Touch)
+ mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}");
+
+ // cycle through range types until we find something that's allowed
+ if (mEffect.mRange == ESM::RT_Target && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget))
+ onRangeButtonClicked(sender);
+ if (mEffect.mRange == ESM::RT_Self && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf))
+ onRangeButtonClicked(sender);
+ if (mEffect.mRange == ESM::RT_Touch && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch))
+ onRangeButtonClicked(sender);
+
+ if(mEffect.mRange == ESM::RT_Self)
+ {
+ mAreaSlider->setScrollPosition(0);
+ onAreaChanged(mAreaSlider,0);
+ }
+ updateBoxes();
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::onDeleteButtonClicked (MyGUI::Widget* sender)
+ {
+ setVisible(false);
+
+ eventEffectRemoved(mEffect);
+ }
+
+ void EditEffectDialog::onOkButtonClicked (MyGUI::Widget* sender)
+ {
+ setVisible(false);
+ }
+
+ void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender)
+ {
+ setVisible(false);
+ if(mEditing)
+ eventEffectModified(mOldEffect);
+ else
+ eventEffectRemoved(mEffect);
+ }
+
+ void EditEffectDialog::setSkill (int skill)
+ {
+ mEffect.mSkill = skill;
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::setAttribute (int attribute)
+ {
+ mEffect.mAttribute = attribute;
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos)
+ {
+ mMagnitudeMinValue->setCaption(boost::lexical_cast<std::string>(pos+1));
+ mEffect.mMagnMin = pos+1;
+
+ // trigger the check again (see below)
+ onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition ());
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos)
+ {
+ // make sure the max value is actually larger or equal than the min value
+ size_t magnMin = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning
+ if (pos+1 < magnMin)
+ {
+ pos = mEffect.mMagnMin-1;
+ sender->setScrollPosition (pos);
+ }
+
+ mEffect.mMagnMax = pos+1;
+
+ mMagnitudeMaxValue->setCaption("- " + boost::lexical_cast<std::string>(pos+1));
+
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::onDurationChanged (MyGUI::ScrollBar* sender, size_t pos)
+ {
+ mDurationValue->setCaption(boost::lexical_cast<std::string>(pos+1));
+ mEffect.mDuration = pos+1;
+ eventEffectModified(mEffect);
+ }
+
+ void EditEffectDialog::onAreaChanged (MyGUI::ScrollBar* sender, size_t pos)
+ {
+ mAreaValue->setCaption(boost::lexical_cast<std::string>(pos));
+ mEffect.mArea = pos;
+ eventEffectModified(mEffect);
+ }
+
+ // ------------------------------------------------------------------------------------------------
+
+ SpellCreationDialog::SpellCreationDialog()
+ : WindowBase("openmw_spellcreation_dialog.layout")
+ , EffectEditorBase()
+ {
+ getWidget(mNameEdit, "NameEdit");
+ getWidget(mMagickaCost, "MagickaCost");
+ getWidget(mSuccessChance, "SuccessChance");
+ getWidget(mAvailableEffectsList, "AvailableEffects");
+ getWidget(mUsedEffectsView, "UsedEffects");
+ getWidget(mPriceLabel, "PriceLabel");
+ getWidget(mBuyButton, "BuyButton");
+ getWidget(mCancelButton, "CancelButton");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked);
+ mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked);
+
+ setWidgets(mAvailableEffectsList, mUsedEffectsView);
+ }
+
+ void SpellCreationDialog::startSpellMaking (MWWorld::Ptr actor)
+ {
+ mPtr = actor;
+ mNameEdit->setCaption("");
+
+ startEditing();
+ }
+
+ void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation);
+ }
+
+ void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender)
+ {
+ if (mEffects.size() <= 0)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}");
+ return;
+ }
+
+ if (mNameEdit->getCaption () == "")
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}");
+ return;
+ }
+
+ if (mMagickaCost->getCaption() == "0")
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu8}");
+ return;
+ }
+
+ if (boost::lexical_cast<int>(mPriceLabel->getCaption()) > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
+ return;
+ }
+
+ mSpell.mName = mNameEdit->getCaption();
+
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast<int>(mPriceLabel->getCaption()));
+
+ 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);
+
+ MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation);
+ }
+
+ void SpellCreationDialog::open()
+ {
+ center();
+ }
+
+ void SpellCreationDialog::onReferenceUnavailable ()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation);
+ }
+
+ void SpellCreationDialog::notifyEffectsChanged ()
+ {
+ float y = 0;
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ float x = 0.5 * it->mMagnMin + it->mMagnMax;
+
+ const ESM::MagicEffect* effect =
+ store.get<ESM::MagicEffect>().find(it->mEffectID);
+
+ x *= 0.1 * effect->mData.mBaseCost;
+ x *= 1 + it->mDuration;
+ x += 0.05 * std::max(1, it->mArea) * effect->mData.mBaseCost;
+
+ float fEffectCostMult =
+ store.get<ESM::GameSetting>().find("fEffectCostMult")->getFloat();
+
+ y += x * fEffectCostMult;
+ y = std::max(1.f,y);
+
+ if (effect->mData.mFlags & ESM::MagicEffect::CastTarget)
+ y *= 1.5;
+ }
+
+ mSpell.mData.mCost = int(y);
+
+ ESM::EffectList effectList;
+ effectList.mList = mEffects;
+ mSpell.mEffects = effectList;
+ mSpell.mData.mType = ESM::Spell::ST_Spell;
+
+ mMagickaCost->setCaption(boost::lexical_cast<std::string>(int(y)));
+
+ float fSpellMakingValueMult =
+ store.get<ESM::GameSetting>().find("fSpellMakingValueMult")->getFloat();
+
+ int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,int(y) * fSpellMakingValueMult,true);
+
+ mPriceLabel->setCaption(boost::lexical_cast<std::string>(int(price)));
+
+ float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mSuccessChance->setCaption(boost::lexical_cast<std::string>(int(chance)));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+
+
+ EffectEditorBase::EffectEditorBase()
+ : mAddEffectDialog()
+ , mSelectAttributeDialog(NULL)
+ , mSelectSkillDialog(NULL)
+ {
+ mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded);
+ mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified);
+ mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved);
+
+ mAddEffectDialog.setVisible (false);
+ }
+
+ EffectEditorBase::~EffectEditorBase()
+ {
+ }
+
+ void EffectEditorBase::startEditing ()
+ {
+ // get the list of magic effects that are known to the player
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MWMechanics::Spells& spells = stats.getSpells();
+
+ std::vector<short> knownEffects;
+
+ 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);
+
+ // only normal spells count
+ if (spell->mData.mType != ESM::Spell::ST_Spell)
+ continue;
+
+ const std::vector<ESM::ENAMstruct>& list = spell->mEffects.mList;
+ for (std::vector<ESM::ENAMstruct>::const_iterator it2 = list.begin(); it2 != list.end(); ++it2)
+ {
+ if (std::find(knownEffects.begin(), knownEffects.end(), it2->mEffectID) == knownEffects.end())
+ knownEffects.push_back(it2->mEffectID);
+ }
+ }
+
+ std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects);
+
+ mAvailableEffectsList->clear ();
+
+ int i=0;
+ for (std::vector<short>::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it)
+ {
+ mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get<ESM::GameSetting>().find(
+ ESM::MagicEffect::effectIdToString (*it))->getString());
+ mButtonMapping[i] = *it;
+ ++i;
+ }
+ mAvailableEffectsList->adjustSize ();
+
+ for (std::vector<short>::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it)
+ {
+ std::string name = MWBase::Environment::get().getWorld ()->getStore ().get<ESM::GameSetting>().find(
+ ESM::MagicEffect::effectIdToString (*it))->getString();
+ MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name);
+
+ ToolTips::createMagicEffectToolTip (w, *it);
+ }
+
+ mEffects.clear();
+ updateEffectsView ();
+ }
+
+ void EffectEditorBase::setWidgets (Widgets::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView)
+ {
+ mAvailableEffectsList = availableEffectsList;
+ mUsedEffectsView = usedEffectsView;
+
+ mAvailableEffectsList->eventWidgetSelected += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked);
+ }
+
+ void EffectEditorBase::onSelectAttribute ()
+ {
+ mAddEffectDialog.setVisible(true);
+ mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId());
+ MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog);
+ mSelectAttributeDialog = 0;
+ }
+
+ void EffectEditorBase::onSelectSkill ()
+ {
+ mAddEffectDialog.setVisible(true);
+ mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId ());
+ MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog);
+ mSelectSkillDialog = 0;
+ }
+
+ void EffectEditorBase::onAttributeOrSkillCancel ()
+ {
+ if (mSelectSkillDialog)
+ MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog);
+ if (mSelectAttributeDialog)
+ MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog);
+
+ mSelectSkillDialog = 0;
+ mSelectAttributeDialog = 0;
+ }
+
+ void EffectEditorBase::onAvailableEffectClicked (MyGUI::Widget* sender)
+ {
+ if (mEffects.size() >= 8)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}");
+ return;
+ }
+
+ int buttonId = *sender->getUserData<int>();
+ short effectId = mButtonMapping[buttonId];
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ if (it->mEffectID == effectId)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}");
+ return;
+ }
+ }
+
+ const ESM::MagicEffect* effect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
+
+ mAddEffectDialog.newEffect (effect);
+
+ if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill)
+ {
+ delete mSelectSkillDialog;
+ mSelectSkillDialog = new SelectSkillDialog();
+ mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel);
+ mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill);
+ mSelectSkillDialog->setVisible (true);
+ }
+ else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
+ {
+ delete mSelectAttributeDialog;
+ mSelectAttributeDialog = new SelectAttributeDialog();
+ mSelectAttributeDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel);
+ mSelectAttributeDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute);
+ mSelectAttributeDialog->setVisible (true);
+ }
+ else
+ {
+ mAddEffectDialog.setVisible(true);
+ }
+ }
+
+ void EffectEditorBase::onEffectModified (ESM::ENAMstruct effect)
+ {
+ mEffects[mSelectedEffect] = effect;
+
+ updateEffectsView();
+ }
+
+ void EffectEditorBase::onEffectRemoved (ESM::ENAMstruct effect)
+ {
+ mEffects.erase(mEffects.begin() + mSelectedEffect);
+ updateEffectsView();
+ }
+
+ void EffectEditorBase::updateEffectsView ()
+ {
+ MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator ();
+ MyGUI::Gui::getInstance ().destroyWidgets (oldWidgets);
+
+ MyGUI::IntSize size(0,0);
+
+ int i = 0;
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ Widgets::SpellEffectParams params;
+ params.mEffectID = it->mEffectID;
+ params.mSkill = it->mSkill;
+ params.mAttribute = it->mAttribute;
+ params.mDuration = it->mDuration;
+ params.mMagnMin = it->mMagnMin;
+ params.mMagnMax = it->mMagnMax;
+ params.mRange = it->mRange;
+ params.mArea = it->mArea;
+
+ MyGUI::Button* button = mUsedEffectsView->createWidget<MyGUI::Button>("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default);
+ button->setUserData(i);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect);
+ button->setNeedMouseFocus (true);
+
+ Widgets::MWSpellEffectPtr effect = button->createWidget<Widgets::MWSpellEffect>("MW_EffectImage", MyGUI::IntCoord(0,0,0,24), MyGUI::Align::Default);
+
+ effect->setNeedMouseFocus (false);
+ effect->setSpellEffect (params);
+
+ effect->setSize(effect->getRequestedWidth (), 24);
+ button->setSize(effect->getRequestedWidth (), 24);
+
+ size.width = std::max(size.width, effect->getRequestedWidth ());
+ size.height += 24;
+ ++i;
+ }
+
+ mUsedEffectsView->setCanvasSize(size);
+
+ notifyEffectsChanged();
+ }
+
+ void EffectEditorBase::onEffectAdded (ESM::ENAMstruct effect)
+ {
+ mEffects.push_back(effect);
+ mSelectedEffect=mEffects.size()-1;
+
+ updateEffectsView();
+ }
+
+ void EffectEditorBase::onEditEffect (MyGUI::Widget *sender)
+ {
+ int id = *sender->getUserData<int>();
+
+ mSelectedEffect = id;
+
+ mAddEffectDialog.editEffect (mEffects[id]);
+ mAddEffectDialog.setVisible (true);
+ }
+}
diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp
new file mode 100644
index 0000000000..e424d73957
--- /dev/null
+++ b/apps/openmw/mwgui/spellcreationdialog.hpp
@@ -0,0 +1,156 @@
+#ifndef MWGUI_SPELLCREATION_H
+#define MWGUI_SPELLCREATION_H
+
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+#include "list.hpp"
+#include "widgets.hpp"
+
+namespace MWGui
+{
+
+ class SelectSkillDialog;
+ class SelectAttributeDialog;
+
+ class EditEffectDialog : public WindowModal
+ {
+ public:
+ EditEffectDialog();
+
+ virtual void open();
+
+ void setSkill(int skill);
+ void setAttribute(int attribute);
+
+ void newEffect (const ESM::MagicEffect* effect);
+ void editEffect (ESM::ENAMstruct effect);
+ bool constantEffect;
+ typedef MyGUI::delegates::CMultiDelegate1<ESM::ENAMstruct> EventHandle_Effect;
+
+ EventHandle_Effect eventEffectAdded;
+ EventHandle_Effect eventEffectModified;
+ EventHandle_Effect eventEffectRemoved;
+
+ protected:
+ MyGUI::Button* mCancelButton;
+ MyGUI::Button* mOkButton;
+ MyGUI::Button* mDeleteButton;
+
+ MyGUI::Button* mRangeButton;
+
+ MyGUI::Widget* mDurationBox;
+ MyGUI::Widget* mMagnitudeBox;
+ MyGUI::Widget* mAreaBox;
+
+ MyGUI::TextBox* mMagnitudeMinValue;
+ MyGUI::TextBox* mMagnitudeMaxValue;
+ MyGUI::TextBox* mDurationValue;
+ MyGUI::TextBox* mAreaValue;
+
+ MyGUI::ScrollBar* mMagnitudeMinSlider;
+ MyGUI::ScrollBar* mMagnitudeMaxSlider;
+ MyGUI::ScrollBar* mDurationSlider;
+ MyGUI::ScrollBar* mAreaSlider;
+
+ MyGUI::TextBox* mAreaText;
+
+ MyGUI::ImageBox* mEffectImage;
+ MyGUI::TextBox* mEffectName;
+
+ bool mEditing;
+
+ protected:
+ void onRangeButtonClicked (MyGUI::Widget* sender);
+ void onDeleteButtonClicked (MyGUI::Widget* sender);
+ void onOkButtonClicked (MyGUI::Widget* sender);
+ void onCancelButtonClicked (MyGUI::Widget* sender);
+
+ void onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos);
+ void onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos);
+ void onDurationChanged (MyGUI::ScrollBar* sender, size_t pos);
+ void onAreaChanged (MyGUI::ScrollBar* sender, size_t pos);
+ void setMagicEffect(const ESM::MagicEffect* effect);
+
+ void updateBoxes();
+
+ protected:
+ ESM::ENAMstruct mEffect;
+ ESM::ENAMstruct mOldEffect;
+
+ const ESM::MagicEffect* mMagicEffect;
+ };
+
+
+ class EffectEditorBase
+ {
+ public:
+ EffectEditorBase();
+ virtual ~EffectEditorBase();
+
+ protected:
+ std::map<int, short> mButtonMapping; // maps button ID to effect ID
+
+ Widgets::MWList* mAvailableEffectsList;
+ MyGUI::ScrollView* mUsedEffectsView;
+
+ EditEffectDialog mAddEffectDialog;
+ SelectAttributeDialog* mSelectAttributeDialog;
+ SelectSkillDialog* mSelectSkillDialog;
+
+ int mSelectedEffect;
+
+ std::vector<ESM::ENAMstruct> mEffects;
+
+ void onEffectAdded(ESM::ENAMstruct effect);
+ void onEffectModified(ESM::ENAMstruct effect);
+ void onEffectRemoved(ESM::ENAMstruct effect);
+
+ void onAvailableEffectClicked (MyGUI::Widget* sender);
+
+ void onAttributeOrSkillCancel();
+ void onSelectAttribute();
+ void onSelectSkill();
+
+ void onEditEffect(MyGUI::Widget* sender);
+
+ void updateEffectsView();
+
+ void startEditing();
+ void setWidgets (Widgets::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView);
+
+ virtual void notifyEffectsChanged () {}
+ };
+
+ class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase
+ {
+ public:
+ SpellCreationDialog();
+
+ virtual void open();
+
+ void startSpellMaking(MWWorld::Ptr actor);
+
+ protected:
+ virtual void onReferenceUnavailable ();
+
+ void onCancelButtonClicked (MyGUI::Widget* sender);
+ void onBuyButtonClicked (MyGUI::Widget* sender);
+
+ virtual void notifyEffectsChanged ();
+
+ MyGUI::EditBox* mNameEdit;
+ MyGUI::TextBox* mMagickaCost;
+ MyGUI::TextBox* mSuccessChance;
+ MyGUI::Button* mBuyButton;
+ MyGUI::Button* mCancelButton;
+ MyGUI::TextBox* mPriceLabel;
+
+ Widgets::MWEffectList* mUsedEffectsList;
+
+ ESM::Spell mSpell;
+
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp
new file mode 100644
index 0000000000..9812c0f8a9
--- /dev/null
+++ b/apps/openmw/mwgui/spellicons.cpp
@@ -0,0 +1,298 @@
+#include "spellicons.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "tooltips.hpp"
+
+
+namespace MWGui
+{
+
+ void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+
+ 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // 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();
+
+ 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;
+
+ effectInfo.mMagnitude = static_cast<int> (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost));
+ }
+
+ effects[effectIt->mEffectID].push_back (effectInfo);
+ }
+ }
+
+ int w=2;
+
+ for (std::map <int, std::vector<MagicEffectInfo> >::const_iterator it = effects.begin(); it != effects.end(); ++it)
+ {
+ const ESM::MagicEffect* effect =
+ MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(it->first);
+
+ float remainingDuration = 0;
+
+ std::string sourcesDescription;
+
+ const float fadeTime = 5.f;
+
+ for (std::vector<MagicEffectInfo>::const_iterator effectIt = it->second.begin();
+ effectIt != it->second.end(); ++effectIt)
+ {
+ if (effectIt != it->second.begin())
+ sourcesDescription += "\n";
+
+ // if at least one of the effect sources is permanent, the effect will never wear off
+ if (effectIt->mPermanent)
+ remainingDuration = fadeTime;
+ else
+ remainingDuration = std::max(remainingDuration, effectIt->mRemainingTime);
+
+ sourcesDescription += effectIt->mSource;
+
+ if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill)
+ sourcesDescription += " (" +
+ MWBase::Environment::get().getWindowManager()->getGameSettingString(
+ ESM::Skill::sSkillNameIds[effectIt->mKey.mArg], "") + ")";
+ if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
+ sourcesDescription += " (" +
+ MWBase::Environment::get().getWindowManager()->getGameSettingString(
+ ESM::Attribute::sGmstAttributeIds[effectIt->mKey.mArg], "") + ")";
+
+ if (!(effect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
+ {
+ std::string pt = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "");
+ std::string pts = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", "");
+
+ sourcesDescription += ": " + boost::lexical_cast<std::string>(effectIt->mMagnitude);
+ sourcesDescription += " " + ((effectIt->mMagnitude > 1) ? pts : pt);
+ }
+ }
+
+ if (remainingDuration > 0.f)
+ {
+ MyGUI::ImageBox* image;
+ if (mWidgetMap.find(it->first) == mWidgetMap.end())
+ {
+ image = parent->createWidget<MyGUI::ImageBox>
+ ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default);
+ mWidgetMap[it->first] = image;
+
+ std::string icon = effect->mIcon;
+ icon[icon.size()-3] = 'd';
+ icon[icon.size()-2] = 'd';
+ icon[icon.size()-1] = 's';
+ icon = "icons\\" + icon;
+
+ image->setImageTexture(icon);
+
+ std::string name = ESM::MagicEffect::effectIdToString (it->first);
+
+ ToolTipInfo tooltipInfo;
+ tooltipInfo.caption = "#{" + name + "}";
+ tooltipInfo.icon = effect->mIcon;
+ tooltipInfo.imageSize = 16;
+ tooltipInfo.wordWrap = false;
+
+ image->setUserData(tooltipInfo);
+ image->setUserString("ToolTipType", "ToolTipInfo");
+ }
+ else
+ image = mWidgetMap[it->first];
+
+ image->setPosition(w,2);
+ image->setVisible(true);
+ w += 16;
+
+ ToolTipInfo* tooltipInfo = image->getUserData<ToolTipInfo>();
+ tooltipInfo->text = sourcesDescription;
+
+ // Fade out during the last 5 seconds
+ image->setAlpha(std::min(remainingDuration/fadeTime, 1.f));
+ }
+ else if (mWidgetMap.find(it->first) != mWidgetMap.end())
+ {
+ mWidgetMap[it->first]->setVisible(false);
+ }
+ }
+
+ if (adjustSize)
+ {
+ int s = w + 2;
+ if (effects.empty())
+ s = 0;
+ int diff = parent->getWidth() - s;
+ parent->setSize(s, parent->getHeight());
+ parent->setPosition(parent->getLeft()+diff, parent->getTop());
+ }
+
+ // hide inactive effects
+ for (std::map<int, MyGUI::ImageBox*>::iterator it = mWidgetMap.begin(); it != mWidgetMap.end(); ++it)
+ {
+ if (effects.find(it->first) == effects.end())
+ it->second->setVisible(false);
+ }
+ }
+
+
+ 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
new file mode 100644
index 0000000000..818d67b5bb
--- /dev/null
+++ b/apps/openmw/mwgui/spellicons.hpp
@@ -0,0 +1,51 @@
+#ifndef MWGUI_SPELLICONS_H
+#define MWGUI_SPELLICONS_H
+
+#include <string>
+
+#include "../mwmechanics/magiceffects.hpp"
+
+namespace MyGUI
+{
+ class Widget;
+ class ImageBox;
+}
+namespace ESM
+{
+ struct ENAMstruct;
+ struct EffectList;
+}
+
+namespace MWGui
+{
+
+ // information about a single magic effect source as required for display in the tooltip
+ struct MagicEffectInfo
+ {
+ MagicEffectInfo()
+ : mPermanent(false)
+ , mMagnitude(0)
+ , mRemainingTime(0)
+ {}
+ std::string mSource; // display name for effect source (e.g. potion name)
+ MWMechanics::EffectKey mKey;
+ int mMagnitude;
+ float mRemainingTime;
+ bool mPermanent; // the effect is permanent
+ };
+
+ 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;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp
new file mode 100644
index 0000000000..d5e3abc110
--- /dev/null
+++ b/apps/openmw/mwgui/spellwindow.cpp
@@ -0,0 +1,456 @@
+#include "spellwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/actionequip.hpp"
+
+#include "../mwmechanics/spellsuccess.hpp"
+
+#include "spellicons.hpp"
+#include "inventorywindow.hpp"
+#include "confirmationdialog.hpp"
+
+namespace
+{
+ bool sortSpells(const std::string& left, const std::string& right)
+ {
+ const MWWorld::Store<ESM::Spell> &spells =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>();
+
+ const ESM::Spell* a = spells.find(left);
+ const ESM::Spell* b = spells.find(right);
+
+ int cmp = a->mName.compare(b->mName);
+ return cmp < 0;
+ }
+
+ bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
+ {
+ int cmp = MWWorld::Class::get(left).getName(left).compare(
+ MWWorld::Class::get(right).getName(right));
+ return cmp < 0;
+ }
+}
+
+namespace MWGui
+{
+ SpellWindow::SpellWindow()
+ : WindowPinnableBase("openmw_spell_window.layout")
+ , mHeight(0)
+ , mWidth(0)
+ {
+ mSpellIcons = new SpellIcons();
+
+ getWidget(mSpellView, "SpellView");
+ getWidget(mEffectBox, "EffectsBox");
+
+ setCoord(498, 300, 302, 300);
+
+ updateSpells();
+
+ mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize);
+ }
+
+ SpellWindow::~SpellWindow()
+ {
+ delete mSpellIcons;
+ }
+
+ void SpellWindow::onPinToggled()
+ {
+ MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned);
+ }
+
+ void SpellWindow::open()
+ {
+ updateSpells();
+ }
+
+ void SpellWindow::updateSpells()
+ {
+ mSpellIcons->updateWidgets(mEffectBox, false);
+
+ const int spellHeight = 18;
+
+ mHeight = 0;
+ while (mSpellView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0));
+
+ // retrieve all player spells, divide them into Powers and Spells and sort them
+ std::vector<std::string> spellList;
+ 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();
+
+ // 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();
+
+ std::vector<std::string> powers;
+ std::vector<std::string>::iterator it = spellList.begin();
+ while (it != spellList.end())
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+
+ if (spell->mData.mType == ESM::Spell::ST_Power)
+ {
+ powers.push_back(*it);
+ it = spellList.erase(it);
+ }
+ else if (spell->mData.mType == ESM::Spell::ST_Ability
+ || spell->mData.mType == ESM::Spell::ST_Blight
+ || spell->mData.mType == ESM::Spell::ST_Curse
+ || spell->mData.mType == ESM::Spell::ST_Disease)
+ {
+ it = spellList.erase(it);
+ }
+ else
+ ++it;
+ }
+ std::sort(powers.begin(), powers.end(), sortSpells);
+ std::sort(spellList.begin(), spellList.end(), sortSpells);
+
+ // retrieve player's enchanted items
+ std::vector<MWWorld::Ptr> items;
+ for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
+ {
+ std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it);
+ if (enchantId != "")
+ {
+ // only add items with "Cast once" or "Cast on use"
+ const ESM::Enchantment* enchant =
+ esmStore.get<ESM::Enchantment>().find(enchantId);
+
+ int type = enchant->mData.mType;
+ if (type != ESM::Enchantment::CastOnce
+ && type != ESM::Enchantment::WhenUsed)
+ continue;
+
+ items.push_back(*it);
+ }
+ }
+ std::sort(items.begin(), items.end(), sortItems);
+
+
+ int height = estimateHeight(items.size() + powers.size() + spellList.size());
+ bool scrollVisible = height > mSpellView->getHeight();
+ mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0);
+
+ // powers
+ addGroup("#{sPowers}", "");
+
+ for (std::vector<std::string>::const_iterator it = powers.begin(); it != powers.end(); ++it)
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+ MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>("SpellText",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(spell->mName);
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserString("ToolTipType", "Spell");
+ t->setUserString("Spell", *it);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
+
+ if (*it == selectedSpell)
+ t->setStateSelected(true);
+
+ mHeight += spellHeight;
+ }
+
+ // other spells
+ addGroup("#{sSpells}", "#{sCostChance}");
+ for (std::vector<std::string>::const_iterator it = spellList.begin(); it != spellList.end(); ++it)
+ {
+ const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
+ MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>("SpellText",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(spell->mName);
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserString("ToolTipType", "Spell");
+ t->setUserString("Spell", *it);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
+ t->setStateSelected(*it == selectedSpell);
+
+ // cost / success chance
+ MyGUI::Button* costChance = mSpellView->createWidget<MyGUI::Button>("SpellText",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ std::string cost = boost::lexical_cast<std::string>(spell->mData.mCost);
+ std::string chance = boost::lexical_cast<std::string>(int(MWMechanics::getSpellSuccessChance(*it, player)));
+ costChance->setCaption(cost + "/" + chance);
+ costChance->setTextAlign(MyGUI::Align::Right);
+ costChance->setNeedMouseFocus(false);
+ costChance->setStateSelected(*it == selectedSpell);
+
+
+ mHeight += spellHeight;
+ }
+
+
+ // enchanted items
+ addGroup("#{sMagicItem}", "#{sCostCharge}");
+
+ for (std::vector<MWWorld::Ptr>::const_iterator it = items.begin(); it != items.end(); ++it)
+ {
+ MWWorld::Ptr item = *it;
+
+ const ESM::Enchantment* enchant =
+ esmStore.get<ESM::Enchantment>().find(MWWorld::Class::get(item).getEnchantment(item));
+
+ // check if the item is currently equipped (will display in a different color)
+ bool equipped = false;
+ for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
+ {
+ if (store.getSlot(i) != store.end() && *store.getSlot(i) == item)
+ {
+ equipped = true;
+ break;
+ }
+ }
+
+ MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
+ t->setCaption(MWWorld::Class::get(item).getName(item));
+ t->setTextAlign(MyGUI::Align::Left);
+ t->setUserData(item);
+ t->setUserString("ToolTipType", "ItemPtr");
+ t->setUserString("Equipped", equipped ? "true" : "false");
+ t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
+ t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
+ t->setStateSelected(item == selectedItem);
+
+ // 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
+ if (enchant->mData.mType == ESM::Enchantment::CastOnce)
+ {
+ // this is Morrowind behaviour
+ cost = "100";
+ charge = "100";
+ }
+
+ costCharge->setCaption(cost + "/" + charge);
+ costCharge->setTextAlign(MyGUI::Align::Right);
+ costCharge->setNeedMouseFocus(false);
+ costCharge->setStateSelected(item == selectedItem);
+
+ mHeight += spellHeight;
+ }
+
+ mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight));
+ }
+
+ void SpellWindow::addGroup(const std::string &label, const std::string& label2)
+ {
+ if (mSpellView->getChildCount() > 0)
+ {
+ MyGUI::ImageBox* separator = mSpellView->createWidget<MyGUI::ImageBox>("MW_HLine",
+ MyGUI::IntCoord(4, mHeight, mWidth-8, 18),
+ MyGUI::Align::Left | MyGUI::Align::Top);
+ separator->setNeedMouseFocus(false);
+ mHeight += 18;
+ }
+
+ MyGUI::TextBox* groupWidget = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, mHeight, mWidth, 24),
+ MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ groupWidget->setCaptionWithReplacing(label);
+ groupWidget->setTextAlign(MyGUI::Align::Left);
+ groupWidget->setNeedMouseFocus(false);
+
+ if (label2 != "")
+ {
+ MyGUI::TextBox* groupWidget2 = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, mHeight, mWidth-4, 24),
+ MyGUI::Align::Left | MyGUI::Align::Top);
+ groupWidget2->setCaptionWithReplacing(label2);
+ groupWidget2->setTextAlign(MyGUI::Align::Right);
+ groupWidget2->setNeedMouseFocus(false);
+ }
+
+ mHeight += 24;
+ }
+
+ void SpellWindow::onWindowResize(MyGUI::Window* _sender)
+ {
+ updateSpells();
+ }
+
+ 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
+ MWWorld::ContainerStoreIterator it = store.begin();
+ for (; it != store.end(); ++it)
+ {
+ if (*it == item)
+ {
+ break;
+ }
+ }
+ assert(it != store.end());
+
+ // equip, if it can be equipped and is not already equipped
+ if (_sender->getUserString("Equipped") == "false"
+ && !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
+ {
+ // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
+
+ 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);
+
+ updateSpells();
+ }
+
+ void SpellWindow::onSpellSelected(MyGUI::Widget* _sender)
+ {
+ 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())
+ {
+ // delete spell, if allowed
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+
+ if (spell->mData.mFlags & ESM::Spell::F_Always
+ || spell->mData.mType == ESM::Spell::ST_Power)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}");
+ }
+ else
+ {
+ // ask for confirmation
+ mSpellToDelete = spellId;
+ ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
+ std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?");
+ question = boost::str(boost::format(question) % spell->mName);
+ dialog->open(question);
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept);
+ dialog->eventCancelClicked.clear();
+ }
+ }
+ else
+ {
+ spells.setSelectedSpell(spellId);
+ store.setSelectedEnchantItem(store.end());
+ MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
+ }
+
+ updateSpells();
+ }
+
+ int SpellWindow::estimateHeight(int numSpells) const
+ {
+ int height = 0;
+ height += 24 * 3 + 18 * 2; // group headings
+ height += numSpells * 18;
+ return height;
+ }
+
+ void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mSpellView->getViewOffset().top + _rel*0.3 > 0)
+ mSpellView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3));
+ }
+
+ void SpellWindow::onDeleteSpellAccept()
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MWMechanics::Spells& spells = stats.getSpells();
+
+ if (spells.getSelectedSpell() == mSpellToDelete)
+ {
+ spells.setSelectedSpell("");
+ MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
+ }
+
+ spells.remove(mSpellToDelete);
+
+ updateSpells();
+ }
+}
diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp
new file mode 100644
index 0000000000..521e73d767
--- /dev/null
+++ b/apps/openmw/mwgui/spellwindow.hpp
@@ -0,0 +1,44 @@
+#ifndef MWGUI_SPELLWINDOW_H
+#define MWGUI_SPELLWINDOW_H
+
+#include "windowpinnablebase.hpp"
+
+namespace MWGui
+{
+ class SpellIcons;
+
+ class SpellWindow : public WindowPinnableBase
+ {
+ public:
+ SpellWindow();
+ virtual ~SpellWindow();
+
+ void updateSpells();
+
+ protected:
+ MyGUI::ScrollView* mSpellView;
+ MyGUI::Widget* mEffectBox;
+
+ int mHeight;
+ int mWidth;
+
+ std::string mSpellToDelete;
+
+ void addGroup(const std::string& label, const std::string& label2);
+
+ int estimateHeight(int numSpells) const;
+
+ void onWindowResize(MyGUI::Window* _sender);
+ void onEnchantedItemSelected(MyGUI::Widget* _sender);
+ void onSpellSelected(MyGUI::Widget* _sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void onDeleteSpellAccept();
+
+ virtual void onPinToggled();
+ virtual void open();
+
+ SpellIcons* mSpellIcons;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
new file mode 100644
index 0000000000..9facdac404
--- /dev/null
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -0,0 +1,573 @@
+#include "statswindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "tooltips.hpp"
+
+namespace MWGui
+{
+
+ const int StatsWindow::sLineHeight = 18;
+
+ StatsWindow::StatsWindow ()
+ : WindowPinnableBase("openmw_stats_window.layout")
+ , mSkillView(NULL)
+ , mMajorSkills()
+ , mMinorSkills()
+ , mMiscSkills()
+ , mSkillValues()
+ , mSkillWidgetMap()
+ , mFactionWidgetMap()
+ , mFactions()
+ , mBirthSignId()
+ , mReputation(0)
+ , mBounty(0)
+ , mSkillWidgets()
+ , mChanged(true)
+ {
+ setCoord(0,0,498, 342);
+
+ const char *names[][2] =
+ {
+ { "Attrib1", "sAttributeStrength" },
+ { "Attrib2", "sAttributeIntelligence" },
+ { "Attrib3", "sAttributeWillpower" },
+ { "Attrib4", "sAttributeAgility" },
+ { "Attrib5", "sAttributeSpeed" },
+ { "Attrib6", "sAttributeEndurance" },
+ { "Attrib7", "sAttributePersonality" },
+ { "Attrib8", "sAttributeLuck" },
+ { 0, 0 }
+ };
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ for (int i=0; names[i][0]; ++i)
+ {
+ setText (names[i][0], store.get<ESM::GameSetting>().find (names[i][1])->getString());
+ }
+
+ getWidget(mSkillView, "SkillView");
+ getWidget(mLeftPane, "LeftPane");
+ getWidget(mRightPane, "RightPane");
+
+ for (int i = 0; i < ESM::Skill::Length; ++i)
+ {
+ mSkillValues.insert(std::pair<int, MWMechanics::Stat<float> >(i, MWMechanics::Stat<float>()));
+ mSkillWidgetMap.insert(std::pair<int, MyGUI::TextBox*>(i, (MyGUI::TextBox*)NULL));
+ }
+
+ MyGUI::WindowPtr t = static_cast<MyGUI::WindowPtr>(mMainWidget);
+ t->eventWindowChangeCoord += MyGUI::newDelegate(this, &StatsWindow::onWindowResize);
+ }
+
+ void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mSkillView->getViewOffset().top + _rel*0.3 > 0)
+ mSkillView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mSkillView->setViewOffset(MyGUI::IntPoint(0, mSkillView->getViewOffset().top + _rel*0.3));
+ }
+
+ void StatsWindow::onWindowResize(MyGUI::Window* window)
+ {
+ mLeftPane->setCoord( MyGUI::IntCoord(0, 0, 0.44*window->getSize().width, window->getSize().height) );
+ mRightPane->setCoord( MyGUI::IntCoord(0.44*window->getSize().width, 0, 0.56*window->getSize().width, window->getSize().height) );
+ mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height);
+ }
+
+ void StatsWindow::setBar(const std::string& name, const std::string& tname, int val, int max)
+ {
+ MyGUI::ProgressPtr pt;
+ getWidget(pt, name);
+ pt->setProgressRange(max);
+ pt->setProgressPosition(val);
+
+ std::stringstream out;
+ out << val << "/" << max;
+ setText(tname, out.str().c_str());
+ }
+
+ void StatsWindow::setPlayerName(const std::string& playerName)
+ {
+ static_cast<MyGUI::Window*>(mMainWidget)->setCaption(playerName);
+ adjustWindowCaption();
+ }
+
+ void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ {
+ static const char *ids[] =
+ {
+ "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5",
+ "AttribVal6", "AttribVal7", "AttribVal8",
+ 0
+ };
+
+ for (int i=0; ids[i]; ++i)
+ if (ids[i]==id)
+ {
+ std::ostringstream valueString;
+ valueString << value.getModified();
+ setText (id, valueString.str());
+
+ MyGUI::TextBox* box;
+ getWidget(box, id);
+
+ if (value.getModified()>value.getBase())
+ box->_setWidgetState("increased");
+ else if (value.getModified()<value.getBase())
+ box->_setWidgetState("decreased");
+ else
+ box->_setWidgetState("normal");
+
+ break;
+ }
+ }
+
+ void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
+ {
+ static const char *ids[] =
+ {
+ "HBar", "MBar", "FBar",
+ 0
+ };
+
+ 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()));
+
+ // 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);
+ }
+ }
+ }
+ }
+
+ void StatsWindow::setValue (const std::string& id, const std::string& value)
+ {
+ if (id=="name")
+ setPlayerName (value);
+ else if (id=="race")
+ setText ("RaceText", value);
+ else if (id=="class")
+ setText ("ClassText", value);
+ }
+
+ void StatsWindow::setValue (const std::string& id, int value)
+ {
+ if (id=="level")
+ {
+ std::ostringstream text;
+ text << value;
+ setText("LevelText", text.str());
+ }
+ }
+
+ void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value)
+ {
+ mSkillValues[parSkill] = value;
+ MyGUI::TextBox* widget = mSkillWidgetMap[(int)parSkill];
+ if (widget)
+ {
+ float modified = value.getModified(), base = value.getBase();
+ std::string text = boost::lexical_cast<std::string>(std::floor(modified));
+ std::string state = "normal";
+ if (modified > base)
+ state = "increased";
+ else if (modified < base)
+ state = "decreased";
+
+ widget->setCaption(text);
+ widget->_setWidgetState(state);
+ }
+ }
+
+ void StatsWindow::configureSkills (const std::vector<int>& major, const std::vector<int>& minor)
+ {
+ mMajorSkills = major;
+ mMinorSkills = minor;
+
+ // Update misc skills with the remaining skills not in major or minor
+ std::set<int> skillSet;
+ std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin()));
+ std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
+ boost::array<ESM::Skill::SkillEnum, ESM::Skill::Length>::const_iterator end = ESM::Skill::sSkillIds.end();
+ mMiscSkills.clear();
+ for (boost::array<ESM::Skill::SkillEnum, ESM::Skill::Length>::const_iterator it = ESM::Skill::sSkillIds.begin(); it != end; ++it)
+ {
+ int skill = *it;
+ if (skillSet.find(skill) == skillSet.end())
+ mMiscSkills.push_back(skill);
+ }
+
+ updateSkillArea();
+ }
+
+ void StatsWindow::onFrame ()
+ {
+ if (!mMainWidget->getVisible())
+ return;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player);
+
+ // level progress
+ MyGUI::Widget* levelWidget;
+ for (int i=0; i<2; ++i)
+ {
+ getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
+ levelWidget->setUserString("RangePosition_LevelProgress", boost::lexical_cast<std::string>(PCstats.getLevelProgress()));
+ levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast<std::string>(PCstats.getLevelProgress()) + "/10");
+ }
+
+ setFactions(PCstats.getFactionRanks());
+ setExpelled(PCstats.getExpelled ());
+
+ const std::string &signId =
+ MWBase::Environment::get().getWorld()->getPlayer().getBirthSign();
+
+ setBirthSign(signId);
+ setReputation (PCstats.getReputation ());
+ setBounty (PCstats.getBounty ());
+
+ if (mChanged)
+ updateSkillArea();
+ }
+
+ void StatsWindow::setFactions (const FactionList& factions)
+ {
+ if (mFactions != factions)
+ {
+ mFactions = factions;
+ mChanged = true;
+ }
+ }
+
+ void StatsWindow::setExpelled (const std::set<std::string>& expelled)
+ {
+ if (mExpelled != expelled)
+ {
+ mExpelled = expelled;
+ mChanged = true;
+ }
+ }
+
+ void StatsWindow::setBirthSign (const std::string& signId)
+ {
+ if (signId != mBirthSignId)
+ {
+ mBirthSignId = signId;
+ mChanged = true;
+ }
+ }
+
+ void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::ImageBox* separator = mSkillView->createWidget<MyGUI::ImageBox>("MW_HLine",
+ MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18),
+ MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ separator->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel);
+ mSkillWidgets.push_back(separator);
+
+ coord1.top += separator->getHeight();
+ coord2.top += separator->getHeight();
+ }
+
+ void StatsWindow::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox* groupWidget = mSkillView->createWidget<MyGUI::TextBox>("SandBrightText",
+ MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height),
+ MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ groupWidget->setCaption(label);
+ groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel);
+ mSkillWidgets.push_back(groupWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+ }
+
+ MyGUI::TextBox* StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox *skillNameWidget, *skillValueWidget;
+
+ skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
+ skillNameWidget->setCaption(text);
+ skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel);
+
+ skillValueWidget = mSkillView->createWidget<MyGUI::TextBox>("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top);
+ skillValueWidget->setCaption(value);
+ skillValueWidget->_setWidgetState(state);
+ skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel);
+
+ mSkillWidgets.push_back(skillNameWidget);
+ mSkillWidgets.push_back(skillValueWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+
+ return skillValueWidget;
+ }
+
+ MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ MyGUI::TextBox* skillNameWidget;
+
+ skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default);
+ skillNameWidget->setCaption(text);
+ skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel);
+
+ mSkillWidgets.push_back(skillNameWidget);
+
+ coord1.top += sLineHeight;
+ coord2.top += sLineHeight;
+
+ return skillNameWidget;
+ }
+
+ void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
+ {
+ // Add a line separator if there are items above
+ if (!mSkillWidgets.empty())
+ {
+ addSeparator(coord1, coord2);
+ }
+
+ addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
+
+ SkillList::const_iterator end = skills.end();
+ for (SkillList::const_iterator it = skills.begin(); it != end; ++it)
+ {
+ int skillId = *it;
+ if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes
+ continue;
+ assert(skillId >= 0 && skillId < ESM::Skill::Length);
+ const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId];
+ const MWMechanics::Stat<float> &stat = mSkillValues.find(skillId)->second;
+ float base = stat.getBase();
+ float modified = stat.getModified();
+ int progressPercent = (modified - float(static_cast<int>(modified))) * 100;
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::Skill* skill = esmStore.get<ESM::Skill>().find(skillId);
+ assert(skill);
+
+ std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId];
+
+ const ESM::Attribute* attr =
+ esmStore.get<ESM::Attribute>().find(skill->mData.mAttribute);
+ assert(attr);
+
+ std::string state = "normal";
+ if (modified > base)
+ state = "increased";
+ else if (modified < base)
+ state = "decreased";
+ MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId),
+ boost::lexical_cast<std::string>(static_cast<int>(modified)), state, coord1, coord2);
+
+ for (int i=0; i<2; ++i)
+ {
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->mDescription);
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon);
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast<std::string>(progressPercent)+"/100");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast<std::string>(progressPercent));
+ }
+
+ mSkillWidgetMap[skillId] = widget;
+ }
+ }
+
+ void StatsWindow::updateSkillArea()
+ {
+ mChanged = false;
+
+ for (std::vector<MyGUI::Widget*>::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it)
+ {
+ MyGUI::Gui::getInstance().destroyWidget(*it);
+ }
+ 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);
+
+ if (!mMajorSkills.empty())
+ addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2);
+
+ if (!mMinorSkills.empty())
+ addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2);
+
+ if (!mMiscSkills.empty())
+ addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2);
+
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::ESMStore &store = world->getStore();
+ const ESM::NPC *player =
+ world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+
+ // race tooltip
+ const ESM::Race* playerRace = store.get<ESM::Race>().find(player->mRace);
+
+ MyGUI::Widget* raceWidget;
+ getWidget(raceWidget, "RaceText");
+ ToolTips::createRaceToolTip(raceWidget, playerRace);
+ getWidget(raceWidget, "Race_str");
+ ToolTips::createRaceToolTip(raceWidget, playerRace);
+
+ // class tooltip
+ MyGUI::Widget* classWidget;
+
+ const ESM::Class *playerClass =
+ store.get<ESM::Class>().find(player->mClass);
+
+ getWidget(classWidget, "ClassText");
+ ToolTips::createClassToolTip(classWidget, *playerClass);
+ getWidget(classWidget, "Class_str");
+ ToolTips::createClassToolTip(classWidget, *playerClass);
+
+ if (!mFactions.empty())
+ {
+ // Add a line separator if there are items above
+ if (!mSkillWidgets.empty())
+ addSeparator(coord1, coord2);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player);
+ const std::set<std::string> &expelled = PCstats.getExpelled();
+
+ addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), coord1, coord2);
+ FactionList::const_iterator end = mFactions.end();
+ for (FactionList::const_iterator it = mFactions.begin(); it != end; ++it)
+ {
+ const ESM::Faction *faction =
+ store.get<ESM::Faction>().find(it->first);
+ MyGUI::Widget* w = addItem(faction->mName, coord1, coord2);
+
+ std::string text;
+
+ text += std::string("#DDC79E") + faction->mName;
+
+ if (expelled.find(it->first) != expelled.end())
+ text += "\n#{sExpelled}";
+ else
+ {
+ text += std::string("\n#BF9959") + faction->mRanks[it->second];
+
+ if (it->second < 9)
+ {
+ // player doesn't have max rank yet
+ text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[it->second+1];
+
+ ESM::RankData rankData = faction->mData.mRankData[it->second+1];
+ const ESM::Attribute* attr1 = store.get<ESM::Attribute>().find(faction->mData.mAttribute[0]);
+ const ESM::Attribute* attr2 = store.get<ESM::Attribute>().find(faction->mData.mAttribute[1]);
+ assert(attr1 && attr2);
+
+ text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast<std::string>(rankData.mAttribute1)
+ + ", #{" + attr2->mName + "}: " + boost::lexical_cast<std::string>(rankData.mAttribute2);
+
+ text += "\n\n#DDC79E#{sFavoriteSkills}";
+ text += "\n#BF9959";
+ for (int i=0; i<6; ++i)
+ {
+ text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}";
+ if (i<5)
+ text += ", ";
+ }
+
+ text += "\n";
+
+ if (rankData.mSkill1 > 0)
+ text += "\n#{sNeedOneSkill} " + boost::lexical_cast<std::string>(rankData.mSkill1);
+ if (rankData.mSkill2 > 0)
+ text += "\n#{sNeedTwoSkills} " + boost::lexical_cast<std::string>(rankData.mSkill2);
+ }
+ }
+
+ w->setUserString("ToolTipType", "Layout");
+ w->setUserString("ToolTipLayout", "TextToolTip");
+ w->setUserString("Caption_Text", text);
+ }
+ }
+
+ if (!mBirthSignId.empty())
+ {
+ // Add a line separator if there are items above
+ if (!mSkillWidgets.empty())
+ addSeparator(coord1, coord2);
+
+ addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBirthSign", "Sign"), coord1, coord2);
+ const ESM::BirthSign *sign =
+ store.get<ESM::BirthSign>().find(mBirthSignId);
+ MyGUI::Widget* w = addItem(sign->mName, coord1, coord2);
+
+ ToolTips::createBirthsignToolTip(w, mBirthSignId);
+ }
+
+ // Add a line separator if there are items above
+ if (!mSkillWidgets.empty())
+ addSeparator(coord1, coord2);
+
+ addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sReputation", "Reputation"),
+ boost::lexical_cast<std::string>(static_cast<int>(mReputation)), "normal", coord1, coord2);
+
+ for (int i=0; i<2; ++i)
+ {
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}");
+ }
+
+ addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBounty", "Bounty"),
+ boost::lexical_cast<std::string>(static_cast<int>(mBounty)), "normal", coord1, coord2);
+
+ for (int i=0; i<2; ++i)
+ {
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip");
+ mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}");
+ }
+
+ mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top));
+ }
+
+ void StatsWindow::onPinToggled()
+ {
+ MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned);
+ }
+}
diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp
new file mode 100644
index 0000000000..ac8319bdcd
--- /dev/null
+++ b/apps/openmw/mwgui/statswindow.hpp
@@ -0,0 +1,77 @@
+#ifndef MWGUI_STATS_WINDOW_H
+#define MWGUI_STATS_WINDOW_H
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwmechanics/stat.hpp"
+#include "windowpinnablebase.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+
+ class StatsWindow : public WindowPinnableBase
+ {
+ public:
+ typedef std::map<std::string, int> FactionList;
+
+ typedef std::vector<int> SkillList;
+
+ StatsWindow();
+
+ /// automatically updates all the data in the stats window, but only if it has changed.
+ void onFrame();
+
+ void setBar(const std::string& name, const std::string& tname, int val, int max);
+ void setPlayerName(const std::string& playerName);
+
+ /// Set value for the given ID.
+ 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 std::string& id, const std::string& value);
+ void setValue (const std::string& id, int value);
+ void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value);
+
+ void configureSkills (const SkillList& major, const SkillList& minor);
+ void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; }
+ void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; }
+ void updateSkillArea();
+
+ 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);
+ void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+ MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+ MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2);
+
+ void setFactions (const FactionList& factions);
+ void setExpelled (const std::set<std::string>& expelled);
+ void setBirthSign (const std::string &signId);
+
+ void onWindowResize(MyGUI::Window* window);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+ static const int sLineHeight;
+
+ MyGUI::Widget* mLeftPane;
+ MyGUI::Widget* mRightPane;
+
+ MyGUI::ScrollView* mSkillView;
+
+ SkillList mMajorSkills, mMinorSkills, mMiscSkills;
+ std::map<int, MWMechanics::Stat<float> > mSkillValues;
+ std::map<int, MyGUI::TextBox*> mSkillWidgetMap;
+ std::map<std::string, MyGUI::Widget*> mFactionWidgetMap;
+ FactionList mFactions; ///< Stores a list of factions and the current rank
+ std::string mBirthSignId;
+ int mReputation, mBounty;
+ std::vector<MyGUI::Widget*> mSkillWidgets; //< Skills and other information
+ std::set<std::string> mExpelled;
+
+ bool mChanged;
+
+ protected:
+ virtual void onPinToggled();
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp
new file mode 100644
index 0000000000..954bc41abb
--- /dev/null
+++ b/apps/openmw/mwgui/textinput.cpp
@@ -0,0 +1,73 @@
+#include "textinput.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+
+namespace MWGui
+{
+
+ TextInputDialog::TextInputDialog()
+ : WindowModal("openmw_text_input.layout")
+ {
+ // Centre dialog
+ center();
+
+ getWidget(mTextEdit, "TextEdit");
+ mTextEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted);
+
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+ okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked);
+
+ // Make sure the edit box has focus
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit);
+ }
+
+ void TextInputDialog::setNextButtonShow(bool shown)
+ {
+ MyGUI::Button* okButton;
+ getWidget(okButton, "OKButton");
+
+ if (shown)
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
+ else
+ okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ }
+
+ void TextInputDialog::setTextLabel(const std::string &label)
+ {
+ setText("LabelT", label);
+ }
+
+ void TextInputDialog::open()
+ {
+ WindowModal::open();
+ // Make sure the edit box has focus
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit);
+ }
+
+ // widget controls
+
+ void TextInputDialog::onOkClicked(MyGUI::Widget* _sender)
+ {
+ if (mTextEdit->getCaption() == "")
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}");
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit);
+ }
+ else
+ eventDone(this);
+ }
+
+ void TextInputDialog::onTextAccepted(MyGUI::Edit* _sender)
+ {
+ if (mTextEdit->getCaption() == "")
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}");
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit);
+ }
+ else
+ eventDone(this);
+ }
+
+}
diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp
new file mode 100644
index 0000000000..1f53263ecd
--- /dev/null
+++ b/apps/openmw/mwgui/textinput.hpp
@@ -0,0 +1,33 @@
+#ifndef MWGUI_TEXT_INPUT_H
+#define MWGUI_TEXT_INPUT_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+namespace MWGui
+{
+ class TextInputDialog : public WindowModal
+ {
+ public:
+ TextInputDialog();
+
+ std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; }
+ void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); }
+
+ void setNextButtonShow(bool shown);
+ void setTextLabel(const std::string &label);
+ virtual void open();
+
+ protected:
+ void onOkClicked(MyGUI::Widget* _sender);
+ void onTextAccepted(MyGUI::Edit* _sender);
+
+ private:
+ MyGUI::EditBox* mTextEdit;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp
new file mode 100644
index 0000000000..3a609aa91f
--- /dev/null
+++ b/apps/openmw/mwgui/tooltips.cpp
@@ -0,0 +1,764 @@
+#include "tooltips.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/class.hpp"
+
+#include "mapwindow.hpp"
+#include "inventorywindow.hpp"
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ ToolTips::ToolTips() :
+ Layout("openmw_tooltips.layout")
+ , mFullHelp(false)
+ , mEnabled(true)
+ , mFocusToolTipX(0.0)
+ , mFocusToolTipY(0.0)
+ , mDelay(0.0)
+ , mRemainingDelay(0.0)
+ , mLastMouseX(0)
+ , mLastMouseY(0)
+ , mHorizontalScrollIndex(0)
+ {
+ getWidget(mDynamicToolTipBox, "DynamicToolTipBox");
+
+ mDynamicToolTipBox->setVisible(false);
+
+ // turn off mouse focus so that getMouseFocusWidget returns the correct widget,
+ // even if the mouse is over the tooltip
+ mDynamicToolTipBox->setNeedMouseFocus(false);
+ mMainWidget->setNeedMouseFocus(false);
+
+ mDelay = Settings::Manager::getFloat("tooltip delay", "GUI");
+ mRemainingDelay = mDelay;
+
+ for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i)
+ {
+ mMainWidget->getChildAt(i)->setVisible(false);
+ }
+ }
+
+ void ToolTips::setEnabled(bool enabled)
+ {
+ mEnabled = enabled;
+ }
+
+ void ToolTips::onFrame(float frameDuration)
+ {
+
+ while (mDynamicToolTipBox->getChildCount())
+ {
+ MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0));
+ }
+
+ // start by hiding everything
+ for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i)
+ {
+ mMainWidget->getChildAt(i)->setVisible(false);
+ }
+
+ const MyGUI::IntSize &viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ if (!mEnabled)
+ {
+ return;
+ }
+
+ bool gameMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
+
+ if (gameMode)
+ {
+ const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition();
+
+ if (MWBase::Environment::get().getWindowManager()->getWorldMouseOver() && ((MWBase::Environment::get().getWindowManager()->getMode() == GM_Console)
+ || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Container)
+ || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Inventory)))
+ {
+ mFocusObject = MWBase::Environment::get().getWorld()->getFacedObject();
+
+ if (mFocusObject.isEmpty ())
+ return;
+
+ const MWWorld::Class& objectclass = MWWorld::Class::get (mFocusObject);
+
+ MyGUI::IntSize tooltipSize;
+ if ((!objectclass.hasToolTip(mFocusObject))&&(MWBase::Environment::get().getWindowManager()->getMode() == GM_Console))
+ {
+ setCoord(0, 0, 300, 300);
+ mDynamicToolTipBox->setVisible(true);
+ ToolTipInfo info;
+ info.caption=mFocusObject.getCellRef().mRefID;
+ info.icon="";
+ tooltipSize = createToolTip(info);
+ }
+ else
+ tooltipSize = getToolTipViaPtr(true);
+
+ MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition();
+ position(tooltipPosition, tooltipSize, viewSize);
+
+ setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height);
+ }
+
+ 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;
+ }
+ else
+ {
+ mHorizontalScrollIndex = 0;
+ mRemainingDelay = mDelay;
+ }
+ mLastMouseX = mousePos.left;
+ mLastMouseY = mousePos.top;
+
+
+ if (mRemainingDelay > 0)
+ return;
+
+ MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget();
+ if (focus == 0)
+ return;
+
+ MyGUI::IntSize tooltipSize;
+
+ // try to go 1 level up until there is a widget that has tooltip
+ // this is necessary because some skin elements are actually separate widgets
+ int i=0;
+ while (!focus->isUserString("ToolTipType"))
+ {
+ focus = focus->getParent();
+ if (!focus)
+ return;
+ ++i;
+ }
+
+ std::string type = focus->getUserString("ToolTipType");
+
+ if (type == "")
+ {
+ return;
+ }
+
+
+ // special handling for markers on the local map: the tooltip should only be visible
+ // if the marker is not hidden due to the fog of war.
+ if (focus->getUserString ("IsMarker") == "true")
+ {
+ LocalMapBase::MarkerPosition pos = *focus->getUserData<LocalMapBase::MarkerPosition>();
+
+ if (!MWBase::Environment::get().getWorld ()->isPositionExplored (pos.nX, pos.nY, pos.cellX, pos.cellY, pos.interior))
+ return;
+ }
+
+ if (type == "ItemPtr")
+ {
+ mFocusObject = *focus->getUserData<MWWorld::Ptr>();
+ tooltipSize = getToolTipViaPtr(false);
+ }
+ else if (type == "ItemModelIndex")
+ {
+ std::pair<ItemModel::ModelIndex, ItemModel*> pair = *focus->getUserData<std::pair<ItemModel::ModelIndex, ItemModel*> >();
+ mFocusObject = pair.second->getItem(pair.first).mBase;
+ // HACK: To get the correct count for multiple item stack sources
+ int oldCount = mFocusObject.getRefData().getCount();
+ mFocusObject.getRefData().setCount(pair.second->getItem(pair.first).mCount);
+ tooltipSize = getToolTipViaPtr(false);
+ mFocusObject.getRefData().setCount(oldCount);
+ }
+ else if (type == "ToolTipInfo")
+ {
+ tooltipSize = createToolTip(*focus->getUserData<MWGui::ToolTipInfo>());
+ }
+ else if (type == "AvatarItemSelection")
+ {
+ MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord ();
+ MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top);
+ int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f );
+ int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f );
+ MWWorld::Ptr item = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarSelectedItem (realX, realY);
+
+ mFocusObject = item;
+ if (!mFocusObject.isEmpty ())
+ tooltipSize = getToolTipViaPtr(false);
+ }
+ else if (type == "Spell")
+ {
+ ToolTipInfo info;
+
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(focus->getUserString("Spell"));
+ info.caption = spell->mName;
+ Widgets::SpellEffectList effects;
+ std::vector<ESM::ENAMstruct>::const_iterator end = spell->mEffects.mList.end();
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it)
+ {
+ Widgets::SpellEffectParams params;
+ params.mEffectID = it->mEffectID;
+ params.mSkill = it->mSkill;
+ params.mAttribute = it->mAttribute;
+ params.mDuration = it->mDuration;
+ params.mMagnMin = it->mMagnMin;
+ params.mMagnMax = it->mMagnMax;
+ params.mRange = it->mRange;
+ params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
+ params.mNoTarget = false;
+ effects.push_back(params);
+ }
+ info.effects = effects;
+ tooltipSize = createToolTip(info);
+ }
+ else if (type == "Layout")
+ {
+ // tooltip defined in the layout
+ MyGUI::Widget* tooltip;
+ getWidget(tooltip, focus->getUserString("ToolTipLayout"));
+
+ tooltip->setVisible(true);
+
+ std::map<std::string, std::string> userStrings = focus->getUserStrings();
+ for (std::map<std::string, std::string>::iterator it = userStrings.begin();
+ it != userStrings.end(); ++it)
+ {
+ if (it->first == "ToolTipType"
+ || it->first == "ToolTipLayout"
+ || it->first == "IsMarker")
+ continue;
+
+
+ size_t underscorePos = it->first.find("_");
+ std::string propertyKey = it->first.substr(0, underscorePos);
+ std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1));
+
+ MyGUI::Widget* w;
+ getWidget(w, widgetName);
+ w->setProperty(propertyKey, it->second);
+ }
+
+ tooltipSize = tooltip->getSize();
+
+ tooltip->setCoord(0, 0, tooltipSize.width, tooltipSize.height);
+ }
+ else
+ throw std::runtime_error ("unknown tooltip type");
+
+ MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition();
+
+ position(tooltipPosition, tooltipSize, viewSize);
+
+ setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height);
+ }
+ }
+ else
+ {
+ if (!mFocusObject.isEmpty())
+ {
+ MyGUI::IntSize tooltipSize = getToolTipViaPtr();
+
+ setCoord(viewSize.width/2 - tooltipSize.width/2,
+ std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)),
+ tooltipSize.width,
+ tooltipSize.height);
+
+ mDynamicToolTipBox->setVisible(true);
+ }
+ }
+ }
+
+ void ToolTips::position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize)
+ {
+ position += MyGUI::IntPoint(0, 32)
+ - MyGUI::IntPoint((MyGUI::InputManager::getInstance().getMousePosition().left / float(viewportSize.width) * size.width), 0);
+
+ if ((position.left + size.width) > viewportSize.width)
+ {
+ position.left = viewportSize.width - size.width;
+ }
+ if ((position.top + size.height) > viewportSize.height)
+ {
+ position.top = MyGUI::InputManager::getInstance().getMousePosition().top - size.height - 8;
+ }
+ }
+
+ void ToolTips::setFocusObject(const MWWorld::Ptr& focus)
+ {
+ mFocusObject = focus;
+ }
+
+ MyGUI::IntSize ToolTips::getToolTipViaPtr (bool image)
+ {
+ // this the maximum width of the tooltip before it starts word-wrapping
+ setCoord(0, 0, 300, 300);
+
+ MyGUI::IntSize tooltipSize;
+
+ const MWWorld::Class& object = MWWorld::Class::get (mFocusObject);
+ if (!object.hasToolTip(mFocusObject))
+ {
+ mDynamicToolTipBox->setVisible(false);
+ }
+ else
+ {
+ mDynamicToolTipBox->setVisible(true);
+
+ ToolTipInfo info = object.getToolTipInfo(mFocusObject);
+ if (!image)
+ info.icon = "";
+ tooltipSize = createToolTip(info);
+ }
+
+ return tooltipSize;
+ }
+
+ void ToolTips::findImageExtension(std::string& image)
+ {
+ int len = image.size();
+ if (len < 4) return;
+
+ if (!Ogre::ResourceGroupManager::getSingleton().resourceExists(Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, image))
+ {
+ // Change texture extension to .dds
+ image[len-3] = 'd';
+ image[len-2] = 'd';
+ image[len-1] = 's';
+ }
+ }
+
+ MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info)
+ {
+ mDynamicToolTipBox->setVisible(true);
+
+ std::string caption = info.caption;
+ std::string image = info.icon;
+ int imageSize = (image != "") ? info.imageSize : 0;
+ std::string text = info.text;
+
+ // remove the first newline (easier this way)
+ if (text.size() > 0 && text[0] == '\n')
+ text.erase(0, 1);
+
+ const ESM::Enchantment* enchant = 0;
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ if (info.enchant != "")
+ {
+ enchant = store.get<ESM::Enchantment>().find(info.enchant);
+ if (enchant->mData.mType == ESM::Enchantment::CastOnce)
+ text += "\n#{sItemCastOnce}";
+ else if (enchant->mData.mType == ESM::Enchantment::WhenStrikes)
+ text += "\n#{sItemCastWhenStrikes}";
+ else if (enchant->mData.mType == ESM::Enchantment::WhenUsed)
+ text += "\n#{sItemCastWhenUsed}";
+ else if (enchant->mData.mType == ESM::Enchantment::ConstantEffect)
+ text += "\n#{sItemCastConstant}";
+ }
+
+ // this the maximum width of the tooltip before it starts word-wrapping
+ setCoord(0, 0, 300, 300);
+
+ const MyGUI::IntPoint padding(8, 8);
+
+ const int maximumWidth = 500;
+
+ const int imageCaptionHPadding = (caption != "" ? 8 : 0);
+ const int imageCaptionVPadding = (caption != "" ? 4 : 0);
+
+ std::string realImage = "icons\\" + image;
+ findImageExtension(realImage);
+
+ MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget<MyGUI::EditBox>("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption");
+ captionWidget->setProperty("Static", "true");
+ captionWidget->setCaptionWithReplacing(caption);
+ MyGUI::IntSize captionSize = captionWidget->getTextSize();
+
+ int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize);
+
+ MyGUI::EditBox* textWidget = mDynamicToolTipBox->createWidget<MyGUI::EditBox>("SandText", MyGUI::IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText");
+ textWidget->setProperty("Static", "true");
+ textWidget->setProperty("MultiLine", "true");
+ textWidget->setProperty("WordWrap", info.wordWrap ? "true" : "false");
+ textWidget->setCaptionWithReplacing(text);
+ textWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top);
+ MyGUI::IntSize textSize = textWidget->getTextSize();
+
+ captionSize += MyGUI::IntSize(imageSize, 0); // adjust for image
+ MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth),
+ ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight );
+
+ if (!info.effects.empty())
+ {
+ MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget<MyGUI::Widget>("",
+ MyGUI::IntCoord(0, totalSize.height, 300, 300-totalSize.height),
+ MyGUI::Align::Stretch, "ToolTipEffectArea");
+
+ MyGUI::IntCoord coord(0, 6, totalSize.width, 24);
+
+ Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget<Widgets::MWEffectList>
+ ("MW_StatName", coord, MyGUI::Align::Default, "ToolTipEffectsWidget");
+ effectsWidget->setEffectList(info.effects);
+
+ std::vector<MyGUI::Widget*> effectItems;
+ effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0);
+ totalSize.height += coord.top-6;
+ totalSize.width = std::max(totalSize.width, coord.width);
+ }
+
+ if (info.enchant != "")
+ {
+ assert(enchant);
+ MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget<MyGUI::Widget>("",
+ MyGUI::IntCoord(0, totalSize.height, 300, 300-totalSize.height),
+ MyGUI::Align::Stretch, "ToolTipEnchantArea");
+
+ MyGUI::IntCoord coord(0, 6, totalSize.width, 24);
+
+ Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget<Widgets::MWEffectList>
+ ("MW_StatName", coord, MyGUI::Align::Default, "ToolTipEnchantWidget");
+ enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects));
+
+ std::vector<MyGUI::Widget*> enchantEffectItems;
+ int flag = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0;
+ enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, true, flag);
+ totalSize.height += coord.top-6;
+ totalSize.width = std::max(totalSize.width, coord.width);
+
+ if (enchant->mData.mType == ESM::Enchantment::WhenStrikes
+ || enchant->mData.mType == ESM::Enchantment::WhenUsed)
+ {
+ int maxCharge = enchant->mData.mCharge;
+ int charge = (info.remainingEnchantCharge == -1) ? maxCharge : info.remainingEnchantCharge;
+
+ const int chargeWidth = 204;
+
+ MyGUI::TextBox* chargeText = enchantArea->createWidget<MyGUI::TextBox>("SandText", MyGUI::IntCoord(0, 0, 10, 18), MyGUI::Align::Default, "ToolTipEnchantChargeText");
+ chargeText->setCaptionWithReplacing("#{sCharges}");
+
+ const int chargeTextWidth = chargeText->getTextSize().width + 5;
+
+ const int chargeAndTextWidth = chargeWidth + chargeTextWidth;
+
+ totalSize.width = std::max(totalSize.width, chargeAndTextWidth);
+
+ chargeText->setCoord((totalSize.width - chargeAndTextWidth)/2, coord.top+6, chargeTextWidth, 18);
+
+ MyGUI::IntCoord chargeCoord;
+ if (totalSize.width < chargeWidth)
+ {
+ totalSize.width = chargeWidth;
+ chargeCoord = MyGUI::IntCoord(0, coord.top+6, chargeWidth, 18);
+ }
+ else
+ {
+ chargeCoord = MyGUI::IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18);
+ }
+ Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget<Widgets::MWDynamicStat>
+ ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge");
+ chargeWidget->setValue(charge, charge);
+ totalSize.height += 24;
+ }
+ }
+
+ captionWidget->setCoord( (totalSize.width - captionSize.width)/2 + imageSize,
+ (captionHeight-captionSize.height)/2,
+ captionSize.width-imageSize,
+ captionSize.height);
+
+ //if its too long we do hscroll with the caption
+ if (captionSize.width > maximumWidth)
+ {
+ mHorizontalScrollIndex = mHorizontalScrollIndex + 2;
+ if (mHorizontalScrollIndex > captionSize.width){
+ mHorizontalScrollIndex = -totalSize.width;
+ }
+ int horizontal_scroll = mHorizontalScrollIndex;
+ if (horizontal_scroll < 40){
+ horizontal_scroll = 40;
+ }else{
+ horizontal_scroll = 80 - mHorizontalScrollIndex;
+ }
+ captionWidget->setPosition (MyGUI::IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top));
+ } else {
+ captionWidget->setPosition (captionWidget->getPosition() + padding);
+ }
+
+ textWidget->setPosition (textWidget->getPosition() + MyGUI::IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter
+
+ if (image != "")
+ {
+ MyGUI::ImageBox* imageWidget = mDynamicToolTipBox->createWidget<MyGUI::ImageBox>("ImageBox",
+ MyGUI::IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize),
+ MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipImage");
+ imageWidget->setImageTexture(realImage);
+ imageWidget->setPosition (imageWidget->getPosition() + padding);
+ }
+
+ totalSize += MyGUI::IntSize(padding.left*2, padding.top*2);
+
+ return totalSize;
+ }
+
+ std::string ToolTips::toString(const float value)
+ {
+ std::ostringstream stream;
+ stream << std::setprecision(3) << value;
+ return stream.str();
+ }
+
+ std::string ToolTips::toString(const int value)
+ {
+ std::ostringstream stream;
+ stream << value;
+ return stream.str();
+ }
+
+ std::string ToolTips::getValueString(const int value, const std::string& prefix)
+ {
+ if (value == 0)
+ return "";
+ else
+ return "\n" + prefix + ": " + toString(value);
+ }
+
+ std::string ToolTips::getMiscString(const std::string& text, const std::string& prefix)
+ {
+ if (text == "")
+ return "";
+ else
+ return "\n" + prefix + ": " + text;
+ }
+
+ std::string ToolTips::getCountString(const int value)
+ {
+ if (value == 1)
+ return "";
+ else
+ return " (" + boost::lexical_cast<std::string>(value) + ")";
+ }
+
+ void ToolTips::toggleFullHelp()
+ {
+ mFullHelp = !mFullHelp;
+ }
+
+ bool ToolTips::getFullHelp() const
+ {
+ return mFullHelp;
+ }
+
+ void ToolTips::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y)
+ {
+ mFocusToolTipX = (min_x + max_x) / 2;
+ mFocusToolTipY = min_y;
+ }
+
+ void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId)
+ {
+ if (skillId == -1)
+ return;
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId];
+ const ESM::Skill* skill = store.get<ESM::Skill>().find(skillId);
+ assert(skill);
+
+ const ESM::Attribute* attr =
+ store.get<ESM::Attribute>().find(skill->mData.mAttribute);
+ assert(attr);
+ std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId];
+
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip");
+ widget->setUserString("Caption_SkillNoProgressName", "#{"+skillNameId+"}");
+ widget->setUserString("Caption_SkillNoProgressDescription", skill->mDescription);
+ widget->setUserString("Caption_SkillNoProgressAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}");
+ widget->setUserString("ImageTexture_SkillNoProgressImage", icon);
+ }
+
+ void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId)
+ {
+ if (attributeId == -1)
+ return;
+
+ std::string icon = ESM::Attribute::sAttributeIcons[attributeId];
+ std::string name = ESM::Attribute::sGmstAttributeIds[attributeId];
+ std::string desc = ESM::Attribute::sGmstAttributeDescIds[attributeId];
+
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "AttributeToolTip");
+ widget->setUserString("Caption_AttributeName", "#{"+name+"}");
+ widget->setUserString("Caption_AttributeDescription", "#{"+desc+"}");
+ widget->setUserString("ImageTexture_AttributeImage", icon);
+ }
+
+ void ToolTips::createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId)
+ {
+ widget->setUserString("Caption_CenteredCaption", name);
+ std::string specText;
+ // get all skills of this specialisation
+ const MWWorld::Store<ESM::Skill> &skills =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>();
+
+ MWWorld::Store<ESM::Skill>::iterator it = skills.begin();
+ for (; it != skills.end(); ++it)
+ {
+ if (it->mData.mSpecialization == specId)
+ specText += std::string("\n#{") + ESM::Skill::sSkillNameIds[it->mIndex] + "}";
+ }
+ widget->setUserString("Caption_CenteredCaptionText", specText);
+ widget->setUserString("ToolTipLayout", "TextWithCenteredCaptionToolTip");
+ widget->setUserString("ToolTipType", "Layout");
+ }
+
+ void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId)
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::BirthSign *sign = store.get<ESM::BirthSign>().find(birthsignId);
+
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "BirthSignToolTip");
+ std::string image = sign->mTexture;
+ image.replace(image.size()-3, 3, "dds");
+ widget->setUserString("ImageTexture_BirthSignImage", "textures\\" + image);
+ std::string text;
+
+ text += sign->mName;
+ text += "\n#BF9959" + sign->mDescription;
+
+ std::vector<std::string> abilities, powers, spells;
+
+ std::vector<std::string>::const_iterator it = sign->mPowers.mList.begin();
+ std::vector<std::string>::const_iterator end = sign->mPowers.mList.end();
+ for (; it != end; ++it)
+ {
+ const std::string &spellId = *it;
+ const ESM::Spell *spell = store.get<ESM::Spell>().search(spellId);
+ if (!spell)
+ continue; // Skip spells which cannot be found
+ ESM::Spell::SpellType type = static_cast<ESM::Spell::SpellType>(spell->mData.mType);
+ if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power)
+ continue; // We only want spell, ability and powers.
+
+ if (type == ESM::Spell::ST_Ability)
+ abilities.push_back(spellId);
+ else if (type == ESM::Spell::ST_Power)
+ powers.push_back(spellId);
+ else if (type == ESM::Spell::ST_Spell)
+ spells.push_back(spellId);
+ }
+
+ struct {
+ const std::vector<std::string> &spells;
+ std::string label;
+ }
+ categories[3] = {
+ {abilities, "sBirthsignmenu1"},
+ {powers, "sPowers"},
+ {spells, "sBirthsignmenu2"}
+ };
+
+ for (int category = 0; category < 3; ++category)
+ {
+ for (std::vector<std::string>::const_iterator it = categories[category].spells.begin(); it != categories[category].spells.end(); ++it)
+ {
+ if (it == categories[category].spells.begin())
+ {
+ text += std::string("\n#DDC79E") + std::string("#{") + categories[category].label + "}";
+ }
+
+ const std::string &spellId = *it;
+
+ const ESM::Spell *spell = store.get<ESM::Spell>().find(spellId);
+ text += "\n#BF9959" + spell->mName;
+ }
+ }
+
+ widget->setUserString("Caption_BirthSignText", text);
+ }
+
+ void ToolTips::createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace)
+ {
+ widget->setUserString("Caption_CenteredCaption", playerRace->mName);
+ widget->setUserString("Caption_CenteredCaptionText", playerRace->mDescription);
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "TextWithCenteredCaptionToolTip");
+ }
+
+ void ToolTips::createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass)
+ {
+ if (playerClass.mName == "")
+ return;
+
+ int spec = playerClass.mData.mSpecialization;
+ std::string specStr;
+ if (spec == 0)
+ specStr = "#{sSpecializationCombat}";
+ else if (spec == 1)
+ specStr = "#{sSpecializationMagic}";
+ else if (spec == 2)
+ specStr = "#{sSpecializationStealth}";
+
+ widget->setUserString("Caption_ClassName", playerClass.mName);
+ widget->setUserString("Caption_ClassDescription", playerClass.mDescription);
+ widget->setUserString("Caption_ClassSpecialisation", "#{sSpecialization}: " + specStr);
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "ClassToolTip");
+ }
+
+ void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, short id)
+ {
+ const ESM::MagicEffect* effect =
+ MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(id);
+ const std::string &name = ESM::MagicEffect::effectIdToString (id);
+
+ std::string icon = effect->mIcon;
+
+ int slashPos = icon.find("\\");
+ icon.insert(slashPos+1, "b_");
+
+ icon[icon.size()-3] = 'd';
+ icon[icon.size()-2] = 'd';
+ icon[icon.size()-1] = 's';
+
+ icon = "icons\\" + icon;
+
+ std::vector<std::string> schools;
+ schools.push_back ("#{sSchoolAlteration}");
+ schools.push_back ("#{sSchoolConjuration}");
+ schools.push_back ("#{sSchoolDestruction}");
+ schools.push_back ("#{sSchoolIllusion}");
+ schools.push_back ("#{sSchoolMysticism}");
+ schools.push_back ("#{sSchoolRestoration}");
+
+ widget->setUserString("ToolTipType", "Layout");
+ widget->setUserString("ToolTipLayout", "MagicEffectToolTip");
+ widget->setUserString("Caption_MagicEffectName", "#{" + name + "}");
+ widget->setUserString("Caption_MagicEffectDescription", effect->mDescription);
+ widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + schools[effect->mData.mSchool]);
+ widget->setUserString("ImageTexture_MagicEffectImage", icon);
+ }
+
+ void ToolTips::setDelay(float delay)
+ {
+ mDelay = delay;
+ mRemainingDelay = mDelay;
+ }
+
+}
diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp
new file mode 100644
index 0000000000..be5c631913
--- /dev/null
+++ b/apps/openmw/mwgui/tooltips.hpp
@@ -0,0 +1,112 @@
+
+#ifndef MWGUI_TOOLTIPS_H
+#define MWGUI_TOOLTIPS_H
+
+#include <openengine/gui/layout.hpp>
+#include "../mwworld/ptr.hpp"
+
+#include "widgets.hpp"
+
+namespace MWGui
+{
+ // Info about tooltip that is supplied by the MWWorld::Class object
+ struct ToolTipInfo
+ {
+ public:
+ ToolTipInfo()
+ : isPotion(false)
+ , imageSize(32)
+ , wordWrap(true)
+ , remainingEnchantCharge(-1)
+ {}
+
+ std::string caption;
+ std::string text;
+ std::string icon;
+ int imageSize;
+
+ // enchantment (for cloth, armor, weapons)
+ std::string enchant;
+ int remainingEnchantCharge;
+
+ // effects (for potions, ingredients)
+ Widgets::SpellEffectList effects;
+
+ bool isPotion; // potions do not show target in the tooltip
+ bool wordWrap;
+ };
+
+ class ToolTips : public OEngine::GUI::Layout
+ {
+ public:
+ ToolTips();
+
+ void onFrame(float frameDuration);
+
+ void setEnabled(bool enabled);
+
+ void toggleFullHelp(); ///< show extra info in item tooltips (owner, script)
+ bool getFullHelp() const;
+
+ void setDelay(float delay);
+
+ void setFocusObject(const MWWorld::Ptr& focus);
+ void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y);
+ ///< set the screen-space position of the tooltip for focused object
+
+ static std::string getValueString(const int value, const std::string& prefix);
+ ///< @return "prefix: value" or "" if value is 0
+
+ static std::string getMiscString(const std::string& text, const std::string& prefix);
+ ///< @return "prefix: text" or "" if text is empty
+
+ static std::string toString(const float value);
+ static std::string toString(const int value);
+
+ static std::string getCountString(const int value);
+ ///< @return blank string if count is 1, or else " (value)"
+
+ // these do not create an actual tooltip, but they fill in the data that is required so the tooltip
+ // system knows what to show in case this widget is hovered
+ static void createSkillToolTip(MyGUI::Widget* widget, int skillId);
+ static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId);
+ static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId);
+ static void createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId);
+ static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace);
+ static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass);
+ static void createMagicEffectToolTip(MyGUI::Widget* widget, short id);
+
+ private:
+ MyGUI::Widget* mDynamicToolTipBox;
+
+ MWWorld::Ptr mFocusObject;
+
+ void findImageExtension(std::string& image);
+
+ MyGUI::IntSize getToolTipViaPtr (bool image=true);
+ ///< @return requested tooltip size
+
+ MyGUI::IntSize createToolTip(const ToolTipInfo& info);
+ ///< @return requested tooltip size
+
+ float mFocusToolTipX;
+ float mFocusToolTipY;
+
+ /// Adjust position for a tooltip so that it doesn't leave the screen and does not obscure the mouse cursor
+ void position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize);
+
+ int mHorizontalScrollIndex;
+
+
+ float mDelay;
+ float mRemainingDelay; // remaining time until tooltip will show
+
+ int mLastMouseX;
+ int mLastMouseY;
+
+ bool mEnabled;
+
+ bool mFullHelp;
+ };
+}
+#endif
diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp
new file mode 100644
index 0000000000..e836355d3e
--- /dev/null
+++ b/apps/openmw/mwgui/tradeitemmodel.cpp
@@ -0,0 +1,201 @@
+#include "tradeitemmodel.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+namespace MWGui
+{
+
+ TradeItemModel::TradeItemModel(ItemModel *sourceModel, const MWWorld::Ptr& merchant)
+ : mMerchant(merchant)
+ {
+ mSourceModel = sourceModel;
+ }
+
+ ItemStack TradeItemModel::getItem (ModelIndex index)
+ {
+ if (index < 0)
+ throw std::runtime_error("Invalid index supplied");
+ if (mItems.size() <= static_cast<size_t>(index))
+ throw std::runtime_error("Item index out of range");
+ return mItems[index];
+ }
+
+ size_t TradeItemModel::getItemCount()
+ {
+ return mItems.size();
+ }
+
+ void TradeItemModel::borrowImpl(const ItemStack &item, std::vector<ItemStack> &out)
+ {
+ std::vector<ItemStack>::iterator it = out.begin();
+ bool found = false;
+ for (; it != out.end(); ++it)
+ {
+ if (it->stacks(item))
+ {
+ it->mCount += item.mCount;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ out.push_back(item);
+ }
+
+ void TradeItemModel::unborrowImpl(const ItemStack &item, size_t count, std::vector<ItemStack> &out)
+ {
+ std::vector<ItemStack>::iterator it = out.begin();
+ bool found = false;
+ for (; it != out.end(); ++it)
+ {
+ if (it->stacks(item))
+ {
+ if (it->mCount < count)
+ throw std::runtime_error("Not enough borrowed items to return");
+ it->mCount -= count;
+ if (it->mCount == 0)
+ out.erase(it);
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ throw std::runtime_error("Can't find borrowed item to return");
+ }
+
+ void TradeItemModel::borrowItemFromUs (ModelIndex itemIndex, size_t count)
+ {
+ ItemStack item = getItem(itemIndex);
+ item.mCount = count;
+ borrowImpl(item, mBorrowedFromUs);
+ }
+
+ void TradeItemModel::borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count)
+ {
+ ItemStack item = source->getItem(itemIndex);
+ item.mCount = count;
+ borrowImpl(item, mBorrowedToUs);
+ }
+
+ void TradeItemModel::returnItemBorrowedToUs (ModelIndex itemIndex, size_t count)
+ {
+ ItemStack item = getItem(itemIndex);
+ unborrowImpl(item, count, mBorrowedToUs);
+ }
+
+ void TradeItemModel::returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count)
+ {
+ ItemStack item = source->getItem(itemIndex);
+ unborrowImpl(item, count, mBorrowedFromUs);
+ }
+
+ void TradeItemModel::abort()
+ {
+ mBorrowedFromUs.clear();
+ mBorrowedToUs.clear();
+ }
+
+ std::vector<ItemStack> TradeItemModel::getItemsBorrowedToUs()
+ {
+ return mBorrowedToUs;
+ }
+
+ void TradeItemModel::transferItems()
+ {
+ std::vector<ItemStack>::iterator it = mBorrowedToUs.begin();
+ for (; it != mBorrowedToUs.end(); ++it)
+ {
+ // get index in the source model
+ ItemModel* sourceModel = it->mCreator;
+ size_t i=0;
+ for (; i<sourceModel->getItemCount(); ++i)
+ {
+ if (it->stacks(sourceModel->getItem(i)))
+ break;
+ }
+ if (i == sourceModel->getItemCount())
+ throw std::runtime_error("The borrowed item disappeared");
+
+ // reset owner before copying
+ const ItemStack& item = sourceModel->getItem(i);
+ std::string owner = item.mBase.getCellRef().mOwner;
+ if (mMerchant.isEmpty()) // only for items bought by player
+ item.mBase.getCellRef().mOwner = "";
+ // copy the borrowed items to our model
+ copyItem(item, it->mCount);
+ item.mBase.getCellRef().mOwner = owner;
+ // then remove them from the source model
+ sourceModel->removeItem(item, it->mCount);
+ }
+ mBorrowedToUs.clear();
+ mBorrowedFromUs.clear();
+ }
+
+ void TradeItemModel::update()
+ {
+ mSourceModel->update();
+
+ int services = 0;
+ if (!mMerchant.isEmpty())
+ services = MWWorld::Class::get(mMerchant).getServices(mMerchant);
+
+ mItems.clear();
+ // add regular items
+ for (size_t i=0; i<mSourceModel->getItemCount(); ++i)
+ {
+ ItemStack item = mSourceModel->getItem(i);
+ if(!mMerchant.isEmpty())
+ {
+ MWWorld::Ptr base = item.mBase;
+ if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, "gold_001"))
+ continue;
+ if(!MWWorld::Class::get(base).canSell(base, services))
+ continue;
+
+ // don't show equipped items
+ if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
+ {
+ bool isEquipped = false;
+ MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);
+ for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
+ {
+ MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
+ if (equipped == store.end())
+ continue;
+ if (*equipped == base)
+ isEquipped = true;
+ }
+ if (isEquipped)
+ continue;
+ }
+ }
+
+ // don't show items that we borrowed to someone else
+ std::vector<ItemStack>::iterator it = mBorrowedFromUs.begin();
+ for (; it != mBorrowedFromUs.end(); ++it)
+ {
+ if (it->stacks(item))
+ {
+ if (item.mCount < it->mCount)
+ throw std::runtime_error("Lent more items than present");
+ item.mCount -= it->mCount;
+ }
+ }
+
+ if (item.mCount > 0)
+ mItems.push_back(item);
+ }
+
+ // add items borrowed to us
+ std::vector<ItemStack>::iterator it = mBorrowedToUs.begin();
+ for (; it != mBorrowedToUs.end(); ++it)
+ {
+ ItemStack item = *it;
+ item.mType = ItemStack::Type_Barter;
+ mItems.push_back(item);
+ }
+ }
+
+}
diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp
new file mode 100644
index 0000000000..5cfaaafc76
--- /dev/null
+++ b/apps/openmw/mwgui/tradeitemmodel.hpp
@@ -0,0 +1,53 @@
+#ifndef MWGUI_TRADE_ITEM_MODEL_H
+#define MWGUI_TRADE_ITEM_MODEL_H
+
+#include "itemmodel.hpp"
+
+namespace MWGui
+{
+
+ class ItemModel;
+
+ /// @brief An item model that allows 'borrowing' items from another item model. Used for previewing barter offers.
+ /// Also filters items that the merchant does not sell.
+ class TradeItemModel : public ProxyItemModel
+ {
+ public:
+ TradeItemModel (ItemModel* sourceModel, const MWWorld::Ptr& merchant);
+
+ virtual ItemStack getItem (ModelIndex index);
+ virtual size_t getItemCount();
+
+ virtual void update();
+
+ void borrowItemFromUs (ModelIndex itemIndex, size_t count);
+
+ void borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count);
+ ///< @note itemIndex points to an item in \a source
+
+ void returnItemBorrowedToUs (ModelIndex itemIndex, size_t count);
+
+ void returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count);
+
+ /// Permanently transfers items that were borrowed to us from another model to this model
+ void transferItems ();
+ /// Aborts trade
+ void abort();
+
+ std::vector<ItemStack> getItemsBorrowedToUs();
+
+ private:
+ void borrowImpl(const ItemStack& item, std::vector<ItemStack>& out);
+ void unborrowImpl(const ItemStack& item, size_t count, std::vector<ItemStack>& out);
+
+ std::vector<ItemStack> mItems;
+
+ std::vector<ItemStack> mBorrowedToUs;
+ std::vector<ItemStack> mBorrowedFromUs;
+
+ MWWorld::Ptr mMerchant;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp
new file mode 100644
index 0000000000..94141b1a0a
--- /dev/null
+++ b/apps/openmw/mwgui/tradewindow.cpp
@@ -0,0 +1,466 @@
+#include "tradewindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwworld/player.hpp"
+
+#include "inventorywindow.hpp"
+#include "itemview.hpp"
+#include "sortfilteritemmodel.hpp"
+#include "containeritemmodel.hpp"
+#include "tradeitemmodel.hpp"
+#include "countdialog.hpp"
+
+namespace MWGui
+{
+ const float TradeWindow::sBalanceChangeInitialPause = 0.5;
+ const float TradeWindow::sBalanceChangeInterval = 0.1;
+
+ TradeWindow::TradeWindow()
+ : WindowBase("openmw_trade_window.layout")
+ , mCurrentBalance(0)
+ , mBalanceButtonsState(BBS_None)
+ , mBalanceChangePause(0.0)
+ , mItemToSell(-1)
+ , mTradeModel(NULL)
+ , mSortModel(NULL)
+ , mCurrentMerchantOffer(0)
+ {
+ getWidget(mFilterAll, "AllButton");
+ getWidget(mFilterWeapon, "WeaponButton");
+ getWidget(mFilterApparel, "ApparelButton");
+ getWidget(mFilterMagic, "MagicButton");
+ getWidget(mFilterMisc, "MiscButton");
+
+ getWidget(mMaxSaleButton, "MaxSaleButton");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mOfferButton, "OfferButton");
+ getWidget(mPlayerGold, "PlayerGold");
+ getWidget(mMerchantGold, "MerchantGold");
+ getWidget(mIncreaseButton, "IncreaseButton");
+ getWidget(mDecreaseButton, "DecreaseButton");
+ getWidget(mTotalBalance, "TotalBalance");
+ getWidget(mTotalBalanceLabel, "TotalBalanceLabel");
+ getWidget(mBottomPane, "BottomPane");
+
+ getWidget(mItemView, "ItemView");
+ mItemView->eventItemClicked += MyGUI::newDelegate(this, &TradeWindow::onItemSelected);
+
+ mFilterAll->setStateSelected(true);
+
+ mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged);
+ mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged);
+ mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged);
+ mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged);
+ mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged);
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onCancelButtonClicked);
+ mOfferButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onOfferButtonClicked);
+ mMaxSaleButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onMaxSaleButtonClicked);
+ mIncreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onIncreaseButtonPressed);
+ mIncreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased);
+ mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &TradeWindow::onDecreaseButtonPressed);
+ mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased);
+
+ setCoord(400, 0, 400, 300);
+ }
+
+ void TradeWindow::startTrade(const MWWorld::Ptr& actor)
+ {
+ mPtr = actor;
+ setTitle(MWWorld::Class::get(actor).getName(actor));
+
+ mCurrentBalance = 0;
+ mCurrentMerchantOffer = 0;
+
+ std::vector<MWWorld::Ptr> itemSources;
+ MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources);
+ // Important: actor goes last, so that items purchased by the merchant go into his inventory
+ itemSources.push_back(actor);
+ std::vector<MWWorld::Ptr> worldItems;
+ MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems);
+
+ mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources, worldItems), mPtr);
+ mSortModel = new SortFilterItemModel(mTradeModel);
+ mItemView->setModel (mSortModel);
+
+ updateLabels();
+ }
+
+ void TradeWindow::onFilterChanged(MyGUI::Widget* _sender)
+ {
+ if (_sender == mFilterAll)
+ mSortModel->setCategory(SortFilterItemModel::Category_All);
+ else if (_sender == mFilterWeapon)
+ mSortModel->setCategory(SortFilterItemModel::Category_Weapon);
+ else if (_sender == mFilterApparel)
+ mSortModel->setCategory(SortFilterItemModel::Category_Apparel);
+ else if (_sender == mFilterMagic)
+ mSortModel->setCategory(SortFilterItemModel::Category_Magic);
+ else if (_sender == mFilterMisc)
+ mSortModel->setCategory(SortFilterItemModel::Category_Misc);
+
+ mFilterAll->setStateSelected(false);
+ mFilterWeapon->setStateSelected(false);
+ mFilterApparel->setStateSelected(false);
+ mFilterMagic->setStateSelected(false);
+ mFilterMisc->setStateSelected(false);
+
+ static_cast<MyGUI::Button*>(_sender)->setStateSelected(true);
+
+ mItemView->update();
+ }
+
+ int TradeWindow::getMerchantServices()
+ {
+ return MWWorld::Class::get(mPtr).getServices(mPtr);
+ }
+
+ void TradeWindow::onItemSelected (int index)
+ {
+ const ItemStack& item = mSortModel->getItem(index);
+
+ MWWorld::Ptr object = item.mBase;
+ int count = item.mCount;
+ bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
+ if (MyGUI::InputManager::getInstance().isControlPressed())
+ count = 1;
+
+ if (count > 1 && !shift)
+ {
+ CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
+ std::string message = "#{sQuanityMenuMessage02}";
+ dialog->open(MWWorld::Class::get(object).getName(object), message, count);
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem);
+ mItemToSell = mSortModel->mapToSource(index);
+ }
+ else
+ {
+ mItemToSell = mSortModel->mapToSource(index);
+ sellItem (NULL, count);
+ }
+ }
+
+ void TradeWindow::sellItem(MyGUI::Widget* sender, int count)
+ {
+ const ItemStack& item = mTradeModel->getItem(mItemToSell);
+ std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase);
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+
+ TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel();
+
+ if (item.mType == ItemStack::Type_Barter)
+ {
+ // this was an item borrowed to us by the player
+ mTradeModel->returnItemBorrowedToUs(mItemToSell, count);
+ playerTradeModel->returnItemBorrowedFromUs(mItemToSell, mTradeModel, count);
+ buyFromNpc(item.mBase, count, true);
+ }
+ else
+ {
+ // borrow item to player
+ playerTradeModel->borrowItemToUs(mItemToSell, mTradeModel, count);
+ mTradeModel->borrowItemFromUs(mItemToSell, count);
+ buyFromNpc(item.mBase, count, false);
+ }
+
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
+ mItemView->update();
+ }
+
+ void TradeWindow::borrowItem (int index, size_t count)
+ {
+ TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel();
+ mTradeModel->borrowItemToUs(index, playerTradeModel, count);
+ mItemView->update();
+ sellToNpc(playerTradeModel->getItem(index).mBase, count, false);
+ }
+
+ void TradeWindow::returnItem (int index, size_t count)
+ {
+ TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel();
+ const ItemStack& item = playerTradeModel->getItem(index);
+ mTradeModel->returnItemBorrowedFromUs(index, playerTradeModel, count);
+ mItemView->update();
+ sellToNpc(item.mBase, count, true);
+ }
+
+ void TradeWindow::addOrRemoveGold(int amount)
+ {
+ bool goldFound = false;
+ MWWorld::Ptr gold;
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player);
+
+ 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)
+ {
+ gold.getRefData().setCount(gold.getRefData().getCount() + amount);
+ }
+ else
+ {
+ assert(amount > 0);
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001");
+ ref.getPtr().getRefData().setCount(amount);
+ playerStore.add(ref.getPtr(), player);
+ }
+ }
+
+ void TradeWindow::onFrame(float frameDuration)
+ {
+ if (!mMainWidget->getVisible() || mBalanceButtonsState == BBS_None)
+ return;
+
+ mBalanceChangePause -= frameDuration;
+ if (mBalanceChangePause < 0.0) {
+ mBalanceChangePause += sBalanceChangeInterval;
+ if (mBalanceButtonsState == BBS_Increase)
+ onIncreaseButtonTriggered();
+ else if (mBalanceButtonsState == BBS_Decrease)
+ onDecreaseButtonTriggered();
+ }
+ }
+
+ void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender)
+ {
+ TradeItemModel* playerItemModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel();
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ // were there any items traded at all?
+ std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs();
+ std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs();
+ if (!playerBought.size() && !merchantBought.size())
+ {
+ // user notification
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sBarterDialog11}");
+ return;
+ }
+
+ // check if the player can afford this
+ if (mCurrentBalance < 0 && MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold() < std::abs(mCurrentBalance))
+ {
+ // user notification
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sBarterDialog1}");
+ return;
+ }
+
+ // check if the merchant can afford this
+ if (mCurrentBalance > 0 && getMerchantGold() < mCurrentBalance)
+ {
+ // user notification
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sBarterDialog2}");
+ return;
+ }
+
+ if(mCurrentBalance > mCurrentMerchantOffer)
+ {
+ //if npc is a creature: reject (no haggle)
+ if (mPtr.getTypeName() != typeid(ESM::NPC).name())
+ {
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sNotifyMessage9}");
+ return;
+ }
+
+ int a = abs(mCurrentMerchantOffer);
+ int b = abs(mCurrentBalance);
+ int d = 0;
+ if (mCurrentBalance<0)
+ d = int(100 * (a - b) / a);
+ else
+ d = int(100 * (b - a) / a);
+
+ float clampedDisposition = std::max<int>(0,std::min<int>(int(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
+ + 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);
+ float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
+ float c1 = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
+ float d1 = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
+ float e1 = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
+ float f1 = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
+
+ float pcTerm = (clampedDisposition - 50 + a1 + b1 + c1) * playerStats.getFatigueTerm();
+ float npcTerm = (d1 + e1 + f1) * sellerStats.getFatigueTerm();
+ float x = gmst.find("fBargainOfferMulti")->getFloat() * d + gmst.find("fBargainOfferBase")->getFloat();
+ if (mCurrentBalance<0)
+ x += abs(int(pcTerm - npcTerm));
+ else
+ x += abs(int(npcTerm - pcTerm));
+
+ int roll = std::rand()%100 + 1;
+ if(roll > x) //trade refused
+ {
+ MWBase::Environment::get().getWindowManager()->
+ messageBox("#{sNotifyMessage9}");
+
+ int iBarterFailDisposition = gmst.find("iBarterFailDisposition")->getInt();
+ MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterFailDisposition);
+ return;
+ }
+
+ //skill use!
+ MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0);
+ }
+
+ int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt();
+ MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition);
+
+ // make the item transfer
+ mTradeModel->transferItems();
+ playerItemModel->transferItems();
+
+ // add or remove gold from the player.
+ if (mCurrentBalance != 0)
+ addOrRemoveGold(mCurrentBalance);
+
+ std::string sound = "Item Gold Up";
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
+ }
+
+ void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ mTradeModel->abort();
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort();
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
+ }
+
+ void TradeWindow::onMaxSaleButtonClicked(MyGUI::Widget* _sender)
+ {
+ mCurrentBalance = getMerchantGold();
+ updateLabels();
+ }
+
+ void TradeWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ mBalanceButtonsState = BBS_Increase;
+ mBalanceChangePause = sBalanceChangeInitialPause;
+ onIncreaseButtonTriggered();
+ }
+
+ void TradeWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ mBalanceButtonsState = BBS_Decrease;
+ mBalanceChangePause = sBalanceChangeInitialPause;
+ onDecreaseButtonTriggered();
+ }
+
+ void TradeWindow::onBalanceButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ mBalanceButtonsState = BBS_None;
+ }
+
+ void TradeWindow::onIncreaseButtonTriggered()
+ {
+ if(mCurrentBalance<=-1) mCurrentBalance -= 1;
+ if(mCurrentBalance>=1) mCurrentBalance += 1;
+ updateLabels();
+ }
+
+ void TradeWindow::onDecreaseButtonTriggered()
+ {
+ if(mCurrentBalance<-1) mCurrentBalance += 1;
+ if(mCurrentBalance>1) mCurrentBalance -= 1;
+ updateLabels();
+ }
+
+ void TradeWindow::updateLabels()
+ {
+ mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+
+ if (mCurrentBalance > 0)
+ {
+ mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalSold}");
+ mTotalBalance->setCaption(boost::lexical_cast<std::string>(mCurrentBalance));
+ }
+ else
+ {
+ mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}");
+ mTotalBalance->setCaption(boost::lexical_cast<std::string>(-mCurrentBalance));
+ }
+
+ mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast<std::string>(getMerchantGold()));
+ }
+
+ void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem)
+ {
+ int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, boughtItem);
+
+ mCurrentBalance += diff;
+ mCurrentMerchantOffer += diff;
+
+ updateLabels();
+ }
+
+ void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem)
+ {
+ int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, !soldItem);
+
+ mCurrentBalance -= diff;
+ mCurrentMerchantOffer -= diff;
+
+ updateLabels();
+ }
+
+ void TradeWindow::onReferenceUnavailable()
+ {
+ // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked to)
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ }
+
+ int TradeWindow::getMerchantGold()
+ {
+ int merchantGold;
+
+ if (mPtr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ 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;
+ }
+
+ return merchantGold;
+ }
+}
diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp
new file mode 100644
index 0000000000..4e905915a0
--- /dev/null
+++ b/apps/openmw/mwgui/tradewindow.hpp
@@ -0,0 +1,106 @@
+#ifndef MWGUI_TRADEWINDOW_H
+#define MWGUI_TRADEWINDOW_H
+
+#include "container.hpp"
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+
+namespace MWGui
+{
+ class ItemView;
+ class SortFilterItemModel;
+ class TradeItemModel;
+
+ class TradeWindow : public WindowBase, public ReferenceInterface
+ {
+ public:
+ TradeWindow();
+
+ void startTrade(const MWWorld::Ptr& actor);
+
+ void addOrRemoveGold(int gold);
+
+ void onFrame(float frameDuration);
+
+ void borrowItem (int index, size_t count);
+ void returnItem (int index, size_t count);
+
+ int getMerchantServices();
+
+
+ private:
+ ItemView* mItemView;
+ SortFilterItemModel* mSortModel;
+ TradeItemModel* mTradeModel;
+
+ static const float sBalanceChangeInitialPause; // in seconds
+ static const float sBalanceChangeInterval; // in seconds
+
+ MyGUI::Button* mFilterAll;
+ MyGUI::Button* mFilterWeapon;
+ MyGUI::Button* mFilterApparel;
+ MyGUI::Button* mFilterMagic;
+ MyGUI::Button* mFilterMisc;
+
+ MyGUI::Button* mIncreaseButton;
+ MyGUI::Button* mDecreaseButton;
+ MyGUI::TextBox* mTotalBalanceLabel;
+ MyGUI::TextBox* mTotalBalance;
+
+ MyGUI::Widget* mBottomPane;
+
+ MyGUI::Button* mMaxSaleButton;
+ MyGUI::Button* mCancelButton;
+ MyGUI::Button* mOfferButton;
+ MyGUI::TextBox* mPlayerGold;
+ MyGUI::TextBox* mMerchantGold;
+
+ int mItemToSell;
+
+ int mCurrentBalance;
+ int mCurrentMerchantOffer;
+
+ enum BalanceButtonsState {
+ BBS_None,
+ BBS_Increase,
+ BBS_Decrease
+ } mBalanceButtonsState;
+ /// pause before next balance change will trigger while user holds +/- button pressed
+ float mBalanceChangePause;
+
+ void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance
+ void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance
+
+ void onItemSelected (int index);
+ void sellItem (MyGUI::Widget* sender, int count);
+
+ void onFilterChanged(MyGUI::Widget* _sender);
+ void onOfferButtonClicked(MyGUI::Widget* _sender);
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onMaxSaleButtonClicked(MyGUI::Widget* _sender);
+ void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onBalanceButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+
+ void onIncreaseButtonTriggered();
+ void onDecreaseButtonTriggered();
+
+ void updateLabels();
+
+ virtual void onReferenceUnavailable();
+
+ int getMerchantGold();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp
new file mode 100644
index 0000000000..7ddac38f54
--- /dev/null
+++ b/apps/openmw/mwgui/trainingwindow.cpp
@@ -0,0 +1,168 @@
+#include "trainingwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <openengine/ogre/fader.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "inventorywindow.hpp"
+#include "tradewindow.hpp"
+#include "tooltips.hpp"
+
+namespace MWGui
+{
+
+ TrainingWindow::TrainingWindow()
+ : WindowBase("openmw_trainingwindow.layout")
+ , mFadeTimeRemaining(0)
+ {
+ getWidget(mTrainingOptions, "TrainingOptions");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mPlayerGold, "PlayerGold");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onCancelButtonClicked);
+ }
+
+ void TrainingWindow::open()
+ {
+ center();
+ }
+
+ void TrainingWindow::startTraining (MWWorld::Ptr actor)
+ {
+ mPtr = actor;
+
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats (actor);
+
+ // NPC can train you in his best 3 skills
+ std::vector< std::pair<int, int> > bestSkills;
+ bestSkills.push_back (std::make_pair(-1, -1));
+ bestSkills.push_back (std::make_pair(-1, -1));
+ bestSkills.push_back (std::make_pair(-1, -1));
+
+ for (int i=0; i<ESM::Skill::Length; ++i)
+ {
+ int value = npcStats.getSkill (i).getBase ();
+
+ for (int j=0; j<3; ++j)
+ {
+ if (value > bestSkills[j].second)
+ {
+ if (j<2)
+ {
+ bestSkills[j+1] = bestSkills[j];
+ }
+ bestSkills[j] = std::make_pair(i, value);
+ break;
+ }
+ }
+ }
+
+ MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator ();
+ MyGUI::Gui::getInstance ().destroyWidgets (widgets);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ for (int i=0; i<3; ++i)
+ {
+ int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer
+ (mPtr,pcStats.getSkill (bestSkills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true);
+
+ std::string skin = (price > MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getPlayerGold ()) ? "SandTextGreyedOut" : "SandTextButton";
+
+ MyGUI::Button* button = mTrainingOptions->createWidget<MyGUI::Button>(skin,
+ MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default);
+
+ button->setUserData(bestSkills[i].first);
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected);
+
+ button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[bestSkills[i].first] + "} - " + boost::lexical_cast<std::string>(price));
+
+ button->setSize(button->getTextSize ().width+12, button->getSize().height);
+
+ ToolTips::createSkillToolTip (button, bestSkills[i].first);
+ }
+
+ center();
+ }
+
+ void TrainingWindow::onReferenceUnavailable ()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Training);
+ }
+
+ void TrainingWindow::onCancelButtonClicked (MyGUI::Widget *sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
+ }
+
+ void TrainingWindow::onTrainingSelected (MyGUI::Widget *sender)
+ {
+ int skillId = *sender->getUserData<int>();
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ int price = pcStats.getSkill (skillId).getBase() * store.get<ESM::GameSetting>().find("iTrainingMod")->getInt ();
+ price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true);
+
+ if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()<price)
+ return;
+
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mPtr).getNpcStats (mPtr);
+ if (npcStats.getSkill (skillId).getBase () <= pcStats.getSkill (skillId).getBase ())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}");
+ return;
+ }
+
+ // increase skill
+ MWWorld::LiveCellRef<ESM::NPC> *playerRef = player.get<ESM::NPC>();
+
+ const ESM::Class *class_ =
+ store.get<ESM::Class>().find(playerRef->mBase->mClass);
+ pcStats.increaseSkill (skillId, *class_, true);
+
+ // remove gold
+ MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
+
+ // go back to game mode
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
+
+ // advance time
+ MWBase::Environment::get().getWorld ()->advanceTime (2);
+
+ MWBase::Environment::get().getWorld ()->getFader()->fadeOut(0.25);
+ mFadeTimeRemaining = 0.5;
+ }
+
+ void TrainingWindow::onFrame(float dt)
+ {
+ if (mFadeTimeRemaining <= 0)
+ return;
+
+ mFadeTimeRemaining -= dt;
+
+ if (mFadeTimeRemaining <= 0)
+ MWBase::Environment::get().getWorld ()->getFader()->fadeIn(0.25);
+ }
+}
diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp
new file mode 100644
index 0000000000..740115cdfc
--- /dev/null
+++ b/apps/openmw/mwgui/trainingwindow.hpp
@@ -0,0 +1,36 @@
+#ifndef MWGUI_TRAININGWINDOW_H
+#define MWGUI_TRAININGWINDOW_H
+
+#include "windowbase.hpp"
+#include "referenceinterface.hpp"
+
+namespace MWGui
+{
+
+ class TrainingWindow : public WindowBase, public ReferenceInterface
+ {
+ public:
+ TrainingWindow();
+
+ virtual void open();
+
+ void startTraining(MWWorld::Ptr actor);
+
+ void onFrame(float dt);
+
+ protected:
+ virtual void onReferenceUnavailable ();
+
+ void onCancelButtonClicked (MyGUI::Widget* sender);
+ void onTrainingSelected(MyGUI::Widget* sender);
+
+ MyGUI::Widget* mTrainingOptions;
+ MyGUI::Button* mCancelButton;
+ MyGUI::TextBox* mPlayerGold;
+
+ float mFadeTimeRemaining;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp
new file mode 100644
index 0000000000..93ac8299d9
--- /dev/null
+++ b/apps/openmw/mwgui/travelwindow.cpp
@@ -0,0 +1,188 @@
+#include "travelwindow.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <libs/openengine/ogre/fader.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "inventorywindow.hpp"
+#include "tradewindow.hpp"
+
+namespace MWGui
+{
+ const int TravelWindow::sLineHeight = 18;
+
+ TravelWindow::TravelWindow() :
+ WindowBase("openmw_travel_window.layout")
+ , mCurrentY(0)
+ , mLastPos(0)
+ {
+ setCoord(0, 0, 450, 300);
+
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mPlayerGold, "PlayerGold");
+ getWidget(mSelect, "Select");
+ getWidget(mDestinations, "Travel");
+ getWidget(mDestinationsView, "DestinationsView");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onCancelButtonClicked);
+
+ mDestinations->setCoord(450/2-mDestinations->getTextSize().width/2,
+ mDestinations->getTop(),
+ mDestinations->getTextSize().width,
+ mDestinations->getHeight());
+ mSelect->setCoord(8,
+ mSelect->getTop(),
+ mSelect->getTextSize().width,
+ mSelect->getHeight());
+ }
+
+ void TravelWindow::addDestination(const std::string& travelId,ESM::Position pos,bool interior)
+ {
+ int price = 0;
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ if(interior)
+ {
+ price = gmst.find("fMagesGuildTravel")->getFloat();
+ }
+ else
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ ESM::Position PlayerPos = player.getRefData().getPosition();
+ float d = sqrt( pow(pos.pos[0] - PlayerPos.pos[0],2) + pow(pos.pos[1] - PlayerPos.pos[1],2) + pow(pos.pos[2] - PlayerPos.pos[2],2) );
+ price = d/gmst.find("fTravelMult")->getFloat();
+ }
+
+ price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true);
+
+ MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>((price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default);
+ mCurrentY += sLineHeight;
+ if(interior)
+ toAdd->setUserString("interior","y");
+ else
+ toAdd->setUserString("interior","n");
+
+ std::ostringstream oss;
+ oss << price;
+ toAdd->setUserString("price",oss.str());
+
+ toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast<std::string>(price)+"#{sgp}");
+ toAdd->setSize(toAdd->getTextSize().width,sLineHeight);
+ toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel);
+ toAdd->setUserString("Destination", travelId);
+ toAdd->setUserData(pos);
+ toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onTravelButtonClick);
+ mDestinationsWidgetMap.insert(std::make_pair (toAdd, travelId));
+ }
+
+ void TravelWindow::clearDestinations()
+ {
+ mDestinationsView->setViewOffset(MyGUI::IntPoint(0,0));
+ mCurrentY = 0;
+ while (mDestinationsView->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0));
+ mDestinationsWidgetMap.clear();
+ }
+
+ void TravelWindow::startTravel(const MWWorld::Ptr& actor)
+ {
+ center();
+ mPtr = actor;
+ clearDestinations();
+
+ for(unsigned int i = 0;i<mPtr.get<ESM::NPC>()->mBase->mTransport.size();i++)
+ {
+ std::string cellname = mPtr.get<ESM::NPC>()->mBase->mTransport[i].mCellName;
+ bool interior = true;
+ int x,y;
+ MWBase::Environment::get().getWorld()->positionToIndex(mPtr.get<ESM::NPC>()->mBase->mTransport[i].mPos.pos[0],
+ mPtr.get<ESM::NPC>()->mBase->mTransport[i].mPos.pos[1],x,y);
+ if(cellname == "") {cellname = MWBase::Environment::get().getWorld()->getExterior(x,y)->mCell->mName; interior= false;}
+ addDestination(cellname,mPtr.get<ESM::NPC>()->mBase->mTransport[i].mPos,interior);
+ }
+
+ updateLabels();
+ mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY)));
+ }
+
+ void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender)
+ {
+ std::istringstream iss(_sender->getUserString("price"));
+ int price;
+ iss >> price;
+
+ if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()<price)
+ return;
+
+ MWBase::Environment::get().getWindowManager()->getTradeWindow ()->addOrRemoveGold (-price);
+
+ 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;
+ bool interior = _sender->getUserString("interior") == "y";
+ MWBase::Environment::get().getWorld()->positionToIndex(pos.pos[0],pos.pos[1],x,y);
+ if(interior)
+ MWBase::Environment::get().getWorld()->changeToInteriorCell(cellname, pos);
+ else
+ {
+ ESM::Position playerPos = player.getRefData().getPosition();
+ float d = Ogre::Vector3(pos.pos[0], pos.pos[1], 0).distance(
+ Ogre::Vector3(playerPos.pos[0], playerPos.pos[1], 0));
+ int hours = static_cast<int>(d /MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fTravelTimeMult")->getFloat());
+ for(int i = 0;i < hours;i++)
+ {
+ MWBase::Environment::get().getMechanicsManager ()->restoreDynamicStats ();
+ }
+ MWBase::Environment::get().getWorld()->advanceTime(hours);
+
+ MWBase::Environment::get().getWorld()->changeToExteriorCell(pos);
+ }
+
+ MWWorld::Class::get(player).adjustPosition(player);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0);
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(1);
+ }
+
+ void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender)
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel);
+ }
+
+ void TravelWindow::updateLabels()
+ {
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ mPlayerGold->setCoord(8,
+ mPlayerGold->getTop(),
+ mPlayerGold->getTextSize().width,
+ mPlayerGold->getHeight());
+ }
+
+ void TravelWindow::onReferenceUnavailable()
+ {
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ }
+
+ void TravelWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
+ {
+ if (mDestinationsView->getViewOffset().top + _rel*0.3 > 0)
+ mDestinationsView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mDestinationsView->setViewOffset(MyGUI::IntPoint(0, mDestinationsView->getViewOffset().top + _rel*0.3));
+ }
+}
+
diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp
new file mode 100644
index 0000000000..f2a23b0486
--- /dev/null
+++ b/apps/openmw/mwgui/travelwindow.hpp
@@ -0,0 +1,52 @@
+#ifndef MWGUI_TravelWINDOW_H
+#define MWGUI_TravelWINDOW_H
+
+#include "container.hpp"
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+}
+
+
+namespace MWGui
+{
+ class TravelWindow : public ReferenceInterface, public WindowBase
+ {
+ public:
+ TravelWindow();
+
+ void startTravel(const MWWorld::Ptr& actor);
+
+ protected:
+ MyGUI::Button* mCancelButton;
+ MyGUI::TextBox* mPlayerGold;
+ MyGUI::TextBox* mDestinations;
+ MyGUI::TextBox* mSelect;
+
+ MyGUI::ScrollView* mDestinationsView;
+
+ std::map<MyGUI::Widget*, std::string> mDestinationsWidgetMap;
+
+ void onCancelButtonClicked(MyGUI::Widget* _sender);
+ void onTravelButtonClick(MyGUI::Widget* _sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+ void addDestination(const std::string& destinationID,ESM::Position pos,bool interior);
+ void clearDestinations();
+ int mLastPos,mCurrentY;
+
+ static const int sLineHeight;
+
+ void updateLabels();
+
+ virtual void onReferenceUnavailable();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp
new file mode 100644
index 0000000000..2467f6c40d
--- /dev/null
+++ b/apps/openmw/mwgui/waitdialog.cpp
@@ -0,0 +1,292 @@
+#include "waitdialog.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <libs/openengine/ogre/fader.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+
+namespace MWGui
+{
+
+ WaitDialogProgressBar::WaitDialogProgressBar()
+ : WindowBase("openmw_wait_dialog_progressbar.layout")
+ {
+ getWidget(mProgressBar, "ProgressBar");
+ getWidget(mProgressText, "ProgressText");
+ }
+
+ void WaitDialogProgressBar::open()
+ {
+ center();
+ }
+
+ void WaitDialogProgressBar::setProgress (int cur, int total)
+ {
+ mProgressBar->setProgressRange (total);
+ mProgressBar->setProgressPosition (cur);
+ mProgressText->setCaption(boost::lexical_cast<std::string>(cur) + "/" + boost::lexical_cast<std::string>(total));
+ }
+
+ // ---------------------------------------------------------------------------------------------------------
+
+ WaitDialog::WaitDialog()
+ : WindowBase("openmw_wait_dialog.layout")
+ , mProgressBar()
+ , mWaiting(false)
+ , mSleeping(false)
+ , mHours(1)
+ , mRemainingTime(0.05)
+ , mCurHour(0)
+ , mManualHours(1)
+ {
+ getWidget(mDateTimeText, "DateTimeText");
+ getWidget(mRestText, "RestText");
+ getWidget(mHourText, "HourText");
+ getWidget(mUntilHealedButton, "UntilHealedButton");
+ getWidget(mWaitButton, "WaitButton");
+ getWidget(mCancelButton, "CancelButton");
+ getWidget(mHourSlider, "HourSlider");
+
+ mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onCancelButtonClicked);
+ mUntilHealedButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onUntilHealedButtonClicked);
+ mWaitButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onWaitButtonClicked);
+ mHourSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &WaitDialog::onHourSliderChangedPosition);
+
+ mProgressBar.setVisible (false);
+ }
+
+ void WaitDialog::open()
+ {
+ if (!MWBase::Environment::get().getWindowManager ()->getRestEnabled ())
+ {
+ MWBase::Environment::get().getWindowManager()->popGuiMode ();
+ }
+
+ int canRest = MWBase::Environment::get().getWorld ()->canRest ();
+
+ if (canRest == 2)
+ {
+ // resting underwater or mid-air not allowed
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}");
+ MWBase::Environment::get().getWindowManager()->popGuiMode ();
+ }
+
+ setCanRest(canRest == 0);
+
+ onHourSliderChangedPosition(mHourSlider, 0);
+ mHourSlider->setScrollPosition (0);
+
+ // http://www.uesp.net/wiki/Lore:Calendar
+ std::string month;
+ int m = MWBase::Environment::get().getWorld ()->getMonth ();
+ switch (m) {
+ case 0:
+ month = "#{sMonthMorningstar}";
+ break;
+ case 1:
+ month = "#{sMonthSunsdawn}";
+ break;
+ case 2:
+ month = "#{sMonthFirstseed}";
+ break;
+ case 3:
+ month = "#{sMonthRainshand}";
+ break;
+ case 4:
+ month = "#{sMonthSecondseed}";
+ break;
+ case 5:
+ month = "#{sMonthMidyear}";
+ break;
+ case 6:
+ month = "#{sMonthSunsheight}";
+ break;
+ case 7:
+ month = "#{sMonthLastseed}";
+ break;
+ case 8:
+ month = "#{sMonthHeartfire}";
+ break;
+ case 9:
+ month = "#{sMonthFrostfall}";
+ break;
+ case 10:
+ month = "#{sMonthSunsdusk}";
+ break;
+ case 11:
+ month = "#{sMonthEveningstar}";
+ break;
+ default:
+ break;
+ }
+ int hour = MWBase::Environment::get().getWorld ()->getTimeStamp ().getHour ();
+ bool pm = hour >= 12;
+ if (hour >= 13) hour -= 12;
+ if (hour == 0) hour = 12;
+
+ std::string dateTimeText =
+ boost::lexical_cast<std::string>(MWBase::Environment::get().getWorld ()->getDay ()) + " "
+ + month + " (#{sDay} " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWorld ()->getTimeStamp ().getDay())
+ + ") " + boost::lexical_cast<std::string>(hour) + " " + (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
+
+ mDateTimeText->setCaptionWithReplacing (dateTimeText);
+ }
+
+ void WaitDialog::onUntilHealedButtonClicked(MyGUI::Widget* sender)
+ {
+ // we need to sleep for a specific time, and since that isn't calculated yet, we'll do it here
+ // I'm making the assumption here that the # of hours rested is calculated when rest is started
+ // TODO: the rougher logic here (calculating the hourly deltas) should really go into helper funcs elsewhere
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats stats = MWWorld::Class::get(player).getCreatureStats(player);
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ float hourlyHealthDelta = stats.getAttribute(ESM::Attribute::Endurance).getModified() * 0.1;
+
+ bool stunted = (stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0);
+ float fRestMagicMult = store.get<ESM::GameSetting>().find("fRestMagicMult")->getFloat();
+ float hourlyMagickaDelta = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
+
+ // this massive duplication is why it has to be put into helper functions instead
+ float fFatigueReturnBase = store.get<ESM::GameSetting>().find("fFatigueReturnBase")->getFloat();
+ float fFatigueReturnMult = store.get<ESM::GameSetting>().find("fFatigueReturnMult")->getFloat();
+ float fEndFatigueMult = store.get<ESM::GameSetting>().find("fEndFatigueMult")->getFloat();
+ float capacity = MWWorld::Class::get(player).getCapacity(player);
+ float encumbrance = MWWorld::Class::get(player).getEncumbrance(player);
+ float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity);
+ if (normalizedEncumbrance > 1)
+ normalizedEncumbrance = 1;
+ float hourlyFatigueDelta = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
+ hourlyFatigueDelta *= 3600 * fEndFatigueMult * stats.getAttribute(ESM::Attribute::Endurance).getModified();
+
+ float healthHours = hourlyHealthDelta >= 0.0
+ ? (stats.getHealth().getBase() - stats.getHealth().getCurrent()) / hourlyHealthDelta
+ : 1.0f;
+ float magickaHours = stunted ? 0.0 :
+ hourlyMagickaDelta >= 0.0
+ ? (stats.getMagicka().getBase() - stats.getMagicka().getCurrent()) / hourlyMagickaDelta
+ : 1.0f;
+ float fatigueHours = hourlyFatigueDelta >= 0.0
+ ? (stats.getFatigue().getBase() - stats.getFatigue().getCurrent()) / hourlyFatigueDelta
+ : 1.0f;
+
+ int autoHours = int(std::ceil( std::max(std::max(healthHours, magickaHours), std::max(fatigueHours, 1.0f)) )); // this should use a variadic max if possible
+
+ startWaiting(autoHours);
+ }
+
+ void WaitDialog::onWaitButtonClicked(MyGUI::Widget* sender)
+ {
+ startWaiting(mManualHours);
+ }
+
+ void WaitDialog::startWaiting(int hoursToWait)
+ {
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.2);
+ setVisible(false);
+ mProgressBar.setVisible (true);
+
+ mWaiting = true;
+ mCurHour = 0;
+ mHours = hoursToWait;
+
+ mRemainingTime = 0.05;
+ mProgressBar.setProgress (0, mHours);
+ }
+
+ void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender)
+ {
+ MWBase::Environment::get().getWindowManager()->popGuiMode ();
+ }
+
+ void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position)
+ {
+ mHourText->setCaptionWithReplacing (boost::lexical_cast<std::string>(position+1) + " #{sRestMenu2}");
+ mManualHours = position+1;
+ }
+
+ void WaitDialog::setCanRest (bool canRest)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified())
+ && (stats.getHealth().getCurrent() >= stats.getHealth().getModified())
+ && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified());
+ MWMechanics::NpcStats& npcstats = MWWorld::Class::get(player).getNpcStats(player);
+ bool werewolf = npcstats.isWerewolf();
+
+ mUntilHealedButton->setVisible(canRest && !full);
+ mWaitButton->setCaptionWithReplacing (canRest ? "#{sRest}" : "#{sWait}");
+ mRestText->setCaptionWithReplacing (canRest ? "#{sRestMenu3}"
+ : (werewolf ? "#{sWerewolfRestMessage}"
+ : "#{sRestIllegal}"));
+
+ mSleeping = canRest;
+
+ dynamic_cast<Widgets::Box*>(mMainWidget)->notifyChildrenSizeChanged();
+ center();
+ }
+
+ void WaitDialog::onFrame(float dt)
+ {
+ if (!mWaiting)
+ return;
+
+ mRemainingTime -= dt;
+
+ while (mRemainingTime < 0)
+ {
+ mRemainingTime += 0.05;
+ ++mCurHour;
+ mProgressBar.setProgress (mCurHour, mHours);
+
+ if (mCurHour <= mHours)
+ {
+ MWBase::Environment::get().getWorld ()->advanceTime (1);
+ if (mSleeping)
+ MWBase::Environment::get().getMechanicsManager ()->restoreDynamicStats ();
+ }
+ }
+
+ if (mCurHour > mHours)
+ stopWaiting();
+
+ }
+
+ void WaitDialog::stopWaiting ()
+ {
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.2);
+ mProgressBar.setVisible (false);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed);
+ mWaiting = false;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::NpcStats &pcstats = MWWorld::Class::get(player).getNpcStats(player);
+
+ // trigger levelup if possible
+ if (mSleeping && pcstats.getLevelProgress () >= 10)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup);
+ }
+ }
+
+ void WaitDialog::wakeUp ()
+ {
+ mSleeping = false;
+ mWaiting = false;
+ stopWaiting();
+ }
+
+}
diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp
new file mode 100644
index 0000000000..2723f7a805
--- /dev/null
+++ b/apps/openmw/mwgui/waitdialog.hpp
@@ -0,0 +1,69 @@
+#ifndef MWGUI_WAIT_DIALOG_H
+#define MWGUI_WAIT_DIALOG_H
+
+#include "windowbase.hpp"
+#include "widgets.hpp"
+
+namespace MWGui
+{
+
+ class WaitDialogProgressBar : public WindowBase
+ {
+ public:
+ WaitDialogProgressBar();
+
+ virtual void open();
+
+ void setProgress(int cur, int total);
+
+ protected:
+ MyGUI::ProgressBar* mProgressBar;
+ MyGUI::TextBox* mProgressText;
+ };
+
+ class WaitDialog : public WindowBase
+ {
+ public:
+ WaitDialog();
+
+ virtual void open();
+
+ void onFrame(float dt);
+
+ void bedActivated() { setCanRest(true); }
+
+ bool getSleeping() { return mWaiting && mSleeping; }
+ void wakeUp();
+
+ protected:
+ MyGUI::TextBox* mDateTimeText;
+ MyGUI::TextBox* mRestText;
+ MyGUI::TextBox* mHourText;
+ MyGUI::Button* mUntilHealedButton;
+ MyGUI::Button* mWaitButton;
+ MyGUI::Button* mCancelButton;
+ MWGui::Widgets::MWScrollBar* mHourSlider;
+
+ bool mWaiting;
+ bool mSleeping;
+ int mCurHour;
+ int mHours;
+ int mManualHours; // stores the hours to rest selected via slider
+ float mRemainingTime;
+
+ WaitDialogProgressBar mProgressBar;
+
+ void onUntilHealedButtonClicked(MyGUI::Widget* sender);
+ void onWaitButtonClicked(MyGUI::Widget* sender);
+ void onCancelButtonClicked(MyGUI::Widget* sender);
+ void onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position);
+
+ void setCanRest(bool canRest);
+
+ void startWaiting(int hoursToWait);
+ void stopWaiting();
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp
new file mode 100644
index 0000000000..dea64ae8cf
--- /dev/null
+++ b/apps/openmw/mwgui/widgets.cpp
@@ -0,0 +1,1007 @@
+#include "widgets.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <MyGUI_ProgressBar.h>
+#include <MyGUI_ImageBox.h>
+#include <MyGUI_ControllerManager.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#undef min
+#undef max
+
+namespace MWGui
+{
+ namespace Widgets
+ {
+
+ /* Helper functions */
+
+ /*
+ * Fixes the filename of a texture path to use the correct .dds extension.
+ * This is needed on some ESM entries which point to a .tga file instead.
+ */
+ void fixTexturePath(std::string &path)
+ {
+ int offset = path.rfind(".");
+ if (offset < 0)
+ return;
+ path.replace(offset, path.length() - offset, ".dds");
+ }
+
+ /* MWSkill */
+
+ MWSkill::MWSkill()
+ : mSkillId(ESM::Skill::Length)
+ , mSkillNameWidget(NULL)
+ , mSkillValueWidget(NULL)
+ {
+ }
+
+ void MWSkill::setSkillId(ESM::Skill::SkillEnum skill)
+ {
+ mSkillId = skill;
+ updateWidgets();
+ }
+
+ void MWSkill::setSkillNumber(int skill)
+ {
+ if (skill < 0)
+ setSkillId(ESM::Skill::Length);
+ else if (skill < ESM::Skill::Length)
+ setSkillId(static_cast<ESM::Skill::SkillEnum>(skill));
+ else
+ throw new std::runtime_error("Skill number out of range");
+ }
+
+ void MWSkill::setSkillValue(const SkillValue& value)
+ {
+ mValue = value;
+ updateWidgets();
+ }
+
+ void MWSkill::updateWidgets()
+ {
+ if (mSkillNameWidget)
+ {
+ if (mSkillId == ESM::Skill::Length)
+ {
+ static_cast<MyGUI::TextBox*>(mSkillNameWidget)->setCaption("");
+ }
+ else
+ {
+ const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mSkillId], "");
+ static_cast<MyGUI::TextBox*>(mSkillNameWidget)->setCaption(name);
+ }
+ }
+ if (mSkillValueWidget)
+ {
+ SkillValue::Type modified = mValue.getModified(), base = mValue.getBase();
+ static_cast<MyGUI::TextBox*>(mSkillValueWidget)->setCaption(boost::lexical_cast<std::string>(modified));
+ if (modified > base)
+ mSkillValueWidget->_setWidgetState("increased");
+ else if (modified < base)
+ mSkillValueWidget->_setWidgetState("decreased");
+ else
+ mSkillValueWidget->_setWidgetState("normal");
+ }
+ }
+
+ void MWSkill::onClicked(MyGUI::Widget* _sender)
+ {
+ eventClicked(this);
+ }
+
+ MWSkill::~MWSkill()
+ {
+ }
+
+ void MWSkill::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mSkillNameWidget, "StatName");
+ assignWidget(mSkillValueWidget, "StatValue");
+
+ MyGUI::Button* button;
+ assignWidget(button, "StatNameButton");
+ if (button)
+ {
+ mSkillNameWidget = button;
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked);
+ }
+
+ button = 0;
+ assignWidget(button, "StatValueButton");
+ if (button)
+ {
+ mSkillNameWidget = button;
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked);
+ }
+ }
+
+ /* MWAttribute */
+
+ MWAttribute::MWAttribute()
+ : mId(-1)
+ , mAttributeNameWidget(NULL)
+ , mAttributeValueWidget(NULL)
+ {
+ }
+
+ void MWAttribute::setAttributeId(int attributeId)
+ {
+ mId = attributeId;
+ updateWidgets();
+ }
+
+ void MWAttribute::setAttributeValue(const AttributeValue& value)
+ {
+ mValue = value;
+ updateWidgets();
+ }
+
+ void MWAttribute::onClicked(MyGUI::Widget* _sender)
+ {
+ eventClicked(this);
+ }
+
+ void MWAttribute::updateWidgets()
+ {
+ if (mAttributeNameWidget)
+ {
+ if (mId < 0 || mId >= 8)
+ {
+ static_cast<MyGUI::TextBox*>(mAttributeNameWidget)->setCaption("");
+ }
+ else
+ {
+ static const char *attributes[8] = {
+ "sAttributeStrength",
+ "sAttributeIntelligence",
+ "sAttributeWillpower",
+ "sAttributeAgility",
+ "sAttributeSpeed",
+ "sAttributeEndurance",
+ "sAttributePersonality",
+ "sAttributeLuck"
+ };
+ const std::string &name = MWBase::Environment::get().getWindowManager()->getGameSettingString(attributes[mId], "");
+ static_cast<MyGUI::TextBox*>(mAttributeNameWidget)->setCaption(name);
+ }
+ }
+ if (mAttributeValueWidget)
+ {
+ AttributeValue::Type modified = mValue.getModified(), base = mValue.getBase();
+ static_cast<MyGUI::TextBox*>(mAttributeValueWidget)->setCaption(boost::lexical_cast<std::string>(modified));
+ if (modified > base)
+ mAttributeValueWidget->_setWidgetState("increased");
+ else if (modified < base)
+ mAttributeValueWidget->_setWidgetState("decreased");
+ else
+ mAttributeValueWidget->_setWidgetState("normal");
+ }
+ }
+
+ MWAttribute::~MWAttribute()
+ {
+ }
+
+ void MWAttribute::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mAttributeNameWidget, "StatName");
+ assignWidget(mAttributeValueWidget, "StatValue");
+
+ MyGUI::Button* button;
+ assignWidget(button, "StatNameButton");
+ if (button)
+ {
+ mAttributeNameWidget = button;
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked);
+ }
+
+ button = 0;
+ assignWidget(button, "StatValueButton");
+ if (button)
+ {
+ mAttributeValueWidget = button;
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked);
+ }
+ }
+
+ /* MWSpell */
+
+ MWSpell::MWSpell()
+ : mSpellNameWidget(NULL)
+ {
+ }
+
+ void MWSpell::setSpellId(const std::string &spellId)
+ {
+ mId = spellId;
+ updateWidgets();
+ }
+
+ void MWSpell::createEffectWidgets(std::vector<MyGUI::Widget*> &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags)
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::Spell *spell = store.get<ESM::Spell>().search(mId);
+ MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found");
+
+ MWSpellEffectPtr effect = NULL;
+ std::vector<ESM::ENAMstruct>::const_iterator end = spell->mEffects.mList.end();
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it)
+ {
+ effect = creator->createWidget<MWSpellEffect>("MW_EffectImage", coord, MyGUI::Align::Default);
+ SpellEffectParams params;
+ params.mEffectID = it->mEffectID;
+ params.mSkill = it->mSkill;
+ params.mAttribute = it->mAttribute;
+ params.mDuration = it->mDuration;
+ params.mMagnMin = it->mMagnMin;
+ params.mMagnMax = it->mMagnMax;
+ params.mRange = it->mRange;
+ params.mIsConstant = (flags & MWEffectList::EF_Constant);
+ params.mNoTarget = (flags & MWEffectList::EF_NoTarget);
+ effect->setSpellEffect(params);
+ effects.push_back(effect);
+ coord.top += effect->getHeight();
+ coord.width = std::max(coord.width, effect->getRequestedWidth());
+ }
+ }
+
+ void MWSpell::updateWidgets()
+ {
+ if (mSpellNameWidget && MWBase::Environment::get().getWindowManager())
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::Spell *spell = store.get<ESM::Spell>().search(mId);
+ if (spell)
+ static_cast<MyGUI::TextBox*>(mSpellNameWidget)->setCaption(spell->mName);
+ else
+ static_cast<MyGUI::TextBox*>(mSpellNameWidget)->setCaption("");
+ }
+ }
+
+ void MWSpell::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mSpellNameWidget, "StatName");
+ }
+
+ MWSpell::~MWSpell()
+ {
+ }
+
+ /* MWEffectList */
+
+ MWEffectList::MWEffectList()
+ : mEffectList(0)
+ {
+ }
+
+ void MWEffectList::setEffectList(const SpellEffectList& list)
+ {
+ mEffectList = list;
+ updateWidgets();
+ }
+
+ void MWEffectList::createEffectWidgets(std::vector<MyGUI::Widget*> &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags)
+ {
+ // We don't know the width of all the elements beforehand, so we do it in
+ // 2 steps: first, create all widgets and check their width....
+ MWSpellEffectPtr effect = NULL;
+ int maxwidth = coord.width;
+
+ for (SpellEffectList::iterator it=mEffectList.begin();
+ it != mEffectList.end(); ++it)
+ {
+ effect = creator->createWidget<MWSpellEffect>("MW_EffectImage", coord, MyGUI::Align::Default);
+ it->mIsConstant = (flags & EF_Constant) || it->mIsConstant;
+ it->mNoTarget = (flags & EF_NoTarget) || it->mNoTarget;
+ effect->setSpellEffect(*it);
+ effects.push_back(effect);
+ if (effect->getRequestedWidth() > maxwidth)
+ maxwidth = effect->getRequestedWidth();
+
+ coord.top += effect->getHeight();
+ }
+
+ // ... then adjust the size for all widgets
+ for (std::vector<MyGUI::Widget*>::iterator it = effects.begin(); it != effects.end(); ++it)
+ {
+ effect = static_cast<MWSpellEffectPtr>(*it);
+ bool needcenter = center && (maxwidth > effect->getRequestedWidth());
+ int diff = maxwidth - effect->getRequestedWidth();
+ if (needcenter)
+ {
+ effect->setCoord(diff/2, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height);
+ }
+ else
+ {
+ effect->setCoord(0, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height);
+ }
+ }
+
+ // inform the parent about width
+ coord.width = maxwidth;
+ }
+
+ void MWEffectList::updateWidgets()
+ {
+ }
+
+ void MWEffectList::initialiseOverride()
+ {
+ Base::initialiseOverride();
+ }
+
+ MWEffectList::~MWEffectList()
+ {
+ }
+
+ SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects)
+ {
+ SpellEffectList result;
+ std::vector<ESM::ENAMstruct>::const_iterator end = effects->mList.end();
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = effects->mList.begin(); it != end; ++it)
+ {
+ SpellEffectParams params;
+ params.mEffectID = it->mEffectID;
+ params.mSkill = it->mSkill;
+ params.mAttribute = it->mAttribute;
+ params.mDuration = it->mDuration;
+ params.mMagnMin = it->mMagnMin;
+ params.mMagnMax = it->mMagnMax;
+ params.mRange = it->mRange;
+ params.mArea = it->mArea;
+ result.push_back(params);
+ }
+ return result;
+ }
+
+ /* MWSpellEffect */
+
+ MWSpellEffect::MWSpellEffect()
+ : mImageWidget(NULL)
+ , mTextWidget(NULL)
+ , mRequestedWidth(0)
+ {
+ }
+
+ void MWSpellEffect::setSpellEffect(const SpellEffectParams& params)
+ {
+ mEffectParams = params;
+ updateWidgets();
+ }
+
+ void MWSpellEffect::updateWidgets()
+ {
+ if (!mEffectParams.mKnown)
+ {
+ mTextWidget->setCaption ("?");
+ mRequestedWidth = mTextWidget->getTextSize().width + 24;
+ mImageWidget->setImageTexture ("");
+ return;
+ }
+
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::MagicEffect *magicEffect =
+ store.get<ESM::MagicEffect>().search(mEffectParams.mEffectID);
+
+ assert(magicEffect);
+
+ std::string pt = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "");
+ std::string pts = MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", "");
+ std::string to = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "") + " ";
+ std::string sec = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("ssecond", "");
+ std::string secs = " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sseconds", "");
+
+ std::string effectIDStr = ESM::MagicEffect::effectIdToString(mEffectParams.mEffectID);
+ std::string spellLine = MWBase::Environment::get().getWindowManager()->getGameSettingString(effectIDStr, "");
+
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
+ {
+ spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Skill::sSkillNameIds[mEffectParams.mSkill], "");
+ }
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
+ {
+ spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Attribute::sGmstAttributeIds[mEffectParams.mAttribute], "");
+ }
+
+ if ((mEffectParams.mMagnMin >= 0 || mEffectParams.mMagnMax >= 0) && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
+ {
+ if (mEffectParams.mMagnMin == mEffectParams.mMagnMax)
+ spellLine += " " + boost::lexical_cast<std::string>(mEffectParams.mMagnMin) + " " + ((mEffectParams.mMagnMin == 1) ? pt : pts);
+ else
+ {
+ spellLine += " " + boost::lexical_cast<std::string>(mEffectParams.mMagnMin) + to + boost::lexical_cast<std::string>(mEffectParams.mMagnMax) + " " + pts;
+ }
+ }
+
+ // constant effects have no duration and no target
+ if (!mEffectParams.mIsConstant)
+ {
+ if (mEffectParams.mDuration >= 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
+ {
+ spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + boost::lexical_cast<std::string>(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs);
+ }
+
+ if (mEffectParams.mArea > 0)
+ {
+ spellLine += " #{sin} " + boost::lexical_cast<std::string>(mEffectParams.mArea) + " #{sfootarea}";
+ }
+
+ // potions have no target
+ if (!mEffectParams.mNoTarget)
+ {
+ std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sonword", "");
+ if (mEffectParams.mRange == ESM::RT_Self)
+ spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeSelf", "");
+ else if (mEffectParams.mRange == ESM::RT_Touch)
+ spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTouch", "");
+ else if (mEffectParams.mRange == ESM::RT_Target)
+ spellLine += " " + on + " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sRangeTarget", "");
+ }
+ }
+
+ static_cast<MyGUI::TextBox*>(mTextWidget)->setCaptionWithReplacing(spellLine);
+ mRequestedWidth = mTextWidget->getTextSize().width + 24;
+
+ std::string path = std::string("icons\\") + magicEffect->mIcon;
+ fixTexturePath(path);
+ mImageWidget->setImageTexture(path);
+ }
+
+ MWSpellEffect::~MWSpellEffect()
+ {
+ }
+
+ void MWSpellEffect::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mTextWidget, "Text");
+ assignWidget(mImageWidget, "Image");
+ }
+
+ /* MWDynamicStat */
+
+ MWDynamicStat::MWDynamicStat()
+ : mValue(0)
+ , mMax(1)
+ , mTextWidget(NULL)
+ , mBarWidget(NULL)
+ , mBarTextWidget(NULL)
+ {
+ }
+
+ void MWDynamicStat::setValue(int cur, int max)
+ {
+ mValue = cur;
+ mMax = max;
+
+ if (mBarWidget)
+ {
+ mBarWidget->setProgressRange(mMax);
+ mBarWidget->setProgressPosition(mValue);
+ }
+
+
+ if (mBarTextWidget)
+ {
+ if (mValue >= 0 && mMax > 0)
+ {
+ std::stringstream out;
+ out << mValue << "/" << mMax;
+ static_cast<MyGUI::TextBox*>(mBarTextWidget)->setCaption(out.str().c_str());
+ }
+ else
+ static_cast<MyGUI::TextBox*>(mBarTextWidget)->setCaption("");
+ }
+ }
+ void MWDynamicStat::setTitle(const std::string& text)
+ {
+ if (mTextWidget)
+ static_cast<MyGUI::TextBox*>(mTextWidget)->setCaption(text);
+ }
+
+ MWDynamicStat::~MWDynamicStat()
+ {
+ }
+
+ void MWDynamicStat::initialiseOverride()
+ {
+ Base::initialiseOverride();
+
+ assignWidget(mTextWidget, "Text");
+ assignWidget(mBarWidget, "Bar");
+ assignWidget(mBarTextWidget, "BarText");
+ }
+
+
+
+
+ // ---------------------------------------------------------------------------------------------------------------------
+
+ void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w)
+ {
+ if (w->getParent () != 0)
+ {
+ Box* b = dynamic_cast<Box*>(w->getParent());
+ if (b)
+ b->notifyChildrenSizeChanged ();
+ else
+ {
+ if (mExpandDirection == MyGUI::Align::Left)
+ {
+ int hdiff = getRequestedSize ().width - w->getSize().width;
+ w->setPosition(w->getPosition() - MyGUI::IntPoint(hdiff, 0));
+ }
+ w->setSize(getRequestedSize ());
+ }
+ }
+ }
+
+
+ MyGUI::IntSize AutoSizedTextBox::getRequestedSize()
+ {
+ return getTextSize();
+ }
+
+ void AutoSizedTextBox::setCaption(const MyGUI::UString& _value)
+ {
+ TextBox::setCaption(_value);
+
+ notifySizeChange (this);
+ }
+
+ void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value)
+ {
+ if (_key == "ExpandDirection")
+ {
+ mExpandDirection = MyGUI::Align::parse (_value);
+ }
+ else
+ {
+ TextBox::setPropertyOverride (_key, _value);
+ }
+ }
+
+ MyGUI::IntSize AutoSizedEditBox::getRequestedSize()
+ {
+ if (getAlign().isHStretch())
+ throw std::runtime_error("AutoSizedEditBox can't have HStretch align (" + getName() + ")");
+ return MyGUI::IntSize(getSize().width, getTextSize().height);
+ }
+
+ void AutoSizedEditBox::setCaption(const MyGUI::UString& _value)
+ {
+ EditBox::setCaption(_value);
+
+ notifySizeChange (this);
+ }
+
+ void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value)
+ {
+ if (_key == "ExpandDirection")
+ {
+ mExpandDirection = MyGUI::Align::parse (_value);
+ }
+ else
+ {
+ EditBox::setPropertyOverride (_key, _value);
+ }
+ }
+
+
+ MyGUI::IntSize AutoSizedButton::getRequestedSize()
+ {
+ MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(24,0);
+ size.height = std::max(24, size.height);
+ return size;
+ }
+
+ void AutoSizedButton::setCaption(const MyGUI::UString& _value)
+ {
+ Button::setCaption(_value);
+
+ notifySizeChange (this);
+ }
+
+ void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value)
+ {
+ if (_key == "ExpandDirection")
+ {
+ mExpandDirection = MyGUI::Align::parse (_value);
+ }
+ else
+ {
+ Button::setPropertyOverride (_key, _value);
+ }
+ }
+
+ Box::Box()
+ : mSpacing(4)
+ , mPadding(0)
+ , mAutoResize(false)
+ {
+
+ }
+
+ void Box::notifyChildrenSizeChanged ()
+ {
+ align();
+ }
+
+ void Box::_setPropertyImpl(const std::string& _key, const std::string& _value)
+ {
+ if (_key == "Spacing")
+ mSpacing = MyGUI::utility::parseValue<int>(_value);
+ else if (_key == "Padding")
+ mPadding = MyGUI::utility::parseValue<int>(_value);
+ else if (_key == "AutoResize")
+ mAutoResize = MyGUI::utility::parseValue<bool>(_value);
+ }
+
+ void HBox::align ()
+ {
+ unsigned int count = getChildCount ();
+ size_t h_stretched_count = 0;
+ int total_width = 0;
+ int total_height = 0;
+ std::vector< std::pair<MyGUI::IntSize, bool> > sizes;
+
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ MyGUI::Widget* w = getChildAt(i);
+ bool hstretch = w->getUserString ("HStretch") == "true";
+ h_stretched_count += hstretch;
+ AutoSizedWidget* aw = dynamic_cast<AutoSizedWidget*>(w);
+ if (aw)
+ {
+ sizes.push_back(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));
+ total_width += w->getSize().width;
+ if (!(w->getUserString("VStretch") == "true"))
+ total_height = std::max(total_height, w->getSize().height);
+ }
+
+ if (i != count-1)
+ total_width += mSpacing;
+ }
+
+ if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height))
+ {
+ setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2));
+ return;
+ }
+
+
+ int curX = 0;
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ if (i == 0)
+ curX += mPadding;
+
+ MyGUI::Widget* w = getChildAt(i);
+
+ bool vstretch = w->getUserString ("VStretch") == "true";
+ int height = vstretch ? total_height : sizes[i].first.height;
+
+ MyGUI::IntCoord widgetCoord;
+ widgetCoord.left = curX;
+ widgetCoord.top = mPadding + (getSize().height-mPadding*2 - height) / 2;
+ int width = sizes[i].second ? sizes[i].first.width + (getSize().width-mPadding*2 - total_width)/h_stretched_count
+ : sizes[i].first.width;
+ widgetCoord.width = width;
+ widgetCoord.height = height;
+ w->setCoord(widgetCoord);
+ curX += width;
+
+ if (i != count-1)
+ curX += mSpacing;
+ }
+ }
+
+ void HBox::setPropertyOverride(const std::string& _key, const std::string& _value)
+ {
+ Box::_setPropertyImpl (_key, _value);
+ }
+
+ void HBox::setSize (const MyGUI::IntSize& _value)
+ {
+ MyGUI::Widget::setSize (_value);
+ align();
+ }
+
+ void HBox::setCoord (const MyGUI::IntCoord& _value)
+ {
+ MyGUI::Widget::setCoord (_value);
+ align();
+ }
+
+ void HBox::onWidgetCreated(MyGUI::Widget* _widget)
+ {
+ align();
+ }
+
+ MyGUI::IntSize HBox::getRequestedSize ()
+ {
+ MyGUI::IntSize size(0,0);
+ for (unsigned int i = 0; i < getChildCount (); ++i)
+ {
+ AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
+ if (w)
+ {
+ MyGUI::IntSize requested = w->getRequestedSize ();
+ size.height = std::max(size.height, requested.height);
+ size.width = size.width + requested.width;
+ if (i != getChildCount()-1)
+ size.width += mSpacing;
+ }
+ else
+ {
+ MyGUI::IntSize requested = getChildAt(i)->getSize ();
+ size.height = std::max(size.height, requested.height);
+
+ if (getChildAt(i)->getUserString("HStretch") != "true")
+ size.width = size.width + requested.width;
+
+ if (i != getChildCount()-1)
+ size.width += mSpacing;
+ }
+ size.height += mPadding*2;
+ size.width += mPadding*2;
+ }
+ return size;
+ }
+
+
+
+
+ void VBox::align ()
+ {
+ unsigned int count = getChildCount ();
+ size_t v_stretched_count = 0;
+ int total_height = 0;
+ int total_width = 0;
+ std::vector< std::pair<MyGUI::IntSize, bool> > sizes;
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ MyGUI::Widget* w = getChildAt(i);
+ 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));
+ total_height += aw->getRequestedSize ().height;
+ total_width = std::max(total_width, aw->getRequestedSize ().width);
+ }
+ else
+ {
+ sizes.push_back (std::make_pair(w->getSize(), vstretch));
+ total_height += w->getSize().height;
+
+ if (!(w->getUserString("HStretch") == "true"))
+ total_width = std::max(total_width, w->getSize().width);
+ }
+
+ if (i != count-1)
+ total_height += mSpacing;
+ }
+
+ if (mAutoResize && (total_width+mPadding*2 != getSize().width || total_height+mPadding*2 != getSize().height))
+ {
+ setSize(MyGUI::IntSize(total_width+mPadding*2, total_height+mPadding*2));
+ return;
+ }
+
+
+ int curY = 0;
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ if (i==0)
+ curY += mPadding;
+
+ MyGUI::Widget* w = getChildAt(i);
+
+ bool hstretch = w->getUserString ("HStretch") == "true";
+ int width = hstretch ? total_width : sizes[i].first.width;
+
+ MyGUI::IntCoord widgetCoord;
+ widgetCoord.top = curY;
+ widgetCoord.left = mPadding + (getSize().width-mPadding*2 - width) / 2;
+ int height = sizes[i].second ? sizes[i].first.height + (getSize().height-mPadding*2 - total_height)/v_stretched_count
+ : sizes[i].first.height;
+ widgetCoord.height = height;
+ widgetCoord.width = width;
+ w->setCoord(widgetCoord);
+ curY += height;
+
+ if (i != count-1)
+ curY += mSpacing;
+ }
+ }
+
+ void VBox::setPropertyOverride(const std::string& _key, const std::string& _value)
+ {
+ Box::_setPropertyImpl (_key, _value);
+ }
+
+ void VBox::setSize (const MyGUI::IntSize& _value)
+ {
+ MyGUI::Widget::setSize (_value);
+ align();
+ }
+
+ void VBox::setCoord (const MyGUI::IntCoord& _value)
+ {
+ MyGUI::Widget::setCoord (_value);
+ align();
+ }
+
+ MyGUI::IntSize VBox::getRequestedSize ()
+ {
+ MyGUI::IntSize size(0,0);
+ for (unsigned int i = 0; i < getChildCount (); ++i)
+ {
+ AutoSizedWidget* w = dynamic_cast<AutoSizedWidget*>(getChildAt(i));
+ if (w)
+ {
+ MyGUI::IntSize requested = w->getRequestedSize ();
+ size.width = std::max(size.width, requested.width);
+ size.height = size.height + requested.height;
+ if (i != getChildCount()-1)
+ size.height += mSpacing;
+ }
+ else
+ {
+ MyGUI::IntSize requested = getChildAt(i)->getSize ();
+ size.width = std::max(size.width, requested.width);
+
+ if (getChildAt(i)->getUserString("VStretch") != "true")
+ size.height = size.height + requested.height;
+
+ if (i != getChildCount()-1)
+ size.height += mSpacing;
+ }
+ size.height += mPadding*2;
+ size.width += mPadding*2;
+ }
+ return size;
+ }
+
+ void VBox::onWidgetCreated(MyGUI::Widget* _widget)
+ {
+ align();
+ }
+
+ MWScrollBar::MWScrollBar()
+ : mEnableRepeat(true)
+ , mRepeatTriggerTime(0.5)
+ , mRepeatStepTime(0.1)
+ , mIsIncreasing(true)
+ {
+ }
+
+ MWScrollBar::~MWScrollBar()
+ {
+ }
+
+ void MWScrollBar::initialiseOverride()
+ {
+ ScrollBar::initialiseOverride();
+
+ if(mWidgetStart)
+ {
+ mWidgetStart->eventMouseButtonPressed += MyGUI::newDelegate(this, &MWScrollBar::onDecreaseButtonPressed);
+ mWidgetStart->eventMouseButtonReleased += MyGUI::newDelegate(this, &MWScrollBar::onDecreaseButtonReleased);
+ }
+ if(mWidgetEnd)
+ {
+ mWidgetEnd->eventMouseButtonPressed += MyGUI::newDelegate(this, &MWScrollBar::onIncreaseButtonPressed);
+ mWidgetEnd->eventMouseButtonReleased += MyGUI::newDelegate(this, &MWScrollBar::onIncreaseButtonReleased);
+ }
+ }
+
+ void MWScrollBar::setEnableRepeat(bool enable)
+ {
+ mEnableRepeat = enable;
+ }
+
+ bool MWScrollBar::getEnableRepeat()
+ {
+ return mEnableRepeat;
+ }
+
+ void MWScrollBar::getRepeat(float &trigger, float &step)
+ {
+ trigger = mRepeatTriggerTime;
+ step = mRepeatStepTime;
+ }
+
+ void MWScrollBar::setRepeat(float trigger, float step)
+ {
+ mRepeatTriggerTime = trigger;
+ mRepeatStepTime = step;
+ }
+
+ void MWScrollBar::repeatClick(MyGUI::Widget* _widget, MyGUI::ControllerItem* _controller)
+ {
+ int stepSize = mScrollPage;
+
+ if(mIsIncreasing && mScrollPosition < mScrollRange-1)
+ {
+ if(mScrollPosition + stepSize > mScrollRange-1)
+ mScrollPosition = mScrollRange-1;
+ else
+ mScrollPosition += stepSize;
+
+ eventScrollChangePosition(this, mScrollPosition);
+ updateTrack();
+ }
+ else if(!mIsIncreasing && mScrollPosition > 0)
+ {
+ int newPos = mScrollPosition - stepSize;
+ if(newPos < 0)
+ mScrollPosition = 0;
+ else
+ mScrollPosition -= stepSize;
+
+ eventScrollChangePosition(this, mScrollPosition);
+ updateTrack();
+ }
+ }
+
+ void MWScrollBar::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ mIsIncreasing = false;
+ MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatClick::getClassTypeName());
+ MWGui::Controllers::ControllerRepeatClick* controller = item->castType<MWGui::Controllers::ControllerRepeatClick>();
+ controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick);
+ controller->setEnabled(mEnableRepeat);
+ controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime);
+ MyGUI::ControllerManager::getInstance().addItem(this, controller);
+ }
+
+ void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ MyGUI::ControllerManager::getInstance().removeItem(this);
+ }
+
+ void MWScrollBar::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ mIsIncreasing = true;
+ MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatClick::getClassTypeName());
+ MWGui::Controllers::ControllerRepeatClick* controller = item->castType<MWGui::Controllers::ControllerRepeatClick>();
+ controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick);
+ controller->setEnabled(mEnableRepeat);
+ controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime);
+ MyGUI::ControllerManager::getInstance().addItem(this, controller);
+ }
+
+ void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id)
+ {
+ MyGUI::ControllerManager::getInstance().removeItem(this);
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp
new file mode 100644
index 0000000000..1630ab3c9f
--- /dev/null
+++ b/apps/openmw/mwgui/widgets.hpp
@@ -0,0 +1,444 @@
+#ifndef MWGUI_WIDGETS_H
+#define MWGUI_WIDGETS_H
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwmechanics/stat.hpp"
+#include "controllers.hpp"
+
+#include <MyGUI_Button.h>
+#include <MyGUI_EditBox.h>
+#include <MyGUI_ScrollBar.h>
+
+namespace MyGUI
+{
+ class ImageBox;
+}
+
+namespace MWBase
+{
+ class WindowManager;
+}
+
+/*
+ This file contains various custom widgets used in OpenMW.
+ */
+
+namespace MWGui
+{
+ namespace Widgets
+ {
+ class MWEffectList;
+
+ void fixTexturePath(std::string &path);
+
+ struct SpellEffectParams
+ {
+ SpellEffectParams()
+ : mMagnMin(-1)
+ , mMagnMax(-1)
+ , mRange(-1)
+ , mDuration(-1)
+ , mSkill(-1)
+ , mArea(0)
+ , mAttribute(-1)
+ , mEffectID(-1)
+ , mNoTarget(false)
+ , mIsConstant(false)
+ , mKnown(true)
+ {
+ }
+
+ bool mNoTarget; // potion effects for example have no target (target is always the player)
+ bool mIsConstant; // constant effect means that duration will not be displayed
+
+ bool mKnown; // is this effect known to the player? (If not, will display as a question mark instead)
+
+ // value of -1 here means the effect is unknown to the player
+ short mEffectID;
+
+ // value of -1 here means there is no skill/attribute
+ signed char mSkill, mAttribute;
+
+ // value of -1 here means the value is unavailable
+ int mMagnMin, mMagnMax, mRange, mDuration;
+
+ // value of 0 -> no area effect
+ int mArea;
+
+ bool operator==(const SpellEffectParams& other) const
+ {
+ if (mEffectID != other.mEffectID)
+ return false;
+
+ bool involvesAttribute = (mEffectID == 74 // restore attribute
+ || mEffectID == 85 // absorb attribute
+ || mEffectID == 17 // drain attribute
+ || mEffectID == 79 // fortify attribute
+ || mEffectID == 22); // damage attribute
+ bool involvesSkill = (mEffectID == 78 // restore skill
+ || mEffectID == 89 // absorb skill
+ || mEffectID == 21 // drain skill
+ || mEffectID == 83 // fortify skill
+ || mEffectID == 26); // damage skill
+ return ((other.mSkill == mSkill) || !involvesSkill) && ((other.mAttribute == mAttribute) && !involvesAttribute) && (other.mArea == mArea);
+ }
+ };
+
+ typedef std::vector<SpellEffectParams> SpellEffectList;
+
+ class MWSkill : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWSkill )
+ public:
+ MWSkill();
+
+ typedef MWMechanics::Stat<float> SkillValue;
+
+ void setSkillId(ESM::Skill::SkillEnum skillId);
+ void setSkillNumber(int skillId);
+ void setSkillValue(const SkillValue& value);
+
+ ESM::Skill::SkillEnum getSkillId() const { return mSkillId; }
+ const SkillValue& getSkillValue() const { return mValue; }
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate1<MWSkill*> EventHandle_SkillVoid;
+
+ /** Event : Skill clicked.\n
+ signature : void method(MWSkill* _sender)\n
+ */
+ EventHandle_SkillVoid eventClicked;
+
+ protected:
+ virtual ~MWSkill();
+
+ virtual void initialiseOverride();
+
+ void onClicked(MyGUI::Widget* _sender);
+
+ private:
+
+ void updateWidgets();
+
+ ESM::Skill::SkillEnum mSkillId;
+ SkillValue mValue;
+ MyGUI::Widget* mSkillNameWidget;
+ MyGUI::Widget* mSkillValueWidget;
+ };
+ typedef MWSkill* MWSkillPtr;
+
+ class MWAttribute : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWAttribute )
+ public:
+ MWAttribute();
+
+ typedef MWMechanics::Stat<int> AttributeValue;
+
+ void setAttributeId(int attributeId);
+ void setAttributeValue(const AttributeValue& value);
+
+ int getAttributeId() const { return mId; }
+ const AttributeValue& getAttributeValue() const { return mValue; }
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate1<MWAttribute*> EventHandle_AttributeVoid;
+
+ /** Event : Attribute clicked.\n
+ signature : void method(MWAttribute* _sender)\n
+ */
+ EventHandle_AttributeVoid eventClicked;
+
+ protected:
+ virtual ~MWAttribute();
+
+ virtual void initialiseOverride();
+
+ void onClicked(MyGUI::Widget* _sender);
+
+ private:
+
+ void updateWidgets();
+
+ int mId;
+ AttributeValue mValue;
+ MyGUI::Widget* mAttributeNameWidget;
+ MyGUI::Widget* mAttributeValueWidget;
+ };
+ typedef MWAttribute* MWAttributePtr;
+
+ /**
+ * @todo remove this class and use MWEffectList instead
+ */
+ class MWSpellEffect;
+ class MWSpell : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWSpell )
+ public:
+ MWSpell();
+
+ typedef MWMechanics::Stat<int> SpellValue;
+
+ void setSpellId(const std::string &id);
+
+ /**
+ * @param vector to store the created effect widgets
+ * @param parent widget
+ * @param coordinates to use, will be expanded if more space is needed
+ * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. duration
+ * @param various flags, see MWEffectList::EffectFlags
+ */
+ void createEffectWidgets(std::vector<MyGUI::Widget*> &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, int flags);
+
+ const std::string &getSpellId() const { return mId; }
+
+ protected:
+ virtual ~MWSpell();
+
+ virtual void initialiseOverride();
+
+ private:
+ void updateWidgets();
+
+ std::string mId;
+ MyGUI::TextBox* mSpellNameWidget;
+ };
+ typedef MWSpell* MWSpellPtr;
+
+ class MWEffectList : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWEffectList )
+ public:
+ MWEffectList();
+
+ typedef MWMechanics::Stat<int> EnchantmentValue;
+
+ enum EffectFlags
+ {
+ EF_NoTarget = 0x01, // potions have no target (target is always the player)
+ EF_Constant = 0x02 // constant effect means that duration will not be displayed
+ };
+
+ void setEffectList(const SpellEffectList& list);
+
+ static SpellEffectList effectListFromESM(const ESM::EffectList* effects);
+
+ /**
+ * @param vector to store the created effect widgets
+ * @param parent widget
+ * @param coordinates to use, will be expanded if more space is needed
+ * @param center the effect widgets horizontally
+ * @param various flags, see MWEffectList::EffectFlags
+ */
+ void createEffectWidgets(std::vector<MyGUI::Widget*> &effects, MyGUI::Widget* creator, MyGUI::IntCoord &coord, bool center, int flags);
+
+ protected:
+ virtual ~MWEffectList();
+
+ virtual void initialiseOverride();
+
+ private:
+ void updateWidgets();
+
+ SpellEffectList mEffectList;
+ };
+ typedef MWEffectList* MWEffectListPtr;
+
+ class MWSpellEffect : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWSpellEffect )
+ public:
+ MWSpellEffect();
+
+ typedef ESM::ENAMstruct SpellEffectValue;
+
+ void setSpellEffect(const SpellEffectParams& params);
+
+ int getRequestedWidth() const { return mRequestedWidth; }
+
+ protected:
+ virtual ~MWSpellEffect();
+
+ virtual void initialiseOverride();
+
+ private:
+
+ void updateWidgets();
+
+ SpellEffectParams mEffectParams;
+ MyGUI::ImageBox* mImageWidget;
+ MyGUI::TextBox* mTextWidget;
+ int mRequestedWidth;
+ };
+ typedef MWSpellEffect* MWSpellEffectPtr;
+
+ class MWDynamicStat : public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( MWDynamicStat )
+ public:
+ MWDynamicStat();
+
+ void setValue(int value, int max);
+ void setTitle(const std::string& text);
+
+ int getValue() const { return mValue; }
+ int getMax() const { return mMax; }
+
+ protected:
+ virtual ~MWDynamicStat();
+
+ virtual void initialiseOverride();
+
+ private:
+
+ int mValue, mMax;
+ MyGUI::TextBox* mTextWidget;
+ MyGUI::ProgressPtr mBarWidget;
+ MyGUI::TextBox* mBarTextWidget;
+ };
+ typedef MWDynamicStat* MWDynamicStatPtr;
+
+
+
+
+
+ // ---------------------------------------------------------------------------------------------------------------------
+
+
+
+ class AutoSizedWidget
+ {
+ public:
+ virtual MyGUI::IntSize getRequestedSize() = 0;
+
+ protected:
+ void notifySizeChange(MyGUI::Widget* w);
+
+ MyGUI::Align mExpandDirection;
+ };
+
+ class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox
+ {
+ MYGUI_RTTI_DERIVED( AutoSizedTextBox )
+
+ public:
+ virtual MyGUI::IntSize getRequestedSize();
+ virtual void setCaption(const MyGUI::UString& _value);
+
+ protected:
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+ };
+
+ class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox
+ {
+ MYGUI_RTTI_DERIVED( AutoSizedEditBox )
+
+ public:
+ virtual MyGUI::IntSize getRequestedSize();
+ virtual void setCaption(const MyGUI::UString& _value);
+
+ protected:
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+ };
+
+ class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button
+ {
+ MYGUI_RTTI_DERIVED( AutoSizedButton )
+
+ public:
+ virtual MyGUI::IntSize getRequestedSize();
+ virtual void setCaption(const MyGUI::UString& _value);
+
+ protected:
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+ };
+
+ /**
+ * @brief A container widget that automatically sizes its children
+ * @note the box being an AutoSizedWidget as well allows to put boxes inside a box
+ */
+ class Box : public AutoSizedWidget
+ {
+ public:
+ Box();
+
+ void notifyChildrenSizeChanged();
+
+ protected:
+ virtual void align() = 0;
+
+ virtual void _setPropertyImpl(const std::string& _key, const std::string& _value);
+
+ int mSpacing; // how much space to put between elements
+
+ int mPadding; // outer padding
+
+ bool mAutoResize; // auto resize the box so that it exactly fits all elements
+ };
+
+ class HBox : public Box, public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( HBox )
+
+ public:
+ virtual void setSize (const MyGUI::IntSize &_value);
+ virtual void setCoord (const MyGUI::IntCoord &_value);
+
+ protected:
+ virtual void align();
+ virtual MyGUI::IntSize getRequestedSize();
+
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+
+ virtual void onWidgetCreated(MyGUI::Widget* _widget);
+ };
+
+ class VBox : public Box, public MyGUI::Widget
+ {
+ MYGUI_RTTI_DERIVED( VBox)
+
+ public:
+ virtual void setSize (const MyGUI::IntSize &_value);
+ virtual void setCoord (const MyGUI::IntCoord &_value);
+
+ protected:
+ virtual void align();
+ virtual MyGUI::IntSize getRequestedSize();
+
+ virtual void setPropertyOverride(const std::string& _key, const std::string& _value);
+
+ virtual void onWidgetCreated(MyGUI::Widget* _widget);
+ };
+
+ class MWScrollBar : public MyGUI::ScrollBar
+ {
+ MYGUI_RTTI_DERIVED(MWScrollBar)
+
+ public:
+ MWScrollBar();
+ virtual ~MWScrollBar();
+
+ void setEnableRepeat(bool enable);
+ bool getEnableRepeat();
+ void getRepeat(float &trigger, float &step);
+ void setRepeat(float trigger, float step);
+
+ protected:
+ virtual void initialiseOverride();
+ void repeatClick(MyGUI::Widget* _widget, MyGUI::ControllerItem* _controller);
+
+ bool mEnableRepeat;
+ float mRepeatTriggerTime;
+ float mRepeatStepTime;
+ bool mIsIncreasing;
+
+ private:
+ void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ void onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
+ };
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp
new file mode 100644
index 0000000000..cc74579abf
--- /dev/null
+++ b/apps/openmw/mwgui/windowbase.cpp
@@ -0,0 +1,52 @@
+#include "windowbase.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+
+using namespace MWGui;
+
+WindowBase::WindowBase(const std::string& parLayout)
+ : Layout(parLayout)
+{
+}
+
+void WindowBase::setVisible(bool visible)
+{
+ bool wasVisible = mMainWidget->getVisible();
+ mMainWidget->setVisible(visible);
+
+ if (visible)
+ open();
+ else if (wasVisible && !visible)
+ close();
+}
+
+void WindowBase::center()
+{
+ // Centre dialog
+
+ // MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize();
+ // Note by scrawl: The following works more reliably in the case when the window was _just_
+ // resized and MyGUI RenderManager doesn't know about the new size yet
+ MyGUI::IntSize gameWindowSize = MyGUI::IntSize(Settings::Manager::getInt("resolution x", "Video"),
+ Settings::Manager::getInt("resolution y", "Video"));
+
+ MyGUI::IntCoord coord = mMainWidget->getCoord();
+ coord.left = (gameWindowSize.width - coord.width)/2;
+ coord.top = (gameWindowSize.height - coord.height)/2;
+ mMainWidget->setCoord(coord);
+}
+
+WindowModal::WindowModal(const std::string& parLayout)
+ : WindowBase(parLayout)
+{
+}
+
+void WindowModal::open()
+{
+ MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget);
+}
+
+void WindowModal::close()
+{
+ MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget);
+}
diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp
new file mode 100644
index 0000000000..2c014baf0b
--- /dev/null
+++ b/apps/openmw/mwgui/windowbase.hpp
@@ -0,0 +1,47 @@
+#ifndef MWGUI_WINDOW_BASE_H
+#define MWGUI_WINDOW_BASE_H
+
+#include <openengine/gui/layout.hpp>
+
+namespace MWBase
+{
+ class WindowManager;
+}
+
+namespace MWGui
+{
+ class WindowManager;
+
+ class WindowBase: public OEngine::GUI::Layout
+ {
+ public:
+ WindowBase(const std::string& parLayout);
+
+ // Events
+ typedef MyGUI::delegates::CMultiDelegate1<WindowBase*> EventHandle_WindowBase;
+
+ virtual void open() {}
+ virtual void close () {}
+ virtual void setVisible(bool visible);
+ void center();
+
+ /** Event : Dialog finished, OK button clicked.\n
+ signature : void method()\n
+ */
+ EventHandle_WindowBase eventDone;
+ };
+
+
+ /*
+ * "Modal" windows cause the rest of the interface to be unaccessible while they are visible
+ */
+ class WindowModal : public WindowBase
+ {
+ public:
+ WindowModal(const std::string& parLayout);
+ virtual void open();
+ virtual void close();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp
new file mode 100644
index 0000000000..bf8b664daa
--- /dev/null
+++ b/apps/openmw/mwgui/windowmanagerimp.cpp
@@ -0,0 +1,1362 @@
+#include "windowmanagerimp.hpp"
+
+#include <cassert>
+#include <iterator>
+
+#include "MyGUI_UString.h"
+#include "MyGUI_IPointer.h"
+#include "MyGUI_ResourceImageSetPointer.h"
+#include "MyGUI_TextureUtility.h"
+
+#include <openengine/ogre/renderer.hpp>
+#include <openengine/gui/manager.hpp>
+
+#include <extern/sdl4ogre/sdlcursormanager.hpp>
+
+#include "../mwbase/inputmanager.hpp"
+
+#include "../mwworld/class.hpp"
+
+#include "console.hpp"
+#include "journalwindow.hpp"
+#include "journalviewmodel.hpp"
+#include "charactercreation.hpp"
+#include "dialogue.hpp"
+#include "statswindow.hpp"
+#include "messagebox.hpp"
+#include "tooltips.hpp"
+#include "scrollwindow.hpp"
+#include "bookwindow.hpp"
+#include "hud.hpp"
+#include "mainmenu.hpp"
+#include "countdialog.hpp"
+#include "tradewindow.hpp"
+#include "spellbuyingwindow.hpp"
+#include "travelwindow.hpp"
+#include "settingswindow.hpp"
+#include "confirmationdialog.hpp"
+#include "alchemywindow.hpp"
+#include "spellwindow.hpp"
+#include "quickkeysmenu.hpp"
+#include "loadingscreen.hpp"
+#include "levelupdialog.hpp"
+#include "waitdialog.hpp"
+#include "enchantingdialog.hpp"
+#include "trainingwindow.hpp"
+#include "exposedwindow.hpp"
+#include "cursor.hpp"
+#include "merchantrepair.hpp"
+#include "repair.hpp"
+#include "soulgemdialog.hpp"
+#include "companionwindow.hpp"
+#include "inventorywindow.hpp"
+#include "bookpage.hpp"
+#include "itemview.hpp"
+#include "fontloader.hpp"
+
+namespace MWGui
+{
+
+ WindowManager::WindowManager(
+ const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre,
+ const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts,
+ Translation::Storage& translationDataStorage, ToUTF8::FromType encoding)
+ : mGuiManager(NULL)
+ , mConsoleOnlyScripts(consoleOnlyScripts)
+ , mRendering(ogre)
+ , mHud(NULL)
+ , mMap(NULL)
+ , mMenu(NULL)
+ , mStatsWindow(NULL)
+ , mToolTips(NULL)
+ , mMessageBoxManager(NULL)
+ , mConsole(NULL)
+ , mJournal(NULL)
+ , mDialogueWindow(NULL)
+ , mBookWindow(NULL)
+ , mScrollWindow(NULL)
+ , mCountDialog(NULL)
+ , mTradeWindow(NULL)
+ , mSpellBuyingWindow(NULL)
+ , mTravelWindow(NULL)
+ , mSettingsWindow(NULL)
+ , mConfirmationDialog(NULL)
+ , mAlchemyWindow(NULL)
+ , mSpellWindow(NULL)
+ , mLoadingScreen(NULL)
+ , mCharGen(NULL)
+ , mLevelupDialog(NULL)
+ , mWaitDialog(NULL)
+ , mSpellCreationDialog(NULL)
+ , mEnchantingDialog(NULL)
+ , mTrainingWindow(NULL)
+ , mMerchantRepair(NULL)
+ , mRepair(NULL)
+ , mSoulgemDialog(NULL)
+ , mCompanionWindow(NULL)
+ , mPlayerName()
+ , mPlayerRaceId()
+ , mPlayerAttributes()
+ , mPlayerMajorSkills()
+ , mPlayerMinorSkills()
+ , mPlayerSkillValues()
+ , mPlayerHealth()
+ , mPlayerMagicka()
+ , mPlayerFatigue()
+ , mGui(NULL)
+ , mGarbageDialogs()
+ , mShown(GW_ALL)
+ , mForceHidden(GW_None)
+ , mAllowed(GW_ALL)
+ , mRestAllowed(true)
+ , mShowFPSLevel(fpsLevel)
+ , mFPS(0.0f)
+ , mTriangleCount(0)
+ , mBatchCount(0)
+ , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD"))
+ , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI"))
+ , mHudEnabled(true)
+ , mTranslationDataStorage (translationDataStorage)
+ , mCursorManager(NULL)
+ , mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI"))
+ , mCursorVisible(true)
+ {
+ // Set up the GUI system
+ mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath);
+ mGui = mGuiManager->getGui();
+
+ // Load fonts
+ FontLoader fontLoader (encoding);
+ fontLoader.loadAllFonts();
+
+ //Register own widgets with MyGUI
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWAttribute>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSpell>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWEffectList>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSpellEffect>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWDynamicStat>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWList>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::HBox>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::VBox>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::AutoSizedTextBox>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::AutoSizedEditBox>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::AutoSizedButton>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::ImageButton>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::ExposedWindow>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollView>("Widget");
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWScrollBar>("Widget");
+ BookPage::registerMyGUIComponents ();
+ ItemView::registerComponents();
+
+ MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerRepeatClick>("Controller");
+
+ MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer");
+ MyGUI::ResourceManager::getInstance().load("core.xml");
+
+ MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag);
+
+ // Get size info from the Gui object
+ int w = MyGUI::RenderManager::getInstance().getViewSize().width;
+ int h = MyGUI::RenderManager::getInstance().getViewSize().height;
+
+ mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ());
+ 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);
+ }
+
+ void WindowManager::initUI()
+ {
+ // Get size info from the Gui object
+ int w = MyGUI::RenderManager::getInstance().getViewSize().width;
+ int h = MyGUI::RenderManager::getInstance().getViewSize().height;
+
+ MyGUI::Widget* dragAndDropWidget = mGui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget");
+ dragAndDropWidget->setVisible(false);
+
+ mDragAndDrop = new DragAndDrop();
+ mDragAndDrop->mIsOnDragAndDrop = false;
+ mDragAndDrop->mDraggedWidget = 0;
+ mDragAndDrop->mDragAndDropWidget = dragAndDropWidget;
+
+ mMenu = new MainMenu(w,h);
+ mMap = new MapWindow("");
+ mStatsWindow = new StatsWindow();
+ mConsole = new Console(w,h, mConsoleOnlyScripts);
+ mJournal = JournalWindow::create(JournalViewModel::create ());
+ mMessageBoxManager = new MessageBoxManager();
+ mInventoryWindow = new InventoryWindow(mDragAndDrop);
+ mTradeWindow = new TradeWindow();
+ mSpellBuyingWindow = new SpellBuyingWindow();
+ mTravelWindow = new TravelWindow();
+ mDialogueWindow = new DialogueWindow();
+ mContainerWindow = new ContainerWindow(mDragAndDrop);
+ mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop);
+ mToolTips = new ToolTips();
+ mScrollWindow = new ScrollWindow();
+ mBookWindow = new BookWindow();
+ mCountDialog = new CountDialog();
+ mSettingsWindow = new SettingsWindow();
+ mConfirmationDialog = new ConfirmationDialog();
+ mAlchemyWindow = new AlchemyWindow();
+ mSpellWindow = new SpellWindow();
+ mQuickKeysMenu = new QuickKeysMenu();
+ mLevelupDialog = new LevelupDialog();
+ mWaitDialog = new WaitDialog();
+ mSpellCreationDialog = new SpellCreationDialog();
+ mEnchantingDialog = new EnchantingDialog();
+ mTrainingWindow = new TrainingWindow();
+ mMerchantRepair = new MerchantRepair();
+ mRepair = new Repair();
+ mSoulgemDialog = new SoulgemDialog(mMessageBoxManager);
+ mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager);
+
+ mInputBlocker = mGui->createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Default,"Windows","");
+
+ mHud->setVisible(mHudEnabled);
+
+ mCharGen = new CharacterCreation();
+
+ // Setup player stats
+ for (int i = 0; i < ESM::Attribute::Length; ++i)
+ {
+ mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::Stat<int>()));
+ }
+
+ for (int i = 0; i < ESM::Skill::Length; ++i)
+ {
+ mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat<float>()));
+ }
+
+ unsetSelectedSpell();
+ unsetSelectedWeapon();
+
+ // Set up visibility
+ updateVisible();
+
+ MWBase::Environment::get().getInputManager()->changeInputMode(false);
+ }
+
+ void WindowManager::renderWorldMap()
+ {
+ mMap->renderGlobalMap(mLoadingScreen);
+ }
+
+ void WindowManager::setNewGame(bool newgame)
+ {
+ if (newgame)
+ {
+ disallowAll();
+ delete mCharGen;
+ mCharGen = new CharacterCreation();
+ mGuiModes.clear();
+ mHud->unsetSelectedWeapon();
+ mHud->unsetSelectedSpell();
+ unsetForceHide(GW_ALL);
+ }
+ else
+ allow(GW_ALL);
+
+ mRestAllowed = !newgame;
+ }
+
+ WindowManager::~WindowManager()
+ {
+ delete mConsole;
+ delete mMessageBoxManager;
+ delete mHud;
+ delete mMap;
+ delete mMenu;
+ delete mStatsWindow;
+ delete mJournal;
+ delete mDialogueWindow;
+ delete mContainerWindow;
+ delete mInventoryWindow;
+ delete mToolTips;
+ delete mCharGen;
+ delete mDragAndDrop;
+ delete mBookWindow;
+ delete mScrollWindow;
+ delete mTradeWindow;
+ delete mSpellBuyingWindow;
+ delete mTravelWindow;
+ delete mSettingsWindow;
+ delete mConfirmationDialog;
+ delete mAlchemyWindow;
+ delete mSpellWindow;
+ delete mLoadingScreen;
+ delete mLevelupDialog;
+ delete mWaitDialog;
+ delete mSpellCreationDialog;
+ delete mEnchantingDialog;
+ delete mTrainingWindow;
+ delete mCountDialog;
+ delete mQuickKeysMenu;
+ delete mMerchantRepair;
+ delete mRepair;
+ delete mSoulgemDialog;
+ delete mSoftwareCursor;
+ delete mCursorManager;
+
+ cleanupGarbage();
+
+ delete mGuiManager;
+ }
+
+ void WindowManager::cleanupGarbage()
+ {
+ // Delete any dialogs which are no longer in use
+ if (!mGarbageDialogs.empty())
+ {
+ for (std::vector<OEngine::GUI::Layout*>::iterator it = mGarbageDialogs.begin(); it != mGarbageDialogs.end(); ++it)
+ {
+ delete *it;
+ }
+ mGarbageDialogs.clear();
+ }
+ }
+
+ void WindowManager::update()
+ {
+ cleanupGarbage();
+
+ mHud->setFPS(mFPS);
+ mHud->setTriangleCount(mTriangleCount);
+ mHud->setBatchCount(mBatchCount);
+
+ mHud->update();
+
+ mSoftwareCursor->update();
+ }
+
+ void WindowManager::updateVisible()
+ {
+ if (!mMap)
+ return; // UI not created yet
+ // Start out by hiding everything except the HUD
+ mMap->setVisible(false);
+ mMenu->setVisible(false);
+ mStatsWindow->setVisible(false);
+ mConsole->setVisible(false);
+ mJournal->setVisible(false);
+ mDialogueWindow->setVisible(false);
+ mContainerWindow->setVisible(false);
+ mInventoryWindow->setVisible(false);
+ mScrollWindow->setVisible(false);
+ mBookWindow->setVisible(false);
+ mTradeWindow->setVisible(false);
+ mSpellBuyingWindow->setVisible(false);
+ mTravelWindow->setVisible(false);
+ mSettingsWindow->setVisible(false);
+ mAlchemyWindow->setVisible(false);
+ mSpellWindow->setVisible(false);
+ mQuickKeysMenu->setVisible(false);
+ mLevelupDialog->setVisible(false);
+ mWaitDialog->setVisible(false);
+ mSpellCreationDialog->setVisible(false);
+ mEnchantingDialog->setVisible(false);
+ mTrainingWindow->setVisible(false);
+ mMerchantRepair->setVisible(false);
+ mRepair->setVisible(false);
+ mCompanionWindow->setVisible(false);
+ mInventoryWindow->setTrading(false);
+
+ mHud->setVisible(mHudEnabled);
+
+ bool gameMode = !isGuiMode();
+
+ mInputBlocker->setVisible (gameMode);
+ setCursorVisible(!gameMode);
+
+ if (gameMode)
+ setKeyFocusWidget (NULL);
+
+ // Icons of forced hidden windows are displayed
+ setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map)));
+ setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory)));
+ setSpellVisibility((mAllowed & GW_Magic) && (!mSpellWindow->pinned() || (mForceHidden & GW_Magic)));
+ setHMSVisibility((mAllowed & GW_Stats) && (!mStatsWindow->pinned() || (mForceHidden & GW_Stats)));
+
+ // If in game mode, show only the pinned windows
+ if (gameMode)
+ {
+ mInventoryWindow->setGuiMode(GM_None);
+ mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map));
+ mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats));
+ mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory));
+ mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic));
+
+ return;
+ }
+
+ GuiMode mode = mGuiModes.back();
+
+ switch(mode) {
+ case GM_QuickKeysMenu:
+ mQuickKeysMenu->setVisible (true);
+ break;
+ case GM_MainMenu:
+ mMenu->setVisible(true);
+ break;
+ case GM_Settings:
+ mSettingsWindow->setVisible(true);
+ break;
+ case GM_Console:
+ mConsole->setVisible(true);
+ break;
+ case GM_Scroll:
+ mScrollWindow->setVisible(true);
+ break;
+ case GM_Book:
+ mBookWindow->setVisible(true);
+ break;
+ case GM_Alchemy:
+ mAlchemyWindow->setVisible(true);
+ break;
+ case GM_Rest:
+ mWaitDialog->setVisible(true);
+ break;
+ case GM_RestBed:
+ mWaitDialog->setVisible(true);
+ mWaitDialog->bedActivated();
+ break;
+ case GM_Levelup:
+ mLevelupDialog->setVisible(true);
+ break;
+ case GM_Name:
+ case GM_Race:
+ case GM_Class:
+ case GM_ClassPick:
+ case GM_ClassCreate:
+ case GM_Birth:
+ case GM_ClassGenerate:
+ case GM_Review:
+ mCharGen->spawnDialog(mode);
+ break;
+ case GM_Inventory:
+ {
+ // First, compute the effective set of windows to show.
+ // This is controlled both by what windows the
+ // user has opened/closed (the 'shown' variable) and by what
+ // windows we are allowed to show (the 'allowed' var.)
+ int eff = mShown & mAllowed & ~mForceHidden;
+
+ // Show the windows we want
+ mMap ->setVisible(eff & GW_Map);
+ mStatsWindow ->setVisible(eff & GW_Stats);
+ mInventoryWindow->setVisible(eff & GW_Inventory);
+ mInventoryWindow->setGuiMode(mode);
+ mSpellWindow ->setVisible(eff & GW_Magic);
+ break;
+ }
+ case GM_Container:
+ mContainerWindow->setVisible(true);
+ mInventoryWindow->setVisible(true);
+ mInventoryWindow->setGuiMode(mode);
+ break;
+ case GM_Companion:
+ mCompanionWindow->setVisible(true);
+ mInventoryWindow->setVisible(true);
+ mInventoryWindow->setGuiMode(mode);
+ break;
+ case GM_Dialogue:
+ mDialogueWindow->setVisible(true);
+ break;
+ case GM_Barter:
+ mInventoryWindow->setVisible(true);
+ mInventoryWindow->setTrading(true);
+ mInventoryWindow->setGuiMode(mode);
+ mTradeWindow->setVisible(true);
+ break;
+ case GM_SpellBuying:
+ mSpellBuyingWindow->setVisible(true);
+ break;
+ case GM_Travel:
+ mTravelWindow->setVisible(true);
+ break;
+ case GM_SpellCreation:
+ mSpellCreationDialog->setVisible(true);
+ break;
+ case GM_Enchanting:
+ mEnchantingDialog->setVisible(true);
+ break;
+ case GM_Training:
+ mTrainingWindow->setVisible(true);
+ break;
+ case GM_MerchantRepair:
+ mMerchantRepair->setVisible(true);
+ break;
+ case GM_Repair:
+ mRepair->setVisible(true);
+ break;
+ case GM_Journal:
+ mJournal->setVisible(true);
+ break;
+ case GM_LoadingWallpaper:
+ mHud->setVisible(false);
+ setCursorVisible(false);
+ break;
+ case GM_Loading:
+ // Show the pinned windows
+ mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map));
+ mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats));
+ mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory));
+ mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic));
+
+ setCursorVisible(false);
+ break;
+ case GM_Video:
+ setCursorVisible(false);
+ mHud->setVisible(false);
+ break;
+ default:
+ // Unsupported mode, switch back to game
+ break;
+ }
+ }
+
+ void WindowManager::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ {
+ mStatsWindow->setValue (id, value);
+ mCharGen->setValue(id, value);
+
+ static const char *ids[] =
+ {
+ "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5",
+ "AttribVal6", "AttribVal7", "AttribVal8"
+ };
+ static ESM::Attribute::AttributeID attributes[] =
+ {
+ ESM::Attribute::Strength,
+ ESM::Attribute::Intelligence,
+ ESM::Attribute::Willpower,
+ ESM::Attribute::Agility,
+ ESM::Attribute::Speed,
+ ESM::Attribute::Endurance,
+ ESM::Attribute::Personality,
+ ESM::Attribute::Luck
+ };
+ for (size_t i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
+ {
+ if (id != ids[i])
+ continue;
+ mPlayerAttributes[attributes[i]] = value;
+ break;
+ }
+ }
+
+
+ void WindowManager::setValue (int parSkill, const MWMechanics::Stat<float>& value)
+ {
+ /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we
+ /// allow custom skills.
+ mStatsWindow->setValue(static_cast<ESM::Skill::SkillEnum> (parSkill), value);
+ mCharGen->setValue(static_cast<ESM::Skill::SkillEnum> (parSkill), value);
+ mPlayerSkillValues[parSkill] = value;
+ }
+
+ void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
+ {
+ 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)
+ {
+ mStatsWindow->setValue (id, value);
+ if (id=="name")
+ mPlayerName = value;
+ else if (id=="race")
+ mPlayerRaceId = value;
+ }
+
+ void WindowManager::setValue (const std::string& id, int value)
+ {
+ mStatsWindow->setValue (id, value);
+ }
+
+ void WindowManager::setDrowningTimeLeft (float time)
+ {
+ mHud->setDrowningTimeLeft(time);
+ }
+
+ void WindowManager::setPlayerClass (const ESM::Class &class_)
+ {
+ mStatsWindow->setValue("class", class_.mName);
+ }
+
+ void WindowManager::configureSkills (const SkillList& major, const SkillList& minor)
+ {
+ mStatsWindow->configureSkills (major, minor);
+ mCharGen->configureSkills(major, minor);
+ mPlayerMajorSkills = major;
+ mPlayerMinorSkills = minor;
+ }
+
+ void WindowManager::setReputation (int reputation)
+ {
+ mStatsWindow->setReputation (reputation);
+ }
+
+ void WindowManager::setBounty (int bounty)
+ {
+ mStatsWindow->setBounty (bounty);
+ }
+
+ void WindowManager::updateSkillArea()
+ {
+ mStatsWindow->updateSkillArea();
+ }
+
+ void WindowManager::removeDialog(OEngine::GUI::Layout*dialog)
+ {
+ if (!dialog)
+ return;
+ dialog->setVisible(false);
+ mGarbageDialogs.push_back(dialog);
+ }
+
+ void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, bool showInDialogueModeOnly)
+ {
+ if (buttons.empty()) {
+ /* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */
+ if (getMode() == GM_Dialogue) {
+ mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message));
+ } else {
+ if (showInDialogueModeOnly) {
+ if (getMode() == GM_Dialogue)
+ mMessageBoxManager->createMessageBox(message);
+ } else {
+ mMessageBoxManager->createMessageBox(message);
+ }
+ }
+ } else {
+ mMessageBoxManager->createInteractiveMessageBox(message, buttons);
+ MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode());
+ }
+ }
+
+ void WindowManager::staticMessageBox(const std::string& message)
+ {
+ mMessageBoxManager->createMessageBox(message, true);
+ }
+
+ void WindowManager::removeStaticMessageBox()
+ {
+ mMessageBoxManager->removeStaticMessageBox();
+ }
+
+ void WindowManager::enterPressed ()
+ {
+ mMessageBoxManager->okayPressed();
+ }
+
+ void WindowManager::activateKeyPressed ()
+ {
+ mMessageBoxManager->okayPressed();
+ mCountDialog->cancel();
+ }
+
+ int WindowManager::readPressedButton ()
+ {
+ return mMessageBoxManager->readPressedButton();
+ }
+
+ std::string WindowManager::getGameSettingString(const std::string &id, const std::string &default_)
+ {
+ const ESM::GameSetting *setting =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().search(id);
+
+ if (setting && setting->mValue.getType()==ESM::VT_String)
+ return setting->mValue.getString();
+
+ return default_;
+ }
+
+ void WindowManager::onFrame (float frameDuration)
+ {
+ mMessageBoxManager->onFrame(frameDuration);
+
+ mToolTips->onFrame(frameDuration);
+
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ {
+ assert(mDragAndDrop->mDraggedWidget);
+ mDragAndDrop->mDraggedWidget->setPosition(MyGUI::InputManager::getInstance().getMousePosition());
+ }
+
+ mDialogueWindow->onFrame();
+
+ mInventoryWindow->onFrame();
+
+ mStatsWindow->onFrame();
+
+ mWaitDialog->onFrame(frameDuration);
+
+ mHud->onFrame(frameDuration);
+
+ mTrainingWindow->onFrame (frameDuration);
+ mTradeWindow->onFrame(frameDuration);
+
+ mTrainingWindow->checkReferenceAvailable();
+ mDialogueWindow->checkReferenceAvailable();
+ mTradeWindow->checkReferenceAvailable();
+ mSpellBuyingWindow->checkReferenceAvailable();
+ mSpellCreationDialog->checkReferenceAvailable();
+ mEnchantingDialog->checkReferenceAvailable();
+ mContainerWindow->checkReferenceAvailable();
+ mCompanionWindow->checkReferenceAvailable();
+ mConsole->checkReferenceAvailable();
+ mCompanionWindow->onFrame();
+ }
+
+ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell)
+ {
+ if (cell->mCell->isExterior())
+ {
+ std::string name;
+ if (cell->mCell->mName != "")
+ {
+ name = cell->mCell->mName;
+ mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->mCell->getGridX (), cell->mCell->getGridY ());
+ }
+ else
+ {
+ const ESM::Region* region =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().search(cell->mCell->mRegion);
+ if (region)
+ name = region->mName;
+ else
+ name = getGameSettingString("sDefaultCellname", "Wilderness");
+ }
+
+ mMap->cellExplored(cell->mCell->getGridX(), cell->mCell->getGridY());
+
+ mMap->setCellName( name );
+ mHud->setCellName( name );
+
+ mMap->setCellPrefix("Cell");
+ mHud->setCellPrefix("Cell");
+ mMap->setActiveCell( cell->mCell->getGridX(), cell->mCell->getGridY() );
+ mHud->setActiveCell( cell->mCell->getGridX(), cell->mCell->getGridY() );
+ }
+ else
+ {
+ mMap->setCellName( cell->mCell->mName );
+ mHud->setCellName( cell->mCell->mName );
+ mMap->setCellPrefix( cell->mCell->mName );
+ mHud->setCellPrefix( cell->mCell->mName );
+ }
+
+ }
+
+ void WindowManager::setInteriorMapTexture(const int x, const int y)
+ {
+ mMap->setActiveCell(x,y, true);
+ mHud->setActiveCell(x,y, true);
+ }
+
+ void WindowManager::setPlayerPos(const float x, const float y)
+ {
+ mMap->setPlayerPos(x,y);
+ mHud->setPlayerPos(x,y);
+ }
+
+ void WindowManager::setPlayerDir(const float x, const float y)
+ {
+ mMap->setPlayerDir(x,y);
+ mHud->setPlayerDir(x,y);
+ }
+
+ void WindowManager::setDrowningBarVisibility(bool visible)
+ {
+ mHud->setDrowningBarVisible(visible);
+ }
+
+ void WindowManager::setHMSVisibility(bool visible)
+ {
+ mHud->setHmsVisible (visible);
+ }
+
+ void WindowManager::setMinimapVisibility(bool visible)
+ {
+ mHud->setMinimapVisible (visible);
+ }
+
+ void WindowManager::toggleFogOfWar()
+ {
+ mMap->toggleFogOfWar();
+ mHud->toggleFogOfWar();
+ }
+
+ void WindowManager::setFocusObject(const MWWorld::Ptr& focus)
+ {
+ mToolTips->setFocusObject(focus);
+ }
+
+ void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y)
+ {
+ mToolTips->setFocusObjectScreenCoords(min_x, min_y, max_x, max_y);
+ }
+
+ void WindowManager::toggleFullHelp()
+ {
+ mToolTips->toggleFullHelp();
+ }
+
+ bool WindowManager::getFullHelp() const
+ {
+ return mToolTips->getFullHelp();
+ }
+
+ void WindowManager::setWeaponVisibility(bool visible)
+ {
+ mHud->setWeapVisible (visible);
+ }
+
+ void WindowManager::setSpellVisibility(bool visible)
+ {
+ mHud->setSpellVisible (visible);
+ mHud->setEffectVisible (visible);
+ }
+
+ void WindowManager::setSneakVisibility(bool visible)
+ {
+ mHud->setSneakVisible(visible);
+ }
+
+ void WindowManager::setDragDrop(bool dragDrop)
+ {
+ mToolTips->setEnabled(!dragDrop);
+ 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)
+ {
+ std::string tag(_tag);
+
+ std::string tokenToFind = "sCell=";
+ size_t tokenLength = tokenToFind.length();
+
+ if (tag.substr(0, tokenLength) == tokenToFind)
+ {
+ _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength));
+ }
+ else
+ {
+ const ESM::GameSetting *setting =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(tag);
+
+ if (setting && setting->mValue.getType()==ESM::VT_String)
+ _result = setting->mValue.getString();
+ else
+ _result = tag;
+ }
+ }
+
+ void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed)
+ {
+ 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)
+ {
+ if (it->first == "HUD" && it->second == "crosshair")
+ mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD");
+ else if (it->first == "GUI" && it->second == "subtitles")
+ mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI");
+ }
+ }
+
+ void WindowManager::windowResized(int x, int y)
+ {
+ mGuiManager->windowResized();
+ mLoadingScreen->onResChange (x,y);
+ if (!mHud)
+ return; // UI not initialized yet
+ mHud->onResChange(x, y);
+ mConsole->onResChange(x, y);
+ mMenu->onResChange(x, y);
+ mSettingsWindow->center();
+ mAlchemyWindow->center();
+ mScrollWindow->center();
+ mBookWindow->center();
+ mQuickKeysMenu->center();
+ mSpellBuyingWindow->center();
+ mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y));
+ mInputBlocker->setSize(MyGUI::IntSize(x,y));
+ }
+
+ void WindowManager::pushGuiMode(GuiMode mode)
+ {
+ if (mode==GM_Inventory && mAllowed==GW_None)
+ return;
+
+
+ // If this mode already exists somewhere in the stack, just bring it to the front.
+ if (std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end())
+ {
+ mGuiModes.erase(std::find(mGuiModes.begin(), mGuiModes.end(), mode));
+ }
+
+ mGuiModes.push_back(mode);
+
+ bool gameMode = !isGuiMode();
+ MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode);
+
+ updateVisible();
+ }
+
+ 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
+ ResourceImageSetPointerFix* imgSetPtr = dynamic_cast<ResourceImageSetPointerFix*>(MyGUI::PointerManager::getInstance().getByName(name));
+ if(imgSetPtr != NULL)
+ {
+ MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet();
+
+ std::string tex_name = imgSet->getIndexInfo(0,0).texture;
+
+ Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName(tex_name);
+
+ //everything looks good, send it to the cursor manager
+ if(!tex.isNull())
+ {
+ Uint8 size_x = imgSetPtr->getSize().width;
+ Uint8 size_y = imgSetPtr->getSize().height;
+ Uint8 hotspot_x = imgSetPtr->getHotSpot().left;
+ Uint8 hotspot_y = imgSetPtr->getHotSpot().top;
+ int rotation = imgSetPtr->getRotation();
+
+ mCursorManager->receiveCursorInfo(name, rotation, tex, size_x, size_y, hotspot_x, hotspot_y);
+ }
+ }
+ }
+
+ void WindowManager::popGuiMode()
+ {
+ if (!mGuiModes.empty())
+ mGuiModes.pop_back();
+
+ bool gameMode = !isGuiMode();
+ MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode);
+
+ updateVisible();
+ }
+
+ void WindowManager::removeGuiMode(GuiMode mode)
+ {
+ std::vector<GuiMode>::iterator it = mGuiModes.begin();
+ while (it != mGuiModes.end())
+ {
+ if (*it == mode)
+ it = mGuiModes.erase(it);
+ else
+ ++it;
+ }
+
+ bool gameMode = !isGuiMode();
+ MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode);
+
+ updateVisible();
+ }
+
+ void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent)
+ {
+ mHud->setSelectedSpell(spellId, successChancePercent);
+
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+
+ mSpellWindow->setTitle(spell->mName);
+ }
+
+ void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item)
+ {
+ const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>()
+ .find(MWWorld::Class::get(item).getEnchantment(item));
+
+ int chargePercent = (item.getCellRef().mEnchantmentCharge == -1) ? 100
+ : (item.getCellRef().mEnchantmentCharge / static_cast<float>(ench->mData.mCharge) * 100);
+ mHud->setSelectedEnchantItem(item, chargePercent);
+ mSpellWindow->setTitle(MWWorld::Class::get(item).getName(item));
+ }
+
+ void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item)
+ {
+ int durabilityPercent = (item.getCellRef().mCharge == -1) ? 100
+ : (item.getCellRef().mCharge / static_cast<float>(MWWorld::Class::get(item).getItemMaxHealth(item)) * 100);
+ mHud->setSelectedWeapon(item, durabilityPercent);
+ mInventoryWindow->setTitle(MWWorld::Class::get(item).getName(item));
+ }
+
+ void WindowManager::unsetSelectedSpell()
+ {
+ mHud->unsetSelectedSpell();
+ mSpellWindow->setTitle("#{sNone}");
+ }
+
+ void WindowManager::unsetSelectedWeapon()
+ {
+ mHud->unsetSelectedWeapon();
+ mInventoryWindow->setTitle("#{sSkillHandtohand}");
+ }
+
+ void WindowManager::getMousePosition(int &x, int &y)
+ {
+ const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition();
+ x = pos.left;
+ y = pos.top;
+ }
+
+ void WindowManager::getMousePosition(float &x, float &y)
+ {
+ const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition();
+ x = pos.left;
+ y = pos.top;
+ const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ x /= viewSize.width;
+ y /= viewSize.height;
+ }
+
+ bool WindowManager::getWorldMouseOver()
+ {
+ return mHud->getWorldMouseOver();
+ }
+
+ void WindowManager::executeInConsole (const std::string& path)
+ {
+ mConsole->executeFile (path);
+ }
+
+ void WindowManager::wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount)
+ {
+ mFPS = fps;
+ mTriangleCount = triangleCount;
+ mBatchCount = batchCount;
+ }
+
+ MyGUI::Gui* WindowManager::getGui() const { return mGui; }
+
+ MWGui::DialogueWindow* WindowManager::getDialogueWindow() { return mDialogueWindow; }
+ MWGui::ContainerWindow* WindowManager::getContainerWindow() { return mContainerWindow; }
+ MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; }
+ MWGui::BookWindow* WindowManager::getBookWindow() { return mBookWindow; }
+ MWGui::ScrollWindow* WindowManager::getScrollWindow() { return mScrollWindow; }
+ MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; }
+ MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; }
+ MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; }
+ MWGui::SpellBuyingWindow* WindowManager::getSpellBuyingWindow() { return mSpellBuyingWindow; }
+ MWGui::TravelWindow* WindowManager::getTravelWindow() { return mTravelWindow; }
+ MWGui::SpellWindow* WindowManager::getSpellWindow() { return mSpellWindow; }
+ MWGui::Console* WindowManager::getConsole() { return mConsole; }
+
+ bool WindowManager::isAllowed (GuiWindow wnd) const
+ {
+ return mAllowed & wnd;
+ }
+
+ void WindowManager::allow (GuiWindow wnd)
+ {
+ mAllowed = (GuiWindow)(mAllowed | wnd);
+
+ if (wnd & GW_Inventory)
+ {
+ mBookWindow->setInventoryAllowed (true);
+ mScrollWindow->setInventoryAllowed (true);
+ }
+
+ updateVisible();
+ }
+
+ void WindowManager::disallowAll()
+ {
+ mAllowed = GW_None;
+
+ mBookWindow->setInventoryAllowed (false);
+ mScrollWindow->setInventoryAllowed (false);
+
+ updateVisible();
+ }
+
+ void WindowManager::toggleVisible (GuiWindow wnd)
+ {
+ if (getMode() != GM_Inventory)
+ return;
+
+ mShown = (GuiWindow)(mShown ^ wnd);
+ updateVisible();
+ }
+
+ void WindowManager::forceHide(GuiWindow wnd)
+ {
+ mForceHidden = (GuiWindow)(mForceHidden | wnd);
+ updateVisible();
+ }
+
+ void WindowManager::unsetForceHide(GuiWindow wnd)
+ {
+ mForceHidden = (GuiWindow)(mForceHidden & ~wnd);
+ updateVisible();
+ }
+
+ bool WindowManager::isGuiMode() const
+ {
+ return !mGuiModes.empty() || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox());
+ }
+
+ bool WindowManager::isConsoleMode() const
+ {
+ if (!mGuiModes.empty() && mGuiModes.back()==GM_Console)
+ return true;
+ return false;
+ }
+
+ MWGui::GuiMode WindowManager::getMode() const
+ {
+ if (mGuiModes.empty())
+ return GM_None;
+ return mGuiModes.back();
+ }
+
+ std::map<int, MWMechanics::Stat<float> > WindowManager::getPlayerSkillValues()
+ {
+ return mPlayerSkillValues;
+ }
+
+ std::map<int, MWMechanics::Stat<int> > WindowManager::getPlayerAttributeValues()
+ {
+ return mPlayerAttributes;
+ }
+
+ WindowManager::SkillList WindowManager::getPlayerMinorSkills()
+ {
+ return mPlayerMinorSkills;
+ }
+
+ WindowManager::SkillList WindowManager::getPlayerMajorSkills()
+ {
+ return mPlayerMajorSkills;
+ }
+
+ void WindowManager::disallowMouse()
+ {
+ mInputBlocker->setVisible (true);
+ }
+
+ void WindowManager::allowMouse()
+ {
+ mInputBlocker->setVisible (!isGuiMode ());
+ }
+
+ void WindowManager::notifyInputActionBound ()
+ {
+ mSettingsWindow->updateControlsBox ();
+ allowMouse();
+ }
+
+ bool WindowManager::containsMode(GuiMode mode) const
+ {
+ if(mGuiModes.empty())
+ return false;
+
+ return std::find(mGuiModes.begin(), mGuiModes.end(), mode) != mGuiModes.end();
+ }
+
+ void WindowManager::showCrosshair (bool show)
+ {
+ if (mHud)
+ mHud->setCrosshairVisible (show && mCrosshairEnabled);
+ }
+
+ void WindowManager::activateQuickKey (int index)
+ {
+ mQuickKeysMenu->activateQuickKey(index);
+ }
+
+ bool WindowManager::getSubtitlesEnabled ()
+ {
+ return mSubtitlesEnabled;
+ }
+
+ void WindowManager::toggleHud ()
+ {
+ mHudEnabled = !mHudEnabled;
+ mHud->setVisible (mHudEnabled);
+ }
+
+ bool WindowManager::getRestEnabled()
+ {
+ //Enable rest dialogue if character creation finished
+ if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalVariable ("chargenstate").mFloat==-1)
+ mRestAllowed=true;
+ return mRestAllowed;
+ }
+
+ bool WindowManager::getPlayerSleeping ()
+ {
+ return mWaitDialog->getSleeping();
+ }
+
+ void WindowManager::wakeUpPlayer()
+ {
+ mWaitDialog->wakeUp();
+ }
+
+ void WindowManager::addVisitedLocation(const std::string& name, int x, int y)
+ {
+ mMap->addVisitedLocation (name, x, y);
+ }
+
+ void WindowManager::startSpellMaking(MWWorld::Ptr actor)
+ {
+ mSpellCreationDialog->startSpellMaking (actor);
+ }
+
+ void WindowManager::startEnchanting (MWWorld::Ptr actor)
+ {
+ mEnchantingDialog->startEnchanting (actor);
+ }
+
+ void WindowManager::startSelfEnchanting(MWWorld::Ptr soulgem)
+ {
+ mEnchantingDialog->startSelfEnchanting(soulgem);
+ }
+
+ void WindowManager::startTraining(MWWorld::Ptr actor)
+ {
+ mTrainingWindow->startTraining(actor);
+ }
+
+ void WindowManager::startRepair(MWWorld::Ptr actor)
+ {
+ mMerchantRepair->startRepair(actor);
+ }
+
+ void WindowManager::startRepairItem(MWWorld::Ptr item)
+ {
+ mRepair->startRepairItem(item);
+ }
+
+ const Translation::Storage& WindowManager::getTranslationDataStorage() const
+ {
+ return mTranslationDataStorage;
+ }
+
+ void WindowManager::showCompanionWindow(MWWorld::Ptr actor)
+ {
+ mCompanionWindow->open(actor);
+ }
+
+ void WindowManager::changePointer(const std::string &name)
+ {
+ onCursorChange(name);
+ }
+
+ void WindowManager::showSoulgemDialog(MWWorld::Ptr item)
+ {
+ mSoulgemDialog->show(item);
+ }
+
+ void WindowManager::frameStarted (float dt)
+ {
+ mInventoryWindow->doRenderUpdate ();
+ mCharGen->doRenderUpdate();
+ }
+
+ void WindowManager::updatePlayer()
+ {
+ mInventoryWindow->updatePlayer();
+ }
+
+ void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget)
+ {
+ if (widget == NULL)
+ MyGUI::InputManager::getInstance().resetKeyFocusWidget();
+ else
+ MyGUI::InputManager::getInstance().setKeyFocusWidget(widget);
+ onKeyFocusChanged(widget);
+ }
+
+ void WindowManager::onKeyFocusChanged(MyGUI::Widget *widget)
+ {
+ if (widget && widget->castType<MyGUI::EditBox>(false))
+ SDL_StartTextInput();
+ else
+ SDL_StopTextInput();
+ }
+
+ void WindowManager::setEnemy(const MWWorld::Ptr &enemy)
+ {
+ mHud->setEnemy(enemy);
+ }
+
+ Loading::Listener* WindowManager::getLoadingScreen()
+ {
+ return mLoadingScreen;
+ }
+
+}
diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp
new file mode 100644
index 0000000000..badb333a7f
--- /dev/null
+++ b/apps/openmw/mwgui/windowmanagerimp.hpp
@@ -0,0 +1,383 @@
+#ifndef MWGUI_WINDOWMANAGERIMP_H
+#define MWGUI_WINDOWMANAGERIMP_H
+
+/**
+ This class owns and controls all the MW specific windows in the
+ GUI. It can enable/disable Gui mode, and is responsible for sending
+ and retrieving information from the Gui.
+
+ MyGUI should be initialized separately before creating instances of
+ this class.
+**/
+
+#include "../mwbase/windowmanager.hpp"
+
+namespace MyGUI
+{
+ class Gui;
+ class Widget;
+ class UString;
+}
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Translation
+{
+ class Storage;
+}
+
+namespace OEngine
+{
+ namespace GUI
+ {
+ class Layout;
+ class MyGUIManager;
+ }
+
+ namespace Render
+ {
+ class OgreRenderer;
+ }
+}
+
+namespace SFO
+{
+ class CursorManager;
+}
+
+namespace MWGui
+{
+ class WindowBase;
+ class HUD;
+ class MapWindow;
+ class MainMenu;
+ class StatsWindow;
+ class InventoryWindow;
+ class JournalWindow;
+ class CharacterCreation;
+ class DragAndDrop;
+ class ToolTips;
+ class TextInputDialog;
+ class InfoBoxDialog;
+ class MessageBoxManager;
+ class SettingsWindow;
+ class AlchemyWindow;
+ class QuickKeysMenu;
+ class LoadingScreen;
+ class LevelupDialog;
+ class WaitDialog;
+ class SpellCreationDialog;
+ class EnchantingDialog;
+ class TrainingWindow;
+ class Cursor;
+ class SpellIcons;
+ class MerchantRepair;
+ class Repair;
+ class SoulgemDialog;
+ class CompanionWindow;
+
+ class WindowManager : public MWBase::WindowManager
+ {
+ public:
+ typedef std::pair<std::string, int> Faction;
+ typedef std::vector<Faction> FactionList;
+
+ WindowManager(const Compiler::Extensions& extensions, int fpsLevel,
+ OEngine::Render::OgreRenderer *mOgre, const std::string& logpath,
+ const std::string& cacheDir, bool consoleOnlyScripts,
+ Translation::Storage& translationDataStorage, ToUTF8::FromType encoding);
+ virtual ~WindowManager();
+
+ void initUI();
+ void renderWorldMap();
+
+ virtual Loading::Listener* getLoadingScreen();
+
+ /**
+ * Should be called each frame to update windows/gui elements.
+ * This could mean updating sizes of gui elements or opening
+ * new dialogs.
+ */
+ virtual void update();
+
+ virtual void setKeyFocusWidget (MyGUI::Widget* widget);
+
+ virtual void setNewGame(bool newgame);
+
+ virtual void pushGuiMode(GuiMode mode);
+ virtual void popGuiMode();
+ virtual void removeGuiMode(GuiMode mode); ///< can be anywhere in the stack
+
+ virtual GuiMode getMode() const;
+ virtual bool containsMode(GuiMode mode) const;
+
+ virtual bool isGuiMode() const;
+
+ virtual bool isConsoleMode() const;
+
+ virtual void toggleVisible(GuiWindow wnd);
+
+ virtual void forceHide(MWGui::GuiWindow wnd);
+ virtual void unsetForceHide(MWGui::GuiWindow wnd);
+
+ // Disallow all inventory mode windows
+ virtual void disallowAll();
+
+ // Allow one or more windows
+ virtual void allow(GuiWindow wnd);
+
+ virtual bool isAllowed(GuiWindow wnd) const;
+
+ /// \todo investigate, if we really need to expose every single lousy UI element to the outside world
+ virtual MWGui::DialogueWindow* getDialogueWindow();
+ virtual MWGui::ContainerWindow* getContainerWindow();
+ virtual MWGui::InventoryWindow* getInventoryWindow();
+ virtual MWGui::BookWindow* getBookWindow();
+ virtual MWGui::ScrollWindow* getScrollWindow();
+ virtual MWGui::CountDialog* getCountDialog();
+ virtual MWGui::ConfirmationDialog* getConfirmationDialog();
+ virtual MWGui::TradeWindow* getTradeWindow();
+ virtual MWGui::SpellBuyingWindow* getSpellBuyingWindow();
+ virtual MWGui::TravelWindow* getTravelWindow();
+ virtual MWGui::SpellWindow* getSpellWindow();
+ virtual MWGui::Console* getConsole();
+
+ virtual MyGUI::Gui* getGui() const;
+
+ virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount);
+
+ ///< Set value for the given ID.
+ virtual void setValue (const std::string& id, const MWMechanics::Stat<int>& value);
+ virtual void setValue (int parSkill, const MWMechanics::Stat<float>& value);
+ virtual void setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value);
+ virtual void setValue (const std::string& id, const std::string& value);
+ virtual void setValue (const std::string& id, int value);
+
+ /// Set time left for the player to start drowning (update the drowning bar)
+ /// @param time value from [0,20]
+ virtual void setDrowningTimeLeft (float time);
+
+ virtual void setPlayerClass (const ESM::Class &class_); ///< set current class of player
+ virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group.
+ virtual void setReputation (int reputation); ///< set the current reputation value
+ virtual void setBounty (int bounty); ///< set the current bounty value
+ virtual void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty
+
+ virtual void changeCell(MWWorld::CellStore* cell); ///< change the active cell
+ virtual void setPlayerPos(const float x, const float y); ///< set player position in map space
+ virtual void setPlayerDir(const float x, const float y); ///< set player view direction in map space
+
+ virtual void setFocusObject(const MWWorld::Ptr& focus);
+ virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y);
+
+ virtual void getMousePosition(int &x, int &y);
+ virtual void getMousePosition(float &x, float &y);
+ virtual void setDragDrop(bool dragDrop);
+ virtual bool getWorldMouseOver();
+
+ virtual void toggleFogOfWar();
+ virtual void toggleFullHelp(); ///< show extra info in item tooltips (owner, script)
+ virtual bool getFullHelp() const;
+
+ virtual void setInteriorMapTexture(const int x, const int y);
+ ///< set the index of the map texture that should be used (for interiors)
+
+ /// sets the visibility of the drowning bar
+ virtual void setDrowningBarVisibility(bool visible);
+
+ // sets the visibility of the hud health/magicka/stamina bars
+ virtual void setHMSVisibility(bool visible);
+ // sets the visibility of the hud minimap
+ virtual void setMinimapVisibility(bool visible);
+ virtual void setWeaponVisibility(bool visible);
+ virtual void setSpellVisibility(bool visible);
+ virtual void setSneakVisibility(bool visible);
+
+ virtual void activateQuickKey (int index);
+
+ virtual void setSelectedSpell(const std::string& spellId, int successChancePercent);
+ virtual void setSelectedEnchantItem(const MWWorld::Ptr& item);
+ virtual void setSelectedWeapon(const MWWorld::Ptr& item);
+ virtual void unsetSelectedSpell();
+ virtual void unsetSelectedWeapon();
+
+ virtual void showCrosshair(bool show);
+ virtual bool getSubtitlesEnabled();
+ virtual void toggleHud();
+
+ virtual void disallowMouse();
+ virtual void allowMouse();
+ virtual void notifyInputActionBound();
+
+ virtual void addVisitedLocation(const std::string& name, int x, int y);
+
+ virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted.
+
+ virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false);
+ virtual void staticMessageBox(const std::string& message);
+ virtual void removeStaticMessageBox();
+ virtual void enterPressed ();
+ virtual void activateKeyPressed ();
+ virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)
+
+ virtual void onFrame (float frameDuration);
+
+ /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed.
+ virtual std::map<int, MWMechanics::Stat<float> > getPlayerSkillValues();
+ virtual std::map<int, MWMechanics::Stat<int> > getPlayerAttributeValues();
+ virtual SkillList getPlayerMinorSkills();
+ virtual SkillList getPlayerMajorSkills();
+
+ /**
+ * Fetches a GMST string from the store, if there is no setting with the given
+ * ID or it is not a string the default string is returned.
+ *
+ * @param id Identifier for the GMST setting, e.g. "aName"
+ * @param default Default value if the GMST setting cannot be used.
+ */
+ virtual std::string getGameSettingString(const std::string &id, const std::string &default_);
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& changed);
+
+ virtual void windowResized(int x, int y);
+
+ virtual void executeInConsole (const std::string& path);
+
+ virtual void enableRest() { mRestAllowed = true; }
+ virtual bool getRestEnabled();
+
+ virtual bool getJournalAllowed() { return (mAllowed & GW_Magic); }
+
+ virtual bool getPlayerSleeping();
+ virtual void wakeUpPlayer();
+
+ virtual void updatePlayer();
+
+ virtual void showCompanionWindow(MWWorld::Ptr actor);
+ virtual void startSpellMaking(MWWorld::Ptr actor);
+ virtual void startEnchanting(MWWorld::Ptr actor);
+ virtual void startSelfEnchanting(MWWorld::Ptr soulgem);
+ virtual void startTraining(MWWorld::Ptr actor);
+ virtual void startRepair(MWWorld::Ptr actor);
+ virtual void startRepairItem(MWWorld::Ptr item);
+
+ virtual void frameStarted(float dt);
+
+ virtual void showSoulgemDialog (MWWorld::Ptr item);
+
+ virtual void changePointer (const std::string& name);
+
+ virtual void setEnemy (const MWWorld::Ptr& enemy);
+
+ virtual const Translation::Storage& getTranslationDataStorage() const;
+
+ void onSoulgemDialogButtonPressed (int button);
+
+ private:
+ bool mConsoleOnlyScripts;
+
+ OEngine::GUI::MyGUIManager *mGuiManager;
+ OEngine::Render::OgreRenderer *mRendering;
+ HUD *mHud;
+ MapWindow *mMap;
+ MainMenu *mMenu;
+ ToolTips *mToolTips;
+ StatsWindow *mStatsWindow;
+ MessageBoxManager *mMessageBoxManager;
+ Console *mConsole;
+ JournalWindow* mJournal;
+ DialogueWindow *mDialogueWindow;
+ ContainerWindow *mContainerWindow;
+ DragAndDrop* mDragAndDrop;
+ InventoryWindow *mInventoryWindow;
+ ScrollWindow* mScrollWindow;
+ BookWindow* mBookWindow;
+ CountDialog* mCountDialog;
+ TradeWindow* mTradeWindow;
+ SpellBuyingWindow* mSpellBuyingWindow;
+ TravelWindow* mTravelWindow;
+ SettingsWindow* mSettingsWindow;
+ ConfirmationDialog* mConfirmationDialog;
+ AlchemyWindow* mAlchemyWindow;
+ SpellWindow* mSpellWindow;
+ QuickKeysMenu* mQuickKeysMenu;
+ LoadingScreen* mLoadingScreen;
+ LevelupDialog* mLevelupDialog;
+ WaitDialog* mWaitDialog;
+ SpellCreationDialog* mSpellCreationDialog;
+ EnchantingDialog* mEnchantingDialog;
+ TrainingWindow* mTrainingWindow;
+ MerchantRepair* mMerchantRepair;
+ SoulgemDialog* mSoulgemDialog;
+ Repair* mRepair;
+ CompanionWindow* mCompanionWindow;
+
+ Translation::Storage& mTranslationDataStorage;
+ Cursor* mSoftwareCursor;
+
+ CharacterCreation* mCharGen;
+
+ MyGUI::Widget* mInputBlocker;
+
+ bool mCrosshairEnabled;
+ bool mSubtitlesEnabled;
+ bool mHudEnabled;
+ bool mCursorVisible;
+
+ void setCursorVisible(bool visible);
+
+ /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed.
+ // Various stats about player as needed by window manager
+ std::string mPlayerName;
+ std::string mPlayerRaceId;
+ 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;
+
+ SFO::CursorManager* mCursorManager;
+
+ std::vector<OEngine::GUI::Layout*> mGarbageDialogs;
+ void cleanupGarbage();
+
+ GuiWindow mShown; // Currently shown windows in inventory mode
+ GuiWindow mForceHidden; // Hidden windows (overrides mShown)
+
+ /* Currently ALLOWED windows in inventory mode. This is used at
+ the start of the game, when windows are enabled one by one
+ through script commands. You can manipulate this through using
+ allow() and disableAll().
+ */
+ GuiWindow mAllowed;
+ // is the rest window allowed?
+ bool mRestAllowed;
+
+ void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings
+
+ int mShowFPSLevel;
+ float mFPS;
+ 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
+ */
+ void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result);
+
+ void onCursorChange(const std::string& name);
+ void onKeyFocusChanged(MyGUI::Widget* widget);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp
new file mode 100644
index 0000000000..47364337c7
--- /dev/null
+++ b/apps/openmw/mwgui/windowpinnablebase.cpp
@@ -0,0 +1,32 @@
+#include "windowpinnablebase.hpp"
+
+#include "exposedwindow.hpp"
+
+namespace MWGui
+{
+ WindowPinnableBase::WindowPinnableBase(const std::string& parLayout)
+ : WindowBase(parLayout), mPinned(false)
+ {
+ ExposedWindow* window = static_cast<ExposedWindow*>(mMainWidget);
+ mPinButton = window->getSkinWidget ("Button");
+
+ mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked);
+ }
+
+ void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender)
+ {
+ mPinned = !mPinned;
+
+ if (mPinned)
+ mPinButton->changeWidgetSkin ("PinDown");
+ else
+ mPinButton->changeWidgetSkin ("PinUp");
+
+ onPinToggled();
+ }
+
+ void WindowPinnableBase::setPinButtonVisible(bool visible)
+ {
+ mPinButton->setVisible(visible);
+ }
+}
diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp
new file mode 100644
index 0000000000..cd393f9187
--- /dev/null
+++ b/apps/openmw/mwgui/windowpinnablebase.hpp
@@ -0,0 +1,28 @@
+#ifndef MWGUI_WINDOW_PINNABLE_BASE_H
+#define MWGUI_WINDOW_PINNABLE_BASE_H
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class WindowManager;
+
+ class WindowPinnableBase: public WindowBase
+ {
+ public:
+ WindowPinnableBase(const std::string& parLayout);
+ bool pinned() { return mPinned; }
+ void setPinButtonVisible(bool visible);
+
+ private:
+ void onPinButtonClicked(MyGUI::Widget* _sender);
+
+ protected:
+ virtual void onPinToggled() = 0;
+
+ MyGUI::Widget* mPinButton;
+ bool mPinned;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp
new file mode 100644
index 0000000000..1039a0dced
--- /dev/null
+++ b/apps/openmw/mwinput/inputmanagerimp.cpp
@@ -0,0 +1,1069 @@
+#include "inputmanagerimp.hpp"
+
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <MyGUI_InputManager.h>
+#include <MyGUI_RenderManager.h>
+#include <MyGUI_Widget.h>
+#include <MyGUI_Button.h>
+
+#include <openengine/ogre/renderer.hpp>
+
+#include "../engine.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwgui/bookwindow.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
+using namespace ICS;
+
+namespace
+{
+ std::vector<unsigned long> utf8ToUnicode(const std::string& utf8)
+ {
+ std::vector<unsigned long> unicode;
+ size_t i = 0;
+ while (i < utf8.size())
+ {
+ unsigned long uni;
+ size_t todo;
+ unsigned char ch = utf8[i++];
+ if (ch <= 0x7F)
+ {
+ uni = ch;
+ todo = 0;
+ }
+ else if (ch <= 0xBF)
+ {
+ throw std::logic_error("not a UTF-8 string");
+ }
+ else if (ch <= 0xDF)
+ {
+ uni = ch&0x1F;
+ todo = 1;
+ }
+ else if (ch <= 0xEF)
+ {
+ uni = ch&0x0F;
+ todo = 2;
+ }
+ else if (ch <= 0xF7)
+ {
+ uni = ch&0x07;
+ todo = 3;
+ }
+ else
+ {
+ throw std::logic_error("not a UTF-8 string");
+ }
+ for (size_t j = 0; j < todo; ++j)
+ {
+ if (i == utf8.size())
+ throw std::logic_error("not a UTF-8 string");
+ unsigned char ch = utf8[i++];
+ if (ch < 0x80 || ch > 0xBF)
+ throw std::logic_error("not a UTF-8 string");
+ uni <<= 6;
+ uni += ch & 0x3F;
+ }
+ if (uni >= 0xD800 && uni <= 0xDFFF)
+ throw std::logic_error("not a UTF-8 string");
+ if (uni > 0x10FFFF)
+ throw std::logic_error("not a UTF-8 string");
+ unicode.push_back(uni);
+ }
+ return unicode;
+ }
+}
+
+namespace MWInput
+{
+ InputManager::InputManager(OEngine::Render::OgreRenderer &ogre,
+ OMW::Engine& engine,
+ const std::string& userFile, bool userFileExists)
+ : mOgre(ogre)
+ , mPlayer(NULL)
+ , mEngine(engine)
+ , mMouseLookEnabled(true)
+ , mMouseX(ogre.getWindow()->getWidth ()/2.f)
+ , mMouseY(ogre.getWindow()->getHeight ()/2.f)
+ , mMouseWheel(0)
+ , mDragDrop(false)
+ , mGuiCursorEnabled(false)
+ , mUserFile(userFile)
+ , mUserFileExists(userFileExists)
+ , mInvertY (Settings::Manager::getBool("invert y axis", "Input"))
+ , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input"))
+ , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input"))
+ , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input"))
+ , mPreviewPOVDelay(0.f)
+ , mTimeIdle(0.f)
+ , mOverencumberedMessageDelay(0.f)
+ , mAlwaysRunActive(false)
+ {
+
+ Ogre::RenderWindow* window = ogre.getWindow ();
+
+ mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow());
+ mInputManager->setMouseEventCallback (this);
+ mInputManager->setKeyboardEventCallback (this);
+ mInputManager->setWindowEventCallback(this);
+
+ std::string file = userFileExists ? userFile : "";
+ mInputBinder = new ICS::InputControlSystem(file, true, this, NULL, A_Last);
+
+ adjustMouseRegion (window->getWidth(), window->getHeight());
+
+ loadKeyDefaults();
+
+ for (int i = 0; i < A_Last; ++i)
+ {
+ mInputBinder->getChannel (i)->addListener (this);
+ }
+
+ mControlSwitch["playercontrols"] = true;
+ mControlSwitch["playerfighting"] = true;
+ mControlSwitch["playerjumping"] = true;
+ mControlSwitch["playerlooking"] = true;
+ mControlSwitch["playermagic"] = true;
+ mControlSwitch["playerviewswitch"] = true;
+ mControlSwitch["vanitymode"] = true;
+ }
+
+ InputManager::~InputManager()
+ {
+ mInputBinder->save (mUserFile);
+
+ delete mInputBinder;
+
+ delete mInputManager;
+ }
+
+ void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue)
+ {
+ if (mDragDrop)
+ return;
+
+ resetIdleTime ();
+
+ int action = channel->getNumber();
+
+ if (action == A_Use)
+ {
+ MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue);
+ if (currentValue == 1)
+ {
+ int type = MWMechanics::CreatureStats::AT_Chop;
+ bool forward = (mInputBinder->getChannel(A_MoveForward)->getValue() > 0
+ || mInputBinder->getChannel(A_MoveBackward)->getValue() > 0);
+ bool side = (mInputBinder->getChannel(A_MoveLeft)->getValue() > 0
+ || mInputBinder->getChannel(A_MoveRight)->getValue() > 0);
+ if (side && !forward)
+ type = MWMechanics::CreatureStats::AT_Slash;
+ if (forward && !side)
+ type = MWMechanics::CreatureStats::AT_Thrust;
+
+ MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackType(type);
+ }
+ }
+
+ if (currentValue == 1)
+ {
+ // trigger action activated
+ switch (action)
+ {
+ case A_GameMenu:
+ toggleMainMenu ();
+ break;
+ case A_Quit:
+ exitNow();
+ break;
+ case A_Screenshot:
+ screenshot();
+ break;
+ case A_Inventory:
+ toggleInventory ();
+ break;
+ case A_Console:
+ toggleConsole ();
+ break;
+ case A_Activate:
+ resetIdleTime();
+
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container)
+ toggleContainer ();
+ else
+ MWBase::Environment::get().getWindowManager()->activateKeyPressed();
+ }
+ else
+ activate();
+ break;
+ case A_Journal:
+ toggleJournal ();
+ break;
+ case A_AutoMove:
+ toggleAutoMove ();
+ break;
+ case A_AlwaysRun:
+ toggleWalking ();
+ break;
+ case A_ToggleWeapon:
+ toggleWeapon ();
+ break;
+ case A_Rest:
+ rest();
+ break;
+ case A_ToggleSpell:
+ toggleSpell ();
+ break;
+ case A_QuickKey1:
+ quickKey(1);
+ break;
+ case A_QuickKey2:
+ quickKey(2);
+ break;
+ case A_QuickKey3:
+ quickKey(3);
+ break;
+ case A_QuickKey4:
+ quickKey(4);
+ break;
+ case A_QuickKey5:
+ quickKey(5);
+ break;
+ case A_QuickKey6:
+ quickKey(6);
+ break;
+ case A_QuickKey7:
+ quickKey(7);
+ break;
+ case A_QuickKey8:
+ quickKey(8);
+ break;
+ case A_QuickKey9:
+ quickKey(9);
+ break;
+ case A_QuickKey10:
+ quickKey(10);
+ break;
+ case A_QuickKeysMenu:
+ showQuickKeysMenu();
+ break;
+ case A_ToggleHUD:
+ MWBase::Environment::get().getWindowManager()->toggleHud();
+ break;
+ }
+ }
+ }
+
+ void InputManager::update(float dt, bool loading)
+ {
+ 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
+ MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX+1), int(mMouseY+1), mMouseWheel);
+ MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel);
+
+ // update values of channels (as a result of pressed keys)
+ if (!loading)
+ mInputBinder->update(dt);
+
+ bool main_menu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu);
+
+ bool was_relative = mInputManager->getMouseRelative();
+ bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode();
+
+ // don't keep the pointer away from the window edge in gui mode
+ // stop using raw mouse motions and switch to system cursor movements
+ mInputManager->setMouseRelative(is_relative);
+
+ //we let the mouse escape in the main menu
+ mInputManager->setGrabPointer(!main_menu);
+
+ //we switched to non-relative mode, move our cursor to where the in-game
+ //cursor is
+ if( !is_relative && was_relative != is_relative )
+ {
+ mInputManager->warpMouse(mMouseX, mMouseY);
+ }
+
+ if (loading)
+ return;
+
+ // Disable movement in Gui mode
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+
+
+ // Configure player movement according to keyboard input. Actual movement will
+ // be done in the physics system.
+ if (mControlSwitch["playercontrols"])
+ {
+ bool triedToMove = false;
+ if (actionIsActive(A_MoveLeft))
+ {
+ triedToMove = true;
+ mPlayer->setLeftRight (-1);
+ }
+ else if (actionIsActive(A_MoveRight))
+ {
+ triedToMove = true;
+ mPlayer->setLeftRight (1);
+ }
+
+ if (actionIsActive(A_MoveForward))
+ {
+ triedToMove = true;
+ mPlayer->setAutoMove (false);
+ mPlayer->setForwardBackward (1);
+ }
+ else if (actionIsActive(A_MoveBackward))
+ {
+ triedToMove = true;
+ mPlayer->setAutoMove (false);
+ mPlayer->setForwardBackward (-1);
+ }
+
+ else if(mPlayer->getAutoMove())
+ {
+ triedToMove = true;
+ mPlayer->setForwardBackward (1);
+ }
+
+ mPlayer->setSneak(actionIsActive(A_Sneak));
+
+ if (actionIsActive(A_Jump) && mControlSwitch["playerjumping"])
+ {
+ mPlayer->setUpDown (1);
+ triedToMove = true;
+ }
+
+ if (mAlwaysRunActive)
+ mPlayer->setRunState(!actionIsActive(A_Run));
+ else
+ mPlayer->setRunState(actionIsActive(A_Run));
+
+ // if player tried to start moving, but can't (due to being overencumbered), display a notification.
+ if (triedToMove)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ mOverencumberedMessageDelay -= dt;
+ if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player))
+ {
+ mPlayer->setAutoMove (false);
+ if (mOverencumberedMessageDelay <= 0)
+ {
+ MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage59}");
+ mOverencumberedMessageDelay = 1.0;
+ }
+ }
+ }
+
+ if (mControlSwitch["playerviewswitch"]) {
+
+ // work around preview mode toggle when pressing Alt+Tab
+ if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(SDL_Keymod(KMOD_ALT))) {
+ if (mPreviewPOVDelay <= 0.5 &&
+ (mPreviewPOVDelay += dt) > 0.5)
+ {
+ mPreviewPOVDelay = 1.f;
+ 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) {
+ MWBase::Environment::get().getWorld()->togglePOV();
+ }
+ mPreviewPOVDelay = 0.f;
+ }
+ }
+ }
+ if (actionIsActive(A_MoveForward) ||
+ actionIsActive(A_MoveBackward) ||
+ actionIsActive(A_MoveLeft) ||
+ actionIsActive(A_MoveRight) ||
+ actionIsActive(A_Jump) ||
+ actionIsActive(A_Sneak) ||
+ actionIsActive(A_TogglePOV))
+ {
+ resetIdleTime();
+ } else {
+ updateIdleTime(dt);
+ }
+ }
+
+ void InputManager::setDragDrop(bool dragDrop)
+ {
+ mDragDrop = dragDrop;
+ }
+
+ void InputManager::changeInputMode(bool guiMode)
+ {
+ mGuiCursorEnabled = guiMode;
+ mMouseLookEnabled = !guiMode;
+ if (guiMode)
+ MWBase::Environment::get().getWindowManager()->showCrosshair(false);
+ MWBase::Environment::get().getWindowManager()->setCursorVisible(guiMode);
+ // if not in gui mode, the camera decides whether to show crosshair or not.
+ }
+
+ void InputManager::processChangedSettings(const Settings::CategorySettingVector& changed)
+ {
+ for (Settings::CategorySettingVector::const_iterator it = changed.begin();
+ it != changed.end(); ++it)
+ {
+ if (it->first == "Input" && it->second == "invert y axis")
+ mInvertY = Settings::Manager::getBool("invert y axis", "Input");
+
+ if (it->first == "Input" && it->second == "camera sensitivity")
+ mCameraSensitivity = Settings::Manager::getFloat("camera sensitivity", "Input");
+
+ if (it->first == "Input" && it->second == "ui sensitivity")
+ mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input");
+
+ }
+ }
+
+ bool InputManager::getControlSwitch (const std::string& sw)
+ {
+ return mControlSwitch[sw];
+ }
+
+ void InputManager::toggleControlSwitch (const std::string& sw, bool value)
+ {
+ if (mControlSwitch[sw] == value) {
+ return;
+ }
+ /// \note 7 switches at all, if-else is relevant
+ if (sw == "playercontrols" && !value) {
+ mPlayer->setLeftRight(0);
+ mPlayer->setForwardBackward(0);
+ mPlayer->setAutoMove(false);
+ mPlayer->setUpDown(0);
+ } else if (sw == "playerjumping" && !value) {
+ /// \fixme maybe crouching at this time
+ mPlayer->setUpDown(0);
+ } else if (sw == "vanitymode") {
+ MWBase::Environment::get().getWorld()->allowVanityMode(value);
+ } else if (sw == "playerlooking") {
+ MWBase::Environment::get().getWorld()->togglePlayerLooking(value);
+ }
+ mControlSwitch[sw] = value;
+ }
+
+ void InputManager::adjustMouseRegion(int width, int height)
+ {
+ mInputBinder->adjustMouseRegion(width, height);
+ }
+
+ bool InputManager::keyPressed( const SDL_KeyboardEvent &arg )
+ {
+ mInputBinder->keyPressed (arg);
+
+ if(arg.keysym.sym == SDLK_RETURN
+ && MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ // Pressing enter when a messagebox is prompting for "ok" will activate the ok button
+ MWBase::Environment::get().getWindowManager()->enterPressed();
+ }
+
+ OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym);
+
+ if (kc != OIS::KC_UNASSIGNED)
+ MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0);
+ return true;
+ }
+
+ void InputManager::textInput(const SDL_TextInputEvent &arg)
+ {
+ const char* text = &arg.text[0];
+ std::vector<unsigned long> unicode = utf8ToUnicode(std::string(text));
+ for (std::vector<unsigned long>::iterator it = unicode.begin(); it != unicode.end(); ++it)
+ MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it);
+ }
+
+ bool InputManager::keyReleased(const SDL_KeyboardEvent &arg )
+ {
+ mInputBinder->keyReleased (arg);
+
+ OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym);
+
+ MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc));
+
+ return true;
+ }
+
+ bool InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id )
+ {
+ mInputBinder->mousePressed (arg, id);
+
+ if (id != SDL_BUTTON_LEFT && id != SDL_BUTTON_RIGHT)
+ return true; // MyGUI has no use for these events
+
+ MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id));
+ if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0)
+ {
+ MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType<MyGUI::Button>(false);
+ if (b)
+ {
+ MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f);
+ }
+ }
+
+ return true;
+ }
+
+ bool InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id )
+ {
+ mInputBinder->mouseReleased (arg, id);
+
+ MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, sdlButtonToMyGUI(id));
+
+ return true;
+ }
+
+ bool InputManager::mouseMoved(const SFO::MouseMotionEvent &arg )
+ {
+ mInputBinder->mouseMoved (arg);
+
+ resetIdleTime ();
+
+ if (mGuiCursorEnabled)
+ {
+ const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+
+ // We keep track of our own mouse position, so that moving the mouse while in
+ // game mode does not move the position of the GUI cursor
+ mMouseX = arg.x;
+ mMouseY = arg.y;
+
+ mMouseX = std::max(0.f, std::min(mMouseX, float(viewSize.width)));
+ mMouseY = std::max(0.f, std::min(mMouseY, float(viewSize.height)));
+
+ mMouseWheel = int(arg.z);
+
+ MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel);
+
+ //if the player is reading a book and flicking the mouse wheel
+ if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Book && arg.zrel)
+ {
+ if (arg.zrel < 0)
+ MWBase::Environment::get().getWindowManager()->getBookWindow()->nextPage();
+ else
+ MWBase::Environment::get().getWindowManager()->getBookWindow()->prevPage();
+ }
+ }
+
+ if (mMouseLookEnabled)
+ {
+ resetIdleTime();
+
+ double x = arg.xrel * mCameraSensitivity * (1.0f/256.f);
+ double y = arg.yrel * mCameraSensitivity * (1.0f/256.f) * (mInvertY ? -1 : 1) * mCameraYMultiplier;
+ float scale = MWBase::Environment::get().getFrameDuration();
+ if(scale <= 0.0f) scale = 1.0f;
+
+ float rot[3];
+ rot[0] = -y;
+ rot[1] = 0.0f;
+ rot[2] = x;
+
+ // Only actually turn player when we're not in vanity mode
+ if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot))
+ {
+ mPlayer->yaw(x/scale);
+ mPlayer->pitch(-y/scale);
+ }
+
+ if (arg.zrel)
+ {
+ MWBase::Environment::get().getWorld()->changeVanityModeScale(arg.zrel);
+ MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true);
+ }
+ }
+
+ return true;
+ }
+
+ void InputManager::windowFocusChange(bool have_focus)
+ {
+ }
+
+ void InputManager::windowVisibilityChange(bool visible)
+ {
+ //TODO: Pause game?
+ }
+
+ void InputManager::windowResized(int x, int y)
+ {
+ mOgre.windowResized(x,y);
+ }
+
+ void InputManager::toggleMainMenu()
+ {
+ if (MyGUI::InputManager::getInstance ().isModalAny())
+ return;
+
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_Video)
+ MWBase::Environment::get().getWorld ()->stopVideo ();
+ else if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu))
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ else
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
+ }
+
+ void InputManager::toggleSpell()
+ {
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+
+ // Not allowed before the magic window is accessible
+ if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Magic))
+ return;
+
+ MWMechanics::DrawState_ state = mPlayer->getDrawState();
+ if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
+ mPlayer->setDrawState(MWMechanics::DrawState_Spell);
+ else
+ mPlayer->setDrawState(MWMechanics::DrawState_Nothing);
+ }
+
+ void InputManager::toggleWeapon()
+ {
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+
+ // Not allowed before the inventory window is accessible
+ if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ return;
+
+ MWMechanics::DrawState_ state = mPlayer->getDrawState();
+ if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
+ mPlayer->setDrawState(MWMechanics::DrawState_Weapon);
+ else
+ mPlayer->setDrawState(MWMechanics::DrawState_Nothing);
+ }
+
+ void InputManager::rest()
+ {
+ if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ())
+ return;
+
+ /// \todo check if resting is currently allowed (enemies nearby?)
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest);
+ }
+
+ void InputManager::screenshot()
+ {
+ mEngine.screenshot();
+
+ std::vector<std::string> empty;
+ MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved", empty);
+ }
+
+ void InputManager::toggleInventory()
+ {
+ if (MyGUI::InputManager::getInstance ().isModalAny())
+ return;
+
+ // Toggle between game mode and inventory mode
+ if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory);
+ else
+ {
+ MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
+ if(mode == MWGui::GM_Inventory || mode == MWGui::GM_Container)
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ }
+
+ // .. but don't touch any other mode, except container.
+ }
+
+ void InputManager::toggleContainer()
+ {
+ if (MyGUI::InputManager::getInstance ().isModalAny())
+ return;
+
+ if(MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Container)
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ else
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container);
+ }
+
+ }
+
+ void InputManager::toggleConsole()
+ {
+ if (MyGUI::InputManager::getInstance ().isModalAny())
+ return;
+
+ // Switch to console mode no matter what mode we are currently
+ // in, except of course if we are already in console mode
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console)
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ else
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console);
+ }
+ else
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console);
+ }
+
+ void InputManager::toggleJournal()
+ {
+ if (MyGUI::InputManager::getInstance ().isModalAny())
+ return;
+
+ // Toggle between game mode and journal mode
+ if(!MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed())
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0);
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal);
+ }
+ else if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Journal)
+ {
+ MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0);
+ MWBase::Environment::get().getWindowManager()->popGuiMode();
+ }
+ // .. but don't touch any other mode.
+ }
+
+ void InputManager::quickKey (int index)
+ {
+ if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
+ MWBase::Environment::get().getWindowManager()->activateQuickKey (index);
+ }
+
+ void InputManager::showQuickKeysMenu()
+ {
+ if (!MWBase::Environment::get().getWindowManager()->isGuiMode ())
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu);
+ else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu)
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu);
+ }
+
+ void InputManager::activate()
+ {
+ if (mControlSwitch["playercontrols"])
+ mEngine.activate();
+ }
+
+ void InputManager::toggleAutoMove()
+ {
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+
+ if (mControlSwitch["playercontrols"])
+ mPlayer->setAutoMove (!mPlayer->getAutoMove());
+ }
+
+ void InputManager::toggleWalking()
+ {
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+ 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)
+ MWBase::Environment::get().getWorld()->toggleVanityMode(false);
+ mTimeIdle = 0.f;
+ }
+
+ void InputManager::updateIdleTime(float dt)
+ {
+ if (mTimeIdle >= 0.f)
+ mTimeIdle += dt;
+ if (mTimeIdle > 30.f) {
+ MWBase::Environment::get().getWorld()->toggleVanityMode(true);
+ mTimeIdle = -1.f;
+ }
+ }
+
+ bool InputManager::actionIsActive (int id)
+ {
+ return mInputBinder->getChannel (id)->getValue () == 1;
+ }
+
+ void InputManager::loadKeyDefaults (bool force)
+ {
+ // using hardcoded key defaults is inevitable, if we want the configuration files to stay valid
+ // across different versions of OpenMW (in the case where another input action is added)
+ std::map<int, int> defaultKeyBindings;
+
+ defaultKeyBindings[A_Activate] = SDLK_SPACE;
+ defaultKeyBindings[A_MoveBackward] = SDLK_s;
+ defaultKeyBindings[A_MoveForward] = SDLK_w;
+ defaultKeyBindings[A_MoveLeft] = SDLK_a;
+ defaultKeyBindings[A_MoveRight] = SDLK_d;
+ defaultKeyBindings[A_ToggleWeapon] = SDLK_f;
+ defaultKeyBindings[A_ToggleSpell] = SDLK_r;
+ defaultKeyBindings[A_QuickKeysMenu] = SDLK_F1;
+ defaultKeyBindings[A_Console] = SDLK_F2;
+ defaultKeyBindings[A_Run] = SDLK_LSHIFT;
+ defaultKeyBindings[A_Sneak] = SDLK_LCTRL;
+ defaultKeyBindings[A_AutoMove] = SDLK_q;
+ defaultKeyBindings[A_Jump] = SDLK_e;
+ defaultKeyBindings[A_Journal] = SDLK_j;
+ defaultKeyBindings[A_Rest] = SDLK_t;
+ defaultKeyBindings[A_GameMenu] = SDLK_ESCAPE;
+ defaultKeyBindings[A_TogglePOV] = SDLK_TAB;
+ defaultKeyBindings[A_QuickKey1] = SDLK_1;
+ defaultKeyBindings[A_QuickKey2] = SDLK_2;
+ defaultKeyBindings[A_QuickKey3] = SDLK_3;
+ defaultKeyBindings[A_QuickKey4] = SDLK_4;
+ defaultKeyBindings[A_QuickKey5] = SDLK_5;
+ defaultKeyBindings[A_QuickKey6] = SDLK_6;
+ defaultKeyBindings[A_QuickKey7] = SDLK_7;
+ defaultKeyBindings[A_QuickKey8] = SDLK_8;
+ defaultKeyBindings[A_QuickKey9] = SDLK_9;
+ defaultKeyBindings[A_QuickKey10] = SDLK_0;
+ defaultKeyBindings[A_Screenshot] = SDLK_F12;
+ defaultKeyBindings[A_ToggleHUD] = SDLK_F11;
+ defaultKeyBindings[A_AlwaysRun] = SDLK_y;
+
+ std::map<int, int> defaultMouseButtonBindings;
+ defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT;
+ defaultMouseButtonBindings[A_Use] = SDL_BUTTON_LEFT;
+
+ for (int i = 0; i < A_Last; ++i)
+ {
+ ICS::Control* control;
+ bool controlExists = mInputBinder->getChannel(i)->getControlsCount () != 0;
+ if (!controlExists)
+ {
+ control = new ICS::Control(boost::lexical_cast<std::string>(i), false, true, 0, ICS::ICS_MAX, ICS::ICS_MAX);
+ mInputBinder->addControl(control);
+ control->attachChannel(mInputBinder->getChannel(i), ICS::Channel::DIRECT);
+ }
+ else
+ {
+ control = mInputBinder->getChannel(i)->getAttachedControls ().front().control;
+ }
+
+ if (!controlExists || force ||
+ ( mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) == SDLK_UNKNOWN
+ && mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS
+ ))
+ {
+ clearAllBindings (control);
+
+ if (defaultKeyBindings.find(i) != defaultKeyBindings.end())
+ mInputBinder->addKeyBinding(control, static_cast<SDL_Keycode>(defaultKeyBindings[i]), ICS::Control::INCREASE);
+ else if (defaultMouseButtonBindings.find(i) != defaultMouseButtonBindings.end())
+ mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE);
+ }
+ }
+
+ // Printscreen key should not be allowed because it's captured by system screenshot function
+ // We check this explicitely here to fix up pre-0.26 config files. Can be removed after a few versions
+ if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Screenshot), ICS::Control::INCREASE) == SDLK_PRINTSCREEN)
+ mInputBinder->addKeyBinding(mInputBinder->getControl(A_Screenshot), SDLK_F12, ICS::Control::INCREASE);
+ }
+
+ std::string InputManager::getActionDescription (int action)
+ {
+ std::map<int, std::string> descriptions;
+
+ if (action == A_Screenshot)
+ return "Screenshot";
+
+ descriptions[A_Use] = "sUse";
+ descriptions[A_Activate] = "sActivate";
+ descriptions[A_MoveBackward] = "sBack";
+ descriptions[A_MoveForward] = "sForward";
+ descriptions[A_MoveLeft] = "sLeft";
+ descriptions[A_MoveRight] = "sRight";
+ descriptions[A_ToggleWeapon] = "sReady_Weapon";
+ descriptions[A_ToggleSpell] = "sReady_Magic";
+ descriptions[A_Console] = "sConsoleTitle";
+ descriptions[A_Run] = "sRun";
+ descriptions[A_Sneak] = "sCrouch_Sneak";
+ descriptions[A_AutoMove] = "sAuto_Run";
+ descriptions[A_Jump] = "sJump";
+ descriptions[A_Journal] = "sJournal";
+ descriptions[A_Rest] = "sRestKey";
+ descriptions[A_Inventory] = "sInventory";
+ descriptions[A_TogglePOV] = "sTogglePOVCmd";
+ descriptions[A_QuickKeysMenu] = "sQuickMenu";
+ descriptions[A_QuickKey1] = "sQuick1Cmd";
+ descriptions[A_QuickKey2] = "sQuick2Cmd";
+ descriptions[A_QuickKey3] = "sQuick3Cmd";
+ descriptions[A_QuickKey4] = "sQuick4Cmd";
+ descriptions[A_QuickKey5] = "sQuick5Cmd";
+ descriptions[A_QuickKey6] = "sQuick6Cmd";
+ descriptions[A_QuickKey7] = "sQuick7Cmd";
+ descriptions[A_QuickKey8] = "sQuick8Cmd";
+ descriptions[A_QuickKey9] = "sQuick9Cmd";
+ descriptions[A_QuickKey10] = "sQuick10Cmd";
+ descriptions[A_AlwaysRun] = "sAlways_Run";
+
+ if (descriptions[action] == "")
+ return ""; // not configurable
+
+ return "#{" + descriptions[action] + "}";
+ }
+
+ std::string InputManager::getActionBindingName (int action)
+ {
+ if (mInputBinder->getChannel (action)->getControlsCount () == 0)
+ return "#{sNone}";
+
+ ICS::Control* c = mInputBinder->getChannel (action)->getAttachedControls ().front().control;
+
+ if (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE) != SDLK_UNKNOWN)
+ return mInputBinder->keyCodeToString (mInputBinder->getKeyBinding (c, ICS::Control::INCREASE));
+ else if (mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS)
+ return "#{sMouse} " + boost::lexical_cast<std::string>(mInputBinder->getMouseButtonBinding (c, ICS::Control::INCREASE));
+ else
+ return "#{sNone}";
+ }
+
+ std::vector<int> InputManager::getActionSorting()
+ {
+ std::vector<int> ret;
+ ret.push_back(A_MoveForward);
+ ret.push_back(A_MoveBackward);
+ ret.push_back(A_MoveLeft);
+ ret.push_back(A_MoveRight);
+ ret.push_back(A_TogglePOV);
+ ret.push_back(A_Run);
+ ret.push_back(A_AlwaysRun);
+ ret.push_back(A_Sneak);
+ ret.push_back(A_Activate);
+ ret.push_back(A_Use);
+ ret.push_back(A_ToggleWeapon);
+ ret.push_back(A_ToggleSpell);
+ ret.push_back(A_AutoMove);
+ ret.push_back(A_Jump);
+ ret.push_back(A_Inventory);
+ ret.push_back(A_Journal);
+ ret.push_back(A_Rest);
+ ret.push_back(A_Console);
+ ret.push_back(A_Screenshot);
+ ret.push_back(A_QuickKeysMenu);
+ ret.push_back(A_QuickKey1);
+ ret.push_back(A_QuickKey2);
+ ret.push_back(A_QuickKey3);
+ ret.push_back(A_QuickKey4);
+ ret.push_back(A_QuickKey5);
+ ret.push_back(A_QuickKey6);
+ ret.push_back(A_QuickKey7);
+ ret.push_back(A_QuickKey8);
+ ret.push_back(A_QuickKey9);
+ ret.push_back(A_QuickKey10);
+
+ return ret;
+ }
+
+ void InputManager::enableDetectingBindingMode (int action)
+ {
+ ICS::Control* c = mInputBinder->getChannel (action)->getAttachedControls ().front().control;
+
+ mInputBinder->enableDetectingBindingState (c, ICS::Control::INCREASE);
+ }
+
+ void InputManager::mouseAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction)
+ {
+ // we don't want mouse movement bindings
+ return;
+ }
+
+ void InputManager::keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , SDL_Keycode key, ICS::Control::ControlChangingDirection direction)
+ {
+ //Disallow binding escape key
+ if(key==SDLK_ESCAPE)
+ return;
+
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::keyBindingDetected (ICS, control, key, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , unsigned int button, ICS::Control::ControlChangingDirection direction)
+ {
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::mouseButtonBindingDetected (ICS, control, button, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::joystickAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int axis, ICS::Control::ControlChangingDirection direction)
+ {
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::joystickAxisBindingDetected (ICS, control, deviceId, axis, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::joystickButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, unsigned int button, ICS::Control::ControlChangingDirection direction)
+ {
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::joystickButtonBindingDetected (ICS, control, deviceId, button, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::joystickPOVBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int pov,ICS:: InputControlSystem::POVAxis axis, ICS::Control::ControlChangingDirection direction)
+ {
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::joystickPOVBindingDetected (ICS, control, deviceId, pov, axis, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::joystickSliderBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int slider, ICS::Control::ControlChangingDirection direction)
+ {
+ clearAllBindings(control);
+ ICS::DetectingBindingListener::joystickSliderBindingDetected (ICS, control, deviceId, slider, direction);
+ MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
+ }
+
+ void InputManager::clearAllBindings (ICS::Control* control)
+ {
+ // right now we don't really need multiple bindings for the same action, so remove all others first
+ if (mInputBinder->getKeyBinding (control, ICS::Control::INCREASE) != SDLK_UNKNOWN)
+ mInputBinder->removeKeyBinding (mInputBinder->getKeyBinding (control, ICS::Control::INCREASE));
+ if (mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS)
+ mInputBinder->removeMouseButtonBinding (mInputBinder->getMouseButtonBinding (control, ICS::Control::INCREASE));
+
+ /// \todo add joysticks here once they are added
+ }
+
+ void InputManager::resetToDefaultBindings()
+ {
+ loadKeyDefaults(true);
+ }
+
+ MyGUI::MouseButton InputManager::sdlButtonToMyGUI(Uint8 button)
+ {
+ //The right button is the second button, according to MyGUI
+ if(button == SDL_BUTTON_RIGHT)
+ button = SDL_BUTTON_MIDDLE;
+ else if(button == SDL_BUTTON_MIDDLE)
+ button = SDL_BUTTON_RIGHT;
+
+ //MyGUI's buttons are 0 indexed
+ return MyGUI::MouseButton::Enum(button - 1);
+ }
+}
diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp
new file mode 100644
index 0000000000..5f9a752d72
--- /dev/null
+++ b/apps/openmw/mwinput/inputmanagerimp.hpp
@@ -0,0 +1,255 @@
+#ifndef _MWINPUT_MWINPUTMANAGERIMP_H
+#define _MWINPUT_MWINPUTMANAGERIMP_H
+
+#include "../mwgui/mode.hpp"
+
+#include <components/settings/settings.hpp>
+
+#include "../mwbase/inputmanager.hpp"
+#include <extern/sdl4ogre/sdlinputwrapper.hpp>
+
+namespace OEngine
+{
+ namespace Render
+ {
+ class OgreRenderer;
+ }
+}
+
+namespace MWWorld
+{
+ class Player;
+}
+
+namespace MWBase
+{
+ class WindowManager;
+}
+
+namespace OMW
+{
+ class Engine;
+}
+
+namespace ICS
+{
+ class InputControlSystem;
+}
+
+namespace MyGUI
+{
+ class MouseButton;
+}
+
+#include <extern/oics/ICSChannelListener.h>
+#include <extern/oics/ICSInputControlSystem.h>
+
+namespace MWInput
+{
+
+ /**
+ * @brief Class that handles all input and key bindings for OpenMW.
+ */
+ class InputManager :
+ public MWBase::InputManager,
+ public SFO::KeyListener,
+ public SFO::MouseListener,
+ public SFO::WindowListener,
+ public ICS::ChannelListener,
+ public ICS::DetectingBindingListener
+ {
+ public:
+ InputManager(OEngine::Render::OgreRenderer &_ogre,
+ OMW::Engine& engine,
+ const std::string& userFile, bool userFileExists);
+
+ virtual ~InputManager();
+
+ virtual void update(float dt, bool loading);
+
+ void setPlayer (MWWorld::Player* player) { mPlayer = player; }
+
+ virtual void changeInputMode(bool guiMode);
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& changed);
+
+ virtual void setDragDrop(bool dragDrop);
+
+ virtual void toggleControlSwitch (const std::string& sw, bool value);
+ virtual bool getControlSwitch (const std::string& sw);
+
+ virtual std::string getActionDescription (int action);
+ virtual std::string getActionBindingName (int action);
+ virtual int getNumActions() { return A_Last; }
+ virtual std::vector<int> getActionSorting ();
+ virtual void enableDetectingBindingMode (int action);
+ virtual void resetToDefaultBindings();
+
+ public:
+ virtual bool keyPressed(const SDL_KeyboardEvent &arg );
+ virtual bool keyReleased( const SDL_KeyboardEvent &arg );
+ virtual void textInput (const SDL_TextInputEvent &arg);
+
+ virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id );
+ virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id );
+ virtual bool mouseMoved( const SFO::MouseMotionEvent &arg );
+
+ virtual void windowVisibilityChange( bool visible );
+ virtual void windowFocusChange( bool have_focus );
+ virtual void windowResized (int x, int y);
+
+ virtual void channelChanged(ICS::Channel* channel, float currentValue, float previousValue);
+
+ virtual void mouseAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , ICS::InputControlSystem::NamedAxis axis, ICS::Control::ControlChangingDirection direction);
+
+ virtual void keyBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , SDL_Keycode key, ICS::Control::ControlChangingDirection direction);
+
+ virtual void mouseButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , unsigned int button, ICS::Control::ControlChangingDirection direction);
+
+ virtual void joystickAxisBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int axis, ICS::Control::ControlChangingDirection direction);
+
+ virtual void joystickButtonBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, unsigned int button, ICS::Control::ControlChangingDirection direction);
+
+ virtual void joystickPOVBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int pov,ICS:: InputControlSystem::POVAxis axis, ICS::Control::ControlChangingDirection direction);
+
+ virtual void joystickSliderBindingDetected(ICS::InputControlSystem* ICS, ICS::Control* control
+ , int deviceId, int slider, ICS::Control::ControlChangingDirection direction);
+
+ void clearAllBindings (ICS::Control* control);
+
+ private:
+ OEngine::Render::OgreRenderer &mOgre;
+ MWWorld::Player* mPlayer;
+ OMW::Engine& mEngine;
+
+ ICS::InputControlSystem* mInputBinder;
+
+
+ SFO::InputWrapper* mInputManager;
+
+ std::string mUserFile;
+
+ bool mDragDrop;
+
+ bool mInvertY;
+
+ float mCameraSensitivity;
+ float mUISensitivity;
+ float mCameraYMultiplier;
+ float mPreviewPOVDelay;
+ float mTimeIdle;
+
+ bool mMouseLookEnabled;
+ bool mGuiCursorEnabled;
+
+ float mOverencumberedMessageDelay;
+
+ float mMouseX;
+ float mMouseY;
+ int mMouseWheel;
+ bool mUserFileExists;
+ bool mAlwaysRunActive;
+
+ std::map<std::string, bool> mControlSwitch;
+
+ private:
+ void adjustMouseRegion(int width, int height);
+ MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button);
+
+ void resetIdleTime();
+ void updateIdleTime(float dt);
+
+ private:
+ void toggleMainMenu();
+ void toggleSpell();
+ void toggleWeapon();
+ void toggleInventory();
+ void toggleContainer();
+ void toggleConsole();
+ void screenshot();
+ void toggleJournal();
+ void activate();
+ void toggleWalking();
+ void toggleAutoMove();
+ void exitNow();
+ void rest();
+
+ void quickKey (int index);
+ void showQuickKeysMenu();
+
+ bool actionIsActive (int id);
+
+ void loadKeyDefaults(bool force = false);
+
+ private:
+ enum Actions
+ {
+ // please add new actions at the bottom, in order to preserve the channel IDs in the key configuration files
+
+ A_GameMenu,
+
+ A_Quit, // Exit the program
+
+ A_Screenshot, // Take a screenshot
+
+ A_Inventory, // Toggle inventory screen
+
+ A_Console, // Toggle console screen
+
+ A_MoveLeft, // Move player left / right
+ A_MoveRight,
+ A_MoveForward, // Forward / Backward
+ A_MoveBackward,
+
+ A_Activate,
+
+ A_Use, //Use weapon, spell, etc.
+ A_Jump,
+ A_AutoMove, //Toggle Auto-move forward
+ A_Rest, //Rest
+ A_Journal, //Journal
+ A_Weapon, //Draw/Sheath weapon
+ A_Spell, //Ready/Unready Casting
+ A_Run, //Run when held
+ A_CycleSpellLeft, //cycling through spells
+ A_CycleSpellRight,
+ A_CycleWeaponLeft,//Cycling through weapons
+ A_CycleWeaponRight,
+ A_ToggleSneak, //Toggles Sneak
+ A_AlwaysRun, //Toggle Walking/Running
+ A_Sneak,
+
+ A_QuickSave,
+ A_QuickLoad,
+ A_QuickMenu,
+ A_ToggleWeapon,
+ A_ToggleSpell,
+
+ A_TogglePOV,
+
+ A_QuickKey1,
+ A_QuickKey2,
+ A_QuickKey3,
+ A_QuickKey4,
+ A_QuickKey5,
+ A_QuickKey6,
+ A_QuickKey7,
+ A_QuickKey8,
+ A_QuickKey9,
+ A_QuickKey10,
+
+ A_QuickKeysMenu,
+
+ A_ToggleHUD,
+
+ A_Last // Marker for the last item
+ };
+ };
+}
+#endif
diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp
new file mode 100644
index 0000000000..9aca6b7b79
--- /dev/null
+++ b/apps/openmw/mwmechanics/activespells.cpp
@@ -0,0 +1,284 @@
+
+#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
+ {
+ bool rebuild = false;
+
+ MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
+
+ if (mLastUpdate!=now)
+ {
+ TContainer::iterator iter (mSpells.begin());
+ while (iter!=mSpells.end())
+ if (!timeToExpire (iter))
+ {
+ mSpells.erase (iter++);
+ rebuild = true;
+ }
+ else
+ ++iter;
+
+ mLastUpdate = now;
+ }
+
+ if (mSpellsChanged)
+ {
+ mSpellsChanged = false;
+ rebuild = true;
+ }
+
+ if (rebuild)
+ rebuildEffects();
+ }
+
+ void ActiveSpells::rebuildEffects() const
+ {
+ MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
+
+ mEffects = MagicEffects();
+
+ 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.first;
+ float magnitude = iter->second.second;
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
+ iter!=effects.first.mList.end(); ++iter)
+ {
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ 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())
+ {}
+
+ 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();
+ return mEffects;
+ }
+
+ 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);
+
+ int duration = 0;
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
+ iter!=effects.first.mList.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;
+
+ if (usedUp>=scaledDuration)
+ return 0;
+
+ return scaledDuration-usedUp;
+ }
+
+ bool ActiveSpells::isSpellActive(std::string id) const
+ {
+ Misc::StringUtils::toLower(id);
+ for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
+ {
+ std::string left = iter->first;
+ Misc::StringUtils::toLower(left);
+
+ if (iter->first == id)
+ return true;
+ }
+ return false;
+ }
+
+ const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
+ {
+ return mSpells;
+ }
+}
diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp
new file mode 100644
index 0000000000..8c859b2cb3
--- /dev/null
+++ b/apps/openmw/mwmechanics/activespells.hpp
@@ -0,0 +1,79 @@
+#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
+#define GAME_MWMECHANICS_ACTIVESPELLS_H
+
+#include <map>
+#include <vector>
+#include <string>
+
+#include "../mwworld/timestamp.hpp"
+
+#include "magiceffects.hpp"
+
+namespace ESM
+{
+ struct Spell;
+ struct EffectList;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWMechanics
+{
+ /// \brief Lasting spell effects
+ ///
+ /// \note The name of this class is slightly misleading, since it also handels lasting potion
+ /// effects.
+ class ActiveSpells
+ {
+ public:
+
+ typedef std::multimap<std::string, std::pair<MWWorld::TimeStamp, float> > TContainer;
+ typedef TContainer::const_iterator TIterator;
+
+ private:
+
+ mutable TContainer mSpells; // spellId, (time of casting, relative magnitude)
+ mutable MagicEffects mEffects;
+ mutable bool mSpellsChanged;
+ mutable MWWorld::TimeStamp mLastUpdate;
+
+ void update() const;
+
+ void rebuildEffects() const;
+
+ std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const;
+ ///< @return (EffectList, (isIngredient, stacks))
+
+ 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.
+ ///
+ /// \return Has the spell been added?
+
+ void removeSpell (const std::string& id);
+
+ bool isSpellActive (std::string id) const;
+ ///< case insensitive
+
+ const MagicEffects& getMagicEffects() const;
+
+ const TContainer& getActiveSpells() const;
+
+ TIterator begin() const;
+
+ TIterator end() const;
+
+ double timeToExpire (const TIterator& iterator) const;
+ ///< Returns time (in in-game hours) until the spell pointed to by \a iterator
+ /// expires.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
new file mode 100644
index 0000000000..566d4bc505
--- /dev/null
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -0,0 +1,354 @@
+
+#include "actors.hpp"
+
+#include <typeinfo>
+
+#include <OgreVector3.h>
+
+#include <components/esm/loadnpc.hpp>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "npcstats.hpp"
+#include "creaturestats.hpp"
+#include "movement.hpp"
+
+namespace MWMechanics
+{
+ void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
+ {
+ // magic effects
+ adjustMagicEffects (ptr);
+ calculateDynamicStats (ptr);
+ calculateCreatureStatModifiers (ptr);
+
+ // AI
+ if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+ creatureStats.getAiSequence().execute (ptr);
+ }
+ }
+
+ void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused)
+ {
+ if(!paused)
+ updateDrowning(ptr, duration);
+ }
+
+ void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
+ {
+ CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature);
+
+ MagicEffects now = creatureStats.getSpells().getMagicEffects();
+
+ if (creature.getTypeName()==typeid (ESM::NPC).name())
+ {
+ MWWorld::InventoryStore& store = MWWorld::Class::get (creature).getInventoryStore (creature);
+ now += store.getMagicEffects();
+ }
+
+ now += creatureStats.getActiveSpells().getMagicEffects();
+
+ MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now);
+
+ creatureStats.setMagicEffects(now);
+
+ // TODO apply diff to other stats
+ }
+
+ void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr)
+ {
+ CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+
+ int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
+ int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase();
+ int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase();
+ int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase();
+ int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
+
+ double magickaFactor =
+ creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5;
+
+ DynamicStat<float> magicka = creatureStats.getMagicka();
+ float diff = (static_cast<int>(intelligence + magickaFactor*intelligence)) - magicka.getBase();
+ magicka.modify(diff);
+ creatureStats.setMagicka(magicka);
+
+ DynamicStat<float> fatigue = creatureStats.getFatigue();
+ diff = (strength+willpower+agility+endurance) - fatigue.getBase();
+ fatigue.modify(diff);
+ creatureStats.setFatigue(fatigue);
+ }
+
+ void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration)
+ {
+ CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+
+ if (duration == 3600)
+ {
+ bool stunted = stats.getMagicEffects ().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0;
+
+ int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
+
+ DynamicStat<float> health = stats.getHealth();
+ health.setCurrent (health.getCurrent() + 0.1 * endurance);
+ stats.setHealth (health);
+
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ float fFatigueReturnBase = store.get<ESM::GameSetting>().find("fFatigueReturnBase")->getFloat ();
+ float fFatigueReturnMult = store.get<ESM::GameSetting>().find("fFatigueReturnMult")->getFloat ();
+ float fEndFatigueMult = store.get<ESM::GameSetting>().find("fEndFatigueMult")->getFloat ();
+
+ float capacity = MWWorld::Class::get(ptr).getCapacity(ptr);
+ float encumbrance = MWWorld::Class::get(ptr).getEncumbrance(ptr);
+ float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity);
+ if (normalizedEncumbrance > 1)
+ normalizedEncumbrance = 1;
+
+ float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
+ x *= fEndFatigueMult * endurance;
+
+ DynamicStat<float> fatigue = stats.getFatigue();
+ fatigue.setCurrent (fatigue.getCurrent() + 3600 * x);
+ stats.setFatigue (fatigue);
+
+ if (!stunted)
+ {
+ float fRestMagicMult = store.get<ESM::GameSetting>().find("fRestMagicMult")->getFloat ();
+
+ DynamicStat<float> magicka = stats.getMagicka();
+ magicka.setCurrent (magicka.getCurrent()
+ + fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified());
+ stats.setMagicka (magicka);
+ }
+ }
+ }
+
+ void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr)
+ {
+ CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
+ const MagicEffects &effects = creatureStats.getMagicEffects();
+
+ // attributes
+ for(int i = 0;i < ESM::Attribute::Length;++i)
+ {
+ Stat<int> stat = creatureStats.getAttribute(i);
+ stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude -
+ effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude);
+
+ creatureStats.setAttribute(i, stat);
+ }
+
+ // dynamic stats
+ 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);
+
+ creatureStats.setDynamic(i, stat);
+ }
+ }
+
+ void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ NpcStats &stats = ptr.getClass().getNpcStats(ptr);
+ if(world->isSubmerged(ptr) &&
+ stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).mMagnitude == 0)
+ {
+ float timeLeft = 0.0f;
+ if(stats.getFatigue().getCurrent() == 0)
+ stats.setTimeToStartDrowning(0);
+ else
+ {
+ timeLeft = stats.getTimeToStartDrowning() - duration;
+ if(timeLeft < 0.0f)
+ timeLeft = 0.0f;
+ stats.setTimeToStartDrowning(timeLeft);
+ }
+ if(timeLeft == 0.0f)
+ {
+ // If drowning, apply 3 points of damage per second
+ ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - 3.0f*duration);
+
+ // Play a drowning sound as necessary for the player
+ if(ptr == world->getPlayer().getPlayer())
+ {
+ MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager();
+ if(!sndmgr->getSoundPlaying(MWWorld::Ptr(), "drown"))
+ sndmgr->playSound("drown", 1.0f, 1.0f);
+ }
+ }
+ }
+ else
+ stats.setTimeToStartDrowning(20);
+ }
+
+ Actors::Actors() : mDuration (0) {}
+
+ void Actors::addActor (const MWWorld::Ptr& ptr)
+ {
+ // erase previous death events since we are currently only tracking them while in an active cell
+ MWWorld::Class::get(ptr).getCreatureStats(ptr).clearHasDied();
+
+ removeActor(ptr);
+
+ MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr);
+ mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim)));
+ }
+
+ void Actors::removeActor (const MWWorld::Ptr& ptr)
+ {
+ PtrControllerMap::iterator iter = mActors.find(ptr);
+ if(iter != mActors.end())
+ {
+ delete iter->second;
+ mActors.erase(iter);
+ }
+ }
+
+ void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
+ {
+ PtrControllerMap::iterator iter = mActors.find(old);
+ if(iter != mActors.end())
+ {
+ CharacterController *ctrl = iter->second;
+ mActors.erase(iter);
+
+ ctrl->updatePtr(ptr);
+ mActors.insert(std::make_pair(ptr, ctrl));
+ }
+ }
+
+ void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore)
+ {
+ PtrControllerMap::iterator iter = mActors.begin();
+ while(iter != mActors.end())
+ {
+ if(iter->first.getCell()==cellStore)
+ {
+ delete iter->second;
+ mActors.erase(iter++);
+ }
+ else
+ ++iter;
+ }
+ }
+
+ void Actors::update (float duration, bool paused)
+ {
+ mDuration += duration;
+
+ //if (mDuration>=0.25)
+ {
+ float totalDuration = mDuration;
+ mDuration = 0;
+
+ for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
+ {
+ const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
+ CreatureStats &stats = cls.getCreatureStats(iter->first);
+
+ stats.setLastHitObject(std::string());
+ if(!stats.isDead())
+ {
+ if(iter->second->isDead())
+ iter->second->resurrect();
+
+ updateActor(iter->first, totalDuration);
+ if(iter->first.getTypeName() == typeid(ESM::NPC).name())
+ updateNpc(iter->first, totalDuration, paused);
+
+ if(!stats.isDead())
+ continue;
+ }
+
+ // workaround: always keep player alive for now
+ // \todo remove workaround, once player death can be handled
+ if(iter->first.getRefData().getHandle()=="player")
+ {
+ MWMechanics::DynamicStat<float> stat(stats.getHealth());
+
+ if(stat.getModified()<1)
+ {
+ stat.setModified(1, 0);
+ stats.setHealth(stat);
+ }
+
+ stats.resurrect();
+ continue;
+ }
+
+ if(iter->second->isDead())
+ continue;
+
+ iter->second->kill();
+
+ ++mDeathCount[cls.getId(iter->first)];
+
+ if(cls.isEssential(iter->first))
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
+ }
+ }
+
+ if(!paused)
+ {
+ 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)
+ calculateRestoration(iter->first, 3600);
+ }
+
+ int Actors::countDeaths (const std::string& id) const
+ {
+ std::map<std::string, int>::const_iterator iter = mDeathCount.find(id);
+ if(iter != mDeathCount.end())
+ return iter->second;
+ return 0;
+ }
+
+ void Actors::forceStateUpdate(const MWWorld::Ptr & ptr)
+ {
+ PtrControllerMap::iterator iter = mActors.find(ptr);
+ if(iter != mActors.end())
+ iter->second->forceStateUpdate();
+ }
+
+ void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number)
+ {
+ PtrControllerMap::iterator iter = mActors.find(ptr);
+ if(iter != mActors.end())
+ iter->second->playGroup(groupName, mode, number);
+ }
+ void Actors::skipAnimation(const MWWorld::Ptr& ptr)
+ {
+ PtrControllerMap::iterator iter = mActors.find(ptr);
+ if(iter != mActors.end())
+ iter->second->skipAnim();
+ }
+
+ bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName)
+ {
+ PtrControllerMap::iterator iter = mActors.find(ptr);
+ if(iter != mActors.end())
+ return iter->second->isAnimPlaying(groupName);
+ return false;
+ }
+}
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
new file mode 100644
index 0000000000..69878a000e
--- /dev/null
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -0,0 +1,88 @@
+#ifndef GAME_MWMECHANICS_ACTORS_H
+#define GAME_MWMECHANICS_ACTORS_H
+
+#include <set>
+#include <vector>
+#include <string>
+#include <map>
+
+#include "character.hpp"
+#include "movement.hpp"
+#include "../mwbase/world.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace MWMechanics
+{
+ class Actors
+ {
+ typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
+ PtrControllerMap mActors;
+
+ std::map<std::string, int> mDeathCount;
+
+ float mDuration;
+
+ 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 calculateRestoration (const MWWorld::Ptr& ptr, float duration);
+
+ void updateDrowning (const MWWorld::Ptr& ptr, float duration);
+
+ public:
+
+ Actors();
+
+ void addActor (const MWWorld::Ptr& ptr);
+ ///< Register an actor for stats management
+ ///
+ /// \note Dead actors are ignored.
+
+ void removeActor (const MWWorld::Ptr& ptr);
+ ///< Deregister an actor for stats management
+ ///
+ /// \note Ignored, if \a ptr is not a registered actor.
+
+ 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 update (float duration, bool paused);
+ ///< Update actor stats and store desired velocity vectors in \a movement
+
+ void updateActor (const MWWorld::Ptr& ptr, float duration);
+ ///< This function is normally called automatically during the update process, but it can
+ /// also be called explicitly at any time to force an update.
+
+ void restoreDynamicStats();
+ ///< If the player is sleeping, this should be called every hour.
+
+ int countDeaths (const std::string& id) const;
+ ///< Return the number of deaths for actors with the given ID.
+
+ void forceStateUpdate(const MWWorld::Ptr &ptr);
+
+ void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
+ void skipAnimation(const MWWorld::Ptr& ptr);
+ bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp
new file mode 100644
index 0000000000..b94c8c2599
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiactivate.cpp
@@ -0,0 +1,21 @@
+#include "aiactivate.hpp"
+#include <iostream>
+
+MWMechanics::AiActivate::AiActivate(const std::string &objectId)
+: mObjectId(objectId)
+{
+}
+MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
+{
+ return new AiActivate(*this);
+}
+bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor)
+{
+ std::cout << "AiActivate completed.\n";
+ return true;
+}
+
+int MWMechanics::AiActivate::getTypeId() const
+{
+ return 4;
+}
diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp
new file mode 100644
index 0000000000..7f3d4016dc
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiactivate.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_MWMECHANICS_AIACTIVATE_H
+#define GAME_MWMECHANICS_AIACTIVATE_H
+
+#include "aipackage.hpp"
+#include <string>
+
+namespace MWMechanics
+{
+
+ class AiActivate : public AiPackage
+ {
+ public:
+ AiActivate(const std::string &objectId);
+ virtual AiActivate *clone() const;
+ virtual bool execute (const MWWorld::Ptr& actor);
+ ///< \return Package completed?
+ virtual int getTypeId() const;
+
+ private:
+ std::string mObjectId;
+ };
+}
+#endif // GAME_MWMECHANICS_AIACTIVATE_H
diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp
new file mode 100644
index 0000000000..556e0b1267
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiescort.cpp
@@ -0,0 +1,184 @@
+#include "aiescort.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"
+
+namespace
+{
+ float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+}
+
+/*
+ TODO: Test vanilla behavior on passing x0, y0, and z0 with duration of anything including 0.
+ TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z.
+ TODO: Take account for actors being in different cells.
+*/
+
+namespace MWMechanics
+{
+ AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z)
+ : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration)
+ , cellX(std::numeric_limits<int>::max())
+ , cellY(std::numeric_limits<int>::max())
+ {
+ mMaxDist = 470;
+
+ // The CS Help File states that if a duration is givin, the AI package will run for that long
+ // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location.
+ if(mX != 0 || mY != 0 || mZ != 0)
+ mDuration = 0;
+
+ else
+ {
+ MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp();
+ mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100);
+ }
+ }
+
+ AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z)
+ : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration)
+ , cellX(std::numeric_limits<int>::max())
+ , cellY(std::numeric_limits<int>::max())
+ {
+ mMaxDist = 470;
+
+ // The CS Help File states that if a duration is givin, the AI package will run for that long
+ // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location.
+ if(mX != 0 || mY != 0 || mZ != 0)
+ mDuration = 0;
+
+ else
+ {
+ MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp();
+ mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100);
+ }
+ }
+
+
+ AiEscort *MWMechanics::AiEscort::clone() const
+ {
+ return new AiEscort(*this);
+ }
+
+ bool AiEscort::execute (const MWWorld::Ptr& actor)
+ {
+ // If AiEscort has ran for as long or longer then the duration specified
+ // and the duration is not infinite, the package is complete.
+ if(mDuration != 0)
+ {
+ MWWorld::TimeStamp current = MWBase::Environment::get().getWorld()->getTimeStamp();
+ unsigned int currentSecond = ((current.getHour() - int(current.getHour())) * 100);
+ if(currentSecond - mStartingSecond >= mDuration)
+ return true;
+ }
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ ESM::Position pos = actor.getRefData().getPosition();
+ bool cellChange = actor.getCell()->mCell->mData.mX != cellX || actor.getCell()->mCell->mData.mY != cellY;
+ const ESM::Pathgrid *pathgrid =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
+
+ if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX)
+ {
+ int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX);
+ // Check if actor is near the border of an inactive cell. If so, disable AiEscort.
+ // FIXME: This *should* pause the AiEscort package instead of terminating it.
+ if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE /
+ 2.0 - 200))
+ {
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ return true;
+ }
+ }
+ if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY)
+ {
+ int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY);
+ // Check if actor is near the border of an inactive cell. If so, disable AiEscort.
+ // FIXME: This *should* pause the AiEscort package instead of terminating it.
+ if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE /
+ 2.0 - 200))
+ {
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ return true;
+ }
+ }
+
+
+ if(!mPathFinder.isPathConstructed() || cellChange)
+ {
+ cellX = actor.getCell()->mCell->mData.mX;
+ cellY = actor.getCell()->mCell->mData.mY;
+ 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 = mX;
+ dest.mY = mY;
+ dest.mZ = mZ;
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ }
+
+ if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
+ {
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ return true;
+ }
+
+ const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
+ const float* const leaderPos = actor.getRefData().getPosition().pos;
+ const float* const followerPos = follower.getRefData().getPosition().pos;
+ double differenceBetween[3];
+
+ for (short counter = 0; counter < 3; counter++)
+ differenceBetween[counter] = (leaderPos[counter] - followerPos[counter]);
+
+ float distanceBetweenResult =
+ (differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] *
+ differenceBetween[2]);
+
+ if(distanceBetweenResult <= mMaxDist * mMaxDist)
+ {
+ 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;
+ mMaxDist = 470;
+ }
+ else
+ {
+ // Stop moving if the player is to far away
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ mMaxDist = 330;
+ }
+
+ return false;
+ }
+
+ int AiEscort::getTypeId() const
+ {
+ return 2;
+ }
+}
+
diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp
new file mode 100644
index 0000000000..3ae604035a
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiescort.hpp
@@ -0,0 +1,41 @@
+#ifndef GAME_MWMECHANICS_AIESCORT_H
+#define GAME_MWMECHANICS_AIESCORT_H
+
+#include "aipackage.hpp"
+#include <string>
+
+#include "pathfinding.hpp"
+
+namespace MWMechanics
+{
+ class AiEscort : public AiPackage
+ {
+ public:
+ AiEscort(const std::string &actorId,int duration, float x, float y, float z);
+ ///< \implement AiEscort
+ AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z);
+ ///< \implement AiEscortCell
+
+ virtual AiEscort *clone() const;
+
+ virtual bool execute (const MWWorld::Ptr& actor);
+ ///< \return Package completed?
+
+ virtual int getTypeId() const;
+
+ private:
+ std::string mActorId;
+ std::string mCellId;
+ float mX;
+ float mY;
+ float mZ;
+ float mMaxDist;
+ unsigned int mStartingSecond;
+ unsigned int mDuration;
+
+ PathFinder mPathFinder;
+ int cellX;
+ int cellY;
+ };
+}
+#endif
diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp
new file mode 100644
index 0000000000..dab9e02839
--- /dev/null
+++ b/apps/openmw/mwmechanics/aifollow.cpp
@@ -0,0 +1,27 @@
+#include "aifollow.hpp"
+#include <iostream>
+
+MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
+: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId)
+{
+}
+MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
+: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId)
+{
+}
+
+MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const
+{
+ return new AiFollow(*this);
+}
+
+ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor)
+{
+ std::cout << "AiFollow completed.\n";
+ return true;
+}
+
+ int MWMechanics::AiFollow::getTypeId() const
+{
+ return 3;
+}
diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp
new file mode 100644
index 0000000000..0b37b0a2d9
--- /dev/null
+++ b/apps/openmw/mwmechanics/aifollow.hpp
@@ -0,0 +1,29 @@
+#ifndef GAME_MWMECHANICS_AIFALLOW_H
+#define GAME_MWMECHANICS_AIFALLOW_H
+
+#include "aipackage.hpp"
+#include <string>
+
+namespace MWMechanics
+{
+
+ class AiFollow : public AiPackage
+ {
+ public:
+ 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);
+ ///< \return Package completed?
+ virtual int getTypeId() const;
+
+ private:
+ float mDuration;
+ float mX;
+ float mY;
+ float mZ;
+ std::string mActorId;
+ std::string mCellId;
+ };
+}
+#endif
diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp
new file mode 100644
index 0000000000..8193a670be
--- /dev/null
+++ b/apps/openmw/mwmechanics/aipackage.cpp
@@ -0,0 +1,4 @@
+
+#include "aipackage.hpp"
+
+MWMechanics::AiPackage::~AiPackage() {}
diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp
new file mode 100644
index 0000000000..d286fbba89
--- /dev/null
+++ b/apps/openmw/mwmechanics/aipackage.hpp
@@ -0,0 +1,29 @@
+#ifndef GAME_MWMECHANICS_AIPACKAGE_H
+#define GAME_MWMECHANICS_AIPACKAGE_H
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWMechanics
+{
+ /// \brief Base class for AI packages
+ class AiPackage
+ {
+ public:
+
+ virtual ~AiPackage();
+
+ virtual AiPackage *clone() const = 0;
+
+ virtual bool execute (const MWWorld::Ptr& actor) = 0;
+ ///< \return Package completed?
+
+ virtual int getTypeId() const = 0;
+ ///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
+ };
+}
+
+#endif
+
diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp
new file mode 100644
index 0000000000..2f06b849a3
--- /dev/null
+++ b/apps/openmw/mwmechanics/aisequence.cpp
@@ -0,0 +1,122 @@
+
+#include "aisequence.hpp"
+
+#include "aipackage.hpp"
+
+#include "aiwander.hpp"
+#include "aiescort.hpp"
+#include "aitravel.hpp"
+#include "aifollow.hpp"
+#include "aiactivate.hpp"
+
+void MWMechanics::AiSequence::copy (const AiSequence& sequence)
+{
+ for (std::list<AiPackage *>::const_iterator iter (sequence.mPackages.begin());
+ iter!=sequence.mPackages.end(); ++iter)
+ mPackages.push_back ((*iter)->clone());
+}
+
+MWMechanics::AiSequence::AiSequence() : mDone (false) {}
+
+MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false)
+{
+ copy (sequence);
+}
+
+MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& sequence)
+{
+ if (this!=&sequence)
+ {
+ clear();
+ copy (sequence);
+ }
+
+ return *this;
+}
+
+MWMechanics::AiSequence::~AiSequence()
+{
+ clear();
+}
+
+int MWMechanics::AiSequence::getTypeId() const
+{
+ if (mPackages.empty())
+ return -1;
+
+ return mPackages.front()->getTypeId();
+}
+
+bool MWMechanics::AiSequence::isPackageDone() const
+{
+ return mDone;
+}
+
+void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor)
+{
+ if (!mPackages.empty())
+ {
+ if (mPackages.front()->execute (actor))
+ {
+ mPackages.erase (mPackages.begin());
+ mDone = true;
+ }
+ else
+ mDone = false;
+ }
+}
+
+void MWMechanics::AiSequence::clear()
+{
+ for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
+ delete *iter;
+
+ mPackages.clear();
+}
+
+void MWMechanics::AiSequence::stack (const AiPackage& package)
+{
+ mPackages.push_front (package.clone());
+}
+
+void MWMechanics::AiSequence::queue (const AiPackage& package)
+{
+ mPackages.push_back (package.clone());
+}
+
+void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list)
+{
+ for (std::vector<ESM::AIPackage>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it)
+ {
+ MWMechanics::AiPackage* package;
+ if (it->mType == ESM::AI_Wander)
+ {
+ ESM::AIWander data = it->mWander;
+ std::vector<int> idles;
+ for (int i=0; i<8; ++i)
+ idles.push_back(data.mIdle[i]);
+ package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mUnk);
+ }
+ else if (it->mType == ESM::AI_Escort)
+ {
+ ESM::AITarget data = it->mTarget;
+ package = new MWMechanics::AiEscort(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
+ }
+ else if (it->mType == ESM::AI_Travel)
+ {
+ ESM::AITravel data = it->mTravel;
+ package = new MWMechanics::AiTravel(data.mX, data.mY, data.mZ);
+ }
+ else if (it->mType == ESM::AI_Activate)
+ {
+ ESM::AIActivate data = it->mActivate;
+ package = new MWMechanics::AiActivate(data.mName.toString());
+ }
+ else //if (it->mType == ESM::AI_Follow)
+ {
+ ESM::AITarget data = it->mTarget;
+ package = new MWMechanics::AiFollow(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ);
+ }
+ mPackages.push_back(package);
+ }
+}
diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp
new file mode 100644
index 0000000000..9f70daeb83
--- /dev/null
+++ b/apps/openmw/mwmechanics/aisequence.hpp
@@ -0,0 +1,58 @@
+#ifndef GAME_MWMECHANICS_AISEQUENCE_H
+#define GAME_MWMECHANICS_AISEQUENCE_H
+
+#include <list>
+
+#include <components/esm/loadnpc.hpp>
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWMechanics
+{
+ class AiPackage;
+
+ /// \brief Sequence of AI-packages for a single actor
+ class AiSequence
+ {
+ std::list<AiPackage *> mPackages;
+ bool mDone;
+
+ void copy (const AiSequence& sequence);
+
+ public:
+
+ AiSequence();
+
+ AiSequence (const AiSequence& sequence);
+
+ AiSequence& operator= (const AiSequence& sequence);
+
+ virtual ~AiSequence();
+
+ int getTypeId() const;
+ ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
+
+ bool isPackageDone() const;
+ ///< Has a package been completed during the last update?
+
+ void execute (const MWWorld::Ptr& actor);
+ ///< Execute package.
+
+ void clear();
+ ///< Remove all packages.
+
+ void stack (const AiPackage& package);
+ ///< Add \a package to the front of the sequence (suspends current package)
+
+ void queue (const AiPackage& package);
+ ///< Add \a package to the end of the sequence (executed after all other packages have been
+ /// completed)
+
+ void fill (const ESM::AIPackageList& list);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp
new file mode 100644
index 0000000000..d47a49c702
--- /dev/null
+++ b/apps/openmw/mwmechanics/aitravel.cpp
@@ -0,0 +1,111 @@
+#include "aitravel.hpp"
+
+#include "movement.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+namespace
+{
+ float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+}
+
+namespace MWMechanics
+{
+ AiTravel::AiTravel(float x, float y, float z)
+ : mX(x),mY(y),mZ(z),mPathFinder()
+ , cellX(std::numeric_limits<int>::max())
+ , cellY(std::numeric_limits<int>::max())
+ {
+ }
+
+ AiTravel *MWMechanics::AiTravel::clone() const
+ {
+ return new AiTravel(*this);
+ }
+
+ bool AiTravel::execute (const MWWorld::Ptr& actor)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ ESM::Position pos = actor.getRefData().getPosition();
+ Movement &movement = actor.getClass().getMovementSettings(actor);
+ const ESM::Cell *cell = actor.getCell()->mCell;
+
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ if(cell->mData.mX != player.getCell()->mCell->mData.mX)
+ {
+ int sideX = sgn(cell->mData.mX - player.getCell()->mCell->mData.mX);
+ //check if actor is near the border of an inactive cell. If so, stop walking.
+ if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
+ sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
+ {
+ movement.mPosition[1] = 0;
+ return false;
+ }
+ }
+ if(cell->mData.mY != player.getCell()->mCell->mData.mY)
+ {
+ int sideY = sgn(cell->mData.mY - player.getCell()->mCell->mData.mY);
+ //check if actor is near the border of an inactive cell. If so, stop walking.
+ if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
+ sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
+ {
+ movement.mPosition[1] = 0;
+ return false;
+ }
+ }
+
+ const ESM::Pathgrid *pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell);
+ bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY;
+ if(!mPathFinder.isPathConstructed() || cellChange)
+ {
+ cellX = cell->mData.mX;
+ cellY = cell->mData.mY;
+ float xCell = 0;
+ float yCell = 0;
+
+ if(cell->isExterior())
+ {
+ xCell = cell->mData.mX * ESM::Land::REAL_SIZE;
+ yCell = cell->mData.mY * ESM::Land::REAL_SIZE;
+ }
+
+ ESM::Pathgrid::Point dest;
+ dest.mX = mX;
+ dest.mY = mY;
+ dest.mZ = mZ;
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ }
+
+ if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
+ {
+ movement.mPosition[1] = 0;
+ return true;
+ }
+
+ float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
+ world->rotateObject(actor, 0, 0, zAngle, false);
+ movement.mPosition[1] = 1;
+
+ return false;
+ }
+
+ int AiTravel::getTypeId() const
+ {
+ return 1;
+ }
+}
+
diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp
new file mode 100644
index 0000000000..6eb9af8cec
--- /dev/null
+++ b/apps/openmw/mwmechanics/aitravel.hpp
@@ -0,0 +1,33 @@
+#ifndef GAME_MWMECHANICS_AITRAVEL_H
+#define GAME_MWMECHANICS_AITRAVEL_H
+
+#include "aipackage.hpp"
+
+#include "pathfinding.hpp"
+
+namespace MWMechanics
+{
+ class AiTravel : public AiPackage
+ {
+ public:
+ AiTravel(float x, float y, float z);
+ virtual AiTravel *clone() const;
+
+ virtual bool execute (const MWWorld::Ptr& actor);
+ ///< \return Package completed?
+
+ virtual int getTypeId() const;
+
+ private:
+ float mX;
+ float mY;
+ float mZ;
+
+ int cellX;
+ int cellY;
+
+ PathFinder mPathFinder;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp
new file mode 100644
index 0000000000..96a41883b9
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiwander.cpp
@@ -0,0 +1,306 @@
+#include "aiwander.hpp"
+
+#include "movement.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include <OgreVector3.h>
+
+namespace
+{
+ float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+}
+
+namespace MWMechanics
+{
+ AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat):
+ mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
+ , mCellX(std::numeric_limits<int>::max())
+ , mCellY(std::numeric_limits<int>::max())
+ , mXCell(0)
+ , mYCell(0)
+ , mX(0)
+ , mY(0)
+ , mZ(0)
+ {
+ for(unsigned short counter = 0; counter < mIdle.size(); counter++)
+ {
+ if(mIdle[counter] >= 127 || mIdle[counter] < 0)
+ mIdle[counter] = 0;
+ }
+
+ if(mDistance < 0)
+ mDistance = 0;
+ if(mDuration < 0)
+ mDuration = 0;
+ if(mDuration == 0)
+ mTimeOfDay = 0;
+
+ mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
+ mPlayedIdle = 0;
+ mPathgrid = NULL;
+ mIdleChanceMultiplier =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->getFloat();
+
+ mStoredAvailableNodes = false;
+ mChooseAction = true;
+ mIdleNow = false;
+ mMoveNow = false;
+ mWalking = false;
+ }
+
+ AiPackage * MWMechanics::AiWander::clone() const
+ {
+ return new AiWander(*this);
+ }
+
+ bool AiWander::execute (const MWWorld::Ptr& actor)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ if(mDuration)
+ {
+ // End package if duration is complete or mid-night hits:
+ MWWorld::TimeStamp currentTime = world->getTimeStamp();
+ if(currentTime.getHour() >= mStartTime.getHour() + mDuration)
+ {
+ if(!mRepeat)
+ {
+ stopWalking(actor);
+ return true;
+ }
+ else
+ mStartTime = currentTime;
+ }
+ else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
+ {
+ if(!mRepeat)
+ {
+ stopWalking(actor);
+ return true;
+ }
+ else
+ mStartTime = currentTime;
+ }
+ }
+
+ ESM::Position pos = actor.getRefData().getPosition();
+
+ if(!mStoredAvailableNodes)
+ {
+ mStoredAvailableNodes = true;
+ mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
+
+ mCellX = actor.getCell()->mCell->mData.mX;
+ mCellY = actor.getCell()->mCell->mData.mY;
+
+ if(!mPathgrid)
+ mDistance = 0;
+ else if(mPathgrid->mPoints.empty())
+ mDistance = 0;
+
+ if(mDistance)
+ {
+ mXCell = 0;
+ mYCell = 0;
+ if(actor.getCell()->mCell->isExterior())
+ {
+ mXCell = mCellX * ESM::Land::REAL_SIZE;
+ mYCell = mCellY * ESM::Land::REAL_SIZE;
+ }
+
+ Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
+ npcPos[0] = npcPos[0] - mXCell;
+ npcPos[1] = npcPos[1] - mYCell;
+
+ for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++)
+ {
+ Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY,
+ mPathgrid->mPoints[counter].mZ);
+ if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
+ mAllowedNodes.push_back(mPathgrid->mPoints[counter]);
+ }
+ if(!mAllowedNodes.empty())
+ {
+ Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ);
+ float closestNode = npcPos.squaredDistance(firstNodePos);
+ unsigned int index = 0;
+ for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
+ {
+ Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY,
+ mAllowedNodes[counterThree].mZ);
+ float tempDist = npcPos.squaredDistance(nodePos);
+ if(tempDist < closestNode)
+ index = counterThree;
+ }
+ mCurrentNode = mAllowedNodes[index];
+ mAllowedNodes.erase(mAllowedNodes.begin() + index);
+ }
+
+ 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;
+
+ if(mChooseAction)
+ {
+ mPlayedIdle = 0;
+ unsigned short idleRoll = 0;
+
+ for(unsigned int counter = 0; counter < mIdle.size(); counter++)
+ {
+ unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter];
+ unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier));
+ if(randSelect < idleChance && randSelect > idleRoll)
+ {
+ mPlayedIdle = counter+2;
+ idleRoll = randSelect;
+ }
+ }
+
+ if(!mPlayedIdle && mDistance)
+ {
+ mChooseAction = false;
+ mMoveNow = true;
+ }
+ else
+ {
+ // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
+ MWWorld::TimeStamp currentTime = world->getTimeStamp();
+ mStartTime = currentTime;
+ playIdle(actor, mPlayedIdle);
+ mChooseAction = false;
+ mIdleNow = true;
+ }
+ }
+
+ if(mIdleNow)
+ {
+ if(!checkIdle(actor, mPlayedIdle))
+ {
+ mPlayedIdle = 0;
+ mIdleNow = false;
+ mChooseAction = true;
+ }
+ }
+
+ if(mMoveNow && mDistance)
+ {
+ if(!mPathFinder.isPathConstructed())
+ {
+ unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size());
+ Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ);
+
+ ESM::Pathgrid::Point dest;
+ dest.mX = destNodePos[0] + mXCell;
+ dest.mY = destNodePos[1] + mYCell;
+ dest.mZ = destNodePos[2];
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ mPathFinder.buildPath(start, dest, mPathgrid, mXCell, mYCell, false);
+
+ if(mPathFinder.isPathConstructed())
+ {
+ // Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
+ ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
+ mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
+ mAllowedNodes.push_back(mCurrentNode);
+ mCurrentNode = temp;
+
+ mMoveNow = false;
+ mWalking = true;
+ }
+ // Choose a different node and delete this one from possible nodes because it is uncreachable:
+ else
+ mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
+ }
+ }
+
+ if(mWalking)
+ {
+ float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
+ world->rotateObject(actor, 0, 0, zAngle, false);
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
+
+ if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
+ {
+ stopWalking(actor);
+ mMoveNow = false;
+ mWalking = false;
+ mChooseAction = true;
+ }
+ }
+
+ return false;
+ }
+
+ int AiWander::getTypeId() const
+ {
+ return 0;
+ }
+
+ void AiWander::stopWalking(const MWWorld::Ptr& actor)
+ {
+ mPathFinder.clearPath();
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
+ }
+
+ void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
+ {
+ if(idleSelect == 2)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle2", 0, 1);
+ else if(idleSelect == 3)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
+ else if(idleSelect == 4)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle4", 0, 1);
+ else if(idleSelect == 5)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle5", 0, 1);
+ else if(idleSelect == 6)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle6", 0, 1);
+ else if(idleSelect == 7)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle7", 0, 1);
+ else if(idleSelect == 8)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle8", 0, 1);
+ else if(idleSelect == 9)
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle9", 0, 1);
+ }
+
+ bool AiWander::checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
+ {
+ if(idleSelect == 2)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle2");
+ else if(idleSelect == 3)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle3");
+ else if(idleSelect == 4)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle4");
+ else if(idleSelect == 5)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle5");
+ else if(idleSelect == 6)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle6");
+ else if(idleSelect == 7)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle7");
+ else if(idleSelect == 8)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle8");
+ else if(idleSelect == 9)
+ return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle9");
+ else
+ return false;
+ }
+}
+
diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp
new file mode 100644
index 0000000000..c82ccc2155
--- /dev/null
+++ b/apps/openmw/mwmechanics/aiwander.hpp
@@ -0,0 +1,64 @@
+#ifndef GAME_MWMECHANICS_AIWANDER_H
+#define GAME_MWMECHANICS_AIWANDER_H
+
+#include "aipackage.hpp"
+#include <vector>
+
+#include "pathfinding.hpp"
+
+#include "../mwworld/timestamp.hpp"
+
+namespace MWMechanics
+{
+ class AiWander : public AiPackage
+ {
+ public:
+
+ 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);
+ ///< \return Package completed?
+ virtual int getTypeId() const;
+ ///< 0: Wander
+
+ private:
+ void stopWalking(const MWWorld::Ptr& actor);
+ void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
+ bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
+
+ int mDistance;
+ int mDuration;
+ int mTimeOfDay;
+ std::vector<int> mIdle;
+ bool mRepeat;
+
+ float mX;
+ float mY;
+ float mZ;
+
+ int mCellX;
+ int mCellY;
+ float mXCell;
+ float mYCell;
+
+ bool mStoredAvailableNodes;
+ bool mChooseAction;
+ bool mIdleNow;
+ bool mMoveNow;
+ bool mWalking;
+
+ float mIdleChanceMultiplier;
+ unsigned short mPlayedIdle;
+
+ MWWorld::TimeStamp mStartTime;
+
+ std::vector<ESM::Pathgrid::Point> mAllowedNodes;
+ ESM::Pathgrid::Point mCurrentNode;
+
+ PathFinder mPathFinder;
+ const ESM::Pathgrid *mPathgrid;
+
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp
new file mode 100644
index 0000000000..1d992be413
--- /dev/null
+++ b/apps/openmw/mwmechanics/alchemy.cpp
@@ -0,0 +1,462 @@
+
+#include "alchemy.hpp"
+
+#include <cassert>
+#include <cstdlib>
+
+#include <algorithm>
+#include <stdexcept>
+#include <map>
+
+#include <components/esm/loadskil.hpp>
+#include <components/esm/loadappa.hpp>
+#include <components/esm/loadgmst.hpp>
+#include <components/esm/loadmgef.hpp>
+
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/cellstore.hpp"
+#include "../mwworld/manualref.hpp"
+
+#include "magiceffects.hpp"
+#include "creaturestats.hpp"
+#include "npcstats.hpp"
+
+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)
+ {
+ EffectKey key (
+ ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ?
+ ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]);
+
+ ++effects[key];
+ }
+ }
+ }
+
+ 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;
+}
+
+void MWMechanics::Alchemy::applyTools (int flags, float& value) const
+{
+ bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude);
+ bool duration = !(flags & ESM::MagicEffect::NoDuration);
+ bool negative = flags & (ESM::MagicEffect::Negative | ESM::MagicEffect::Harmful);
+
+ 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())
+ setup = 2;
+ else if (!mTools[ESM::Apparatus::Calcinator].isEmpty())
+ setup = 3;
+ else
+ return;
+
+ float toolQuality = setup==1 || setup==2 ? mTools[tool].get<ESM::Apparatus>()->mBase->mData.mQuality : 0;
+ float calcinatorQuality = setup==1 || setup==3 ?
+ 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;
+ }
+
+ if (setup==3 || !negative)
+ {
+ value += quality;
+ }
+ else
+ {
+ if (quality==0)
+ throw std::runtime_error ("invalid derived alchemy apparatus quality");
+
+ value /= quality;
+ }
+}
+
+void MWMechanics::Alchemy::updateEffects()
+{
+ mEffects.clear();
+ mValue = 0;
+
+ if (countIngredients()<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty())
+ return;
+
+ // find effects
+ std::set<EffectKey> effects (listEffects());
+
+ // general alchemy factor
+ float x = getChance();
+
+ x *= mTools[ESM::Apparatus::MortarPestle].get<ESM::Apparatus>()->mBase->mData.mQuality;
+ x *= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fPotionStrengthMult")->getFloat();
+
+ // value
+ mValue = static_cast<int> (
+ x * MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("iAlchemyMod")->getFloat());
+
+ // build quantified effect list
+ 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);
+
+ 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();
+
+ if (fPotionT1DurMult<=0)
+ throw std::runtime_error ("invalid gmst: fPotionT1DurMult");
+
+ float magnitude = magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude ?
+ 1 : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost;
+ float duration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ?
+ 1 : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost;
+
+ if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
+ applyTools (magicEffect->mData.mFlags, magnitude);
+
+ if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
+ applyTools (magicEffect->mData.mFlags, duration);
+
+ 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.mDuration = duration;
+ effect.mMagnMin = effect.mMagnMax = magnitude;
+
+ mEffects.push_back (effect);
+ }
+ }
+}
+
+const ESM::Potion *MWMechanics::Alchemy::getRecord() const
+{
+ const MWWorld::Store<ESM::Potion> &potions =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>();
+
+ MWWorld::Store<ESM::Potion>::iterator iter = potions.begin();
+ for (; iter != potions.end(); ++iter)
+ {
+ if (iter->mEffects.mList.size() != mEffects.size())
+ continue;
+
+ 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 ||
+ first.mSkill!=second.mSkill ||
+ first.mAttribute!=second.mAttribute ||
+ first.mMagnMin!=second.mMagnMin ||
+ first.mMagnMax!=second.mMagnMax ||
+ first.mDuration!=second.mDuration)
+ {
+ mismatch = true;
+ 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);
+ if (iter->getRefData().getCount()<1)
+ {
+ needsUpdate = true;
+ *iter = MWWorld::Ptr();
+ }
+ }
+
+ if (needsUpdate)
+ updateEffects();
+}
+
+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);
+ 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);
+}
+
+void MWMechanics::Alchemy::increaseSkill()
+{
+ MWWorld::Class::get (mAlchemist).skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0);
+}
+
+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()
+ + 0.1 * creatureStats.getAttribute (7).getModified());
+}
+
+int MWMechanics::Alchemy::countIngredients() const
+{
+ int ingredients = 0;
+
+ for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter)
+ if (!iter->isEmpty())
+ ++ingredients;
+
+ return ingredients;
+}
+
+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;
+ }
+}
+
+MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::beginTools() const
+{
+ return mTools.begin();
+}
+
+MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::endTools() const
+{
+ return mTools.end();
+}
+
+MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::beginIngredients() const
+{
+ return mIngredients.begin();
+}
+
+MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients() const
+{
+ return mIngredients.end();
+}
+
+void MWMechanics::Alchemy::clear()
+{
+ mAlchemist = MWWorld::Ptr();
+ mTools.clear();
+ mIngredients.clear();
+ mEffects.clear();
+}
+
+int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient)
+{
+ // find a free slot
+ int slot = -1;
+
+ for (int i=0; i<static_cast<int> (mIngredients.size()); ++i)
+ if (mIngredients[i].isEmpty())
+ {
+ 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;
+}
+
+void MWMechanics::Alchemy::removeIngredient (int index)
+{
+ if (index>=0 && index<static_cast<int> (mIngredients.size()))
+ {
+ mIngredients[index] = MWWorld::Ptr();
+ updateEffects();
+ }
+}
+
+MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const
+{
+ return mEffects.begin();
+}
+
+MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const
+{
+ return mEffects.end();
+}
+
+std::string MWMechanics::Alchemy::getPotionName() const
+{
+ if (const ESM::Potion *potion = getRecord())
+ return potion->mName;
+
+ return "";
+}
+
+MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name)
+{
+ 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;
+
+ if (getChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
+ {
+ removeIngredients();
+ return Result_RandomFailure;
+ }
+
+ addPotion (name);
+
+ removeIngredients();
+
+ increaseSkill();
+
+ return Result_Success;
+}
diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp
new file mode 100644
index 0000000000..957e3122c7
--- /dev/null
+++ b/apps/openmw/mwmechanics/alchemy.hpp
@@ -0,0 +1,118 @@
+#ifndef GAME_MWMECHANICS_ALCHEMY_H
+#define GAME_MWMECHANICS_ALCHEMY_H
+
+#include <vector>
+#include <set>
+
+#include <components/esm/effectlist.hpp>
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWMechanics
+{
+ struct EffectKey;
+
+ /// \brief Potion creation via alchemy skill
+ class Alchemy
+ {
+ public:
+
+ typedef std::vector<MWWorld::Ptr> TToolsContainer;
+ typedef TToolsContainer::const_iterator TToolsIterator;
+
+ typedef std::vector<MWWorld::Ptr> TIngredientsContainer;
+ typedef TIngredientsContainer::const_iterator TIngredientsIterator;
+
+ typedef std::vector<ESM::ENAMstruct> TEffectsContainer;
+ typedef TEffectsContainer::const_iterator TEffectsIterator;
+
+ enum Result
+ {
+ Result_Success,
+
+ Result_NoMortarAndPestle,
+ Result_LessThanTwoIngredients,
+ Result_NoName,
+ Result_NoEffects,
+ Result_RandomFailure
+ };
+
+ private:
+
+ MWWorld::Ptr mAlchemist;
+ TToolsContainer mTools;
+ TIngredientsContainer mIngredients;
+ TEffectsContainer mEffects;
+ int mValue;
+
+ std::set<EffectKey> listEffects() const;
+ ///< List all effects shared by at least two ingredients.
+
+ void applyTools (int flags, float& value) const;
+
+ void updateEffects();
+
+ const ESM::Potion *getRecord() const;
+ ///< Return existing recrod for created potion (may return 0)
+
+ void removeIngredients();
+ ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and
+ /// update effect list accordingly.
+
+ void addPotion (const std::string& name);
+ ///< Add a potion to the alchemist's inventory.
+
+ void increaseSkill();
+ ///< Increase alchemist's skill.
+
+ float getChance() const;
+ ///< Return chance of success.
+
+ int countIngredients() const;
+
+ public:
+
+ void setAlchemist (const MWWorld::Ptr& npc);
+ ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that
+ /// there is no alchemist (alchemy session has ended).
+
+ TToolsIterator beginTools() const;
+ ///< \attention Iterates over tool slots, not over tools. Some of the slots may be empty.
+
+ TToolsIterator endTools() const;
+
+ TIngredientsIterator beginIngredients() const;
+ ///< \attention Iterates over ingredient slots, not over ingredients. Some of the slots may be empty.
+
+ TIngredientsIterator endIngredients() const;
+
+ void clear();
+ ///< Remove alchemist, tools and ingredients.
+
+ int addIngredient (const MWWorld::Ptr& ingredient);
+ ///< Add ingredient into the next free slot.
+ ///
+ /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being
+ /// listed already.
+
+ void removeIngredient (int index);
+ ///< Remove ingredient from slot (calling this function on an empty slot is a no-op).
+
+ TEffectsIterator beginEffects() const;
+
+ TEffectsIterator endEffects() const;
+
+ std::string getPotionName() const;
+ ///< Return the name of the potion that would be created when calling create (if a record for such
+ /// a potion already exists) or return an empty string.
+
+ Result create (const std::string& name);
+ ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and
+ /// adjust the skills of the alchemist accordingly.
+ /// \param name must not be an empty string, unless there is already a potion record (
+ /// getPotionName() does not return an empty string).
+ };
+}
+
+#endif
+
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
new file mode 100644
index 0000000000..ec2bb1b59e
--- /dev/null
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -0,0 +1,1045 @@
+/*
+ * OpenMW - The completely unofficial reimplementation of Morrowind
+ *
+ * This file (character.cpp) is part of the OpenMW package.
+ *
+ * OpenMW is distributed as free software: you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with this program. If not, see
+ * http://www.gnu.org/licenses/ .
+ */
+
+#include "character.hpp"
+
+#include <OgreStringConverter.h>
+
+#include "movement.hpp"
+#include "npcstats.hpp"
+#include "creaturestats.hpp"
+#include "security.hpp"
+
+#include "../mwrender/animation.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+namespace
+{
+
+int getBestAttack (const ESM::Weapon* weapon)
+{
+ int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
+ int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
+ int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
+ if (slash >= chop && slash >= thrust)
+ return MWMechanics::CreatureStats::AT_Slash;
+ else if (chop >= slash && chop >= thrust)
+ return MWMechanics::CreatureStats::AT_Chop;
+ else
+ return MWMechanics::CreatureStats::AT_Thrust;
+}
+
+}
+
+namespace MWMechanics
+{
+
+struct StateInfo {
+ CharacterState state;
+ const char groupname[32];
+};
+
+static const StateInfo sDeathList[] = {
+ { CharState_Death1, "death1" },
+ { CharState_Death2, "death2" },
+ { CharState_Death3, "death3" },
+ { CharState_Death4, "death4" },
+ { CharState_Death5, "death5" },
+ { CharState_SwimDeath, "swimdeath" },
+};
+static const StateInfo *sDeathListEnd = &sDeathList[sizeof(sDeathList)/sizeof(sDeathList[0])];
+
+static const StateInfo sMovementList[] = {
+ { CharState_WalkForward, "walkforward" },
+ { CharState_WalkBack, "walkback" },
+ { CharState_WalkLeft, "walkleft" },
+ { CharState_WalkRight, "walkright" },
+
+ { CharState_SwimWalkForward, "swimwalkforward" },
+ { CharState_SwimWalkBack, "swimwalkback" },
+ { CharState_SwimWalkLeft, "swimwalkleft" },
+ { CharState_SwimWalkRight, "swimwalkright" },
+
+ { CharState_RunForward, "runforward" },
+ { CharState_RunBack, "runback" },
+ { CharState_RunLeft, "runleft" },
+ { CharState_RunRight, "runright" },
+
+ { CharState_SwimRunForward, "swimrunforward" },
+ { CharState_SwimRunBack, "swimrunback" },
+ { CharState_SwimRunLeft, "swimrunleft" },
+ { CharState_SwimRunRight, "swimrunright" },
+
+ { CharState_SneakForward, "sneakforward" },
+ { CharState_SneakBack, "sneakback" },
+ { CharState_SneakLeft, "sneakleft" },
+ { CharState_SneakRight, "sneakright" },
+
+ { CharState_Jump, "jump" },
+
+ { CharState_TurnLeft, "turnleft" },
+ { CharState_TurnRight, "turnright" },
+};
+static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])];
+
+
+class FindCharState {
+ CharacterState state;
+
+public:
+ FindCharState(CharacterState _state) : state(_state) { }
+
+ bool operator()(const StateInfo &info) const
+ { return info.state == state; }
+};
+
+
+static const struct WeaponInfo {
+ WeaponType type;
+ const char shortgroup[16];
+ const char longgroup[16];
+} sWeaponTypeList[] = {
+ { WeapType_HandToHand, "hh", "handtohand" },
+ { WeapType_OneHand, "1h", "weapononehand" },
+ { WeapType_TwoHand, "2c", "weapontwohand" },
+ { WeapType_TwoWide, "2w", "weapontwowide" },
+ { WeapType_BowAndArrow, "1h", "bowandarrow" },
+ { WeapType_Crossbow, "crossbow", "crossbow" },
+ { WeapType_ThowWeapon, "1h", "throwweapon" },
+ { WeapType_PickProbe, "1h", "pickprobe" },
+ { WeapType_Spell, "spell", "spellcast" },
+};
+static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])];
+
+class FindWeaponType {
+ WeaponType type;
+
+public:
+ FindWeaponType(WeaponType _type) : type(_type) { }
+
+ bool operator()(const WeaponInfo &weap) const
+ { return weap.type == type; }
+};
+
+
+void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force)
+{
+ const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
+
+ if(force || idle != mIdleState)
+ {
+ mIdleState = idle;
+
+ std::string idle;
+ // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to
+ // "idle"+weapon or "idle".
+ if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim"))
+ idle = "idleswim";
+ else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak"))
+ idle = "idlesneak";
+ else if(mIdleState != CharState_None)
+ {
+ idle = "idle";
+ if(weap != sWeaponTypeListEnd)
+ {
+ idle += weap->shortgroup;
+ if(!mAnimation->hasAnimation(idle))
+ idle = "idle";
+ }
+ }
+
+ mAnimation->disable(mCurrentIdle);
+ mCurrentIdle = idle;
+ if(!mCurrentIdle.empty())
+ mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false,
+ 1.0f, "start", "stop", 0.0f, ~0ul);
+ }
+
+ if(force && mJumpState != JumpState_None)
+ {
+ std::string jump;
+ MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All;
+ if(mJumpState != JumpState_None)
+ {
+ jump = "jump";
+ if(weap != sWeaponTypeListEnd)
+ {
+ jump += weap->shortgroup;
+ if(!mAnimation->hasAnimation(jump))
+ {
+ jumpgroup = MWRender::Animation::Group_LowerBody;
+ jump = "jump";
+ }
+ }
+ }
+
+ if(mJumpState == JumpState_Falling)
+ {
+ int mode = ((jump == mCurrentJump) ? 2 : 1);
+
+ mAnimation->disable(mCurrentJump);
+ mCurrentJump = jump;
+ mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
+ 1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
+ }
+ else
+ {
+ mAnimation->disable(mCurrentJump);
+ mCurrentJump.clear();
+ mAnimation->play(jump, Priority_Jump, jumpgroup, true,
+ 1.0f, "loop stop", "stop", 0.0f, 0);
+ }
+ }
+
+ if(force || movement != mMovementState)
+ {
+ mMovementState = movement;
+
+ std::string movement;
+ MWRender::Animation::Group movegroup = MWRender::Animation::Group_All;
+ const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
+ if(movestate != sMovementListEnd)
+ {
+ movement = movestate->groupname;
+ if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos)
+ {
+ movement += weap->shortgroup;
+ if(!mAnimation->hasAnimation(movement))
+ {
+ movegroup = MWRender::Animation::Group_LowerBody;
+ movement = movestate->groupname;
+ }
+ }
+
+ if(!mAnimation->hasAnimation(movement))
+ {
+ std::string::size_type swimpos = movement.find("swim");
+ if(swimpos == std::string::npos)
+ movement.clear();
+ else
+ {
+ movegroup = MWRender::Animation::Group_LowerBody;
+ movement.erase(swimpos, 4);
+ if(!mAnimation->hasAnimation(movement))
+ movement.clear();
+ }
+ }
+ }
+
+ /* If we're playing the same animation, restart from the loop start instead of the
+ * beginning. */
+ int mode = ((movement == mCurrentMovement) ? 2 : 1);
+
+ mAnimation->disable(mCurrentMovement);
+ mCurrentMovement = movement;
+ if(!mCurrentMovement.empty())
+ {
+ float vel, speedmult = 1.0f;
+ if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f)
+ speedmult = mMovementSpeed / vel;
+ mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
+ speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
+ }
+ }
+}
+
+
+void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group)
+{
+ const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype));
+ if(info != sWeaponTypeListEnd)
+ group = info->longgroup;
+}
+
+
+MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
+{
+ if(stats.getDrawState() == DrawState_Spell)
+ {
+ *weaptype = WeapType_Spell;
+ return inv.end();
+ }
+
+ if(stats.getDrawState() == MWMechanics::DrawState_Weapon)
+ {
+ MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ if(weapon == inv.end())
+ *weaptype = WeapType_HandToHand;
+ else
+ {
+ const std::string &type = weapon->getTypeName();
+ if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
+ *weaptype = WeapType_PickProbe;
+ else if(type == typeid(ESM::Weapon).name())
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
+ ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType;
+ switch(type)
+ {
+ case ESM::Weapon::ShortBladeOneHand:
+ case ESM::Weapon::LongBladeOneHand:
+ case ESM::Weapon::BluntOneHand:
+ case ESM::Weapon::AxeOneHand:
+ case ESM::Weapon::Arrow:
+ case ESM::Weapon::Bolt:
+ *weaptype = WeapType_OneHand;
+ break;
+ case ESM::Weapon::LongBladeTwoHand:
+ case ESM::Weapon::BluntTwoClose:
+ case ESM::Weapon::AxeTwoHand:
+ *weaptype = WeapType_TwoHand;
+ break;
+ case ESM::Weapon::BluntTwoWide:
+ case ESM::Weapon::SpearTwoWide:
+ *weaptype = WeapType_TwoWide;
+ break;
+ case ESM::Weapon::MarksmanBow:
+ *weaptype = WeapType_BowAndArrow;
+ break;
+ case ESM::Weapon::MarksmanCrossbow:
+ *weaptype = WeapType_Crossbow;
+ break;
+ case ESM::Weapon::MarksmanThrown:
+ *weaptype = WeapType_ThowWeapon;
+ break;
+ }
+ }
+ }
+
+ return weapon;
+ }
+
+ return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+}
+
+
+CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
+ : mPtr(ptr)
+ , mAnimation(anim)
+ , mIdleState(CharState_None)
+ , mMovementState(CharState_None)
+ , mMovementSpeed(0.0f)
+ , mDeathState(CharState_None)
+ , mUpperBodyState(UpperCharState_Nothing)
+ , mJumpState(JumpState_None)
+ , mWeaponType(WeapType_None)
+ , mSkipAnim(false)
+ , mSecondsOfRunning(0)
+ , mSecondsOfSwimming(0)
+{
+ if(!mAnimation)
+ return;
+
+ const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
+ if(cls.isActor())
+ {
+ /* Accumulate along X/Y only for now, until we can figure out how we should
+ * handle knockout and death which moves the character down. */
+ mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f));
+
+ if(mPtr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
+ if(mWeaponType != WeapType_None)
+ {
+ getWeaponGroup(mWeaponType, mCurrentWeapon);
+ mUpperBodyState = UpperCharState_WeapEquiped;
+ }
+ }
+
+ if(!cls.getCreatureStats(mPtr).isDead())
+ mIdleState = CharState_Idle;
+ else
+ {
+ /* FIXME: Get the actual death state used. */
+ mDeathState = CharState_Death1;
+ }
+ }
+ else
+ {
+ /* Don't accumulate with non-actors. */
+ mAnimation->setAccumulation(Ogre::Vector3(0.0f));
+
+ mIdleState = CharState_Idle;
+ }
+
+ refreshCurrentAnims(mIdleState, mMovementState, true);
+ if(mDeathState != CharState_None)
+ {
+ const StateInfo *state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState));
+ if(state == sDeathListEnd)
+ throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState));
+
+ mCurrentDeath = state->groupname;
+ mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
+ false, 1.0f, "start", "stop", 1.0f, 0);
+ }
+}
+
+CharacterController::~CharacterController()
+{
+}
+
+
+void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
+{
+ mPtr = ptr;
+}
+
+
+bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak)
+{
+ const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
+ NpcStats &stats = cls.getNpcStats(mPtr);
+ WeaponType weaptype = WeapType_None;
+ MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
+ MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
+ const bool isWerewolf = stats.isWerewolf();
+
+ bool forcestateupdate = false;
+ if(weaptype != mWeaponType)
+ {
+ forcestateupdate = true;
+
+ std::string weapgroup;
+ if(weaptype == WeapType_None)
+ {
+ getWeaponGroup(mWeaponType, weapgroup);
+ mAnimation->play(weapgroup, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, true,
+ 1.0f, "unequip start", "unequip stop", 0.0f, 0);
+ mUpperBodyState = UpperCharState_UnEquipingWeap;
+ }
+ else
+ {
+ getWeaponGroup(weaptype, weapgroup);
+ mAnimation->showWeapons(false);
+ mAnimation->play(weapgroup, Priority_Weapon,
+ 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();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
+ if(sound)
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
+ }
+ }
+ }
+
+ if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell))
+ {
+ std::string soundid = (weaptype == WeapType_None) ?
+ MWWorld::Class::get(*weapon).getDownSoundId(*weapon) :
+ MWWorld::Class::get(*weapon).getUpSoundId(*weapon);
+ if(!soundid.empty())
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f);
+ }
+ }
+
+ mWeaponType = weaptype;
+ getWeaponGroup(mWeaponType, mCurrentWeapon);
+ }
+
+ if(isWerewolf)
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(isrunning && !inwater && mWeaponType == WeapType_None)
+ {
+ if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
+ sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx,
+ MWBase::SoundManager::Play_Loop);
+ }
+ else
+ sndMgr->stopSound3D(mPtr, "WolfRun");
+ }
+
+ bool isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name());
+ float weapSpeed = 1.0f;
+ if(isWeapon)
+ weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
+
+ float complete;
+ bool animPlaying;
+ if(stats.getAttackingOrSpell())
+ {
+ if(mUpperBodyState == UpperCharState_WeapEquiped)
+ {
+ mAttackType.clear();
+ if(mWeaponType == WeapType_Spell)
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+
+ const std::string spellid = stats.getSpells().getSelectedSpell();
+ if(!spellid.empty())
+ {
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+
+ const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
+ const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
+
+ const ESM::MagicEffect *effect;
+ effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
+
+ switch(effectentry.mRange)
+ {
+ case 0: mAttackType = "self"; break;
+ case 1: mAttackType = "touch"; break;
+ case 2: mAttackType = "target"; break;
+ }
+
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, true,
+ weapSpeed, mAttackType+" start", mAttackType+" stop",
+ 0.0f, 0);
+ mUpperBodyState = UpperCharState_CastingSpell;
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(!effect->mCastSound.empty())
+ sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f);
+ else
+ sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
+ }
+ }
+ else if(mWeaponType == WeapType_PickProbe)
+ {
+ MWWorld::Ptr item = *weapon;
+ MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject();
+ std::string resultMessage, resultSound;
+
+ if(!target.isEmpty())
+ {
+ if(item.getTypeName() == typeid(ESM::Lockpick).name())
+ Security(mPtr).pickLock(target, item, resultMessage, resultSound);
+ else if(item.getTypeName() == typeid(ESM::Probe).name())
+ Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
+ }
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, true,
+ 1.0f, "start", "stop", 0.0, 0);
+ mUpperBodyState = UpperCharState_FollowStartToFollowStop;
+
+ if(!resultMessage.empty())
+ MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
+ 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
+ MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
+ }
+ else
+ {
+ if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow ||
+ mWeaponType == WeapType_ThowWeapon)
+ mAttackType = "shoot";
+ else
+ {
+ int attackType = stats.getAttackType();
+ if(isWeapon && Settings::Manager::getBool("best attack", "Game"))
+ attackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
+
+ if (attackType == MWMechanics::CreatureStats::AT_Chop)
+ mAttackType = "chop";
+ else if (attackType == MWMechanics::CreatureStats::AT_Slash)
+ mAttackType = "slash";
+ else
+ mAttackType = "thrust";
+ }
+
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ weapSpeed, mAttackType+" start", mAttackType+" min attack",
+ 0.0f, 0);
+ mUpperBodyState = UpperCharState_StartToMinAttack;
+ }
+ }
+ animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
+ }
+ else
+ {
+ animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
+ if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack)
+ {
+ if(mAttackType != "shoot")
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+
+ if(isWerewolf)
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing");
+ if(sound)
+ sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
+ }
+ else
+ {
+ std::string sound = "SwishM";
+ if(complete < 0.5f)
+ sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
+ else if(complete < 1.0f)
+ sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
+ else
+ sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
+ }
+ }
+ stats.setAttackStrength(complete);
+
+ mAnimation->disable(mCurrentWeapon);
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
+ 1.0f-complete, 0);
+ mUpperBodyState = UpperCharState_MaxAttackToMinHit;
+ }
+ }
+
+ if(!animPlaying)
+ {
+ if(mUpperBodyState == UpperCharState_EquipingWeap ||
+ mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
+ mUpperBodyState == UpperCharState_CastingSpell)
+ mUpperBodyState = UpperCharState_WeapEquiped;
+ else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
+ mUpperBodyState = UpperCharState_Nothing;
+ }
+ else if(complete >= 1.0f)
+ {
+ if(mUpperBodyState == UpperCharState_StartToMinAttack)
+ {
+ mAnimation->disable(mCurrentWeapon);
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ weapSpeed, mAttackType+" min attack", mAttackType+" max attack",
+ 0.0f, 0);
+ mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
+ }
+ else if(mUpperBodyState == UpperCharState_MaxAttackToMinHit)
+ {
+ mAnimation->disable(mCurrentWeapon);
+ if(mAttackType == "shoot")
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ weapSpeed, mAttackType+" min hit", mAttackType+" follow start",
+ 0.0f, 0);
+ else
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ weapSpeed, mAttackType+" min hit", mAttackType+" hit",
+ 0.0f, 0);
+ mUpperBodyState = UpperCharState_MinHitToHit;
+ }
+ else if(mUpperBodyState == UpperCharState_MinHitToHit)
+ {
+ mAnimation->disable(mCurrentWeapon);
+ if(mAttackType == "shoot")
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, true,
+ weapSpeed, mAttackType+" follow start", mAttackType+" follow stop",
+ 0.0f, 0);
+ else
+ {
+ float str = stats.getAttackStrength();
+ std::string start = mAttackType+((str < 0.5f) ? " small follow start"
+ : (str < 1.0f) ? " medium follow start"
+ : " large follow start");
+ std::string stop = mAttackType+((str < 0.5f) ? " small follow stop"
+ : (str < 1.0f) ? " medium follow stop"
+ : " large follow stop");
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, true,
+ weapSpeed, start, stop, 0.0f, 0);
+ }
+ mUpperBodyState = UpperCharState_FollowStartToFollowStop;
+ }
+ }
+
+
+ MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name())
+ {
+ if(!mAnimation->isPlaying("torch"))
+ mAnimation->play("torch", Priority_Torch,
+ MWRender::Animation::Group_LeftArm, false,
+ 1.0f, "start", "stop", 0.0f, (~(size_t)0));
+ }
+ else if(mAnimation->isPlaying("torch"))
+ mAnimation->disable("torch");
+
+ return forcestateupdate;
+}
+
+void CharacterController::update(float duration)
+{
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
+ Ogre::Vector3 movement(0.0f);
+
+ if(!cls.isActor())
+ {
+ if(mAnimQueue.size() > 1)
+ {
+ if(mAnimation->isPlaying(mAnimQueue.front().first) == false)
+ {
+ mAnimation->disable(mAnimQueue.front().first);
+ mAnimQueue.pop_front();
+
+ mAnimation->play(mAnimQueue.front().first, Priority_Default,
+ MWRender::Animation::Group_All, false,
+ 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
+ }
+ }
+ }
+ else if(!cls.getCreatureStats(mPtr).isDead())
+ {
+ bool onground = world->isOnGround(mPtr);
+ bool inwater = world->isSwimming(mPtr);
+ bool isrunning = cls.getStance(mPtr, MWWorld::Class::Run);
+ bool sneak = cls.getStance(mPtr, MWWorld::Class::Sneak);
+ bool flying = world->isFlying(mPtr);
+ Ogre::Vector3 vec = cls.getMovementVector(mPtr);
+ Ogre::Vector3 rot = cls.getRotationVector(mPtr);
+ mMovementSpeed = cls.getSpeed(mPtr);
+
+ vec.x *= mMovementSpeed;
+ vec.y *= mMovementSpeed;
+
+ CharacterState movestate = CharState_None;
+ CharacterState idlestate = CharState_SpecialIdle;
+ bool forcestateupdate = false;
+
+ isrunning = isrunning && std::abs(vec[0])+std::abs(vec[1]) > 0.0f;
+
+ // advance athletics
+ if(std::abs(vec[0])+std::abs(vec[1]) > 0.0f && mPtr.getRefData().getHandle() == "player")
+ {
+ if(inwater)
+ {
+ mSecondsOfSwimming += duration;
+ while(mSecondsOfSwimming > 1)
+ {
+ cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1);
+ mSecondsOfSwimming -= 1;
+ }
+ }
+ else if(isrunning)
+ {
+ mSecondsOfRunning += duration;
+ while(mSecondsOfRunning > 1)
+ {
+ cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0);
+ mSecondsOfRunning -= 1;
+ }
+ }
+ }
+
+ if(sneak || inwater || flying)
+ vec.z = 0.0f;
+
+ if(!onground && !flying && !inwater)
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+
+ forcestateupdate = (mJumpState != JumpState_Falling);
+ mJumpState = JumpState_Falling;
+
+ // This is a guess. All that seems to be known is that "While the player is in the
+ // air, fJumpMoveBase and fJumpMoveMult governs air control." Assuming Acrobatics
+ // plays a role, this makes the most sense.
+ float mult = 0.0f;
+ if(cls.isNpc())
+ {
+ const NpcStats &stats = cls.getNpcStats(mPtr);
+ mult = gmst.find("fJumpMoveBase")->getFloat() +
+ (stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f *
+ gmst.find("fJumpMoveMult")->getFloat());
+ }
+
+ vec.x *= mult;
+ vec.y *= mult;
+ vec.z = 0.0f;
+ }
+ else if(vec.z > 0.0f && mJumpState == JumpState_None)
+ {
+ float z = cls.getJump(mPtr);
+ if(vec.x == 0 && vec.y == 0)
+ vec = Ogre::Vector3(0.0f, 0.0f, z);
+ else
+ {
+ Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy();
+ vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f;
+ }
+
+ //decrease fatigue by fFatigueJumpBase + (1 - normalizedEncumbrance) * fFatigueJumpMult;
+ }
+ else if(mJumpState == JumpState_Falling)
+ {
+ forcestateupdate = true;
+ mJumpState = JumpState_Landing;
+ vec.z = 0.0f;
+ }
+ else
+ {
+ if(!(vec.z > 0.0f))
+ mJumpState = JumpState_None;
+ vec.z = 0.0f;
+
+ if(std::abs(vec.x/2.0f) > std::abs(vec.y))
+ {
+ if(vec.x > 0.0f)
+ movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
+ : (sneak ? CharState_SneakRight
+ : (isrunning ? CharState_RunRight : CharState_WalkRight)));
+ else if(vec.x < 0.0f)
+ movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft)
+ : (sneak ? CharState_SneakLeft
+ : (isrunning ? CharState_RunLeft : CharState_WalkLeft)));
+ }
+ else if(vec.y != 0.0f)
+ {
+ if(vec.y > 0.0f)
+ movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
+ : (sneak ? CharState_SneakForward
+ : (isrunning ? CharState_RunForward : CharState_WalkForward)));
+ else if(vec.y < 0.0f)
+ movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
+ : (sneak ? CharState_SneakBack
+ : (isrunning ? CharState_RunBack : CharState_WalkBack)));
+ }
+ else if(rot.z != 0.0f && !inwater && !sneak)
+ {
+ if(rot.z > 0.0f)
+ movestate = CharState_TurnRight;
+ else if(rot.z < 0.0f)
+ movestate = CharState_TurnLeft;
+ }
+ }
+
+ if(movestate != CharState_None)
+ clearAnimQueue();
+
+ if(mAnimQueue.empty())
+ idlestate = (inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle));
+ else if(mAnimQueue.size() > 1)
+ {
+ if(mAnimation->isPlaying(mAnimQueue.front().first) == false)
+ {
+ mAnimation->disable(mAnimQueue.front().first);
+ mAnimQueue.pop_front();
+
+ mAnimation->play(mAnimQueue.front().first, Priority_Default,
+ MWRender::Animation::Group_All, false,
+ 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
+ }
+ }
+
+ if(cls.isNpc())
+ forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak) || forcestateupdate;
+
+ refreshCurrentAnims(idlestate, movestate, forcestateupdate);
+
+ rot *= duration * Ogre::Math::RadiansToDegrees(1.0f);
+ world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
+
+ world->queueMovement(mPtr, vec);
+ movement = vec;
+ }
+ else if(cls.getCreatureStats(mPtr).isDead())
+ {
+ MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false);
+ world->queueMovement(mPtr, Ogre::Vector3(0.0f));
+ }
+
+ if(mAnimation && !mSkipAnim)
+ {
+ Ogre::Vector3 moved = mAnimation->runAnimation(duration);
+ if(duration > 0.0f)
+ moved /= duration;
+ else
+ moved = Ogre::Vector3(0.0f);
+
+ // Ensure we're moving in generally the right direction
+ if(mMovementSpeed > 0.f)
+ {
+ if((movement.x < 0.0f && movement.x < moved.x*2.0f) ||
+ (movement.x > 0.0f && movement.x > moved.x*2.0f))
+ moved.x = movement.x;
+ if((movement.y < 0.0f && movement.y < moved.y*2.0f) ||
+ (movement.y > 0.0f && movement.y > moved.y*2.0f))
+ moved.y = movement.y;
+ if((movement.z < 0.0f && movement.z < moved.z*2.0f) ||
+ (movement.z > 0.0f && movement.z > moved.z*2.0f))
+ moved.z = movement.z;
+ }
+ // Update movement
+ if(moved.squaredLength() > 1.0f)
+ world->queueMovement(mPtr, moved);
+ }
+ mSkipAnim = false;
+}
+
+
+void CharacterController::playGroup(const std::string &groupname, int mode, int count)
+{
+ if(!mAnimation || !mAnimation->hasAnimation(groupname))
+ std::cerr<< "Animation "<<groupname<<" not found" <<std::endl;
+ else
+ {
+ count = std::max(count, 1);
+ if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().first))
+ {
+ clearAnimQueue();
+ mAnimQueue.push_back(std::make_pair(groupname, count-1));
+
+ mAnimation->disable(mCurrentIdle);
+ mCurrentIdle.clear();
+
+ mIdleState = CharState_SpecialIdle;
+ mAnimation->play(groupname, Priority_Default,
+ MWRender::Animation::Group_All, false, 1.0f,
+ ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1);
+ }
+ else if(mode == 0)
+ {
+ mAnimQueue.resize(1);
+ mAnimQueue.push_back(std::make_pair(groupname, count-1));
+ }
+ }
+}
+
+void CharacterController::skipAnim()
+{
+ mSkipAnim = true;
+}
+
+bool CharacterController::isAnimPlaying(const std::string &groupName)
+{
+ if(mAnimation == NULL)
+ return false;
+ return mAnimation->isPlaying(groupName);
+}
+
+
+void CharacterController::clearAnimQueue()
+{
+ if(!mAnimQueue.empty())
+ mAnimation->disable(mAnimQueue.front().first);
+ mAnimQueue.clear();
+}
+
+
+void CharacterController::forceStateUpdate()
+{
+ if(!mAnimation)
+ return;
+ clearAnimQueue();
+
+ refreshCurrentAnims(mIdleState, mMovementState, true);
+ if(mDeathState != CharState_None)
+ {
+ const StateInfo *state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState));
+ if(state == sDeathListEnd)
+ throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState));
+
+ mCurrentDeath = state->groupname;
+ if(!mAnimation->getInfo(mCurrentDeath))
+ mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
+ false, 1.0f, "start", "stop", 0.0f, 0);
+ }
+}
+
+void CharacterController::kill()
+{
+ if(mDeathState != CharState_None)
+ return;
+
+ if(mPtr.getTypeName() == typeid(ESM::NPC).name())
+ {
+ const StateInfo *state = NULL;
+ if(MWBase::Environment::get().getWorld()->isSwimming(mPtr))
+ {
+ mDeathState = CharState_SwimDeath;
+ state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState));
+ if(state == sDeathListEnd)
+ throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState));
+ }
+
+ static const CharacterState deathstates[5] = {
+ CharState_Death1, CharState_Death2, CharState_Death3, CharState_Death4, CharState_Death5
+ };
+ std::vector<CharacterState> states(&deathstates[0], &deathstates[5]);
+
+ while(states.size() > 1 && (!state || !mAnimation->hasAnimation(state->groupname)))
+ {
+ int pos = (int)(rand()/((double)RAND_MAX+1.0)*states.size());
+ mDeathState = states[pos];
+ states.erase(states.begin()+pos);
+
+ state = std::find_if(sDeathList, sDeathListEnd, FindCharState(mDeathState));
+ if(state == sDeathListEnd)
+ throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState));
+ }
+ mCurrentDeath = state->groupname;
+ }
+ else
+ {
+ mDeathState = CharState_Death1;
+ mCurrentDeath = "death1";
+ }
+
+ if(mAnimation)
+ {
+ mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
+ false, 1.0f, "start", "stop", 0.0f, 0);
+ mAnimation->disable(mCurrentIdle);
+ }
+
+ mIdleState = CharState_None;
+ mCurrentIdle.clear();
+}
+
+void CharacterController::resurrect()
+{
+ if(mDeathState == CharState_None)
+ return;
+
+ if(mAnimation)
+ mAnimation->disable(mCurrentDeath);
+ mCurrentDeath.empty();
+ mDeathState = CharState_None;
+}
+
+}
diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp
new file mode 100644
index 0000000000..c943b95977
--- /dev/null
+++ b/apps/openmw/mwmechanics/character.hpp
@@ -0,0 +1,193 @@
+#ifndef GAME_MWMECHANICS_CHARACTER_HPP
+#define GAME_MWMECHANICS_CHARACTER_HPP
+
+#include <OgreVector3.h>
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWWorld
+{
+ class ContainerStoreIterator;
+ class InventoryStore;
+}
+
+namespace MWRender
+{
+ class Animation;
+}
+
+namespace MWMechanics
+{
+
+class Movement;
+class NpcStats;
+
+enum Priority {
+ Priority_Default,
+ Priority_Jump,
+ Priority_Movement,
+ Priority_Weapon,
+ Priority_Torch,
+
+ Priority_Death,
+
+ Num_Priorities
+};
+
+enum CharacterState {
+ CharState_None,
+
+ CharState_SpecialIdle,
+ CharState_Idle,
+ CharState_Idle2,
+ CharState_Idle3,
+ CharState_Idle4,
+ CharState_Idle5,
+ CharState_Idle6,
+ CharState_Idle7,
+ CharState_Idle8,
+ CharState_Idle9,
+ CharState_IdleSwim,
+ CharState_IdleSneak,
+
+ CharState_WalkForward,
+ CharState_WalkBack,
+ CharState_WalkLeft,
+ CharState_WalkRight,
+
+ CharState_SwimWalkForward,
+ CharState_SwimWalkBack,
+ CharState_SwimWalkLeft,
+ CharState_SwimWalkRight,
+
+ CharState_RunForward,
+ CharState_RunBack,
+ CharState_RunLeft,
+ CharState_RunRight,
+
+ CharState_SwimRunForward,
+ CharState_SwimRunBack,
+ CharState_SwimRunLeft,
+ CharState_SwimRunRight,
+
+ CharState_SneakForward,
+ CharState_SneakBack,
+ CharState_SneakLeft,
+ CharState_SneakRight,
+
+ CharState_TurnLeft,
+ CharState_TurnRight,
+
+ CharState_Jump,
+
+ CharState_Death1,
+ CharState_Death2,
+ CharState_Death3,
+ CharState_Death4,
+ CharState_Death5,
+ CharState_SwimDeath
+};
+
+enum WeaponType {
+ WeapType_None,
+
+ WeapType_HandToHand,
+ WeapType_OneHand,
+ WeapType_TwoHand,
+ WeapType_TwoWide,
+ WeapType_BowAndArrow,
+ WeapType_Crossbow,
+ WeapType_ThowWeapon,
+ WeapType_PickProbe,
+
+ WeapType_Spell
+};
+
+enum UpperBodyCharacterState {
+ UpperCharState_Nothing,
+ UpperCharState_EquipingWeap,
+ UpperCharState_UnEquipingWeap,
+ UpperCharState_WeapEquiped,
+ UpperCharState_StartToMinAttack,
+ UpperCharState_MinAttackToMaxAttack,
+ UpperCharState_MaxAttackToMinHit,
+ UpperCharState_MinHitToHit,
+ UpperCharState_FollowStartToFollowStop,
+ UpperCharState_CastingSpell
+};
+
+enum JumpingState {
+ JumpState_None,
+ JumpState_Falling,
+ JumpState_Landing
+};
+
+class CharacterController
+{
+ MWWorld::Ptr mPtr;
+ MWRender::Animation *mAnimation;
+
+ typedef std::deque<std::pair<std::string,size_t> > AnimationQueue;
+ AnimationQueue mAnimQueue;
+
+ CharacterState mIdleState;
+ std::string mCurrentIdle;
+
+ CharacterState mMovementState;
+ std::string mCurrentMovement;
+ float mMovementSpeed;
+
+ CharacterState mDeathState;
+ std::string mCurrentDeath;
+
+ UpperBodyCharacterState mUpperBodyState;
+
+ JumpingState mJumpState;
+ std::string mCurrentJump;
+
+ WeaponType mWeaponType;
+ std::string mCurrentWeapon;
+
+ bool mSkipAnim;
+
+ // counted for skill increase
+ float mSecondsOfSwimming;
+ float mSecondsOfRunning;
+
+ std::string mAttackType; // slash, chop or thrust
+
+ void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false);
+
+ static void getWeaponGroup(WeaponType weaptype, std::string &group);
+
+ static MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats,
+ MWWorld::InventoryStore &inv,
+ WeaponType *weaptype);
+
+ void clearAnimQueue();
+
+ bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak);
+
+public:
+ CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
+ virtual ~CharacterController();
+
+ void updatePtr(const MWWorld::Ptr &ptr);
+
+ void update(float duration);
+
+ void playGroup(const std::string &groupname, int mode, int count);
+ void skipAnim();
+ bool isAnimPlaying(const std::string &groupName);
+
+ void kill();
+ void resurrect();
+ bool isDead() const
+ { return mDeathState != CharState_None; }
+
+ void forceStateUpdate();
+};
+
+}
+
+#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp
new file mode 100644
index 0000000000..52ed43be37
--- /dev/null
+++ b/apps/openmw/mwmechanics/creaturestats.cpp
@@ -0,0 +1,345 @@
+#include "creaturestats.hpp"
+
+#include <algorithm>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWMechanics
+{
+ CreatureStats::CreatureStats()
+ : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mDied (false), mFriendlyHits (0),
+ mTalkedTo (false), mAlarmed (false),
+ mAttacked (false), mHostile (false),
+ mAttackingOrSpell(false), mAttackType(AT_Chop),
+ mIsWerewolf(false)
+ {
+ for (int i=0; i<4; ++i)
+ mAiSettings[i] = 0;
+ }
+
+ float CreatureStats::getLevelHealthBonus () const
+ {
+ return mLevelHealthBonus;
+ }
+
+ void CreatureStats::levelUp()
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ const int endurance = getAttribute(ESM::Attribute::Endurance).getBase();
+
+ // "When you gain a level, in addition to increasing three primary attributes, your Health
+ // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level,
+ // the Health increase is calculated from the increased Endurance"
+ mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat();
+ updateHealth();
+
+ mLevel++;
+ }
+
+ void CreatureStats::updateHealth()
+ {
+ const int endurance = getAttribute(ESM::Attribute::Endurance).getBase();
+ const int strength = getAttribute(ESM::Attribute::Strength).getBase();
+
+ setHealth(static_cast<int> (0.5 * (strength + endurance)) + mLevelHealthBonus);
+ }
+
+ const AiSequence& CreatureStats::getAiSequence() const
+ {
+ return mAiSequence;
+ }
+
+ AiSequence& CreatureStats::getAiSequence()
+ {
+ return mAiSequence;
+ }
+
+ float CreatureStats::getFatigueTerm() const
+ {
+ int max = getFatigue().getModified();
+ int current = getFatigue().getCurrent();
+
+ float normalised = max==0 ? 1 : std::max (0.0f, static_cast<float> (current)/max);
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ return gmst.find ("fFatigueBase")->getFloat()
+ - gmst.find ("fFatigueMult")->getFloat() * (1-normalised);
+ }
+
+ const Stat<int> &CreatureStats::getAttribute(int index) const
+ {
+ 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::getHealth() const
+ {
+ return mDynamic[0];
+ }
+
+ const DynamicStat<float> &CreatureStats::getMagicka() const
+ {
+ return mDynamic[1];
+ }
+
+ const DynamicStat<float> &CreatureStats::getFatigue() const
+ {
+ return mDynamic[2];
+ }
+
+ const Spells &CreatureStats::getSpells() const
+ {
+ return mSpells;
+ }
+
+ const ActiveSpells &CreatureStats::getActiveSpells() const
+ {
+ return mActiveSpells;
+ }
+
+ const MagicEffects &CreatureStats::getMagicEffects() const
+ {
+ return mMagicEffects;
+ }
+
+ bool CreatureStats::getAttackingOrSpell() const
+ {
+ return mAttackingOrSpell;
+ }
+
+ int CreatureStats::getLevel() const
+ {
+ return mLevel;
+ }
+
+ int CreatureStats::getAiSetting (int index) const
+ {
+ assert (index>=0 && index<4);
+ 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) {
+ throw std::runtime_error("dynamic stat index is out of range");
+ }
+ return mDynamic[index];
+ }
+
+ Spells &CreatureStats::getSpells()
+ {
+ return mSpells;
+ }
+
+ void CreatureStats::setSpells(const Spells &spells)
+ {
+ mSpells = spells;
+ }
+
+ ActiveSpells &CreatureStats::getActiveSpells()
+ {
+ return mActiveSpells;
+ }
+
+ MagicEffects &CreatureStats::getMagicEffects()
+ {
+ return mMagicEffects;
+ }
+
+ void CreatureStats::setAttribute(int index, const Stat<int> &value)
+ {
+ if (index < 0 || index > 7) {
+ throw std::runtime_error("attribute index is out of range");
+ }
+ if(!mIsWerewolf)
+ mAttributes[index] = value;
+ else
+ mWerewolfAttributes[index] = value;
+ }
+
+ void CreatureStats::setHealth(const DynamicStat<float> &value)
+ {
+ setDynamic (0, value);
+ }
+
+ void CreatureStats::setMagicka(const DynamicStat<float> &value)
+ {
+ setDynamic (1, value);
+ }
+
+ void CreatureStats::setFatigue(const DynamicStat<float> &value)
+ {
+ setDynamic (2, value);
+ }
+
+ void CreatureStats::setDynamic (int index, const DynamicStat<float> &value)
+ {
+ if (index < 0 || index > 2)
+ throw std::runtime_error("dynamic stat index is out of range");
+
+ mDynamic[index] = value;
+
+ if (index==0 && mDynamic[index].getCurrent()<1)
+ {
+ if (!mDead)
+ mDied = true;
+
+ mDead = true;
+ }
+ }
+
+ void CreatureStats::setLevel(int level)
+ {
+ mLevel = level;
+ }
+
+ void CreatureStats::setActiveSpells(const ActiveSpells &active)
+ {
+ mActiveSpells = active;
+ }
+
+ void CreatureStats::setMagicEffects(const MagicEffects &effects)
+ {
+ mMagicEffects = effects;
+ }
+
+ void CreatureStats::setAttackingOrSpell(bool attackingOrSpell)
+ {
+ mAttackingOrSpell = attackingOrSpell;
+ }
+
+ void CreatureStats::setAiSetting (int index, int value)
+ {
+ assert (index>=0 && index<4);
+ mAiSettings[index] = value;
+ }
+
+ bool CreatureStats::isDead() const
+ {
+ return mDead;
+ }
+
+ bool CreatureStats::hasDied() const
+ {
+ return mDied;
+ }
+
+ void CreatureStats::clearHasDied()
+ {
+ mDied = false;
+ }
+
+ void CreatureStats::resurrect()
+ {
+ if (mDead)
+ {
+ if (mDynamic[0].getCurrent()<1)
+ mDynamic[0].setCurrent (1);
+
+ if (mDynamic[0].getCurrent()>=1)
+ mDead = false;
+ }
+ }
+
+ bool CreatureStats::hasCommonDisease() const
+ {
+ return mSpells.hasCommonDisease();
+ }
+
+ bool CreatureStats::hasBlightDisease() const
+ {
+ return mSpells.hasBlightDisease();
+ }
+
+ int CreatureStats::getFriendlyHits() const
+ {
+ return mFriendlyHits;
+ }
+
+ void CreatureStats::friendlyHit()
+ {
+ ++mFriendlyHits;
+ }
+
+ bool CreatureStats::hasTalkedToPlayer() const
+ {
+ return mTalkedTo;
+ }
+
+ void CreatureStats::talkedToPlayer()
+ {
+ mTalkedTo = true;
+ }
+
+ bool CreatureStats::isAlarmed() const
+ {
+ return mAlarmed;
+ }
+
+ void CreatureStats::setAlarmed (bool alarmed)
+ {
+ mAlarmed = alarmed;
+ }
+
+ bool CreatureStats::getAttacked() const
+ {
+ return mAttacked;
+ }
+
+ void CreatureStats::setAttacked (bool attacked)
+ {
+ mAttacked = attacked;
+ }
+
+ bool CreatureStats::isHostile() const
+ {
+ return mHostile;
+ }
+
+ void CreatureStats::setHostile (bool hostile)
+ {
+ mHostile = hostile;
+ }
+
+ bool CreatureStats::getCreatureTargetted() const
+ {
+ return false;
+ }
+
+ float CreatureStats::getEvasion() const
+ {
+ float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
+ (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
+ evasion *= getFatigueTerm();
+ evasion += mMagicEffects.get(EffectKey(ESM::MagicEffect::Sanctuary)).mMagnitude;
+
+ return evasion;
+ }
+
+ void CreatureStats::setLastHitObject(const std::string& objectid)
+ {
+ mLastHitObject = objectid;
+ }
+
+ const std::string &CreatureStats::getLastHitObject() const
+ {
+ return mLastHitObject;
+ }
+}
diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp
new file mode 100644
index 0000000000..1a7cb5d698
--- /dev/null
+++ b/apps/openmw/mwmechanics/creaturestats.hpp
@@ -0,0 +1,172 @@
+#ifndef GAME_MWMECHANICS_CREATURESTATS_H
+#define GAME_MWMECHANICS_CREATURESTATS_H
+
+#include <set>
+#include <string>
+#include <stdexcept>
+
+#include "stat.hpp"
+#include "magiceffects.hpp"
+#include "spells.hpp"
+#include "activespells.hpp"
+#include "aisequence.hpp"
+
+namespace MWMechanics
+{
+ /// \brief Common creature stats
+ ///
+ ///
+ class CreatureStats
+ {
+ Stat<int> mAttributes[8];
+ DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
+ int mLevel;
+ Spells mSpells;
+ ActiveSpells mActiveSpells;
+ MagicEffects mMagicEffects;
+ int mAiSettings[4];
+ AiSequence mAiSequence;
+ float mLevelHealthBonus;
+ bool mDead;
+ bool mDied;
+ int mFriendlyHits;
+ bool mTalkedTo;
+ bool mAlarmed;
+ bool mAttacked;
+ bool mHostile;
+ bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not.
+
+ int mAttackType;
+
+ std::string mLastHitObject; // The last object to hit this actor
+
+ protected:
+ bool mIsWerewolf;
+ Stat<int> mWerewolfAttributes[8];
+
+ public:
+ CreatureStats();
+
+ const Stat<int> & getAttribute(int index) const;
+
+ const DynamicStat<float> & getHealth() const;
+
+ const DynamicStat<float> & getMagicka() const;
+
+ const DynamicStat<float> & getFatigue() const;
+
+ const DynamicStat<float> & getDynamic (int index) const;
+
+ const Spells & getSpells() const;
+
+ const ActiveSpells & getActiveSpells() const;
+
+ const MagicEffects & getMagicEffects() const;
+
+ bool getAttackingOrSpell() const;
+
+ int getLevel() const;
+
+ int getAiSetting (int index) const;
+ ///< 0: hello, 1 fight, 2 flee, 3 alarm
+
+ Stat<int> & getAttribute(int index);
+
+ Spells & getSpells();
+
+ ActiveSpells & getActiveSpells();
+
+ MagicEffects & getMagicEffects();
+
+ void setAttribute(int index, const Stat<int> &value);
+
+ void setHealth(const DynamicStat<float> &value);
+
+ void setMagicka(const DynamicStat<float> &value);
+
+ void setFatigue(const DynamicStat<float> &value);
+
+ void setDynamic (int index, const DynamicStat<float> &value);
+
+ void setSpells(const Spells &spells);
+
+ void setActiveSpells(const ActiveSpells &active);
+
+ void setMagicEffects(const MagicEffects &effects);
+
+ void setAttackingOrSpell(bool attackingOrSpell);
+
+ enum AttackType
+ {
+ AT_Slash,
+ AT_Thrust,
+ AT_Chop
+ };
+ void setAttackType(int attackType) { mAttackType = attackType; }
+ int getAttackType() { return mAttackType; }
+
+ void setLevel(int level);
+
+ void setAiSetting (int index, int value);
+ ///< 0: hello, 1 fight, 2 flee, 3 alarm
+
+ const AiSequence& getAiSequence() const;
+
+ AiSequence& getAiSequence();
+
+ float getFatigueTerm() const;
+ ///< Return effective fatigue
+
+ float getLevelHealthBonus() const;
+
+ void levelUp();
+
+ void updateHealth();
+ ///< Calculate health based on endurance and strength.
+ /// Called at character creation and at level up.
+
+ bool isDead() const;
+
+ bool hasDied() const;
+
+ void clearHasDied();
+
+ void resurrect();
+
+ bool hasCommonDisease() const;
+
+ bool hasBlightDisease() const;
+
+ int getFriendlyHits() const;
+ ///< Number of friendly hits received.
+
+ void friendlyHit();
+ ///< Increase number of friendly hits by one.
+
+ bool hasTalkedToPlayer() const;
+ ///< Has this creature talked with the player before?
+
+ void talkedToPlayer();
+
+ bool isAlarmed() const;
+
+ void setAlarmed (bool alarmed);
+
+ bool getAttacked() const;
+
+ void setAttacked (bool attacked);
+
+ bool isHostile() const;
+
+ void setHostile (bool hostile);
+
+ bool getCreatureTargetted() const;
+
+ float getEvasion() const;
+
+ void setLastHitObject(const std::string &objectid);
+ const std::string &getLastHitObject() const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp
new file mode 100644
index 0000000000..5be00505c2
--- /dev/null
+++ b/apps/openmw/mwmechanics/drawstate.hpp
@@ -0,0 +1,15 @@
+#ifndef GAME_MWMECHANICS_DRAWSTATE_H
+#define GAME_MWMECHANICS_DRAWSTATE_H
+
+namespace MWMechanics
+{
+ /// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die!
+ enum DrawState_
+ {
+ DrawState_Weapon = 0,
+ DrawState_Spell = 1,
+ DrawState_Nothing = 2
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp
new file mode 100644
index 0000000000..4e26b5027c
--- /dev/null
+++ b/apps/openmw/mwmechanics/enchanting.cpp
@@ -0,0 +1,318 @@
+#include "enchanting.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "creaturestats.hpp"
+#include "npcstats.hpp"
+#include <boost/algorithm/string.hpp>
+
+namespace MWMechanics
+{
+ Enchanting::Enchanting()
+ : mCastStyle(ESM::Enchantment::CastOnce)
+ , mSelfEnchanting(false)
+ , mOldItemCount(0)
+ {}
+
+ void Enchanting::setOldItem(MWWorld::Ptr oldItem)
+ {
+ mOldItemPtr=oldItem;
+ if(!itemEmpty())
+ {
+ mObjectType = mOldItemPtr.getTypeName();
+ mOldItemId = mOldItemPtr.getCellRef().mRefID;
+ mOldItemCount = mOldItemPtr.getRefData().getCount();
+ }
+ else
+ {
+ mObjectType="";
+ mOldItemId="";
+ }
+ }
+
+ void Enchanting::setNewItemName(const std::string& s)
+ {
+ mNewItemName=s;
+ }
+
+ void Enchanting::setEffect(ESM::EffectList effectList)
+ {
+ mEffectList=effectList;
+ }
+
+ int Enchanting::getCastStyle() const
+ {
+ return mCastStyle;
+ }
+
+ void Enchanting::setSoulGem(MWWorld::Ptr soulGem)
+ {
+ mSoulGemPtr=soulGem;
+ }
+
+ bool Enchanting::create()
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ ESM::Enchantment enchantment;
+ enchantment.mData.mCharge = getGemCharge();
+
+ mSoulGemPtr.getRefData().setCount (mSoulGemPtr.getRefData().getCount()-1);
+
+ //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);
+ }
+
+ if(mSelfEnchanting)
+ {
+ if(getEnchantChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
+ return false;
+
+ MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1);
+ }
+
+ if(mCastStyle==ESM::Enchantment::ConstantEffect)
+ {
+ enchantment.mData.mCharge=0;
+ }
+ enchantment.mData.mType = mCastStyle;
+ 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);
+
+ mOldItemPtr.getRefData().setCount(1);
+
+ MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId);
+ ref.getPtr().getRefData().setCount (mOldItemCount-1);
+
+ MWWorld::Class::get (player).getContainerStore (player).add (ref.getPtr(), player);
+ if(!mSelfEnchanting)
+ payForEnchantment();
+
+ return true;
+ }
+
+ void Enchanting::nextCastStyle()
+ {
+ if (itemEmpty())
+ {
+ mCastStyle = ESM::Enchantment::WhenUsed;
+ return;
+ }
+
+ const bool powerfulSoul = getGemCharge() >= \
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("iSoulAmountForConstantEffect")->getInt();
+ if ((mObjectType == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name()))
+ { // Armor or Clothing
+ switch(mCastStyle)
+ {
+ case ESM::Enchantment::WhenUsed:
+ if (powerfulSoul)
+ mCastStyle = ESM::Enchantment::ConstantEffect;
+ return;
+ default: // takes care of Constant effect too
+ mCastStyle = ESM::Enchantment::WhenUsed;
+ return;
+ }
+ }
+ else if(mObjectType == typeid(ESM::Weapon).name())
+ { // Weapon
+ switch(mCastStyle)
+ {
+ case ESM::Enchantment::WhenStrikes:
+ mCastStyle = ESM::Enchantment::WhenUsed;
+ return;
+ case ESM::Enchantment::WhenUsed:
+ if (powerfulSoul)
+ mCastStyle = ESM::Enchantment::ConstantEffect;
+ else
+ mCastStyle = ESM::Enchantment::WhenStrikes;
+ return;
+ default: // takes care of Constant effect too
+ mCastStyle = ESM::Enchantment::WhenStrikes;
+ return;
+ }
+ }
+ else if(mObjectType == typeid(ESM::Book).name())
+ { // Scroll or Book
+ mCastStyle = ESM::Enchantment::CastOnce;
+ return;
+ }
+
+ // Fail case
+ mCastStyle = ESM::Enchantment::CastOnce;
+ }
+
+ /*
+ * Vanilla enchant cost formula:
+ *
+ * Touch/Self: (min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025
+ * Target: 1.5 * ((min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025)
+ * Constant eff: (min + max) * baseCost * 2.5 + area * baseCost * 0.025
+ *
+ * For multiple effects - cost of each effect is multiplied by number of effects that follows +1.
+ *
+ * Note: Minimal value inside formula for 'min' and 'max' is 1. So in vanilla:
+ * (0 + 0) == (1 + 0) == (1 + 1) => 2 or (2 + 0) == (1 + 2) => 3
+ *
+ * Formula on UESPWiki is not entirely correct.
+ */
+ float Enchanting::getEnchantPoints() const
+ {
+ if (mEffectList.mList.empty())
+ // No effects added, cost = 0
+ return 0;
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ std::vector<ESM::ENAMstruct> mEffects = mEffectList.mList;
+
+ float enchantmentCost = 0;
+ int effectsLeftCnt = mEffects.size();
+ float baseCost, magnitudeCost, areaCost;
+ int magMin, magMax, area;
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
+ {
+ baseCost = (store.get<ESM::MagicEffect>().find(it->mEffectID))->mData.mBaseCost;
+ // To reflect vanilla behavior
+ magMin = (it->mMagnMin == 0) ? 1 : it->mMagnMin;
+ magMax = (it->mMagnMax == 0) ? 1 : it->mMagnMax;
+ area = (it->mArea == 0) ? 1 : it->mArea;
+
+ if (mCastStyle == ESM::Enchantment::ConstantEffect)
+ {
+ magnitudeCost = (magMin + magMax) * baseCost * 2.5;
+ }
+ else
+ {
+ magnitudeCost = (magMin + magMax) * it->mDuration * baseCost * 0.025;
+ if(it->mRange == ESM::RT_Target)
+ magnitudeCost *= 1.5;
+ }
+
+ areaCost = area * 0.025 * baseCost;
+ if (it->mRange == ESM::RT_Target)
+ areaCost *= 1.5;
+
+ enchantmentCost += (magnitudeCost + areaCost) * effectsLeftCnt;
+ --effectsLeftCnt;
+ }
+
+ return enchantmentCost;
+ }
+
+
+ float Enchanting::getCastCost() const
+ {
+ if (mCastStyle == ESM::Enchantment::ConstantEffect)
+ return 0;
+
+ const float enchantCost = getEnchantPoints();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWMechanics::NpcStats &stats = MWWorld::Class::get(player).getNpcStats(player);
+ int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+
+ /*
+ * Each point of enchant skill above/under 10 subtracts/adds
+ * one percent of enchantment cost while minimum is 1.
+ */
+ const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
+
+ return (castCost < 1) ? 1 : castCost;
+ }
+
+
+ int Enchanting::getEnchantPrice() const
+ {
+ if(mEnchanter.isEmpty())
+ return 0;
+
+ float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentValueMult")->getFloat();
+ int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, (getEnchantPoints() * priceMultipler), true);
+ return price;
+ }
+
+ int Enchanting::getGemCharge() const
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ if(soulEmpty())
+ return 0;
+ if(mSoulGemPtr.getCellRef().mSoul=="")
+ return 0;
+ const ESM::Creature* soul = store.get<ESM::Creature>().find(mSoulGemPtr.getCellRef().mSoul);
+ return soul->mData.mSoul;
+ }
+
+ float Enchanting::getMaxEnchantValue() const
+ {
+ if (itemEmpty())
+ return 0;
+ return MWWorld::Class::get(mOldItemPtr).getEnchantmentPoints(mOldItemPtr);
+ }
+ bool Enchanting::soulEmpty() const
+ {
+ return mSoulGemPtr.isEmpty();
+ }
+
+ bool Enchanting::itemEmpty() const
+ {
+ return mOldItemPtr.isEmpty();
+ }
+
+ void Enchanting::setSelfEnchanting(bool selfEnchanting)
+ {
+ mSelfEnchanting = selfEnchanting;
+ }
+
+ void Enchanting::setEnchanter(MWWorld::Ptr enchanter)
+ {
+ mEnchanter = enchanter;
+ }
+
+ float Enchanting::getEnchantChance() const
+ {
+ /*
+ Formula from http://www.uesp.net/wiki/Morrowind:Enchant
+ */
+ const CreatureStats& creatureStats = MWWorld::Class::get (mEnchanter).getCreatureStats (mEnchanter);
+ const NpcStats& npcStats = MWWorld::Class::get (mEnchanter).getNpcStats (mEnchanter);
+
+ float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() +
+ (0.25 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified())
+ + (0.125 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()));
+
+ float chance2 = 2.5 * getEnchantPoints();
+ if(mCastStyle==ESM::Enchantment::ConstantEffect)
+ {
+ float constantChance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentConstantChanceMult")->getFloat();
+ chance2 /= constantChance;
+ }
+ return (chance1-chance2);
+ }
+
+ void Enchanting::payForEnchantment() const
+ {
+ MWWorld::Ptr gold;
+
+ 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());
+ }
+}
diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp
new file mode 100644
index 0000000000..a25fd43abc
--- /dev/null
+++ b/apps/openmw/mwmechanics/enchanting.hpp
@@ -0,0 +1,49 @@
+#ifndef GAME_MWMECHANICS_ENCHANTING_H
+#define GAME_MWMECHANICS_ENCHANTING_H
+#include <string>
+#include "../mwworld/ptr.hpp"
+#include <components/esm/effectlist.hpp>
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+namespace MWMechanics
+{
+ class Enchanting
+ {
+ MWWorld::Ptr mOldItemPtr;
+ MWWorld::Ptr mSoulGemPtr;
+ MWWorld::Ptr mEnchanter;
+
+ int mCastStyle;
+
+ bool mSelfEnchanting;
+
+ ESM::EffectList mEffectList;
+
+ std::string mNewItemName;
+ std::string mObjectType;
+ std::string mOldItemId;
+ int mOldItemCount;
+
+ public:
+ Enchanting();
+ void setEnchanter(MWWorld::Ptr enchanter);
+ void setSelfEnchanting(bool selfEnchanting);
+ void setOldItem(MWWorld::Ptr oldItem);
+ void setNewItemName(const std::string& s);
+ void setEffect(ESM::EffectList effectList);
+ void setSoulGem(MWWorld::Ptr soulGem);
+ bool create(); //Return true if created, false if failed.
+ void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object)
+ int getCastStyle() const;
+ float getEnchantPoints() const;
+ float getCastCost() const;
+ int getEnchantPrice() const;
+ float getMaxEnchantValue() const;
+ int getGemCharge() const;
+ float getEnchantChance() const;
+ bool soulEmpty() const; //Return true if empty
+ bool itemEmpty() const; //Return true if empty
+ void payForEnchantment() const;
+ };
+}
+#endif
diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp
new file mode 100644
index 0000000000..1a7b348170
--- /dev/null
+++ b/apps/openmw/mwmechanics/magiceffects.cpp
@@ -0,0 +1,163 @@
+
+#include "magiceffects.hpp"
+
+#include <cstdlib>
+
+#include <stdexcept>
+
+#include <components/esm/effectlist.hpp>
+
+namespace MWMechanics
+{
+ EffectKey::EffectKey() : mId (0), mArg (-1) {}
+
+ EffectKey::EffectKey (const ESM::ENAMstruct& effect)
+ {
+ mId = effect.mEffectID;
+ mArg = -1;
+
+ if (effect.mSkill!=-1)
+ mArg = effect.mSkill;
+
+ if (effect.mAttribute!=-1)
+ {
+ if (mArg!=-1)
+ throw std::runtime_error (
+ "magic effect can't have both a skill and an attribute argument");
+
+ mArg = effect.mAttribute;
+ }
+ }
+
+ bool operator< (const EffectKey& left, const EffectKey& right)
+ {
+ if (left.mId<right.mId)
+ return true;
+
+ if (left.mId>right.mId)
+ return false;
+
+ return left.mArg<right.mArg;
+ }
+
+ EffectParam::EffectParam() : mMagnitude (0) {}
+
+ EffectParam& EffectParam::operator+= (const EffectParam& param)
+ {
+ mMagnitude += param.mMagnitude;
+ return *this;
+ }
+
+ EffectParam& EffectParam::operator-= (const EffectParam& param)
+ {
+ mMagnitude -= param.mMagnitude;
+ return *this;
+ }
+
+ void MagicEffects::add (const EffectKey& key, const EffectParam& param)
+ {
+ Collection::iterator iter = mCollection.find (key);
+
+ if (iter==mCollection.end())
+ {
+ mCollection.insert (std::make_pair (key, param));
+ }
+ else
+ {
+ iter->second += param;
+ }
+ }
+
+ 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)
+ {
+ MagicEffects temp (effects);
+ *this += temp;
+ return *this;
+ }
+
+ for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
+ {
+ Collection::iterator result = mCollection.find (iter->first);
+
+ if (result!=mCollection.end())
+ result->second += iter->second;
+ else
+ mCollection.insert (*iter);
+ }
+
+ return *this;
+ }
+
+ EffectParam MagicEffects::get (const EffectKey& key) const
+ {
+ Collection::const_iterator iter = mCollection.find (key);
+
+ if (iter==mCollection.end())
+ {
+ return EffectParam();
+ }
+ else
+ {
+ return iter->second;
+ }
+ }
+
+ MagicEffects MagicEffects::diff (const MagicEffects& prev, const MagicEffects& now)
+ {
+ MagicEffects result;
+
+ // adding/changing
+ for (Collection::const_iterator iter (now.begin()); iter!=now.end(); ++iter)
+ {
+ Collection::const_iterator other = prev.mCollection.find (iter->first);
+
+ if (other==prev.end())
+ {
+ // adding
+ result.add (iter->first, iter->second);
+ }
+ else
+ {
+ // changing
+ result.add (iter->first, iter->second - other->second);
+ }
+ }
+
+ // removing
+ for (Collection::const_iterator iter (prev.begin()); iter!=prev.end(); ++iter)
+ {
+ Collection::const_iterator other = now.mCollection.find (iter->first);
+
+ if (other==prev.end())
+ {
+ result.add (iter->first, EffectParam() - iter->second);
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp
new file mode 100644
index 0000000000..b80b5a863d
--- /dev/null
+++ b/apps/openmw/mwmechanics/magiceffects.hpp
@@ -0,0 +1,83 @@
+#ifndef GAME_MWMECHANICS_MAGICEFFECTS_H
+#define GAME_MWMECHANICS_MAGICEFFECTS_H
+
+#include <map>
+
+namespace ESM
+{
+ struct ENAMstruct;
+ struct EffectList;
+}
+
+namespace MWMechanics
+{
+ struct EffectKey
+ {
+ int mId;
+ int mArg; // skill or ability
+
+ EffectKey();
+
+ EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {}
+
+ EffectKey (const ESM::ENAMstruct& effect);
+ };
+
+ bool operator< (const EffectKey& left, const EffectKey& right);
+
+ struct EffectParam
+ {
+ int mMagnitude;
+
+ EffectParam();
+
+ EffectParam& operator+= (const EffectParam& param);
+
+ EffectParam& operator-= (const EffectParam& param);
+ };
+
+ inline EffectParam operator+ (const EffectParam& left, const EffectParam& right)
+ {
+ EffectParam param (left);
+ return param += right;
+ }
+
+ inline EffectParam operator- (const EffectParam& left, const EffectParam& right)
+ {
+ EffectParam param (left);
+ return param -= right;
+ }
+
+ /// \brief Effects currently affecting a NPC or creature
+ class MagicEffects
+ {
+ public:
+
+ typedef std::map<EffectKey, EffectParam> Collection;
+
+ private:
+
+ Collection mCollection;
+
+ public:
+
+ Collection::const_iterator begin() const { return mCollection.begin(); }
+
+ Collection::const_iterator end() const { return mCollection.end(); }
+
+ 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;
+ ///< This function can safely be used for keys that are not present.
+
+ static MagicEffects diff (const MagicEffects& prev, const MagicEffects& now);
+ ///< Return changes from \a prev to \a now.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
new file mode 100644
index 0000000000..8c13db7900
--- /dev/null
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
@@ -0,0 +1,682 @@
+
+#include "mechanicsmanagerimp.hpp"
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+namespace MWMechanics
+{
+ void MechanicsManager::buildPlayer()
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get (ptr).getNpcStats (ptr);
+
+ const ESM::NPC *player = ptr.get<ESM::NPC>()->mBase;
+
+ // reset
+ creatureStats.setLevel(player->mNpdt52.mLevel);
+ creatureStats.getSpells().clear();
+ creatureStats.setMagicEffects(MagicEffects());
+
+ 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);
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ // race
+ if (mRaceSelected)
+ {
+ const ESM::Race *race =
+ esmStore.get<ESM::Race>().find(player->mRace);
+
+ bool male = (player->mFlags & ESM::NPC::Female) == 0;
+
+ for (int i=0; i<8; ++i)
+ {
+ const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
+
+ creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale);
+ }
+
+ for (int i=0; i<27; ++i)
+ {
+ int bonus = 0;
+
+ for (int i2=0; i2<7; ++i2)
+ if (race->mData.mBonus[i2].mSkill==i)
+ {
+ bonus = race->mData.mBonus[i2].mBonus;
+ break;
+ }
+
+ npcStats.getSkill (i).setBase (5 + bonus);
+ }
+
+ for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
+ iter!=race->mPowers.mList.end(); ++iter)
+ {
+ creatureStats.getSpells().add (*iter);
+ }
+ }
+
+ // birthsign
+ const std::string &signId =
+ MWBase::Environment::get().getWorld()->getPlayer().getBirthSign();
+
+ if (!signId.empty())
+ {
+ const ESM::BirthSign *sign =
+ esmStore.get<ESM::BirthSign>().find(signId);
+
+ for (std::vector<std::string>::const_iterator iter (sign->mPowers.mList.begin());
+ iter!=sign->mPowers.mList.end(); ++iter)
+ {
+ creatureStats.getSpells().add (*iter);
+ }
+ }
+
+ // class
+ if (mClassSelected)
+ {
+ const ESM::Class *class_ =
+ esmStore.get<ESM::Class>().find(player->mClass);
+
+ for (int i=0; i<2; ++i)
+ {
+ int attribute = class_->mData.mAttribute[i];
+ if (attribute>=0 && attribute<8)
+ {
+ creatureStats.getAttribute(attribute).setBase (
+ creatureStats.getAttribute(attribute).getBase() + 10);
+ }
+ }
+
+ 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<27)
+ {
+ npcStats.getSkill (index).setBase (
+ npcStats.getSkill (index).getBase() + bonus);
+ }
+ }
+ }
+
+ const MWWorld::Store<ESM::Skill> &skills =
+ esmStore.get<ESM::Skill>();
+
+ MWWorld::Store<ESM::Skill>::iterator iter = skills.begin();
+ for (; iter != skills.end(); ++iter)
+ {
+ if (iter->mData.mSpecialization==class_->mData.mSpecialization)
+ {
+ int index = iter->mIndex;
+
+ if (index>=0 && index<27)
+ {
+ npcStats.getSkill (index).setBase (
+ npcStats.getSkill (index).getBase() + 5);
+ }
+ }
+ }
+ }
+
+ // forced update and current value adjustments
+ mActors.updateActor (ptr, 0);
+
+ for (int i=0; i<3; ++i)
+ {
+ DynamicStat<float> stat = creatureStats.getDynamic (i);
+ stat.setCurrent (stat.getModified());
+ creatureStats.setDynamic (i, stat);
+ }
+
+ // 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.autoEquip(ptr);
+ }
+
+ MechanicsManager::MechanicsManager()
+ : mUpdatePlayer (true), mClassSelected (false),
+ mRaceSelected (false)
+ {
+ buildPlayer();
+ }
+
+ void MechanicsManager::add(const MWWorld::Ptr& ptr)
+ {
+ if(MWWorld::Class::get(ptr).isActor())
+ mActors.addActor(ptr);
+ else
+ mObjects.addObject(ptr);
+ }
+
+ void MechanicsManager::remove(const MWWorld::Ptr& ptr)
+ {
+ if(ptr == mWatched)
+ mWatched = MWWorld::Ptr();
+ mActors.removeActor(ptr);
+ mObjects.removeObject(ptr);
+ }
+
+ void MechanicsManager::updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
+ {
+ if(old == mWatched)
+ mWatched = ptr;
+
+ if(MWWorld::Class::get(ptr).isActor())
+ mActors.updateActor(old, ptr);
+ else
+ mObjects.updateObject(old, ptr);
+ }
+
+
+ void MechanicsManager::drop(const MWWorld::CellStore *cellStore)
+ {
+ if(!mWatched.isEmpty() && mWatched.getCell() == cellStore)
+ mWatched = MWWorld::Ptr();
+
+ mActors.dropActors(cellStore);
+ mObjects.dropObjects(cellStore);
+ }
+
+
+ void MechanicsManager::watchActor(const MWWorld::Ptr& ptr)
+ {
+ mWatched = ptr;
+ }
+
+ void MechanicsManager::update(float duration, bool paused)
+ {
+ if(!mWatched.isEmpty())
+ {
+ MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
+ const MWMechanics::NpcStats &stats = mWatched.getClass().getNpcStats(mWatched);
+ for(int i = 0;i < ESM::Attribute::Length;++i)
+ {
+ if(stats.getAttribute(i) != mWatchedStats.getAttribute(i))
+ {
+ std::stringstream attrname;
+ attrname << "AttribVal"<<(i+1);
+
+ mWatchedStats.setAttribute(i, stats.getAttribute(i));
+ winMgr->setValue(attrname.str(), stats.getAttribute(i));
+ }
+ }
+
+ if(stats.getHealth() != mWatchedStats.getHealth())
+ {
+ static const std::string hbar("HBar");
+ mWatchedStats.setHealth(stats.getHealth());
+ winMgr->setValue(hbar, stats.getHealth());
+ }
+ if(stats.getMagicka() != mWatchedStats.getMagicka())
+ {
+ static const std::string mbar("MBar");
+ mWatchedStats.setMagicka(stats.getMagicka());
+ winMgr->setValue(mbar, stats.getMagicka());
+ }
+ if(stats.getFatigue() != mWatchedStats.getFatigue())
+ {
+ static const std::string fbar("FBar");
+ mWatchedStats.setFatigue(stats.getFatigue());
+ winMgr->setValue(fbar, stats.getFatigue());
+ }
+
+ if(stats.getTimeToStartDrowning() != mWatchedStats.getTimeToStartDrowning())
+ {
+ mWatchedStats.setTimeToStartDrowning(stats.getTimeToStartDrowning());
+ if(stats.getTimeToStartDrowning() >= 20.0f)
+ winMgr->setDrowningBarVisibility(false);
+ else
+ {
+ winMgr->setDrowningBarVisibility(true);
+ winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning());
+ }
+ }
+
+ bool update = false;
+
+ //Loop over ESM::Skill::SkillEnum
+ for(int i = 0; i < ESM::Skill::Length; ++i)
+ {
+ if(stats.getSkill(i) != mWatchedStats.getSkill(i))
+ {
+ update = true;
+ mWatchedStats.getSkill(i) = stats.getSkill(i);
+ winMgr->setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i));
+ }
+ }
+
+ if(update)
+ winMgr->updateSkillArea();
+
+ winMgr->setValue("level", stats.getLevel());
+ }
+
+ if (mUpdatePlayer)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ // basic player profile; should not change anymore after the creation phase is finished.
+ MWBase::WindowManager *winMgr =
+ MWBase::Environment::get().getWindowManager();
+
+ const ESM::NPC *player =
+ world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+
+ const ESM::Race *race =
+ world->getStore().get<ESM::Race>().find(player->mRace);
+ const ESM::Class *cls =
+ world->getStore().get<ESM::Class>().find(player->mClass);
+
+ winMgr->setValue ("name", player->mName);
+ winMgr->setValue ("race", race->mName);
+ winMgr->setValue ("class", cls->mName);
+
+ mUpdatePlayer = false;
+
+ MWBase::WindowManager::SkillList majorSkills (5);
+ MWBase::WindowManager::SkillList minorSkills (5);
+
+ for (int i=0; i<5; ++i)
+ {
+ minorSkills[i] = cls->mData.mSkills[i][0];
+ majorSkills[i] = cls->mData.mSkills[i][1];
+ }
+
+ winMgr->configureSkills (majorSkills, minorSkills);
+
+ // HACK? The player has been changed, so a new Animation object may
+ // have been made for them. Make sure they're properly updated.
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ mActors.removeActor(ptr);
+ mActors.addActor(ptr);
+ }
+
+ mActors.update(duration, paused);
+ mObjects.update(duration, paused);
+ }
+
+ void MechanicsManager::restoreDynamicStats()
+ {
+ mActors.restoreDynamicStats ();
+ }
+
+ void MechanicsManager::setPlayerName (const std::string& name)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ ESM::NPC player =
+ *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ player.mName = name;
+
+ world->createRecord(player);
+
+ mUpdatePlayer = true;
+ }
+
+ void MechanicsManager::setPlayerRace (const std::string& race, bool male, const std::string &head, const std::string &hair)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ ESM::NPC player =
+ *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+
+ player.mRace = race;
+ player.mHead = head;
+ player.mHair = hair;
+ player.setIsMale(male);
+
+ world->createRecord(player);
+
+ mRaceSelected = true;
+ buildPlayer();
+ mUpdatePlayer = true;
+ }
+
+ void MechanicsManager::setPlayerBirthsign (const std::string& id)
+ {
+ MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(id);
+ buildPlayer();
+ mUpdatePlayer = true;
+ }
+
+ void MechanicsManager::setPlayerClass (const std::string& id)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ ESM::NPC player =
+ *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ player.mClass = id;
+
+ world->createRecord(player);
+
+ mClassSelected = true;
+ buildPlayer();
+ mUpdatePlayer = true;
+ }
+
+ void MechanicsManager::setPlayerClass (const ESM::Class &cls)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ const ESM::Class *ptr = world->createRecord(cls);
+
+ ESM::NPC player =
+ *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ player.mClass = ptr->mId;
+
+ world->createRecord(player);
+
+ mClassSelected = true;
+ buildPlayer();
+ mUpdatePlayer = true;
+ }
+
+ int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr)
+ {
+ MWMechanics::NpcStats npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr);
+ float x = npcSkill.getBaseDisposition();
+
+ MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ 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))
+ x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispRaceMod")->getFloat();
+
+ x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispPersonalityMult")->getFloat()
+ * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispPersonalityBase")->getFloat());
+
+ float reaction = 0;
+ int rank = 0;
+ std::string npcFaction = "";
+ if(!npcSkill.getFactionRanks().empty()) npcFaction = npcSkill.getFactionRanks().begin()->first;
+
+ if (playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction)) != playerStats.getFactionRanks().end())
+ {
+ 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;
+ }
+ rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second;
+ }
+ else if (npcFaction != "")
+ {
+ 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(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() )
+ {
+ if(it->mReaction<reaction) reaction = it->mReaction;
+ }
+ }
+ rank = 0;
+ }
+ else
+ {
+ reaction = 0;
+ rank = 0;
+ }
+ x += (MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispFactionRankMult")->getFloat() * rank
+ + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispFactionRankBase")->getFloat())
+ * MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispFactionMod")->getFloat() * reaction;
+
+ x -= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispCrimeMod")->getFloat() * playerStats.getBounty();
+ if (playerStats.hasCommonDisease() || playerStats.hasBlightDisease())
+ x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispDiseaseMod")->getFloat();
+
+ if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon)
+ x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispWeaponDrawn")->getFloat();
+
+ int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used
+ return effective_disposition;
+ }
+
+ int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying)
+ {
+ if (ptr.getTypeName() == typeid(ESM::Creature).name())
+ return basePrice;
+
+ const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(ptr).getNpcStats(ptr);
+
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
+
+ // I suppose the temporary disposition change _has_ to be considered here,
+ // otherwise one would get different prices when exiting and re-entering the dialogue window...
+ int clampedDisposition = std::max(0, std::min(getDerivedDisposition(ptr)
+ + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(),100));
+ float a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
+ float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
+ float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
+ float d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
+ float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
+ float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f);
+
+ float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm();
+ float npcTerm = (d + e + f) * sellerStats.getFatigueTerm();
+ float buyTerm = 0.01 * (100 - 0.5 * (pcTerm - npcTerm));
+ float sellTerm = 0.01 * (50 - 0.5 * (npcTerm - pcTerm));
+
+ float x;
+ if(buying) x = buyTerm;
+ else x = std::min(buyTerm, sellTerm);
+ int offerPrice;
+ if (x < 1)
+ offerPrice = int(x * basePrice);
+ else
+ offerPrice = basePrice + int((x - 1) * basePrice);
+ offerPrice = std::max(1, offerPrice);
+ return offerPrice;
+ }
+
+ int MechanicsManager::countDeaths (const std::string& id) const
+ {
+ return mActors.countDeaths (id);
+ }
+
+
+ void MechanicsManager::getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type,
+ float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange)
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ MWMechanics::NpcStats npcStats = MWWorld::Class::get(npc).getNpcStats(npc);
+
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
+
+ float persTerm = playerStats.getAttribute(ESM::Attribute::Personality).getModified()
+ / gmst.find("fPersonalityMod")->getFloat();
+
+ float luckTerm = playerStats.getAttribute(ESM::Attribute::Luck).getModified()
+ / gmst.find("fLuckMod")->getFloat();
+
+ float repTerm = playerStats.getReputation() * gmst.find("fReputationMod")->getFloat();
+ float levelTerm = playerStats.getLevel() * gmst.find("fLevelMod")->getFloat();
+
+ float fatigueTerm = playerStats.getFatigueTerm();
+
+ float playerRating1 = (repTerm + luckTerm + persTerm + playerStats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm;
+ float playerRating2 = playerRating1 + levelTerm;
+ float playerRating3 = (playerStats.getSkill(ESM::Skill::Mercantile).getModified() + luckTerm + persTerm) * fatigueTerm;
+
+ float npcRating1 = (repTerm + luckTerm + persTerm + playerStats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm;
+ float npcRating2 = (levelTerm + repTerm + luckTerm + persTerm + npcStats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm;
+ float npcRating3 = (playerStats.getSkill(ESM::Skill::Mercantile).getModified() + repTerm + luckTerm + persTerm) * fatigueTerm;
+
+ int currentDisposition = std::min(100, std::max(0, int(getDerivedDisposition(npc) + currentTemporaryDispositionDelta)));
+
+ float d = 1 - 0.02 * abs(currentDisposition - 50);
+ float target1 = d * (playerRating1 - npcRating1 + 50);
+ float target2 = d * (playerRating2 - npcRating2 + 50);
+
+ float bribeMod;
+ if (type == PT_Bribe10) bribeMod = gmst.find("fBribe10Mod")->getFloat();
+ else if (type == PT_Bribe100) bribeMod = gmst.find("fBribe100Mod")->getFloat();
+ else bribeMod = gmst.find("fBribe1000Mod")->getFloat();
+
+ float target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod;
+
+ float iPerMinChance = gmst.find("iPerMinChance")->getInt();
+ float iPerMinChange = gmst.find("iPerMinChange")->getInt();
+ float fPerDieRollMult = gmst.find("fPerDieRollMult")->getFloat();
+ float fPerTempMult = gmst.find("fPerTempMult")->getFloat();
+
+ float x = 0;
+ float y = 0;
+
+ float roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
+
+ if (type == PT_Admire)
+ {
+ target1 = std::max(iPerMinChance, target1);
+ success = (roll <= target1);
+ float c = int(fPerDieRollMult * (target1 - roll));
+ x = success ? std::max(iPerMinChange, c) : c;
+ }
+ else if (type == PT_Intimidate)
+ {
+ target2 = std::max(iPerMinChance, target2);
+
+ success = (roll <= target2);
+
+ float r;
+ if (roll != target2)
+ r = int(target2 - roll);
+ else
+ r = 1;
+
+ if (roll <= target2)
+ {
+ float s = int(r * fPerDieRollMult * fPerTempMult);
+
+ npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + int(std::max(iPerMinChange, s)))));
+ npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + int(std::min(-iPerMinChange, -s)))));
+ }
+
+ float c = -std::abs(int(r * fPerDieRollMult));
+ if (success)
+ {
+ if (std::abs(c) < iPerMinChange)
+ {
+ x = 0;
+ y = -iPerMinChange;
+ }
+ else
+ {
+ x = -int(c * fPerTempMult);
+ y = c;
+ }
+ }
+ else
+ {
+ x = int(c * fPerTempMult);
+ y = c;
+ }
+ }
+ else if (type == PT_Taunt)
+ {
+ target1 = std::max(iPerMinChance, target1);
+ success = (roll <= target1);
+
+ float c = std::abs(int(target1 - roll));
+
+ if (roll <= target1)
+ {
+ float s = c * fPerDieRollMult * fPerTempMult;
+
+ npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + std::min(-int(iPerMinChange), int(-s)))));
+ npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + std::max(int(iPerMinChange), int(s)))));
+ }
+ x = int(-c * fPerDieRollMult);
+
+ if (success && std::abs(x) < iPerMinChange)
+ x = -iPerMinChange;
+ }
+ else // Bribe
+ {
+ target3 = std::max(iPerMinChance, target3);
+ success = (roll <= target3);
+ float c = int((target3 - roll) * fPerDieRollMult);
+
+ x = success ? std::max(iPerMinChange, c) : c;
+ }
+
+ tempChange = type == PT_Intimidate ? x : int(x * fPerTempMult);
+
+
+ float cappedDispositionChange = tempChange;
+ if (currentDisposition + tempChange > 100.f)
+ cappedDispositionChange = 100 - currentDisposition;
+ if (currentDisposition + tempChange < 0.f)
+ cappedDispositionChange = -currentDisposition;
+
+ permChange = int(cappedDispositionChange / fPerTempMult);
+ if (type == PT_Intimidate)
+ {
+ permChange = success ? -int(cappedDispositionChange/ fPerTempMult) : y;
+ }
+ }
+
+ void MechanicsManager::forceStateUpdate(const MWWorld::Ptr &ptr)
+ {
+ if(MWWorld::Class::get(ptr).isActor())
+ mActors.forceStateUpdate(ptr);
+ }
+
+ void MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number)
+ {
+ if(MWWorld::Class::get(ptr).isActor())
+ mActors.playAnimationGroup(ptr, groupName, mode, number);
+ else
+ mObjects.playAnimationGroup(ptr, groupName, mode, number);
+ }
+ void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr)
+ {
+ if(MWWorld::Class::get(ptr).isActor())
+ mActors.skipAnimation(ptr);
+ else
+ mObjects.skipAnimation(ptr);
+ }
+ bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName)
+ {
+ if(MWWorld::Class::get(ptr).isActor())
+ return mActors.checkAnimationPlaying(ptr, groupName);
+ else
+ return false;
+ }
+
+}
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
new file mode 100644
index 0000000000..ad07562c7b
--- /dev/null
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
@@ -0,0 +1,106 @@
+#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
+#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
+
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+#include "creaturestats.hpp"
+#include "npcstats.hpp"
+#include "objects.hpp"
+#include "actors.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace MWWorld
+{
+ class CellStore;
+}
+
+namespace MWMechanics
+{
+ class MechanicsManager : public MWBase::MechanicsManager
+ {
+ MWWorld::Ptr mWatched;
+ NpcStats mWatchedStats;
+ bool mUpdatePlayer;
+ bool mClassSelected;
+ bool mRaceSelected;
+
+ Objects mObjects;
+ Actors mActors;
+
+ 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);
+ ///< Register an object for management
+
+ virtual void remove (const MWWorld::Ptr& ptr);
+ ///< Deregister an object for management
+
+ virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr);
+ ///< Moves an object to a new cell
+
+ virtual void drop(const MWWorld::CellStore *cellStore);
+ ///< Deregister all objects in the given cell.
+
+ virtual void watchActor(const MWWorld::Ptr& ptr);
+ ///< On each update look for changes in a previously registered actor and update the
+ /// GUI accordingly.
+
+ virtual void update (float duration, bool paused);
+ ///< Update objects
+ ///
+ /// \param paused In game type does not currently advance (this usually means some GUI
+ /// component is up).
+
+ virtual void setPlayerName (const std::string& name);
+ ///< Set player name.
+
+ virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair);
+ ///< Set player race.
+
+ virtual void setPlayerBirthsign (const std::string& id);
+ ///< Set player birthsign.
+
+ virtual void setPlayerClass (const std::string& id);
+ ///< Set player class to stock class.
+
+ virtual void setPlayerClass (const ESM::Class& class_);
+ ///< Set player class to custom class.
+
+ virtual void restoreDynamicStats();
+ ///< If the player is sleeping, this should be called every hour.
+
+ virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying);
+ ///< This is used by every service to determine the price of objects given the trading skills of the player and NPC.
+
+ virtual int getDerivedDisposition(const MWWorld::Ptr& ptr);
+ ///< Calculate the diposition of an NPC toward the player.
+
+ 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);
+ ///< Perform a persuasion action on NPC
+
+ virtual void forceStateUpdate(const MWWorld::Ptr &ptr);
+
+ 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);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp
new file mode 100644
index 0000000000..6c9a4b7589
--- /dev/null
+++ b/apps/openmw/mwmechanics/movement.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_MWMECHANICS_MOVEMENT_H
+#define GAME_MWMECHANICS_MOVEMENT_H
+
+namespace MWMechanics
+{
+ /// Desired movement for an actor
+ struct Movement
+ {
+ float mPosition[3];
+ float mRotation[3];
+
+ Movement()
+ {
+ mPosition[0] = mPosition[1] = mPosition[2] = 0.0f;
+ mRotation[0] = mRotation[1] = mRotation[2] = 0.0f;
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp
new file mode 100644
index 0000000000..0b36982890
--- /dev/null
+++ b/apps/openmw/mwmechanics/npcstats.cpp
@@ -0,0 +1,433 @@
+
+#include "npcstats.hpp"
+
+#include <cmath>
+#include <stdexcept>
+#include <vector>
+#include <algorithm>
+
+#include <boost/format.hpp>
+
+#include <components/esm/loadskil.hpp>
+#include <components/esm/loadclas.hpp>
+#include <components/esm/loadgmst.hpp>
+#include <components/esm/loadfact.hpp>
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+MWMechanics::NpcStats::NpcStats()
+: mMovementFlags (0)
+, mDrawState (DrawState_Nothing)
+, mBounty (0)
+, mLevelProgress(0)
+, mDisposition(0)
+, mVampire (0)
+, mReputation(0)
+, mWerewolfKills (0)
+, mProfit(0)
+, mAttackStrength(0.0f)
+, mTimeToStartDrowning(20.0)
+, mLastDrowningHit(0)
+{
+ mSkillIncreases.resize (ESM::Attribute::Length);
+ for (int i=0; i<ESM::Attribute::Length; ++i)
+ mSkillIncreases[i] = 0;
+}
+
+MWMechanics::DrawState_ MWMechanics::NpcStats::getDrawState() const
+{
+ return mDrawState;
+}
+
+void MWMechanics::NpcStats::setDrawState (DrawState_ state)
+{
+ mDrawState = state;
+}
+
+float MWMechanics::NpcStats::getAttackStrength() const
+{
+ return mAttackStrength;
+}
+
+void MWMechanics::NpcStats::setAttackStrength(float value)
+{
+ mAttackStrength = value;
+}
+
+int MWMechanics::NpcStats::getBaseDisposition() const
+{
+ return mDisposition;
+}
+
+void MWMechanics::NpcStats::setBaseDisposition(int disposition)
+{
+ mDisposition = disposition;
+}
+
+bool MWMechanics::NpcStats::getMovementFlag (Flag flag) const
+{
+ return mMovementFlags & flag;
+}
+
+void MWMechanics::NpcStats::setMovementFlag (Flag flag, bool state)
+{
+ if (state)
+ mMovementFlags |= flag;
+ else
+ mMovementFlags &= ~flag;
+}
+
+const MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index) const
+{
+ if (index<0 || index>=27)
+ throw std::runtime_error ("skill index out of range");
+
+ return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]);
+}
+
+MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index)
+{
+ if (index<0 || index>=27)
+ throw std::runtime_error ("skill index out of range");
+
+ return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]);
+}
+
+const std::map<std::string, int>& MWMechanics::NpcStats::getFactionRanks() const
+{
+ return mFactionRank;
+}
+
+std::map<std::string, int>& MWMechanics::NpcStats::getFactionRanks()
+{
+ return mFactionRank;
+}
+
+const std::set<std::string>& MWMechanics::NpcStats::getExpelled() const
+{
+ return mExpelled;
+}
+
+std::set<std::string>& MWMechanics::NpcStats::getExpelled()
+{
+ return mExpelled;
+}
+
+bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const
+{
+ for (std::map<std::string, int>::const_iterator iter (mFactionRank.begin()); iter!=mFactionRank.end();
+ ++iter)
+ if (npcStats.mFactionRank.find (iter->first)!=npcStats.mFactionRank.end())
+ return true;
+
+ return false;
+}
+
+float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& class_, int usageType,
+ int level) const
+{
+ if (level<0)
+ level = static_cast<int> (getSkill (skillIndex).getBase());
+
+ const ESM::Skill *skill =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find (skillIndex);
+
+ float skillFactor = 1;
+
+ if (usageType>=4)
+ throw std::runtime_error ("skill usage type out of range");
+
+ if (usageType>=0)
+ {
+ skillFactor = skill->mData.mUseValue[usageType];
+
+ if (skillFactor<0)
+ throw std::runtime_error ("invalid skill gain factor");
+
+ if (skillFactor==0)
+ return 0;
+ }
+
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ float typeFactor = gmst.find ("fMiscSkillBonus")->getFloat();
+
+ for (int i=0; i<5; ++i)
+ if (class_.mData.mSkills[i][0]==skillIndex)
+ {
+ typeFactor = gmst.find ("fMinorSkillBonus")->getFloat();
+
+ break;
+ }
+
+ for (int i=0; i<5; ++i)
+ if (class_.mData.mSkills[i][1]==skillIndex)
+ {
+ typeFactor = gmst.find ("fMajorSkillBonus")->getFloat();
+
+ break;
+ }
+
+ if (typeFactor<=0)
+ throw std::runtime_error ("invalid skill type factor");
+
+ float specialisationFactor = 1;
+
+ if (skill->mData.mSpecialization==class_.mData.mSpecialization)
+ {
+ specialisationFactor = gmst.find ("fSpecialSkillBonus")->getFloat();
+
+ if (specialisationFactor<=0)
+ throw std::runtime_error ("invalid skill specialisation factor");
+ }
+ return 1.0 / ((level+1) * (1.0/skillFactor) * typeFactor * specialisationFactor);
+}
+
+void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType)
+{
+ // Don't increase skills as a werewolf
+ if(mIsWerewolf)
+ return;
+
+ float base = getSkill (skillIndex).getBase();
+
+ int level = static_cast<int> (base);
+
+ base += getSkillGain (skillIndex, class_, usageType);
+
+ if (static_cast<int> (base)!=level)
+ {
+ // skill leveled up
+ increaseSkill(skillIndex, class_, false);
+ }
+ else
+ getSkill (skillIndex).setBase (base);
+}
+
+void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress)
+{
+ float base = getSkill (skillIndex).getBase();
+
+ int level = static_cast<int> (base);
+
+ if (level >= 100)
+ return;
+
+ if (preserveProgress)
+ base += 1;
+ else
+ base = level+1;
+
+ // if this is a major or minor skill of the class, increase level progress
+ bool levelProgress = false;
+ for (int i=0; i<2; ++i)
+ for (int j=0; j<5; ++j)
+ {
+ int skill = class_.mData.mSkills[j][i];
+ if (skill == skillIndex)
+ levelProgress = true;
+ }
+
+ mLevelProgress += levelProgress;
+
+ // check the attribute this skill belongs to
+ const ESM::Skill* skill =
+ MWBase::Environment::get().getWorld ()->getStore ().get<ESM::Skill>().find(skillIndex);
+ ++mSkillIncreases[skill->mData.mAttribute];
+
+ // Play sound & skill progress notification
+ /// \todo check if character is the player, if levelling is ever implemented for NPCs
+ MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1);
+
+ std::stringstream message;
+ message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""))
+ % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}")
+ % static_cast<int> (base);
+ MWBase::Environment::get().getWindowManager ()->messageBox(message.str());
+
+ if (mLevelProgress >= 10)
+ {
+ // levelup is possible now
+ MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}");
+ }
+
+ getSkill (skillIndex).setBase (base);
+}
+
+int MWMechanics::NpcStats::getLevelProgress () const
+{
+ return mLevelProgress;
+}
+
+void MWMechanics::NpcStats::levelUp()
+{
+ mLevelProgress -= 10;
+ for (int i=0; i<ESM::Attribute::Length; ++i)
+ mSkillIncreases[i] = 0;
+}
+
+int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
+{
+ // Source: http://www.uesp.net/wiki/Morrowind:Level#How_to_Level_Up
+ int num = mSkillIncreases[attribute];
+ if (num <= 1)
+ return 1;
+ else if (num <= 4)
+ return 2;
+ else if (num <= 7)
+ return 3;
+ else if (num <= 9)
+ return 4;
+ else
+ return 5;
+}
+
+void MWMechanics::NpcStats::flagAsUsed (const std::string& id)
+{
+ mUsedIds.insert (id);
+}
+
+bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const
+{
+ return mUsedIds.find (id)!=mUsedIds.end();
+}
+
+int MWMechanics::NpcStats::getBounty() const
+{
+ return mBounty;
+}
+
+void MWMechanics::NpcStats::setBounty (int bounty)
+{
+ mBounty = bounty;
+}
+
+int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const
+{
+ std::map<std::string, int>::const_iterator iter = mFactionReputation.find (faction);
+
+ if (iter==mFactionReputation.end())
+ return 0;
+
+ return iter->second;
+}
+
+void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value)
+{
+ mFactionReputation[faction] = value;
+}
+
+bool MWMechanics::NpcStats::isVampire() const
+{
+ return mVampire;
+}
+
+void MWMechanics::NpcStats::setVampire (bool set)
+{
+ mVampire = set;
+}
+
+int MWMechanics::NpcStats::getReputation() const
+{
+ return mReputation;
+}
+
+void MWMechanics::NpcStats::setReputation(int reputation)
+{
+ mReputation = reputation;
+}
+
+bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int rank) const
+{
+ if (rank<0 || rank>=10)
+ throw std::runtime_error ("rank index out of range");
+
+ const ESM::Faction& faction =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find (factionId);
+
+ std::vector<int> skills;
+
+ for (int i=0; i<6; ++i)
+ skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getModified()));
+
+ std::sort (skills.begin(), skills.end());
+
+ std::vector<int>::const_reverse_iterator iter = skills.rbegin();
+
+ const ESM::RankData& rankData = faction.mData.mRankData[rank];
+
+ if (*iter<rankData.mSkill1)
+ return false;
+
+ return *++iter>=rankData.mSkill2;
+}
+
+bool MWMechanics::NpcStats::isWerewolf() const
+{
+ return mIsWerewolf;
+}
+
+void MWMechanics::NpcStats::setWerewolf (bool set)
+{
+ if(set != false)
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ for(size_t i = 0;i < ESM::Attribute::Length;i++)
+ {
+ mWerewolfAttributes[i] = getAttribute(i);
+ // Oh, Bethesda. It's "Intelligence".
+ std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") :
+ ESM::Attribute::sAttributeNames[i]);
+ mWerewolfAttributes[i].setModified(int(gmst.find(name)->getFloat()), 0);
+ }
+
+ for(size_t i = 0;i < ESM::Skill::Length;i++)
+ {
+ mWerewolfSkill[i] = getSkill(i);
+
+ // Acrobatics is set separately for some reason.
+ if(i == ESM::Skill::Acrobatics)
+ continue;
+
+ // "Mercantile"! >_<
+ std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") :
+ ESM::Skill::sSkillNames[i]);
+ mWerewolfSkill[i].setModified(int(gmst.find(name)->getFloat()), 0);
+ }
+ }
+ mIsWerewolf = set;
+}
+
+int MWMechanics::NpcStats::getWerewolfKills() const
+{
+ return mWerewolfKills;
+}
+
+int MWMechanics::NpcStats::getProfit() const
+{
+ return mProfit;
+}
+
+void MWMechanics::NpcStats::modifyProfit(int diff)
+{
+ mProfit += diff;
+}
+
+float MWMechanics::NpcStats::getTimeToStartDrowning() const
+{
+ return mTimeToStartDrowning;
+}
+void MWMechanics::NpcStats::setTimeToStartDrowning(float time)
+{
+ assert(time>=0 && time<=20);
+ mTimeToStartDrowning=time;
+}
diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp
new file mode 100644
index 0000000000..6b7efa5b72
--- /dev/null
+++ b/apps/openmw/mwmechanics/npcstats.hpp
@@ -0,0 +1,157 @@
+#ifndef GAME_MWMECHANICS_NPCSTATS_H
+#define GAME_MWMECHANICS_NPCSTATS_H
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "stat.hpp"
+#include "drawstate.hpp"
+
+#include "creaturestats.hpp"
+
+namespace ESM
+{
+ struct Class;
+}
+
+namespace MWMechanics
+{
+ /// \brief Additional stats for NPCs
+ ///
+ /// \note For technical reasons the spell list and the currently selected spell is also handled by
+ /// CreatureStats, even though they are actually NPC stats.
+
+ class NpcStats : public CreatureStats
+ {
+ public:
+
+ enum Flag
+ {
+ Flag_ForceRun = 1,
+ Flag_ForceSneak = 2,
+ Flag_Run = 4,
+ Flag_Sneak = 8
+ };
+
+ private:
+
+ /// NPCs other than the player can only have one faction. But for the sake of consistency
+ /// we use the same data structure for the PC and the NPCs.
+ /// \note the faction key must be in lowercase
+ std::map<std::string, int> mFactionRank;
+
+ DrawState_ mDrawState;
+ int mDisposition;
+ unsigned int mMovementFlags;
+ Stat<float> mSkill[27];
+ Stat<float> mWerewolfSkill[27];
+ int mBounty;
+ std::set<std::string> mExpelled;
+ std::map<std::string, int> mFactionReputation;
+ bool mVampire;
+ int mReputation;
+ int mWerewolfKills;
+ int mProfit;
+ float mAttackStrength;
+
+ int mLevelProgress; // 0-10
+
+ std::vector<int> mSkillIncreases; // number of skill increases for each attribute
+
+ std::set<std::string> mUsedIds;
+
+ /// Countdown to getting damage while underwater
+ float mTimeToStartDrowning;
+ /// time since last hit from drowning
+ float mLastDrowningHit;
+
+ public:
+
+ NpcStats();
+
+ /// for mercenary companions. starts out as 0, and changes when items are added or removed through the UI.
+ int getProfit() const;
+ void modifyProfit(int diff);
+
+ DrawState_ getDrawState() const;
+ void setDrawState (DrawState_ state);
+
+ /// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest)
+ float getAttackStrength() const;
+ void setAttackStrength(float value);
+
+ int getBaseDisposition() const;
+
+ void setBaseDisposition(int disposition);
+
+ int getReputation() const;
+
+ void setReputation(int reputation);
+
+ bool getMovementFlag (Flag flag) const;
+
+ void setMovementFlag (Flag flag, bool state);
+
+ const Stat<float>& getSkill (int index) const;
+ Stat<float>& getSkill (int index);
+
+ const std::map<std::string, int>& getFactionRanks() const;
+ std::map<std::string, int>& getFactionRanks();
+
+ const std::set<std::string>& getExpelled() const;
+ std::set<std::string>& getExpelled();
+
+ bool isSameFaction (const NpcStats& npcStats) const;
+ ///< Do *this and \a npcStats share a faction?
+
+ float getSkillGain (int skillIndex, const ESM::Class& class_, int usageType = -1,
+ int level = -1) const;
+ ///< \param usageType: Usage specific factor, specified in the respective skill record;
+ /// -1: use a factor of 1.0 instead.
+ /// \param level Level to base calculation on; -1: use current level.
+
+ void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1);
+ ///< Increase skill by usage.
+
+ void increaseSkill (int skillIndex, const ESM::Class& class_, bool preserveProgress);
+
+ int getLevelProgress() const;
+
+ int getLevelupAttributeMultiplier(int attribute) const;
+
+ void levelUp();
+
+ void flagAsUsed (const std::string& id);
+
+ bool hasBeenUsed (const std::string& id) const;
+
+ int getBounty() const;
+
+ void setBounty (int bounty);
+
+ int getFactionReputation (const std::string& faction) const;
+
+ void setFactionReputation (const std::string& faction, int value);
+
+ bool isVampire() const;
+
+ void setVampire (bool set);
+
+ bool hasSkillsForRank (const std::string& factionId, int rank) const;
+
+ bool isWerewolf() const;
+
+ void setWerewolf(bool set);
+
+ int getWerewolfKills() const;
+
+ float getTimeToStartDrowning() const;
+ /// Sets time left for the creature to drown if it stays underwater.
+ /// @param time value from [0,20]
+ void setTimeToStartDrowning(float time);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp
new file mode 100644
index 0000000000..694987855e
--- /dev/null
+++ b/apps/openmw/mwmechanics/objects.cpp
@@ -0,0 +1,85 @@
+#include "objects.hpp"
+
+#include <OgreVector3.h>
+
+#include "movement.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWMechanics
+{
+
+Objects::Objects()
+{
+}
+
+void Objects::addObject(const MWWorld::Ptr& ptr)
+{
+ removeObject(ptr);
+
+ MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr);
+ if(anim) mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim)));
+}
+
+void Objects::removeObject(const MWWorld::Ptr& ptr)
+{
+ PtrControllerMap::iterator iter = mObjects.find(ptr);
+ if(iter != mObjects.end())
+ {
+ delete iter->second;
+ mObjects.erase(iter);
+ }
+}
+
+void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
+{
+ PtrControllerMap::iterator iter = mObjects.find(old);
+ if(iter != mObjects.end())
+ {
+ CharacterController *ctrl = iter->second;
+ mObjects.erase(iter);
+
+ ctrl->updatePtr(ptr);
+ mObjects.insert(std::make_pair(ptr, ctrl));
+ }
+}
+
+void Objects::dropObjects (const MWWorld::Ptr::CellStore *cellStore)
+{
+ PtrControllerMap::iterator iter = mObjects.begin();
+ while(iter != mObjects.end())
+ {
+ if(iter->first.getCell()==cellStore)
+ {
+ delete iter->second;
+ mObjects.erase(iter++);
+ }
+ else
+ ++iter;
+ }
+}
+
+void Objects::update(float duration, bool paused)
+{
+ if(!paused)
+ {
+ for(PtrControllerMap::iterator iter(mObjects.begin());iter != mObjects.end();++iter)
+ iter->second->update(duration);
+ }
+}
+
+void Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number)
+{
+ PtrControllerMap::iterator iter = mObjects.find(ptr);
+ if(iter != mObjects.end())
+ iter->second->playGroup(groupName, mode, number);
+}
+void Objects::skipAnimation(const MWWorld::Ptr& ptr)
+{
+ PtrControllerMap::iterator iter = mObjects.find(ptr);
+ if(iter != mObjects.end())
+ iter->second->skipAnim();
+}
+
+}
diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp
new file mode 100644
index 0000000000..5cdcdaa0af
--- /dev/null
+++ b/apps/openmw/mwmechanics/objects.hpp
@@ -0,0 +1,45 @@
+#ifndef GAME_MWMECHANICS_ACTIVATORS_H
+#define GAME_MWMECHANICS_ACTIVATORS_H
+
+#include <string>
+#include <map>
+
+#include "character.hpp"
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace MWMechanics
+{
+ class Objects
+ {
+ typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
+ PtrControllerMap mObjects;
+
+ public:
+ Objects();
+
+ void addObject (const MWWorld::Ptr& ptr);
+ ///< Register an animated object
+
+ void removeObject (const MWWorld::Ptr& ptr);
+ ///< Deregister an object
+
+ void updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr);
+ ///< Updates an object with a new Ptr
+
+ void dropObjects(const MWWorld::CellStore *cellStore);
+ ///< Deregister all objects in the given cell.
+
+ void update(float duration, bool paused);
+ ///< Update object animations
+
+ void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
+ void skipAnimation(const MWWorld::Ptr& ptr);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp
new file mode 100644
index 0000000000..8ef0edab83
--- /dev/null
+++ b/apps/openmw/mwmechanics/pathfinding.cpp
@@ -0,0 +1,221 @@
+#include "pathfinding.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+
+#include "OgreMath.h"
+
+#include <boost/graph/dijkstra_shortest_paths.hpp>
+#include <boost/graph/adjacency_list.hpp>
+
+namespace
+{
+ struct found_path {};
+
+ typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS,
+ boost::property<boost::vertex_index_t, int, ESM::Pathgrid::Point>, boost::property<boost::edge_weight_t, float> >
+ PathGridGraph;
+ typedef boost::property_map<PathGridGraph, boost::edge_weight_t>::type WeightMap;
+ typedef PathGridGraph::vertex_descriptor PointID;
+ typedef PathGridGraph::edge_descriptor PointConnectionID;
+
+ class goalVisited : public boost::default_dijkstra_visitor
+ {
+ public:
+ goalVisited(PointID goal) {mGoal = goal;};
+ void examine_vertex(PointID u, const PathGridGraph g) {if(u == mGoal) throw found_path();};
+
+ private:
+ PointID mGoal;
+ };
+
+ float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z)
+ {
+ x -= point.mX;
+ y -= point.mY;
+ z -= point.mZ;
+ return sqrt(x * x + y * y + 0.1 * z * z);
+ }
+
+ float distance(ESM::Pathgrid::Point point, float x, float y, float z)
+ {
+ x -= point.mX;
+ y -= point.mY;
+ z -= point.mZ;
+ return sqrt(x * x + y * y + z * z);
+ }
+
+ float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b)
+ {
+ float x = a.mX - b.mX;
+ float y = a.mY - b.mY;
+ float z = a.mZ - b.mZ;
+ return sqrt(x * x + y * y + z * z);
+ }
+
+ static float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+
+ int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z)
+ {
+ if(!grid || grid->mPoints.empty())
+ return -1;
+
+ float distanceBetween = distance(grid->mPoints[0], x, y, z);
+ int closestIndex = 0;
+
+ for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
+ {
+ if(distance(grid->mPoints[counter], x, y, z) < distanceBetween)
+ {
+ distanceBetween = distance(grid->mPoints[counter], x, y, z);
+ closestIndex = counter;
+ }
+ }
+
+ return closestIndex;
+ }
+
+ PathGridGraph buildGraph(const ESM::Pathgrid* pathgrid, float xCell = 0, float yCell = 0)
+ {
+ PathGridGraph graph;
+
+ for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
+ {
+ PointID pID = boost::add_vertex(graph);
+ graph[pID].mX = pathgrid->mPoints[counter].mX + xCell;
+ graph[pID].mY = pathgrid->mPoints[counter].mY + yCell;
+ graph[pID].mZ = pathgrid->mPoints[counter].mZ;
+ }
+
+ for(unsigned int counterTwo = 0; counterTwo < pathgrid->mEdges.size(); counterTwo++)
+ {
+ PointID u = pathgrid->mEdges[counterTwo].mV0;
+ PointID v = pathgrid->mEdges[counterTwo].mV1;
+
+ PointConnectionID edge;
+ bool done;
+ boost::tie(edge, done) = boost::add_edge(u, v, graph);
+ WeightMap weightmap = boost::get(boost::edge_weight, graph);
+ weightmap[edge] = distance(graph[u], graph[v]);
+ }
+
+ return graph;
+ }
+
+ std::list<ESM::Pathgrid::Point> findPath(PointID start, PointID end, PathGridGraph graph)
+ {
+ std::vector<PointID> p(boost::num_vertices(graph));
+ std::vector<float> d(boost::num_vertices(graph));
+ std::list<ESM::Pathgrid::Point> shortest_path;
+
+ try
+ {
+ boost::dijkstra_shortest_paths(graph, start,
+ boost::predecessor_map(&p[0]).distance_map(&d[0]).visitor(goalVisited(end)));
+ }
+
+ catch(found_path& fg)
+ {
+ for(PointID v = end; ; v = p[v])
+ {
+ shortest_path.push_front(graph[v]);
+ if(p[v] == v)
+ break;
+ }
+ }
+
+ return shortest_path;
+ }
+}
+
+namespace MWMechanics
+{
+ PathFinder::PathFinder()
+ {
+ mIsPathConstructed = false;
+ }
+
+ void PathFinder::clearPath()
+ {
+ if(!mPath.empty())
+ mPath.clear();
+ mIsPathConstructed = false;
+ }
+
+ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
+ const ESM::Pathgrid *pathGrid, float xCell, float yCell, bool allowShortcuts)
+ {
+ if(allowShortcuts)
+ {
+ if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
+ endPoint.mX, endPoint.mY, endPoint.mZ))
+ allowShortcuts = false;
+ }
+
+ if(!allowShortcuts)
+ {
+ int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ);
+ int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ);
+
+ if(startNode != -1 && endNode != -1)
+ {
+ PathGridGraph graph = buildGraph(pathGrid, xCell, yCell);
+ mPath = findPath(startNode, endNode, graph);
+
+ if(!mPath.empty())
+ {
+ mPath.push_back(endPoint);
+ mIsPathConstructed = true;
+ }
+ }
+ }
+ else
+ {
+ mPath.push_back(endPoint);
+ mIsPathConstructed = true;
+ }
+
+ if(mPath.empty())
+ mIsPathConstructed = false;
+ }
+
+ float PathFinder::getZAngleToNext(float x, float y) const
+ {
+ // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call
+ // if otherwise).
+ if(mPath.empty())
+ return 0;
+
+ const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
+ float directionX = nextPoint.mX - x;
+ float directionY = nextPoint.mY - y;
+ float directionResult = sqrt(directionX * directionX + directionY * directionY);
+
+ return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees();
+ }
+
+ bool PathFinder::checkPathCompleted(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;
+ }
+}
+
diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp
new file mode 100644
index 0000000000..35e0fa9087
--- /dev/null
+++ b/apps/openmw/mwmechanics/pathfinding.hpp
@@ -0,0 +1,34 @@
+#ifndef GAME_MWMECHANICS_PATHFINDING_H
+#define GAME_MWMECHANICS_PATHFINDING_H
+
+#include <components/esm/loadpgrd.hpp>
+#include <list>
+
+namespace MWMechanics
+{
+ class PathFinder
+ {
+ public:
+ PathFinder();
+
+ void clearPath();
+ void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
+ const ESM::Pathgrid* pathGrid, float xCell = 0, float yCell = 0,
+ bool allowShortcuts = true);
+
+ bool checkPathCompleted(float x, float y, float z);
+ ///< \Returns true if the last point of the path has been reached.
+ float getZAngleToNext(float x, float y) const;
+
+ bool isPathConstructed() const
+ {
+ return mIsPathConstructed;
+ }
+
+ private:
+ std::list<ESM::Pathgrid::Point> mPath;
+ bool mIsPathConstructed;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp
new file mode 100644
index 0000000000..66c492bf8b
--- /dev/null
+++ b/apps/openmw/mwmechanics/repair.cpp
@@ -0,0 +1,110 @@
+#include "repair.hpp"
+
+#include <boost/format.hpp>
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+namespace MWMechanics
+{
+
+void Repair::repair(const MWWorld::Ptr &itemToRepair)
+{
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::LiveCellRef<ESM::Repair> *ref =
+ mTool.get<ESM::Repair>();
+
+ // reduce number of uses left
+ int uses = (mTool.getCellRef().mCharge != -1) ? mTool.getCellRef().mCharge : ref->mBase->mData.mUses;
+ mTool.getCellRef().mCharge = uses-1;
+
+ // unstack tool if required
+ if (mTool.getRefData().getCount() > 1 && uses == ref->mBase->mData.mUses)
+ {
+ MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
+ MWWorld::ContainerStoreIterator it = store.add(mTool, player);
+ it->getRefData().setCount(mTool.getRefData().getCount()-1);
+ it->getCellRef().mCharge = -1;
+
+ mTool.getRefData().setCount(1);
+ }
+
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player);
+
+ float fatigueTerm = stats.getFatigueTerm();
+ int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified();
+ int pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+ int armorerSkill = npcStats.getSkill(ESM::Skill::Armorer).getModified();
+
+ float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("fRepairAmountMult")->getFloat();
+
+ float toolQuality = ref->mBase->mData.mQuality;
+
+ float x = (0.1 * pcStrength + 0.1 * pcLuck + armorerSkill) * fatigueTerm;
+
+ int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
+ if (roll <= x)
+ {
+ int y = fRepairAmountMult * toolQuality * roll;
+ y = std::max(1, y);
+
+ // repair by 'y' points
+ itemToRepair.getCellRef().mCharge += y;
+ itemToRepair.getCellRef().mCharge = std::min(itemToRepair.getCellRef().mCharge,
+ MWWorld::Class::get(itemToRepair).getItemMaxHealth(itemToRepair));
+
+ // set the OnPCRepair variable on the item's script
+ std::string script = MWWorld::Class::get(itemToRepair).getScript(itemToRepair);
+ if(script != "")
+ itemToRepair.getRefData().getLocals().setVarByInt(script, "onpcrepair", 1);
+
+ // increase skill
+ MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Armorer, 0);
+
+ MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}");
+ }
+ else
+ {
+ MWBase::Environment::get().getSoundManager()->playSound("Repair Fail",1,1);
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairFailed}");
+ }
+
+ // tool used up?
+ if (mTool.getCellRef().mCharge == 0)
+ {
+ mTool.getRefData().setCount(0);
+
+ std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("sNotifyMessage51")->getString();
+
+ 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)
+ {
+ if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, mTool.getCellRef().mRefID))
+ {
+ mTool = *iter;
+ break;
+ }
+ }
+ }
+}
+
+}
diff --git a/apps/openmw/mwmechanics/repair.hpp b/apps/openmw/mwmechanics/repair.hpp
new file mode 100644
index 0000000000..6f9a866af1
--- /dev/null
+++ b/apps/openmw/mwmechanics/repair.hpp
@@ -0,0 +1,23 @@
+#ifndef OPENMW_MWMECHANICS_REPAIR_H
+#define OPENMW_MWMECHANICS_REPAIR_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWMechanics
+{
+
+ class Repair
+ {
+ public:
+ void setTool (const MWWorld::Ptr& tool) { mTool = tool; }
+ MWWorld::Ptr getTool() { return mTool; }
+
+ void repair (const MWWorld::Ptr& itemToRepair);
+
+ private:
+ MWWorld::Ptr mTool;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp
new file mode 100644
index 0000000000..d19da6e2af
--- /dev/null
+++ b/apps/openmw/mwmechanics/security.cpp
@@ -0,0 +1,109 @@
+#include "security.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "npcstats.hpp"
+#include "creaturestats.hpp"
+
+namespace MWMechanics
+{
+
+ Security::Security(const MWWorld::Ptr &actor)
+ : mActor(actor)
+ {
+ CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
+ NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats(actor);
+ mAgility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified();
+ mLuck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified();
+ mSecuritySkill = npcStats.getSkill(ESM::Skill::Security).getModified();
+ mFatigueTerm = creatureStats.getFatigueTerm();
+ }
+
+ void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick,
+ std::string& resultMessage, std::string& resultSound)
+ {
+ if (lock.getCellRef().mLockLevel <= 0)
+ return;
+
+ int lockStrength = lock.getCellRef().mLockLevel;
+
+ float pickQuality = lockpick.get<ESM::Lockpick>()->mBase->mData.mQuality;
+
+ float fPickLockMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fPickLockMult")->getFloat();
+
+ float x = 0.2 * mAgility + 0.1 * mLuck + mSecuritySkill;
+ x *= pickQuality * mFatigueTerm;
+ x += fPickLockMult * lockStrength;
+
+ resultSound = "Open Lock Fail";
+ if (x <= 0)
+ resultMessage = "#{sLockImpossible}";
+ else
+ {
+ int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
+ if (roll <= x)
+ {
+ MWWorld::Class::get(lock).unlock(lock);
+ resultMessage = "#{sLockSuccess}";
+ resultSound = "Open Lock";
+ MWWorld::Class::get(mActor).skillUsageSucceeded(mActor, ESM::Skill::Security, 1);
+ }
+ else
+ resultMessage = "#{sLockFail}";
+ }
+
+ if (lockpick.getCellRef().mCharge == -1)
+ lockpick.getCellRef().mCharge = lockpick.get<ESM::Lockpick>()->mBase->mData.mUses;
+ --lockpick.getCellRef().mCharge;
+ if (!lockpick.getCellRef().mCharge)
+ lockpick.getRefData().setCount(0);
+ }
+
+ void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe,
+ std::string& resultMessage, std::string& resultSound)
+ {
+ if (trap.getCellRef().mTrap == "")
+ return;
+
+ float probeQuality = probe.get<ESM::Probe>()->mBase->mData.mQuality;
+
+ const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(trap.getCellRef().mTrap);
+ float trapSpellPoints = trapSpell->mData.mCost;
+
+ float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fTrapCostMult")->getFloat();
+
+ float x = 0.2 * mAgility + 0.1 * mLuck + mSecuritySkill;
+ x += fTrapCostMult * trapSpellPoints;
+ x *= probeQuality * mFatigueTerm;
+
+ resultSound = "Disarm Trap Fail";
+ if (x <= 0)
+ resultMessage = "#{sTrapImpossible}";
+ else
+ {
+ int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
+ if (roll <= x)
+ {
+ trap.getCellRef().mTrap = "";
+
+ resultSound = "Disarm Trap";
+ resultMessage = "#{sTrapSuccess}";
+ MWWorld::Class::get(mActor).skillUsageSucceeded(mActor, ESM::Skill::Security, 0);
+ }
+ else
+ resultMessage = "#{sTrapFail}";
+ }
+
+ if (probe.getCellRef().mCharge == -1)
+ probe.getCellRef().mCharge = probe.get<ESM::Probe>()->mBase->mData.mUses;
+ --probe.getCellRef().mCharge;
+ if (!probe.getCellRef().mCharge)
+ probe.getRefData().setCount(0);
+ }
+
+}
diff --git a/apps/openmw/mwmechanics/security.hpp b/apps/openmw/mwmechanics/security.hpp
new file mode 100644
index 0000000000..f3efb04ed8
--- /dev/null
+++ b/apps/openmw/mwmechanics/security.hpp
@@ -0,0 +1,27 @@
+#ifndef MWMECHANICS_SECURITY_H
+#define MWMECHANICS_SECURITY_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWMechanics
+{
+
+ /// @brief implementation of Security skill
+ class Security
+ {
+ public:
+ Security (const MWWorld::Ptr& actor);
+
+ void pickLock (const MWWorld::Ptr& lock, const MWWorld::Ptr& lockpick,
+ std::string& resultMessage, std::string& resultSound);
+ void probeTrap (const MWWorld::Ptr& trap, const MWWorld::Ptr& probe,
+ std::string& resultMessage, std::string& resultSound);
+
+ private:
+ float mAgility, mLuck, mSecuritySkill, mFatigueTerm;
+ MWWorld::Ptr mActor;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp
new file mode 100644
index 0000000000..e10dcdc93d
--- /dev/null
+++ b/apps/openmw/mwmechanics/spells.cpp
@@ -0,0 +1,103 @@
+
+#include "spells.hpp"
+
+#include <cstdlib>
+
+#include <components/esm/loadspel.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include "magiceffects.hpp"
+
+namespace MWMechanics
+{
+ Spells::TIterator Spells::begin() const
+ {
+ return mSpells.begin();
+ }
+
+ Spells::TIterator Spells::end() const
+ {
+ return mSpells.end();
+ }
+
+ 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));
+ }
+
+ void Spells::remove (const std::string& spellId)
+ {
+ TContainer::iterator iter = mSpells.find (spellId);
+
+ if (iter!=mSpells.end())
+ mSpells.erase (iter);
+
+ if (spellId==mSelectedSpell)
+ mSelectedSpell.clear();
+ }
+
+ MagicEffects Spells::getMagicEffects() const
+ {
+ MagicEffects effects;
+
+ for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ 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);
+ }
+
+ return effects;
+ }
+
+ void Spells::clear()
+ {
+ mSpells.clear();
+ }
+
+ void Spells::setSelectedSpell (const std::string& spellId)
+ {
+ mSelectedSpell = spellId;
+ }
+
+ const std::string Spells::getSelectedSpell() const
+ {
+ return mSelectedSpell;
+ }
+
+ bool Spells::hasCommonDisease() const
+ {
+ for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType == ESM::Spell::ST_Disease)
+ return true;
+ }
+
+ return false;
+ }
+
+ bool Spells::hasBlightDisease() const
+ {
+ for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
+ {
+ const ESM::Spell *spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
+
+ if (spell->mData.mType == ESM::Spell::ST_Blight)
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp
new file mode 100644
index 0000000000..e00ac259f1
--- /dev/null
+++ b/apps/openmw/mwmechanics/spells.hpp
@@ -0,0 +1,63 @@
+#ifndef GAME_MWMECHANICS_SPELLS_H
+#define GAME_MWMECHANICS_SPELLS_H
+
+#include <map>
+#include <string>
+
+namespace ESM
+{
+ struct Spell;
+}
+
+namespace MWMechanics
+{
+ class MagicEffects;
+
+ /// \brief Spell list
+ ///
+ /// This class manages known spells as well as abilities, powers and permanent negative effects like
+ /// diseaes.
+ class Spells
+ {
+ public:
+
+ typedef std::map<std::string, float> TContainer; // ID, normalised magnitude
+ typedef TContainer::const_iterator TIterator;
+
+ private:
+
+ TContainer mSpells;
+ std::string mSelectedSpell;
+
+ public:
+
+ TIterator begin() const;
+
+ TIterator end() const;
+
+ void add (const std::string& spell);
+ ///< Adding a spell that is already listed in *this is a no-op.
+
+ void remove (const std::string& spell);
+ ///< If the spell to be removed is the selected spell, the selected spell will be changed to
+ /// no spell (empty string).
+
+ MagicEffects getMagicEffects() const;
+ ///< Return sum of magic effects resulting from abilities, blights, deseases and curses.
+
+ void clear();
+ ///< Remove all spells of al types.
+
+ void setSelectedSpell (const std::string& spellId);
+ ///< This function does not verify, if the spell is available.
+
+ const std::string getSelectedSpell() const;
+ ///< May return an empty string.
+
+ bool hasCommonDisease() const;
+
+ bool hasBlightDisease() const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp
new file mode 100644
index 0000000000..57c600df50
--- /dev/null
+++ b/apps/openmw/mwmechanics/spellsuccess.hpp
@@ -0,0 +1,118 @@
+#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
new file mode 100644
index 0000000000..65d47c9c08
--- /dev/null
+++ b/apps/openmw/mwmechanics/stat.hpp
@@ -0,0 +1,198 @@
+#ifndef GAME_MWMECHANICS_STAT_H
+#define GAME_MWMECHANICS_STAT_H
+
+#undef min
+#undef max
+
+#include <limits>
+
+namespace MWMechanics
+{
+ template<typename T>
+ class Stat
+ {
+ T mBase;
+ T mModified;
+
+ public:
+ typedef T Type;
+
+ Stat() : mBase (0), mModified (0) {}
+ Stat(T base) : mBase (base), mModified (base) {}
+ Stat(T base, T modified) : mBase (base), mModified (modified) {}
+
+ const T& getBase() const
+ {
+ return mBase;
+ }
+
+ T getModified() const
+ {
+ return std::max(static_cast<T>(0), mModified);
+ }
+
+ T getModifier() const
+ {
+ return mModified-mBase;
+ }
+
+ /// Set base and modified to \a value.
+ void set (const T& value)
+ {
+ mBase = mModified = value;
+ }
+
+ void modify(const T& diff)
+ {
+ mBase += diff;
+ if(mBase >= static_cast<T>(0))
+ mModified += diff;
+ else
+ {
+ mModified += diff - mBase;
+ mBase = static_cast<T>(0);
+ }
+ }
+
+ /// Set base and adjust modified accordingly.
+ void setBase (const T& value)
+ {
+ T diff = value - mBase;
+ mBase = value;
+ mModified += diff;
+ }
+
+ /// Set modified value an adjust base accordingly.
+ void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max())
+ {
+ T diff = value - mModified;
+
+ if (mBase+diff<min)
+ {
+ value = min + (mModified - mBase);
+ diff = value - mModified;
+ }
+ else if (mBase+diff>max)
+ {
+ value = max + (mModified - mBase);
+ diff = value - mModified;
+ }
+
+ mModified = value;
+ mBase += diff;
+ }
+
+ void setModifier (const T& modifier)
+ {
+ mModified = mBase + modifier;
+ }
+ };
+
+ template<typename T>
+ inline bool operator== (const Stat<T>& left, const Stat<T>& right)
+ {
+ return left.getBase()==right.getBase() &&
+ left.getModified()==right.getModified();
+ }
+
+ template<typename T>
+ inline bool operator!= (const Stat<T>& left, const Stat<T>& right)
+ {
+ return !(left==right);
+ }
+
+ template<typename T>
+ class DynamicStat
+ {
+ Stat<T> mStatic;
+ T mCurrent;
+
+ public:
+ typedef T Type;
+
+ DynamicStat() : mStatic (0), mCurrent (0) {}
+ DynamicStat(T base) : mStatic (base), mCurrent (base) {}
+ DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {}
+ DynamicStat(const Stat<T> &stat, T current) : mStatic(stat), mCurrent (current) {}
+
+ const T& getBase() const
+ {
+ return mStatic.getBase();
+ }
+
+ T getModified() const
+ {
+ return mStatic.getModified();
+ }
+
+ const T& getCurrent() const
+ {
+ return mCurrent;
+ }
+
+ /// Set base, modified and current to \a value.
+ void set (const T& value)
+ {
+ mStatic.set (value);
+ mCurrent = value;
+ }
+
+ /// Set base and adjust modified accordingly.
+ void setBase (const T& value)
+ {
+ mStatic.setBase (value);
+
+ if (mCurrent>getModified())
+ mCurrent = getModified();
+ }
+
+ /// Set modified value an adjust base accordingly.
+ void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max())
+ {
+ mStatic.setModified (value, min, max);
+
+ if (mCurrent>getModified())
+ mCurrent = getModified();
+ }
+
+ /// Change modified relatively.
+ void modify (const T& diff)
+ {
+ mStatic.modify (diff);
+ setCurrent (getCurrent()+diff);
+ }
+
+ void setCurrent (const T& value)
+ {
+ mCurrent = value;
+
+ if (mCurrent<0)
+ mCurrent = 0;
+ else if (mCurrent>getModified())
+ mCurrent = getModified();
+ }
+
+ void setModifier (const T& modifier)
+ {
+ T diff = modifier - mStatic.getModifier();
+ mStatic.setModifier (modifier);
+ setCurrent (getCurrent()+diff);
+ }
+ };
+
+ template<typename T>
+ inline bool operator== (const DynamicStat<T>& left, const DynamicStat<T>& right)
+ {
+ return left.getBase()==right.getBase() &&
+ left.getModified()==right.getModified() &&
+ left.getCurrent()==right.getCurrent();
+ }
+
+ template<typename T>
+ inline bool operator!= (const DynamicStat<T>& left, const DynamicStat<T>& right)
+ {
+ return !(left==right);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwrender/activatoranimation.cpp b/apps/openmw/mwrender/activatoranimation.cpp
new file mode 100644
index 0000000000..7f4be9a688
--- /dev/null
+++ b/apps/openmw/mwrender/activatoranimation.cpp
@@ -0,0 +1,31 @@
+#include "activatoranimation.hpp"
+
+#include "renderconst.hpp"
+
+#include "../mwbase/world.hpp"
+
+namespace MWRender
+{
+
+ActivatorAnimation::~ActivatorAnimation()
+{
+}
+
+ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr)
+ : Animation(ptr, ptr.getRefData().getBaseNode())
+{
+ MWWorld::LiveCellRef<ESM::Activator> *ref = mPtr.get<ESM::Activator>();
+
+ assert(ref->mBase != NULL);
+ if(!ref->mBase->mModel.empty())
+ {
+ const std::string name = "meshes\\"+ref->mBase->mModel;
+
+ setObjectRoot(name, false);
+ setRenderProperties(mObjectRoot, RV_Misc, RQG_Main, RQG_Alpha);
+
+ addAnimSource(name);
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp
new file mode 100644
index 0000000000..f3ea38f447
--- /dev/null
+++ b/apps/openmw/mwrender/activatoranimation.hpp
@@ -0,0 +1,21 @@
+#ifndef _GAME_RENDER_ACTIVATORANIMATION_H
+#define _GAME_RENDER_ACTIVATORANIMATION_H
+
+#include "animation.hpp"
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWRender
+{
+ class ActivatorAnimation : public Animation
+ {
+ public:
+ ActivatorAnimation(const MWWorld::Ptr& ptr);
+ virtual ~ActivatorAnimation();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp
new file mode 100644
index 0000000000..566b6fa81e
--- /dev/null
+++ b/apps/openmw/mwrender/actors.cpp
@@ -0,0 +1,190 @@
+#include "actors.hpp"
+
+#include <OgreSceneNode.h>
+#include <OgreSceneManager.h>
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwrender/renderingmanager.hpp"
+
+#include "animation.hpp"
+#include "activatoranimation.hpp"
+#include "creatureanimation.hpp"
+#include "npcanimation.hpp"
+
+#include "renderconst.hpp"
+
+
+namespace MWRender
+{
+using namespace Ogre;
+
+Actors::~Actors()
+{
+ PtrAnimationMap::iterator it = mAllActors.begin();
+ for(;it != mAllActors.end();++it)
+ {
+ delete it->second;
+ it->second = NULL;
+ }
+}
+
+void Actors::setRootNode(Ogre::SceneNode* root)
+{ mRootNode = root; }
+
+void Actors::insertBegin(const MWWorld::Ptr &ptr)
+{
+ Ogre::SceneNode* cellnode;
+ CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(ptr.getCell());
+ if(celliter != mCellSceneNodes.end())
+ cellnode = celliter->second;
+ else
+ {
+ //Create the scenenode and put it in the map
+ cellnode = mRootNode->createChildSceneNode();
+ mCellSceneNodes[ptr.getCell()] = cellnode;
+ }
+
+ Ogre::SceneNode* insert = cellnode->createChildSceneNode();
+ const float *f = ptr.getRefData().getPosition().pos;
+ insert->setPosition(f[0], f[1], f[2]);
+ insert->setScale(ptr.getCellRef().mScale, ptr.getCellRef().mScale, ptr.getCellRef().mScale);
+
+ // Convert MW rotation to a quaternion:
+ f = ptr.getCellRef().mPos.rot;
+
+ // Rotate around X axis
+ Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X);
+
+ // Rotate around Y axis
+ Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y);
+
+ // Rotate around Z axis
+ Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z);
+
+ // Rotates first around z, then y, then x
+ insert->setOrientation(xr*yr*zr);
+ ptr.getRefData().setBaseNode(insert);
+}
+
+void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv)
+{
+ insertBegin(ptr);
+ NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), inv, RV_Actors);
+ delete mAllActors[ptr];
+ mAllActors[ptr] = anim;
+ mRendering->addWaterRippleEmitter (ptr);
+}
+void Actors::insertCreature (const MWWorld::Ptr& ptr)
+{
+ insertBegin(ptr);
+ CreatureAnimation* anim = new CreatureAnimation(ptr);
+ delete mAllActors[ptr];
+ mAllActors[ptr] = anim;
+ mRendering->addWaterRippleEmitter (ptr);
+}
+void Actors::insertActivator (const MWWorld::Ptr& ptr)
+{
+ insertBegin(ptr);
+ ActivatorAnimation* anim = new ActivatorAnimation(ptr);
+ delete mAllActors[ptr];
+ mAllActors[ptr] = anim;
+}
+
+bool Actors::deleteObject (const MWWorld::Ptr& ptr)
+{
+ if (mAllActors.find(ptr) == mAllActors.end())
+ return false;
+
+ mRendering->removeWaterRippleEmitter (ptr);
+
+ delete mAllActors[ptr];
+ mAllActors.erase(ptr);
+
+ if(Ogre::SceneNode *base=ptr.getRefData().getBaseNode())
+ {
+ Ogre::SceneNode *parent = base->getParentSceneNode();
+ CellSceneNodeMap::const_iterator iter(mCellSceneNodes.begin());
+ for(;iter != mCellSceneNodes.end();++iter)
+ {
+ if(iter->second == parent)
+ {
+ base->removeAndDestroyAllChildren();
+ mRend.getScene()->destroySceneNode (base);
+ ptr.getRefData().setBaseNode (0);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+void Actors::removeCell(MWWorld::Ptr::CellStore* store)
+{
+ for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();)
+ {
+ if(iter->first.getCell() == store)
+ {
+ mRendering->removeWaterRippleEmitter (iter->first);
+ delete iter->second;
+ mAllActors.erase(iter++);
+ }
+ else
+ ++iter;
+ }
+ CellSceneNodeMap::iterator celliter = mCellSceneNodes.find(store);
+ if(celliter != mCellSceneNodes.end())
+ {
+ Ogre::SceneNode *base = celliter->second;
+ base->removeAndDestroyAllChildren();
+ mRend.getScene()->destroySceneNode(base);
+
+ mCellSceneNodes.erase(celliter);
+ }
+}
+
+void Actors::update (float duration)
+{
+ // Nothing to do
+}
+
+Animation* Actors::getAnimation(const MWWorld::Ptr &ptr)
+{
+ PtrAnimationMap::const_iterator iter = mAllActors.find(ptr);
+ if(iter != mAllActors.end())
+ return iter->second;
+ return NULL;
+}
+
+void Actors::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur)
+{
+ Ogre::SceneNode *node;
+ MWWorld::CellStore *newCell = cur.getCell();
+
+ CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(newCell);
+ if(celliter != mCellSceneNodes.end())
+ node = celliter->second;
+ else
+ {
+ node = mRootNode->createChildSceneNode();
+ mCellSceneNodes[newCell] = node;
+ }
+ node->addChild(cur.getRefData().getBaseNode());
+
+ PtrAnimationMap::iterator iter = mAllActors.find(old);
+ if(iter != mAllActors.end())
+ {
+ Animation *anim = iter->second;
+ mAllActors.erase(iter);
+ anim->updatePtr(cur);
+ mAllActors[cur] = anim;
+ }
+
+ mRendering->updateWaterRippleEmitterPtr (old, cur);
+}
+
+}
diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp
new file mode 100644
index 0000000000..4547db9ad2
--- /dev/null
+++ b/apps/openmw/mwrender/actors.hpp
@@ -0,0 +1,58 @@
+#ifndef _GAME_RENDER_ACTORS_H
+#define _GAME_RENDER_ACTORS_H
+
+#include <openengine/ogre/renderer.hpp>
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+ class InventoryStore;
+}
+
+namespace MWRender
+{
+ class Animation;
+ class RenderingManager;
+
+ class Actors
+ {
+ typedef std::map<MWWorld::CellStore*,Ogre::SceneNode*> CellSceneNodeMap;
+ typedef std::map<MWWorld::Ptr,Animation*> PtrAnimationMap;
+
+ OEngine::Render::OgreRenderer &mRend;
+ MWRender::RenderingManager* mRendering;
+ Ogre::SceneNode* mRootNode;
+
+ CellSceneNodeMap mCellSceneNodes;
+ PtrAnimationMap mAllActors;
+
+ void insertBegin(const MWWorld::Ptr &ptr);
+
+ public:
+ Actors(OEngine::Render::OgreRenderer& _rend, MWRender::RenderingManager* rendering)
+ : mRend(_rend)
+ , mRendering(rendering)
+ , mRootNode(NULL)
+ {}
+ ~Actors();
+
+ void setRootNode(Ogre::SceneNode* root);
+
+ void insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv);
+ void insertCreature (const MWWorld::Ptr& ptr);
+ void insertActivator (const MWWorld::Ptr& ptr);
+ bool deleteObject (const MWWorld::Ptr& ptr);
+ ///< \return found?
+
+ void removeCell(MWWorld::CellStore* store);
+
+ void update (float duration);
+
+ /// Updates containing cell for object rendering data
+ void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur);
+
+ Animation* getAnimation(const MWWorld::Ptr &ptr);
+ };
+}
+#endif
diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp
new file mode 100644
index 0000000000..545060fe3a
--- /dev/null
+++ b/apps/openmw/mwrender/animation.cpp
@@ -0,0 +1,1039 @@
+#include "animation.hpp"
+
+#include <OgreSkeletonManager.h>
+#include <OgreSkeletonInstance.h>
+#include <OgreEntity.h>
+#include <OgreSubEntity.h>
+#include <OgreParticleSystem.h>
+#include <OgreBone.h>
+#include <OgreSubMesh.h>
+#include <OgreSceneManager.h>
+#include <OgreControllerManager.h>
+#include <OgreStaticGeometry.h>
+
+#include <libs/openengine/ogre/lights.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwmechanics/character.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/fallback.hpp"
+
+#include "renderconst.hpp"
+
+namespace MWRender
+{
+
+Ogre::Real Animation::AnimationValue::getValue() const
+{
+ AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName);
+ if(iter != mAnimation->mStates.end())
+ return iter->second.mTime;
+ return 0.0f;
+}
+
+void Animation::AnimationValue::setValue(Ogre::Real)
+{
+}
+
+
+void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects)
+{
+ 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)
+ : mPtr(ptr)
+ , mCamera(NULL)
+ , mInsert(node)
+ , mSkelBase(NULL)
+ , mAccumRoot(NULL)
+ , mNonAccumRoot(NULL)
+ , mNonAccumCtrl(NULL)
+ , mAccumulate(0.0f)
+ , mNullAnimationValuePtr(OGRE_NEW NullAnimationValue)
+{
+ for(size_t i = 0;i < sNumGroups;i++)
+ mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this));
+}
+
+Animation::~Animation()
+{
+ mAnimSources.clear();
+
+ Ogre::SceneManager *sceneMgr = mInsert->getCreator();
+ destroyObjectList(sceneMgr, mObjectRoot);
+}
+
+
+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);
+
+ if(model.empty())
+ return;
+
+ std::string mdlname = Misc::StringUtils::lowerCase(model);
+ std::string::size_type p = mdlname.rfind('\\');
+ if(p == std::string::npos)
+ p = mdlname.rfind('/');
+ if(p != std::string::npos)
+ mdlname.insert(mdlname.begin()+p+1, 'x');
+ else
+ mdlname.insert(mdlname.begin(), 'x');
+ if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname))
+ {
+ mdlname = model;
+ Misc::StringUtils::toLower(mdlname);
+ }
+
+ mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) :
+ NifOgre::Loader::createObjectBase(mInsert, mdlname));
+ if(mObjectRoot.mSkelBase)
+ {
+ mSkelBase = mObjectRoot.mSkelBase;
+
+ Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates();
+ Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator();
+ while(asiter.hasMoreElements())
+ {
+ Ogre::AnimationState *state = asiter.getNext();
+ state->setEnabled(false);
+ state->setLoop(false);
+ }
+
+ // Set the bones as manually controlled since we're applying the
+ // transformations manually
+ Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton();
+ Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
+ while(boneiter.hasMoreElements())
+ boneiter.getNext()->setManuallyControlled(true);
+
+ // Reattach any objects that have been attached to this one
+ ObjectAttachMap::iterator iter = mAttachedObjects.begin();
+ while(iter != mAttachedObjects.end())
+ {
+ if(!skelinst->hasBone(iter->second))
+ mAttachedObjects.erase(iter++);
+ else
+ {
+ mSkelBase->attachObjectToBone(iter->second, iter->first);
+ ++iter;
+ }
+ }
+ }
+ else
+ mAttachedObjects.clear();
+
+ for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
+ {
+ if(mObjectRoot.mControllers[i].getSource().isNull())
+ mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]);
+ }
+}
+
+
+class VisQueueSet {
+ Ogre::uint32 mVisFlags;
+ Ogre::uint8 mSolidQueue, mTransQueue;
+ Ogre::Real mDist;
+
+public:
+ VisQueueSet(Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist)
+ : mVisFlags(visflags), mSolidQueue(solidqueue), mTransQueue(transqueue), mDist(dist)
+ { }
+
+ void operator()(Ogre::Entity *entity) const
+ {
+ if(mVisFlags != 0)
+ entity->setVisibilityFlags(mVisFlags);
+ entity->setRenderingDistance(mDist);
+
+ unsigned int numsubs = entity->getNumSubEntities();
+ for(unsigned int i = 0;i < numsubs;++i)
+ {
+ Ogre::SubEntity* subEnt = entity->getSubEntity(i);
+ subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue);
+ }
+ }
+
+ void operator()(Ogre::ParticleSystem *psys) const
+ {
+ if(mVisFlags != 0)
+ psys->setVisibilityFlags(mVisFlags);
+ psys->setRenderingDistance(mDist);
+ // TODO: Check particle material for actual transparency
+ psys->setRenderQueueGroup(mTransQueue);
+ }
+};
+
+void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist)
+{
+ std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(),
+ VisQueueSet(visflags, solidqueue, transqueue, dist));
+ std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(),
+ VisQueueSet(visflags, solidqueue, transqueue, dist));
+}
+
+
+size_t Animation::detectAnimGroup(const Ogre::Node *node)
+{
+ static const char sGroupRoots[sNumGroups][32] = {
+ "", /* Lower body / character root */
+ "Bip01 Spine1", /* Torso */
+ "Bip01 L Clavicle", /* Left arm */
+ "Bip01 R Clavicle", /* Right arm */
+ };
+
+ while(node)
+ {
+ const Ogre::String &name = node->getName();
+ for(size_t i = 1;i < sNumGroups;i++)
+ {
+ if(name == sGroupRoots[i])
+ return i;
+ }
+
+ node = node->getParent();
+ }
+
+ return 0;
+}
+
+
+void Animation::addAnimSource(const std::string &model)
+{
+ OgreAssert(mInsert, "Object is missing a root!");
+ if(!mSkelBase)
+ return;
+
+ std::string kfname = Misc::StringUtils::lowerCase(model);
+ std::string::size_type p = kfname.rfind('\\');
+ if(p == std::string::npos)
+ p = kfname.rfind('/');
+ if(p != std::string::npos)
+ kfname.insert(kfname.begin()+p+1, 'x');
+ else
+ kfname.insert(kfname.begin(), 'x');
+
+ if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
+ kfname.replace(kfname.size()-4, 4, ".kf");
+
+ if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname))
+ return;
+
+ std::vector<Ogre::Controller<Ogre::Real> > ctrls;
+ Ogre::SharedPtr<AnimSource> animsrc(OGRE_NEW AnimSource);
+ NifOgre::Loader::createKfControllers(mSkelBase, kfname, animsrc->mTextKeys, ctrls);
+ if(animsrc->mTextKeys.empty() || ctrls.empty())
+ return;
+
+ mAnimSources.push_back(animsrc);
+
+ std::vector<Ogre::Controller<Ogre::Real> > *grpctrls = animsrc->mControllers;
+ for(size_t i = 0;i < ctrls.size();i++)
+ {
+ NifOgre::NodeTargetValue<Ogre::Real> *dstval;
+ dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
+
+ size_t grp = detectAnimGroup(dstval->getNode());
+
+ if(!mAccumRoot && grp == 0)
+ {
+ mNonAccumRoot = dstval->getNode();
+ mAccumRoot = mNonAccumRoot->getParent();
+ if(!mAccumRoot)
+ {
+ std::cerr<< "Non-Accum root for "<<mPtr.getCellRef().mRefID<<" is skeleton root??" <<std::endl;
+ mNonAccumRoot = NULL;
+ }
+ }
+
+ ctrls[i].setSource(mAnimationValuePtr[grp]);
+ grpctrls[grp].push_back(ctrls[i]);
+ }
+}
+
+void Animation::clearAnimSources()
+{
+ mStates.clear();
+
+ for(size_t i = 0;i < sNumGroups;i++)
+ mAnimationValuePtr[i]->setAnimName(std::string());
+
+ mNonAccumCtrl = NULL;
+
+ mAccumRoot = NULL;
+ mNonAccumRoot = NULL;
+
+ mAnimSources.clear();
+}
+
+
+void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light)
+{
+ const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback();
+
+ const int clr = light->mData.mColor;
+ Ogre::ColourValue color(((clr >> 0) & 0xFF) / 255.0f,
+ ((clr >> 8) & 0xFF) / 255.0f,
+ ((clr >> 16) & 0xFF) / 255.0f);
+ const float radius = float(light->mData.mRadius);
+
+ if((light->mData.mFlags&ESM::Light::Negative))
+ color *= -1;
+
+ objlist.mLights.push_back(sceneMgr->createLight());
+ Ogre::Light *olight = objlist.mLights.back();
+ olight->setDiffuseColour(color);
+
+ Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource());
+ Ogre::ControllerValueRealPtr dest(OGRE_NEW OEngine::Render::LightValue(olight, color));
+ Ogre::ControllerFunctionRealPtr func(OGRE_NEW OEngine::Render::LightFunction(
+ (light->mData.mFlags&ESM::Light::Flicker) ? OEngine::Render::LT_Flicker :
+ (light->mData.mFlags&ESM::Light::FlickerSlow) ? OEngine::Render::LT_FlickerSlow :
+ (light->mData.mFlags&ESM::Light::Pulse) ? OEngine::Render::LT_Pulse :
+ (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));
+
+ bool interior = !(mPtr.isInCell() && mPtr.getCell()->mCell->isExterior());
+ bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ?
+ !interior : fallback->getFallbackBool("LightAttenuation_UseQuadratic");
+
+ // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero,
+ // so we ignore lights if their attenuation falls below this factor.
+ const float threshold = 0.03;
+
+ if (!quadratic)
+ {
+ float r = radius * fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult");
+ float attenuation = fallback->getFallbackFloat("LightAttenuation_LinearValue") / r;
+ float activationRange = 1.0f / (threshold * attenuation);
+ olight->setAttenuation(activationRange, 0, attenuation, 0);
+ }
+ else
+ {
+ float r = radius * fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult");
+ float attenuation = fallback->getFallbackFloat("LightAttenuation_QuadraticValue") / std::pow(r, 2);
+ float activationRange = std::sqrt(1.0f / (threshold * attenuation));
+ olight->setAttenuation(activationRange, 0, 0, attenuation);
+ }
+
+ // 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);
+ else
+ {
+ Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL;
+ for(size_t i = 0;i < objlist.mEntities.size();i++)
+ {
+ Ogre::Entity *ent = objlist.mEntities[i];
+ bounds.merge(ent->getBoundingBox());
+ }
+
+ Ogre::SceneNode *node = bounds.isFinite() ? mInsert->createChildSceneNode(bounds.getCenter())
+ : mInsert->createChildSceneNode();
+ node->attachObject(olight);
+ }
+}
+
+
+Ogre::Node *Animation::getNode(const std::string &name)
+{
+ if(mSkelBase)
+ {
+ Ogre::SkeletonInstance *skel = mSkelBase->getSkeleton();
+ if(skel->hasBone(name))
+ return skel->getBone(name);
+ }
+ return NULL;
+}
+
+
+NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname)
+{
+ NifOgre::TextKeyMap::const_iterator iter(keys.begin());
+ for(;iter != keys.end();iter++)
+ {
+ if(iter->second.compare(0, groupname.size(), groupname) == 0 &&
+ iter->second.compare(groupname.size(), 2, ": ") == 0)
+ break;
+ }
+ return iter;
+}
+
+
+bool Animation::hasAnimation(const std::string &anim)
+{
+ AnimSourceList::const_iterator iter(mAnimSources.begin());
+ for(;iter != mAnimSources.end();iter++)
+ {
+ const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
+ if(findGroupStart(keys, anim) != keys.end())
+ return true;
+ }
+
+ return false;
+}
+
+
+void Animation::setAccumulation(const Ogre::Vector3 &accum)
+{
+ mAccumulate = accum;
+}
+
+
+void Animation::updatePtr(const MWWorld::Ptr &ptr)
+{
+ mPtr = ptr;
+}
+
+
+float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::NodeTargetValue<Ogre::Real> *nonaccumctrl, const Ogre::Vector3 &accum, const std::string &groupname)
+{
+ const std::string start = groupname+": start";
+ const std::string loopstart = groupname+": loop start";
+ const std::string loopstop = groupname+": loop stop";
+ const std::string stop = groupname+": stop";
+ float starttime = std::numeric_limits<float>::max();
+ float stoptime = 0.0f;
+ NifOgre::TextKeyMap::const_iterator keyiter(keys.begin());
+ while(keyiter != keys.end())
+ {
+ if(keyiter->second == start || keyiter->second == loopstart)
+ starttime = keyiter->first;
+ else if(keyiter->second == loopstop || keyiter->second == stop)
+ {
+ stoptime = keyiter->first;
+ break;
+ }
+ keyiter++;
+ }
+
+ if(stoptime > starttime)
+ {
+ Ogre::Vector3 startpos = nonaccumctrl->getTranslation(starttime) * accum;
+ Ogre::Vector3 endpos = nonaccumctrl->getTranslation(stoptime) * accum;
+
+ return startpos.distance(endpos) / (stoptime - starttime);
+ }
+
+ return 0.0f;
+}
+
+float Animation::getVelocity(const std::string &groupname) const
+{
+ /* Look in reverse; last-inserted source has priority. */
+ AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin());
+ for(;animsrc != mAnimSources.rend();animsrc++)
+ {
+ const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys;
+ if(findGroupStart(keys, groupname) != keys.end())
+ break;
+ }
+ if(animsrc == mAnimSources.rend())
+ return 0.0f;
+
+ float velocity = 0.0f;
+ const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys;
+ const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = (*animsrc)->mControllers[0];
+ for(size_t i = 0;i < ctrls.size();i++)
+ {
+ NifOgre::NodeTargetValue<Ogre::Real> *dstval;
+ dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
+ if(dstval->getNode() == mNonAccumRoot)
+ {
+ velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname);
+ break;
+ }
+ }
+
+ // If there's no velocity, keep looking
+ if(!(velocity > 1.0f))
+ {
+ AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin();
+ while(*animiter != *animsrc)
+ ++animiter;
+
+ while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend())
+ {
+ const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys;
+ const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = (*animiter)->mControllers[0];
+ for(size_t i = 0;i < ctrls.size();i++)
+ {
+ NifOgre::NodeTargetValue<Ogre::Real> *dstval;
+ dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
+ if(dstval->getNode() == mNonAccumRoot)
+ {
+ velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname);
+ break;
+ }
+ }
+ }
+ }
+
+ return velocity;
+}
+
+
+static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone)
+{
+ if(skelsrc->hasBone(bone->getName()))
+ {
+ Ogre::Bone *srcbone = skelsrc->getBone(bone->getName());
+ if(!srcbone->getParent() || !bone->getParent())
+ {
+ bone->setOrientation(srcbone->getOrientation());
+ bone->setPosition(srcbone->getPosition());
+ bone->setScale(srcbone->getScale());
+ }
+ else
+ {
+ bone->_setDerivedOrientation(srcbone->_getDerivedOrientation());
+ bone->_setDerivedPosition(srcbone->_getDerivedPosition());
+ bone->setScale(Ogre::Vector3::UNIT_SCALE);
+ }
+ }
+ else
+ {
+ // No matching bone in the source. Make sure it stays properly offset
+ // from its parent.
+ bone->resetToInitialState();
+ }
+
+ Ogre::Node::ChildNodeIterator boneiter = bone->getChildIterator();
+ while(boneiter.hasMoreElements())
+ updateBoneTree(skelsrc, static_cast<Ogre::Bone*>(boneiter.getNext()));
+}
+
+void Animation::updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel)
+{
+ Ogre::Skeleton::BoneIterator boneiter = skel->getRootBoneIterator();
+ while(boneiter.hasMoreElements())
+ updateBoneTree(skelsrc, boneiter.getNext());
+}
+
+
+void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &position)
+{
+ /* Get the non-accumulation root's difference from the last update, and move the position
+ * accordingly.
+ */
+ Ogre::Vector3 off = mNonAccumCtrl->getTranslation(newtime)*mAccumulate;
+ position += off - mNonAccumCtrl->getTranslation(oldtime)*mAccumulate;
+
+ /* Translate the accumulation root back to compensate for the move. */
+ mAccumRoot->setPosition(-off);
+}
+
+bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint)
+{
+ const NifOgre::TextKeyMap::const_iterator groupstart = findGroupStart(keys, groupname);
+
+ std::string starttag = groupname+": "+start;
+ NifOgre::TextKeyMap::const_iterator startkey(groupstart);
+ while(startkey != keys.end() && startkey->second != starttag)
+ startkey++;
+ if(startkey == keys.end() && start == "loop start")
+ {
+ starttag = groupname+": start";
+ startkey = groupstart;
+ while(startkey != keys.end() && startkey->second != starttag)
+ startkey++;
+ }
+ if(startkey == keys.end())
+ return false;
+
+ const std::string stoptag = groupname+": "+stop;
+ NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
+ while(stopkey != keys.end() && stopkey->second != stoptag)
+ stopkey++;
+ if(stopkey == keys.end())
+ return false;
+
+ if(startkey->first > stopkey->first)
+ return false;
+
+ state.mStartTime = startkey->first;
+ state.mLoopStartTime = startkey->first;
+ state.mLoopStopTime = stopkey->first;
+ state.mStopTime = stopkey->first;
+
+ state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint);
+ if(state.mTime > state.mStartTime)
+ {
+ const std::string loopstarttag = groupname+": loop start";
+ const std::string loopstoptag = groupname+": loop stop";
+ NifOgre::TextKeyMap::const_iterator key(groupstart);
+ while(key->first <= state.mTime && key != stopkey)
+ {
+ if(key->second == loopstarttag)
+ state.mLoopStartTime = key->first;
+ else if(key->second == loopstoptag)
+ state.mLoopStopTime = key->first;
+ key++;
+ }
+ }
+
+ return true;
+}
+
+
+void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
+{
+ //float time = key->first;
+ const std::string &evt = key->second;
+
+ if(evt.compare(0, 7, "sound: ") == 0)
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f);
+ return;
+ }
+ if(evt.compare(0, 10, "soundgen: ") == 0)
+ {
+ std::string sound = MWWorld::Class::get(mPtr).getSoundIdFromSndGen(mPtr, evt.substr(10));
+ if(!sound.empty())
+ {
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx;
+ if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0)
+ type = MWBase::SoundManager::Play_TypeFoot;
+ sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type);
+ }
+ return;
+ }
+
+ if(evt.compare(0, groupname.size(), groupname) != 0 ||
+ evt.compare(groupname.size(), 2, ": ") != 0)
+ {
+ // Not ours, skip it
+ return;
+ }
+ size_t off = groupname.size()+2;
+ size_t len = evt.size() - off;
+
+ if(evt.compare(off, len, "loop start") == 0)
+ state.mLoopStartTime = key->first;
+ else if(evt.compare(off, len, "loop stop") == 0)
+ state.mLoopStopTime = key->first;
+ else if(evt.compare(off, len, "equip attach") == 0)
+ showWeapons(true);
+ else if(evt.compare(off, len, "unequip detach") == 0)
+ showWeapons(false);
+ else if(evt.compare(off, len, "chop hit") == 0)
+ MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Chop);
+ else if(evt.compare(off, len, "slash hit") == 0)
+ MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Slash);
+ else if(evt.compare(off, len, "thrust hit") == 0)
+ MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
+ else if(evt.compare(off, len, "hit") == 0)
+ MWWorld::Class::get(mPtr).hit(mPtr);
+}
+
+
+void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops)
+{
+ if(!mSkelBase || mAnimSources.empty())
+ return;
+
+ if(groupname.empty())
+ {
+ resetActiveGroups();
+ return;
+ }
+
+ priority = std::max(0, priority);
+
+ AnimStateMap::iterator stateiter = mStates.begin();
+ while(stateiter != mStates.end())
+ {
+ if(stateiter->second.mPriority == priority)
+ mStates.erase(stateiter++);
+ else
+ ++stateiter;
+ }
+
+ stateiter = mStates.find(groupname);
+ if(stateiter != mStates.end())
+ {
+ stateiter->second.mPriority = priority;
+ resetActiveGroups();
+ return;
+ }
+
+ /* Look in reverse; last-inserted source has priority. */
+ AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
+ for(;iter != mAnimSources.rend();iter++)
+ {
+ const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys;
+ AnimState state;
+ if(reset(state, textkeys, groupname, start, stop, startpoint))
+ {
+ state.mSource = *iter;
+ state.mSpeedMult = speedmult;
+ state.mLoopCount = loops;
+ state.mPlaying = (state.mTime < state.mStopTime);
+ state.mPriority = priority;
+ state.mGroups = groups;
+ state.mAutoDisable = autodisable;
+ mStates[groupname] = state;
+
+ NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime));
+ while(textkey != textkeys.end() && textkey->first <= state.mTime)
+ {
+ handleTextKey(state, groupname, textkey);
+ textkey++;
+ }
+
+ if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
+ {
+ state.mLoopCount--;
+ state.mTime = state.mLoopStartTime;
+ state.mPlaying = true;
+ if(state.mTime >= state.mLoopStopTime)
+ break;
+
+ textkey = textkeys.lower_bound(state.mTime);
+ while(textkey != textkeys.end() && textkey->first <= state.mTime)
+ {
+ handleTextKey(state, groupname, textkey);
+ textkey++;
+ }
+ }
+
+ break;
+ }
+ }
+ if(iter == mAnimSources.rend())
+ std::cerr<< "Failed to find animation "<<groupname<<" for "<<mPtr.getCellRef().mRefID <<std::endl;
+
+ resetActiveGroups();
+}
+
+bool Animation::isPlaying(const std::string &groupname) const
+{
+ AnimStateMap::const_iterator state(mStates.find(groupname));
+ if(state != mStates.end())
+ return state->second.mPlaying;
+ return false;
+}
+
+void Animation::resetActiveGroups()
+{
+ for(size_t grp = 0;grp < sNumGroups;grp++)
+ {
+ AnimStateMap::const_iterator active = mStates.end();
+
+ AnimStateMap::const_iterator state = mStates.begin();
+ for(;state != mStates.end();++state)
+ {
+ if(!(state->second.mGroups&(1<<grp)))
+ continue;
+
+ if(active == mStates.end() || active->second.mPriority < state->second.mPriority)
+ active = state;
+ }
+
+ mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ?
+ std::string() : active->first);
+ }
+ mNonAccumCtrl = NULL;
+
+ if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f))
+ return;
+
+ AnimStateMap::const_iterator state = mStates.find(mAnimationValuePtr[0]->getAnimName());
+ if(state == mStates.end())
+ return;
+
+ const Ogre::SharedPtr<AnimSource> &animsrc = state->second.mSource;
+ const std::vector<Ogre::Controller<Ogre::Real> >&ctrls = animsrc->mControllers[0];
+ for(size_t i = 0;i < ctrls.size();i++)
+ {
+ NifOgre::NodeTargetValue<Ogre::Real> *dstval;
+ dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
+ if(dstval->getNode() == mNonAccumRoot)
+ {
+ mNonAccumCtrl = dstval;
+ break;
+ }
+ }
+}
+
+
+bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const
+{
+ AnimStateMap::const_iterator iter = mStates.find(groupname);
+ if(iter == mStates.end())
+ {
+ if(complete) *complete = 0.0f;
+ if(speedmult) *speedmult = 0.0f;
+ return false;
+ }
+
+ if(complete)
+ {
+ if(iter->second.mStopTime > iter->second.mStartTime)
+ *complete = (iter->second.mTime - iter->second.mStartTime) /
+ (iter->second.mStopTime - iter->second.mStartTime);
+ else
+ *complete = (iter->second.mPlaying ? 0.0f : 1.0f);
+ }
+ if(speedmult) *speedmult = iter->second.mSpeedMult;
+ return true;
+}
+
+
+void Animation::disable(const std::string &groupname)
+{
+ AnimStateMap::iterator iter = mStates.find(groupname);
+ if(iter != mStates.end())
+ mStates.erase(iter);
+ resetActiveGroups();
+}
+
+
+Ogre::Vector3 Animation::runAnimation(float duration)
+{
+ Ogre::Vector3 movement(0.0f);
+
+ AnimStateMap::iterator stateiter = mStates.begin();
+ while(stateiter != mStates.end())
+ {
+ AnimState &state = stateiter->second;
+ const NifOgre::TextKeyMap &textkeys = state.mSource->mTextKeys;
+ NifOgre::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.mTime));
+
+ float timepassed = duration * state.mSpeedMult;
+ while(state.mPlaying)
+ {
+ float targetTime;
+
+ if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
+ goto handle_loop;
+
+ targetTime = state.mTime + timepassed;
+ if(textkey == textkeys.end() || textkey->first > targetTime)
+ {
+ if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
+ updatePosition(state.mTime, targetTime, movement);
+ state.mTime = std::min(targetTime, state.mStopTime);
+ }
+ else
+ {
+ if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
+ updatePosition(state.mTime, textkey->first, movement);
+ state.mTime = textkey->first;
+ }
+
+ state.mPlaying = (state.mTime < state.mStopTime);
+ timepassed = targetTime - state.mTime;
+
+ while(textkey != textkeys.end() && textkey->first <= state.mTime)
+ {
+ handleTextKey(state, stateiter->first, textkey);
+ textkey++;
+ }
+
+ if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0)
+ {
+ handle_loop:
+ state.mLoopCount--;
+ state.mTime = state.mLoopStartTime;
+ state.mPlaying = true;
+
+ textkey = textkeys.lower_bound(state.mTime);
+ while(textkey != textkeys.end() && textkey->first <= state.mTime)
+ {
+ handleTextKey(state, stateiter->first, textkey);
+ textkey++;
+ }
+
+ if(state.mTime >= state.mLoopStopTime)
+ break;
+ }
+
+ if(timepassed <= 0.0f)
+ break;
+ }
+
+ if(!state.mPlaying && state.mAutoDisable)
+ {
+ mStates.erase(stateiter++);
+ resetActiveGroups();
+ }
+ else
+ ++stateiter;
+ }
+
+ 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++)
+ {
+ const std::string &name = mAnimationValuePtr[grp]->getAnimName();
+ if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end())
+ {
+ const Ogre::SharedPtr<AnimSource> &src = stateiter->second.mSource;
+ for(size_t i = 0;i < src->mControllers[grp].size();i++)
+ src->mControllers[grp][i].update();
+ }
+ }
+
+ if(mSkelBase)
+ {
+ // HACK: Dirty the animation state set so that Ogre will apply the
+ // transformations to entities this skeleton instance is shared with.
+ mSkelBase->getAllAnimationStates()->_notifyDirty();
+ }
+
+ return movement;
+}
+
+void Animation::showWeapons(bool showWeapon)
+{
+}
+
+
+class ToggleLight {
+ bool mEnable;
+
+public:
+ ToggleLight(bool enable) : mEnable(enable) { }
+
+ void operator()(Ogre::Light *light) const
+ { light->setVisible(mEnable); }
+};
+
+void Animation::enableLights(bool enable)
+{
+ std::for_each(mObjectRoot.mLights.begin(), mObjectRoot.mLights.end(), ToggleLight(enable));
+}
+
+
+class MergeBounds {
+ Ogre::AxisAlignedBox *mBounds;
+
+public:
+ MergeBounds(Ogre::AxisAlignedBox *bounds) : mBounds(bounds) { }
+
+ void operator()(Ogre::MovableObject *obj)
+ {
+ mBounds->merge(obj->getWorldBoundingBox(true));
+ }
+};
+
+Ogre::AxisAlignedBox Animation::getWorldBounds()
+{
+ Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL;
+ std::for_each(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), MergeBounds(&bounds));
+ return bounds;
+}
+
+
+Ogre::TagPoint *Animation::attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj)
+{
+ Ogre::TagPoint *tag = NULL;
+ Ogre::SkeletonInstance *skel = (mSkelBase ? mSkelBase->getSkeleton() : NULL);
+ if(skel && skel->hasBone(bonename))
+ {
+ tag = mSkelBase->attachObjectToBone(bonename, obj);
+ mAttachedObjects[obj] = bonename;
+ }
+ return tag;
+}
+
+void Animation::detachObjectFromBone(Ogre::MovableObject *obj)
+{
+ ObjectAttachMap::iterator iter = mAttachedObjects.find(obj);
+ if(iter != mAttachedObjects.end())
+ mAttachedObjects.erase(iter);
+ mSkelBase->detachObjectFromBone(obj);
+}
+
+
+ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model)
+ : Animation(ptr, ptr.getRefData().getBaseNode())
+{
+ setObjectRoot(model, false);
+
+ Ogre::Vector3 extents = getWorldBounds().getSize();
+ float size = std::max(std::max(extents.x, extents.y), extents.z);
+
+ bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) &&
+ Settings::Manager::getBool("limit small object distance", "Viewing distance");
+ // do not fade out doors. that will cause holes and look stupid
+ if(ptr.getTypeName().find("Door") != std::string::npos)
+ small = false;
+
+ float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f;
+ setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ?
+ (small ? RV_StaticsSmall : RV_Statics) : RV_Misc,
+ RQG_Main, RQG_Alpha, dist);
+}
+
+void ObjectAnimation::addLight(const ESM::Light *light)
+{
+ addExtraLight(mInsert->getCreator(), mObjectRoot, light);
+}
+
+
+class FindEntityTransparency {
+public:
+ bool operator()(Ogre::Entity *ent) const
+ {
+ unsigned int numsubs = ent->getNumSubEntities();
+ for(unsigned int i = 0;i < numsubs;++i)
+ {
+ if(ent->getSubEntity(i)->getMaterial()->isTransparent())
+ return true;
+ }
+ return false;
+ }
+};
+
+bool ObjectAnimation::canBatch() const
+{
+ if(!mObjectRoot.mParticles.empty() || !mObjectRoot.mLights.empty() || !mObjectRoot.mControllers.empty())
+ return false;
+ 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)
+ {
+ Ogre::Node *node = (*iter)->getParentNode();
+ sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale());
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp
new file mode 100644
index 0000000000..2215fc5827
--- /dev/null
+++ b/apps/openmw/mwrender/animation.hpp
@@ -0,0 +1,256 @@
+#ifndef _GAME_RENDER_ANIMATION_H
+#define _GAME_RENDER_ANIMATION_H
+
+#include <OgreController.h>
+#include <OgreVector3.h>
+
+#include <components/nifogre/ogrenifloader.hpp>
+
+#include "../mwworld/ptr.hpp"
+
+
+namespace MWRender
+{
+class Camera;
+
+class Animation
+{
+public:
+ enum Group {
+ Group_LowerBody = 1<<0,
+
+ Group_Torso = 1<<1,
+ Group_LeftArm = 1<<2,
+ Group_RightArm = 1<<3,
+
+ Group_UpperBody = Group_Torso | Group_LeftArm | Group_RightArm,
+
+ Group_All = Group_LowerBody | Group_UpperBody
+ };
+
+protected:
+ /* This is the number of *discrete* groups. */
+ static const size_t sNumGroups = 4;
+
+ class AnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ {
+ private:
+ Animation *mAnimation;
+ std::string mAnimationName;
+
+ public:
+ AnimationValue(Animation *anim)
+ : mAnimation(anim)
+ { }
+
+ void setAnimName(const std::string &name)
+ { mAnimationName = name; }
+ const std::string &getAnimName() const
+ { return mAnimationName; }
+
+ virtual Ogre::Real getValue() const;
+ virtual void setValue(Ogre::Real value);
+ };
+
+
+ class NullAnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ {
+ public:
+ virtual Ogre::Real getValue() const
+ { return 0.0f; }
+ virtual void setValue(Ogre::Real value)
+ { }
+ };
+
+
+ struct AnimSource : public Ogre::AnimationAlloc {
+ NifOgre::TextKeyMap mTextKeys;
+ std::vector<Ogre::Controller<Ogre::Real> > mControllers[sNumGroups];
+ };
+ typedef std::vector< Ogre::SharedPtr<AnimSource> > AnimSourceList;
+
+ struct AnimState {
+ Ogre::SharedPtr<AnimSource> mSource;
+ float mStartTime;
+ float mLoopStartTime;
+ float mLoopStopTime;
+ float mStopTime;
+
+ float mTime;
+ float mSpeedMult;
+
+ bool mPlaying;
+ size_t mLoopCount;
+
+ int mPriority;
+ int mGroups;
+ bool mAutoDisable;
+
+ AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f),
+ mTime(0.0f), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0),
+ mPriority(0), mGroups(0), mAutoDisable(true)
+ { }
+ };
+ typedef std::map<std::string,AnimState> AnimStateMap;
+
+ typedef std::map<Ogre::MovableObject*,std::string> ObjectAttachMap;
+
+ MWWorld::Ptr mPtr;
+ Camera *mCamera;
+
+ Ogre::SceneNode *mInsert;
+ Ogre::Entity *mSkelBase;
+ NifOgre::ObjectList mObjectRoot;
+ AnimSourceList mAnimSources;
+ Ogre::Node *mAccumRoot;
+ Ogre::Node *mNonAccumRoot;
+ NifOgre::NodeTargetValue<Ogre::Real> *mNonAccumCtrl;
+ Ogre::Vector3 mAccumulate;
+
+ AnimStateMap mStates;
+
+ Ogre::SharedPtr<AnimationValue> mAnimationValuePtr[sNumGroups];
+ Ogre::SharedPtr<NullAnimationValue> mNullAnimationValuePtr;
+
+ ObjectAttachMap mAttachedObjects;
+
+ /* Sets the appropriate animations on the bone groups based on priority.
+ */
+ void resetActiveGroups();
+
+ static size_t detectAnimGroup(const Ogre::Node *node);
+
+ static float calcAnimVelocity(const NifOgre::TextKeyMap &keys,
+ NifOgre::NodeTargetValue<Ogre::Real> *nonaccumctrl,
+ const Ogre::Vector3 &accum,
+ const std::string &groupname);
+
+ /* Updates a skeleton instance so that all bones matching the source skeleton (based on
+ * bone names) are positioned identically. */
+ void updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel);
+
+ /* Updates the position of the accum root node for the given time, and
+ * returns the wanted movement vector from the previous time. */
+ void updatePosition(float oldtime, float newtime, Ogre::Vector3 &position);
+
+ static NifOgre::TextKeyMap::const_iterator findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname);
+
+ /* Resets the animation to the time of the specified start marker, without
+ * moving anything, and set the end time to the specified stop marker. If
+ * the marker is not found, or if the markers are the same, it returns
+ * false.
+ */
+ bool reset(AnimState &state, const NifOgre::TextKeyMap &keys,
+ const std::string &groupname, const std::string &start, const std::string &stop,
+ float startpoint);
+
+ void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key);
+
+ /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle
+ * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for
+ * the root).
+ *
+ * Note that you must make sure all animation sources are cleared before reseting the object
+ * root. All nodes previously retrieved with getNode will also become invalidated.
+ */
+ void setObjectRoot(const std::string &model, bool baseonly);
+
+ /* Adds the keyframe controllers in the specified model as a new animation source. Note that
+ * the filename portion of the provided model name will be prepended with 'x', and the .nif
+ * extension will be replaced with .kf. */
+ 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);
+
+ 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);
+
+ void clearAnimSources();
+
+public:
+ Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node);
+ virtual ~Animation();
+
+ void updatePtr(const MWWorld::Ptr &ptr);
+
+ bool hasAnimation(const std::string &anim);
+
+ // Specifies the axis' to accumulate on. Non-accumulated axis will just
+ // move visually, but not affect the actual movement. Each x/y/z value
+ // should be on the scale of 0 to 1.
+ void setAccumulation(const Ogre::Vector3 &accum);
+
+ /** Plays an animation.
+ * \param groupname Name of the animation group to play.
+ * \param priority Priority of the animation. The animation will play on
+ * bone groups that don't have another animation set of a
+ * higher priority.
+ * \param groups Bone groups to play the animation on.
+ * \param autodisable Automatically disable the animation when it stops
+ * playing.
+ * \param speedmult Speed multiplier for the animation.
+ * \param start Key marker from which to start.
+ * \param stop Key marker to stop at.
+ * \param startpoint How far in between the two markers to start. 0 starts
+ * at the start marker, 1 starts at the stop marker.
+ * \param loops How many times to loop the animation. This will use the
+ * "loop start" and "loop stop" markers if they exist,
+ * otherwise it will use "start" and "stop".
+ */
+ void play(const std::string &groupname, int priority, int groups, bool autodisable,
+ float speedmult, const std::string &start, const std::string &stop,
+ float startpoint, size_t loops);
+
+ /** Returns true if the named animation group is playing. */
+ bool isPlaying(const std::string &groupname) 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.
+ * \param speedmult Stores the animation speed multiplier
+ * \return True if the animation is active, false otherwise.
+ */
+ bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
+
+ /** Disables the specified animation group;
+ * \param groupname Animation group to disable.
+ */
+ void disable(const std::string &groupname);
+
+ /** Retrieves the velocity (in units per second) that the animation will move. */
+ float getVelocity(const std::string &groupname) const;
+
+ virtual Ogre::Vector3 runAnimation(float duration);
+
+ virtual void showWeapons(bool showWeapon);
+
+ void enableLights(bool enable);
+
+ Ogre::AxisAlignedBox getWorldBounds();
+
+ void setCamera(Camera *cam)
+ { mCamera = cam; }
+
+ Ogre::Node *getNode(const std::string &name);
+
+ // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't
+ // exist, the object isn't attached and NULL is returned. The returned TagPoint is only
+ // valid until the next setObjectRoot call.
+ Ogre::TagPoint *attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj);
+ void detachObjectFromBone(Ogre::MovableObject *obj);
+};
+
+class ObjectAnimation : public Animation {
+public:
+ ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model);
+
+ void addLight(const ESM::Light *light);
+
+ bool canBatch() const;
+ void fillBatch(Ogre::StaticGeometry *sg);
+};
+
+}
+#endif
diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp
new file mode 100644
index 0000000000..36f53c0fea
--- /dev/null
+++ b/apps/openmw/mwrender/camera.cpp
@@ -0,0 +1,346 @@
+#include "camera.hpp"
+
+#include <OgreSceneNode.h>
+#include <OgreCamera.h>
+#include <OgreSceneManager.h>
+#include <OgreTagPoint.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/refdata.hpp"
+
+#include "npcanimation.hpp"
+
+namespace MWRender
+{
+ Camera::Camera (Ogre::Camera *camera)
+ : mCamera(camera),
+ mCameraNode(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)
+ {
+ mVanity.enabled = false;
+ mVanity.allowed = true;
+
+ mPreviewCam.yaw = 0.f;
+ mPreviewCam.offset = 400.f;
+ mMainCam.yaw = 0.f;
+ mMainCam.offset = 400.f;
+ }
+
+ Camera::~Camera()
+ {
+ }
+
+ void Camera::reset()
+ {
+ togglePreviewMode(false);
+ toggleVanityMode(false);
+ if (!mFirstPersonView)
+ toggleViewMode();
+ }
+
+ void Camera::rotateCamera(const Ogre::Vector3 &rot, bool adjust)
+ {
+ if (adjust) {
+ setYaw(getYaw() + rot.z);
+ setPitch(getPitch() + rot.x);
+ } else {
+ setYaw(rot.z);
+ setPitch(rot.x);
+ }
+
+ Ogre::Quaternion xr(Ogre::Radian(getPitch() + Ogre::Math::HALF_PI), Ogre::Vector3::UNIT_X);
+ if (!mVanity.enabled && !mPreviewMode) {
+ mCamera->getParentNode()->setOrientation(xr);
+ } else {
+ Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z);
+ mCamera->getParentNode()->setOrientation(zr * xr);
+ }
+ }
+
+ const std::string &Camera::getHandle() const
+ {
+ return mTrackingPtr.getRefData().getHandle();
+ }
+
+ void Camera::attachTo(const MWWorld::Ptr &ptr)
+ {
+ mTrackingPtr = ptr;
+ Ogre::SceneNode *node = mTrackingPtr.getRefData().getBaseNode()->createChildSceneNode(Ogre::Vector3(0.0f, 0.0f, mHeight));
+ if(mCameraNode)
+ {
+ node->setOrientation(mCameraNode->getOrientation());
+ node->setPosition(mCameraNode->getPosition());
+ node->setScale(mCameraNode->getScale());
+ mCameraNode->getCreator()->destroySceneNode(mCameraNode);
+ }
+ mCameraNode = node;
+ if(!mCamera->isAttached())
+ mCameraNode->attachObject(mCamera);
+ }
+
+ void Camera::updateListener()
+ {
+ Ogre::Vector3 pos = mCamera->getRealPosition();
+ Ogre::Vector3 dir = mCamera->getRealDirection();
+ Ogre::Vector3 up = mCamera->getRealUp();
+
+ MWBase::Environment::get().getSoundManager()->setListenerPosDir(pos, dir, up);
+ }
+
+ void Camera::update(float duration, bool paused)
+ {
+ updateListener();
+ if (paused)
+ return;
+
+ // only show the crosshair in game mode and in first person mode.
+ MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
+ wm->showCrosshair(!wm->isGuiMode() && (mFirstPersonView && !mVanity.enabled && !mPreviewMode));
+
+ if(mVanity.enabled)
+ {
+ Ogre::Vector3 rot(0.f, 0.f, 0.f);
+ rot.z = Ogre::Degree(3.f * duration).valueRadians();
+ rotateCamera(rot, true);
+ }
+ }
+
+ void Camera::toggleViewMode()
+ {
+ mFirstPersonView = !mFirstPersonView;
+ processViewChange();
+
+ if (mFirstPersonView) {
+ mCamera->setPosition(0.f, 0.f, 0.f);
+ } else {
+ mCamera->setPosition(0.f, 0.f, mCameraDistance);
+ }
+ }
+
+ void Camera::allowVanityMode(bool allow)
+ {
+ if (!allow && mVanity.enabled)
+ toggleVanityMode(false);
+ mVanity.allowed = allow;
+ }
+
+ bool Camera::toggleVanityMode(bool enable)
+ {
+ if(!mVanity.allowed && enable)
+ return false;
+
+ if(mVanity.enabled == enable)
+ return true;
+ mVanity.enabled = enable;
+
+ processViewChange();
+
+ float offset = mPreviewCam.offset;
+ Ogre::Vector3 rot(0.f, 0.f, 0.f);
+ if (mVanity.enabled) {
+ rot.x = Ogre::Degree(-30.f).valueRadians();
+ mMainCam.offset = mCamera->getPosition().z;
+ } else {
+ rot.x = getPitch();
+ offset = mMainCam.offset;
+ }
+ rot.z = getYaw();
+
+ mCamera->setPosition(0.f, 0.f, offset);
+ rotateCamera(rot, false);
+
+ return true;
+ }
+
+ void Camera::togglePreviewMode(bool enable)
+ {
+ if(mPreviewMode == enable)
+ return;
+
+ mPreviewMode = enable;
+ processViewChange();
+
+ float offset = mCamera->getPosition().z;
+ if (mPreviewMode) {
+ mMainCam.offset = offset;
+ offset = mPreviewCam.offset;
+ } else {
+ mPreviewCam.offset = offset;
+ offset = mMainCam.offset;
+ }
+
+ mCamera->setPosition(0.f, 0.f, offset);
+ rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false);
+ }
+
+ float Camera::getYaw()
+ {
+ if(mVanity.enabled || mPreviewMode)
+ return mPreviewCam.yaw;
+ return mMainCam.yaw;
+ }
+
+ void Camera::setYaw(float angle)
+ {
+ if (angle > Ogre::Math::PI) {
+ angle -= Ogre::Math::TWO_PI;
+ } else if (angle < -Ogre::Math::PI) {
+ angle += Ogre::Math::TWO_PI;
+ }
+ if (mVanity.enabled || mPreviewMode) {
+ mPreviewCam.yaw = angle;
+ } else {
+ mMainCam.yaw = angle;
+ }
+ }
+
+ float Camera::getPitch()
+ {
+ if (mVanity.enabled || mPreviewMode) {
+ return mPreviewCam.pitch;
+ }
+ return mMainCam.pitch;
+ }
+
+ void Camera::setPitch(float angle)
+ {
+ const float epsilon = 0.000001f;
+ float limit = Ogre::Math::HALF_PI - epsilon;
+ if(mPreviewMode)
+ limit /= 2;
+
+ if(angle > limit)
+ angle = limit;
+ else if(angle < -limit)
+ angle = -limit;
+
+ if (mVanity.enabled || mPreviewMode) {
+ mPreviewCam.pitch = angle;
+ } else {
+ mMainCam.pitch = angle;
+ }
+ }
+
+ void Camera::setCameraDistance(float dist, bool adjust, bool override)
+ {
+ if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
+ return;
+
+ mIsFurthest = false;
+ mIsNearest = false;
+
+ Ogre::Vector3 v(0.f, 0.f, dist);
+ if (adjust) {
+ v += mCamera->getPosition();
+ }
+ if (v.z >= mFurthest) {
+ v.z = mFurthest;
+ mIsFurthest = true;
+ } else if (!override && v.z < 10.f) {
+ v.z = 10.f;
+ } else if (override && v.z <= mNearest) {
+ v.z = mNearest;
+ mIsNearest = true;
+ }
+ mCamera->setPosition(v);
+
+ if (override) {
+ if (mVanity.enabled || mPreviewMode) {
+ mPreviewCam.offset = v.z;
+ } else if (!mFirstPersonView) {
+ mCameraDistance = v.z;
+ }
+ } else {
+ mDistanceAdjusted = true;
+ }
+ }
+
+ void Camera::setCameraDistance()
+ {
+ if (mDistanceAdjusted) {
+ if (mVanity.enabled || mPreviewMode) {
+ mCamera->setPosition(0, 0, mPreviewCam.offset);
+ } else if (!mFirstPersonView) {
+ mCamera->setPosition(0, 0, mCameraDistance);
+ }
+ }
+ mDistanceAdjusted = false;
+ }
+
+ void Camera::setAnimation(NpcAnimation *anim)
+ {
+ // If we're switching to a new NpcAnimation, ensure the old one is
+ // using a normal view mode
+ if(mAnimation && mAnimation != anim)
+ {
+ mAnimation->setViewMode(NpcAnimation::VM_Normal);
+ mAnimation->setCamera(NULL);
+ mAnimation->detachObjectFromBone(mCamera);
+ }
+ mAnimation = anim;
+ mAnimation->setCamera(this);
+
+ processViewChange();
+ }
+
+ void Camera::processViewChange()
+ {
+ mAnimation->detachObjectFromBone(mCamera);
+ mCamera->detachFromParent();
+
+ if(isFirstPerson())
+ {
+ mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
+ Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera);
+ tag->setInheritOrientation(false);
+ }
+ else
+ {
+ mAnimation->setViewMode(NpcAnimation::VM_Normal);
+ mCameraNode->attachObject(mCamera);
+ }
+ }
+
+ void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera)
+ {
+ mCamera->getParentSceneNode()->needUpdate(true);
+
+ camera = mCamera->getRealPosition();
+ focal = Ogre::Vector3((mCamera->getParentNode()->_getFullTransform() *
+ Ogre::Vector4(0.0f, 0.0f, 0.0f, 1.0f)).ptr());
+ }
+
+ void Camera::togglePlayerLooking(bool enable)
+ {
+ mFreeLook = enable;
+ }
+
+ bool Camera::isVanityOrPreviewModeEnabled()
+ {
+ return mPreviewMode || mVanity.enabled;
+ }
+
+ bool Camera::isNearest()
+ {
+ return mIsNearest;
+ }
+
+ bool Camera::isFurthest()
+ {
+ return mIsFurthest;
+ }
+}
diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp
new file mode 100644
index 0000000000..dc552371e6
--- /dev/null
+++ b/apps/openmw/mwrender/camera.hpp
@@ -0,0 +1,113 @@
+#ifndef GAME_MWRENDER_CAMERA_H
+#define GAME_MWRENDER_CAMERA_H
+
+#include <string>
+
+#include "../mwworld/ptr.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+ class Camera;
+ class SceneNode;
+}
+
+namespace MWRender
+{
+ class NpcAnimation;
+
+ /// \brief Camera control
+ class Camera
+ {
+ struct CamData {
+ float pitch, yaw, offset;
+ };
+
+ MWWorld::Ptr mTrackingPtr;
+
+ Ogre::Camera *mCamera;
+ Ogre::SceneNode *mCameraNode;
+
+ NpcAnimation *mAnimation;
+
+ bool mFirstPersonView;
+ bool mPreviewMode;
+ bool mFreeLook;
+ float mNearest;
+ float mFurthest;
+ bool mIsNearest;
+ bool mIsFurthest;
+
+ struct {
+ bool enabled, allowed;
+ } mVanity;
+
+ float mHeight, mCameraDistance;
+ CamData mMainCam, mPreviewCam;
+
+ bool mDistanceAdjusted;
+
+ /// Updates sound manager listener data
+ void updateListener();
+
+ public:
+ Camera(Ogre::Camera *camera);
+ ~Camera();
+
+ /// Reset to defaults
+ void reset();
+
+ /// Set where the camera is looking at. Uses Morrowind (euler) angles
+ /// \param rot Rotation angles in radians
+ void rotateCamera(const Ogre::Vector3 &rot, bool adjust);
+
+ float getYaw();
+ void setYaw(float angle);
+
+ float getPitch();
+ void setPitch(float angle);
+
+ const std::string &getHandle() const;
+
+ /// Attach camera to object
+ void attachTo(const MWWorld::Ptr &);
+
+ void toggleViewMode();
+
+ bool toggleVanityMode(bool enable);
+ void allowVanityMode(bool allow);
+
+ void togglePreviewMode(bool enable);
+
+ bool isFirstPerson() const
+ { return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); }
+
+ void processViewChange();
+
+ void update(float duration, bool paused=false);
+
+ /// Set camera distance for current mode. Don't work on 1st person view.
+ /// \param adjust Indicates should distance be adjusted or set.
+ /// \param override If true new distance will be used as default.
+ /// If false, default distance can be restored with setCameraDistance().
+ void setCameraDistance(float dist, bool adjust = false, bool override = true);
+
+ /// Restore default camera distance for current mode.
+ void setCameraDistance();
+
+ void setAnimation(NpcAnimation *anim);
+
+ /// Stores focal and camera world positions in passed arguments
+ void getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera);
+
+ void togglePlayerLooking(bool enable);
+
+ bool isVanityOrPreviewModeEnabled();
+
+ bool isNearest();
+
+ bool isFurthest();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/cell.hpp b/apps/openmw/mwrender/cell.hpp
new file mode 100644
index 0000000000..8fa3f9f0f0
--- /dev/null
+++ b/apps/openmw/mwrender/cell.hpp
@@ -0,0 +1,35 @@
+#ifndef GAME_RENDER_CELL_H
+#define GAME_RENDER_CELL_H
+
+#include <string>
+
+namespace MWRender
+{
+ class CellRender
+ {
+ public:
+
+ virtual ~CellRender() {};
+
+ /// Make the cell visible. Load the cell if necessary.
+ virtual void show() = 0;
+
+ /// Remove the cell from rendering, but don't remove it from
+ /// memory.
+ virtual void hide() = 0;
+
+ /// Destroy all rendering objects connected with this cell.
+ virtual void destroy() = 0;
+
+ /// Make the reference with the given handle visible.
+ virtual void enable (const std::string& handle) = 0;
+
+ /// Make the reference with the given handle invisible.
+ virtual void disable (const std::string& handle) = 0;
+
+ /// Remove the reference with the given handle permanently from the scene.
+ virtual void deleteObject (const std::string& handle) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp
new file mode 100644
index 0000000000..e42218bc2d
--- /dev/null
+++ b/apps/openmw/mwrender/characterpreview.cpp
@@ -0,0 +1,272 @@
+#include "characterpreview.hpp"
+
+
+#include <OgreSceneManager.h>
+#include <OgreRoot.h>
+#include <OgreHardwarePixelBuffer.h>
+
+#include <libs/openengine/ogre/selectionbuffer.hpp>
+
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "renderconst.hpp"
+#include "npcanimation.hpp"
+
+namespace MWRender
+{
+
+ CharacterPreview::CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name,
+ Ogre::Vector3 position, Ogre::Vector3 lookAt)
+ : mSceneMgr (0)
+ , mPosition(position)
+ , mLookAt(lookAt)
+ , mCharacter(character)
+ , mAnimation(NULL)
+ , mName(name)
+ , mSizeX(sizeX)
+ , mSizeY(sizeY)
+ , mRenderTarget(NULL)
+ , mViewport(NULL)
+ , mCamera(NULL)
+ , mNode(NULL)
+ {
+
+ }
+
+ void CharacterPreview::onSetup()
+ {
+
+ }
+
+ void CharacterPreview::setup ()
+ {
+ mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
+
+ // This is a dummy light to turn off shadows without having to use a separate set of shaders
+ Ogre::Light* l = mSceneMgr->createLight();
+ l->setType (Ogre::Light::LT_DIRECTIONAL);
+ l->setDiffuseColour (Ogre::ColourValue(0,0,0));
+
+ /// \todo Read the fallback values from INIImporter (Inventory:Directional*)
+ l = mSceneMgr->createLight();
+ l->setType (Ogre::Light::LT_DIRECTIONAL);
+ l->setDirection (Ogre::Vector3(0.3, -0.7, 0.3));
+ l->setDiffuseColour (Ogre::ColourValue(1,1,1));
+
+ mSceneMgr->setAmbientLight (Ogre::ColourValue(0.5, 0.5, 0.5));
+
+ mCamera = mSceneMgr->createCamera (mName);
+ mCamera->setAspectRatio (float(mSizeX) / float(mSizeY));
+
+ Ogre::SceneNode* renderRoot = mSceneMgr->getRootSceneNode()->createChildSceneNode("renderRoot");
+
+ //we do this with mwRoot in renderingManager, do it here too.
+ renderRoot->pitch(Ogre::Degree(-90));
+
+ mNode = renderRoot->createChildSceneNode();
+
+ mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter),
+ 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal));
+
+ Ogre::Vector3 scale = mNode->getScale();
+ mCamera->setPosition(mPosition * scale);
+ mCamera->lookAt(mLookAt * scale);
+
+ mCamera->setNearClipDistance (0.01);
+ mCamera->setFarClipDistance (1000);
+
+ mTexture = Ogre::TextureManager::getSingleton().getByName (mName);
+ if (mTexture.isNull ())
+ mTexture = Ogre::TextureManager::getSingleton().createManual(mName,
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET);
+
+ mRenderTarget = mTexture->getBuffer()->getRenderTarget();
+ mRenderTarget->removeAllViewports ();
+ mViewport = mRenderTarget->addViewport(mCamera);
+ mViewport->setOverlaysEnabled(false);
+ mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0));
+ mViewport->setShadowsEnabled(false);
+ mRenderTarget->setActive(true);
+ mRenderTarget->setAutoUpdated (false);
+
+ onSetup ();
+ }
+
+ CharacterPreview::~CharacterPreview ()
+ {
+ if (mSceneMgr)
+ {
+ //Ogre::TextureManager::getSingleton().remove(mName);
+ mSceneMgr->destroyAllCameras();
+ delete mAnimation;
+ Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
+ }
+ }
+
+ void CharacterPreview::rebuild()
+ {
+ 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));
+
+ float scale=1.f;
+ MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale);
+ mNode->setScale(Ogre::Vector3(scale));
+
+ mCamera->setPosition(mPosition * mNode->getScale());
+ mCamera->lookAt(mLookAt * mNode->getScale());
+
+ onSetup();
+ }
+
+ // --------------------------------------------------------------------------------------------------
+
+
+ InventoryPreview::InventoryPreview(MWWorld::Ptr character)
+ : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0))
+ , mSelectionBuffer(NULL)
+ {
+ }
+
+ InventoryPreview::~InventoryPreview()
+ {
+ delete mSelectionBuffer;
+ }
+
+ void InventoryPreview::update(int sizeX, int sizeY)
+ {
+ MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter);
+ MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ std::string groupname;
+ if(iter == inv.end())
+ groupname = "inventoryhandtohand";
+ else
+ {
+ const std::string &type = iter->getTypeName();
+ if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
+ groupname = "inventoryweapononehand";
+ else if(type == typeid(ESM::Weapon).name())
+ {
+ MWWorld::LiveCellRef<ESM::Weapon> *ref = iter->get<ESM::Weapon>();
+
+ int type = ref->mBase->mData.mType;
+ if(type == ESM::Weapon::ShortBladeOneHand ||
+ type == ESM::Weapon::LongBladeOneHand ||
+ type == ESM::Weapon::BluntOneHand ||
+ type == ESM::Weapon::AxeOneHand)
+ groupname = "inventoryweapononehand";
+ else if(type == ESM::Weapon::LongBladeTwoHand ||
+ type == ESM::Weapon::BluntTwoClose ||
+ type == ESM::Weapon::AxeTwoHand)
+ groupname = "inventoryweapontwohand";
+ else if(type == ESM::Weapon::BluntTwoWide ||
+ type == ESM::Weapon::SpearTwoWide)
+ groupname = "inventoryweapontwowide";
+ else
+ groupname = "inventoryhandtohand";
+ }
+ else
+ groupname = "inventoryhandtohand";
+ }
+
+ 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())
+ {
+ if(!mAnimation->getInfo("torch"))
+ mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false,
+ 1.0f, "start", "stop", 0.0f, ~0ul);
+ }
+ else if(mAnimation->getInfo("torch"))
+ mAnimation->disable("torch");
+
+ mAnimation->updateParts(true);
+ mAnimation->runAnimation(0.0f);
+
+ mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024)));
+
+ mNode->setOrientation (Ogre::Quaternion::IDENTITY);
+
+ mRenderTarget->update();
+
+ mSelectionBuffer->update();
+ }
+
+ int InventoryPreview::getSlotSelected (int posX, int posY)
+ {
+ return mSelectionBuffer->getSelected (posX, posY);
+ }
+
+ void InventoryPreview::onSetup ()
+ {
+ delete mSelectionBuffer;
+ mSelectionBuffer = new OEngine::Render::SelectionBuffer(mCamera, 512, 1024, 0);
+
+ mAnimation->showWeapons(true);
+
+ mCurrentAnimGroup = "inventoryhandtohand";
+ mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
+ }
+
+ // --------------------------------------------------------------------------------------------------
+
+ RaceSelectionPreview::RaceSelectionPreview()
+ : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayer().getPlayer(),
+ 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0))
+ , mRef(&mBase)
+ {
+ mBase = *mCharacter.get<ESM::NPC>()->mBase;
+ mCharacter = MWWorld::Ptr(&mRef, mCharacter.getCell());
+ }
+
+ void RaceSelectionPreview::update(float angle)
+ {
+ mAnimation->runAnimation(0.0f);
+ mNode->roll(Ogre::Radian(angle), Ogre::SceneNode::TS_LOCAL);
+
+ updateCamera();
+ }
+
+ void RaceSelectionPreview::render()
+ {
+ mRenderTarget->update();
+ }
+
+ void RaceSelectionPreview::setPrototype(const ESM::NPC &proto)
+ {
+ mBase = proto;
+ mBase.mId = "player";
+ rebuild();
+ update(0);
+ }
+
+ void RaceSelectionPreview::onSetup ()
+ {
+ mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
+
+ updateCamera();
+ }
+
+ void RaceSelectionPreview::updateCamera()
+ {
+ Ogre::Vector3 scale = mNode->getScale();
+ Ogre::Vector3 headOffset = mAnimation->getNode("Bip01 Head")->_getDerivedPosition();
+ headOffset = mNode->convertLocalToWorldPosition(headOffset);
+
+ mCamera->setPosition(headOffset + mPosition * scale);
+ mCamera->lookAt(headOffset + mPosition*Ogre::Vector3(0,1,0) * scale);
+ }
+}
diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp
new file mode 100644
index 0000000000..b2dfc96795
--- /dev/null
+++ b/apps/openmw/mwrender/characterpreview.hpp
@@ -0,0 +1,109 @@
+#ifndef MWRENDER_CHARACTERPREVIEW_H
+#define MWRENDER_CHARACTERPREVIEW_H
+
+#include <OgreRenderTarget.h>
+#include <OgreMaterialManager.h>
+
+#include <components/esm/loadnpc.hpp>
+
+#include "externalrendering.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+namespace OEngine
+{
+namespace Render
+{
+class SelectionBuffer;
+}
+}
+
+namespace MWRender
+{
+
+ class NpcAnimation;
+
+ class CharacterPreview
+ {
+ public:
+ CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name,
+ Ogre::Vector3 position, Ogre::Vector3 lookAt);
+ virtual ~CharacterPreview();
+
+ virtual void setup ();
+ virtual void onSetup();
+
+ virtual void rebuild();
+
+ protected:
+ virtual bool renderHeadOnly() { return false; }
+
+ Ogre::TexturePtr mTexture;
+ Ogre::RenderTarget* mRenderTarget;
+ Ogre::Viewport* mViewport;
+
+ Ogre::Camera* mCamera;
+
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::SceneNode* mNode;
+
+ Ogre::Vector3 mPosition;
+ Ogre::Vector3 mLookAt;
+
+ MWWorld::Ptr mCharacter;
+
+ MWRender::NpcAnimation* mAnimation;
+ std::string mCurrentAnimGroup;
+
+ std::string mName;
+
+ int mSizeX;
+ int mSizeY;
+ };
+
+ class InventoryPreview : public CharacterPreview
+ {
+ public:
+
+ InventoryPreview(MWWorld::Ptr character);
+ virtual ~InventoryPreview();
+ virtual void onSetup();
+
+ void update(int sizeX, int sizeY);
+
+ int getSlotSelected(int posX, int posY);
+
+ private:
+
+ OEngine::Render::SelectionBuffer* mSelectionBuffer;
+ };
+
+ class RaceSelectionPreview : public CharacterPreview
+ {
+ ESM::NPC mBase;
+ MWWorld::LiveCellRef<ESM::NPC> mRef;
+
+ protected:
+
+ virtual bool renderHeadOnly() { return true; }
+
+ void updateCamera();
+
+ public:
+ RaceSelectionPreview();
+
+ virtual void onSetup();
+ void render();
+
+ void update(float angle);
+
+ const ESM::NPC &getPrototype() const {
+ return mBase;
+ }
+
+ void setPrototype(const ESM::NPC &proto);
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp
new file mode 100644
index 0000000000..b1c98a3067
--- /dev/null
+++ b/apps/openmw/mwrender/compositors.cpp
@@ -0,0 +1,108 @@
+#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
new file mode 100644
index 0000000000..e5dd7503ce
--- /dev/null
+++ b/apps/openmw/mwrender/compositors.hpp
@@ -0,0 +1,64 @@
+#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/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp
new file mode 100644
index 0000000000..c3ad512ddd
--- /dev/null
+++ b/apps/openmw/mwrender/creatureanimation.cpp
@@ -0,0 +1,33 @@
+#include "creatureanimation.hpp"
+
+#include "renderconst.hpp"
+
+#include "../mwbase/world.hpp"
+
+namespace MWRender
+{
+
+CreatureAnimation::~CreatureAnimation()
+{
+}
+
+CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
+ : Animation(ptr, ptr.getRefData().getBaseNode())
+{
+ MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
+
+ assert (ref->mBase != NULL);
+ if(!ref->mBase->mModel.empty())
+ {
+ std::string model = "meshes\\"+ref->mBase->mModel;
+
+ setObjectRoot(model, false);
+ setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
+
+ if((ref->mBase->mFlags&ESM::Creature::Biped))
+ addAnimSource("meshes\\base_anim.nif");
+ addAnimSource(model);
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp
new file mode 100644
index 0000000000..0c277d1985
--- /dev/null
+++ b/apps/openmw/mwrender/creatureanimation.hpp
@@ -0,0 +1,21 @@
+#ifndef _GAME_RENDER_CREATUREANIMATION_H
+#define _GAME_RENDER_CREATUREANIMATION_H
+
+#include "animation.hpp"
+
+namespace MWWorld
+{
+ class Ptr;
+}
+
+namespace MWRender
+{
+ class CreatureAnimation : public Animation
+ {
+ public:
+ CreatureAnimation(const MWWorld::Ptr& ptr);
+ virtual ~CreatureAnimation();
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp
new file mode 100644
index 0000000000..b318c2d569
--- /dev/null
+++ b/apps/openmw/mwrender/debugging.cpp
@@ -0,0 +1,296 @@
+#include "debugging.hpp"
+
+#include <cassert>
+
+#include <OgreNode.h>
+#include <OgreSceneManager.h>
+#include <OgreMaterial.h>
+#include <OgreMaterialManager.h>
+#include <OgreManualObject.h>
+
+#include <openengine/bullet/physic.hpp>
+
+#include <components/esm/loadstat.hpp>
+#include <components/esm/loadpgrd.hpp>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone
+#include "../mwbase/environment.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+#include "renderconst.hpp"
+
+using namespace Ogre;
+
+namespace MWRender
+{
+
+static const std::string PATHGRID_POINT_MATERIAL = "pathgridPointMaterial";
+static const std::string PATHGRID_LINE_MATERIAL = "pathgridLineMaterial";
+static const std::string DEBUGGING_GROUP = "debugging";
+static const int POINT_MESH_BASE = 35;
+
+void Debugging::createGridMaterials()
+{
+ if (mGridMatsCreated) return;
+
+ if (MaterialManager::getSingleton().getByName(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP).isNull())
+ {
+ MaterialPtr lineMatPtr = MaterialManager::getSingleton().create(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP);
+ lineMatPtr->setReceiveShadows(false);
+ lineMatPtr->getTechnique(0)->setLightingEnabled(true);
+ lineMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,1,0,0);
+ lineMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,1,0);
+ lineMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,1,0);
+ }
+
+ if (MaterialManager::getSingleton().getByName(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP).isNull())
+ {
+ MaterialPtr pointMatPtr = MaterialManager::getSingleton().create(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP);
+ pointMatPtr->setReceiveShadows(false);
+ pointMatPtr->getTechnique(0)->setLightingEnabled(true);
+ pointMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,0,0,0);
+ pointMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,0,0);
+ pointMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,0,0);
+ }
+ mGridMatsCreated = true;
+}
+
+void Debugging::destroyGridMaterials()
+{
+ if (mGridMatsCreated)
+ {
+ MaterialManager::getSingleton().remove(PATHGRID_POINT_MATERIAL);
+ MaterialManager::getSingleton().remove(PATHGRID_LINE_MATERIAL);
+ mGridMatsCreated = false;
+ }
+}
+
+ManualObject *Debugging::createPathgridLines(const ESM::Pathgrid *pathgrid)
+{
+ ManualObject *result = mSceneMgr->createManualObject();
+
+ result->begin(PATHGRID_LINE_MATERIAL, RenderOperation::OT_LINE_LIST);
+ for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->mEdges.begin();
+ it != pathgrid->mEdges.end();
+ ++it)
+ {
+ const ESM::Pathgrid::Edge &edge = *it;
+ const ESM::Pathgrid::Point &p1 = pathgrid->mPoints[edge.mV0], &p2 = pathgrid->mPoints[edge.mV1];
+ Vector3 direction = (Vector3(p2.mX, p2.mY, p2.mZ) - Vector3(p1.mX, p1.mY, p1.mZ));
+ Vector3 lineDisplacement = direction.crossProduct(Vector3::UNIT_Z).normalisedCopy();
+ lineDisplacement = lineDisplacement * POINT_MESH_BASE +
+ Vector3(0, 0, 10); // move lines up a little, so they will be less covered by meshes/landscape
+ result->position(Vector3(p1.mX, p1.mY, p1.mZ) + lineDisplacement);
+ result->position(Vector3(p2.mX, p2.mY, p2.mZ) + lineDisplacement);
+ }
+ result->end();
+
+ result->setVisibilityFlags (RV_Debug);
+
+ return result;
+}
+
+ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid)
+{
+ ManualObject *result = mSceneMgr->createManualObject();
+ const float height = POINT_MESH_BASE * sqrtf(2);
+
+ result->begin(PATHGRID_POINT_MATERIAL, RenderOperation::OT_TRIANGLE_STRIP);
+
+ bool first = true;
+ uint32 startIndex = 0;
+ for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin();
+ it != pathgrid->mPoints.end();
+ it++, startIndex += 6)
+ {
+ Vector3 pointPos(it->mX, it->mY, it->mZ);
+
+ if (!first)
+ {
+ // degenerate triangle from previous octahedron
+ result->index(startIndex - 4); // 2nd point of previous octahedron
+ result->index(startIndex); // start point of current octahedron
+ }
+
+ result->position(pointPos + Vector3(0, 0, height)); // 0
+ result->position(pointPos + Vector3(-POINT_MESH_BASE, -POINT_MESH_BASE, 0)); // 1
+ result->position(pointPos + Vector3(POINT_MESH_BASE, -POINT_MESH_BASE, 0)); // 2
+ result->position(pointPos + Vector3(POINT_MESH_BASE, POINT_MESH_BASE, 0)); // 3
+ result->position(pointPos + Vector3(-POINT_MESH_BASE, POINT_MESH_BASE, 0)); // 4
+ result->position(pointPos + Vector3(0, 0, -height)); // 5
+
+ result->index(startIndex + 0);
+ result->index(startIndex + 1);
+ result->index(startIndex + 2);
+ result->index(startIndex + 5);
+ result->index(startIndex + 3);
+ result->index(startIndex + 4);
+ // degenerates
+ result->index(startIndex + 4);
+ result->index(startIndex + 5);
+ result->index(startIndex + 5);
+ // end degenerates
+ result->index(startIndex + 1);
+ result->index(startIndex + 4);
+ result->index(startIndex + 0);
+ result->index(startIndex + 3);
+ result->index(startIndex + 2);
+
+ first = false;
+ }
+
+ result->end();
+
+ result->setVisibilityFlags (RV_Debug);
+
+ return result;
+}
+
+Debugging::Debugging(SceneNode *root, OEngine::Physic::PhysicEngine *engine) :
+ mRootNode(root), mEngine(engine),
+ mSceneMgr(root->getCreator()),
+ mPathgridEnabled(false),
+ mInteriorPathgridNode(NULL), mPathGridRoot(NULL),
+ mGridMatsCreated(false)
+{
+ ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP);
+}
+
+Debugging::~Debugging()
+{
+ if (mPathgridEnabled)
+ {
+ togglePathgrid();
+ }
+
+ ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP);
+}
+
+
+bool Debugging::toggleRenderMode (int mode){
+ switch (mode)
+ {
+ case MWBase::World::Render_CollisionDebug:
+
+ return mEngine->toggleDebugRendering();
+
+ case MWBase::World::Render_Pathgrid:
+ togglePathgrid();
+ return mPathgridEnabled;
+ }
+
+ return false;
+}
+
+void Debugging::cellAdded(MWWorld::Ptr::CellStore *store)
+{
+ mActiveCells.push_back(store);
+ if (mPathgridEnabled)
+ enableCellPathgrid(store);
+}
+
+void Debugging::cellRemoved(MWWorld::Ptr::CellStore *store)
+{
+ mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end());
+ if (mPathgridEnabled)
+ disableCellPathgrid(store);
+}
+
+void Debugging::togglePathgrid()
+{
+ mPathgridEnabled = !mPathgridEnabled;
+ if (mPathgridEnabled)
+ {
+ createGridMaterials();
+
+ // add path grid meshes to already loaded cells
+ mPathGridRoot = mRootNode->createChildSceneNode();
+ for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it)
+ {
+ enableCellPathgrid(*it);
+ }
+ }
+ else
+ {
+ // remove path grid meshes from already loaded cells
+ for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it)
+ {
+ disableCellPathgrid(*it);
+ }
+ mPathGridRoot->removeAndDestroyAllChildren();
+ mSceneMgr->destroySceneNode(mPathGridRoot);
+ mPathGridRoot = NULL;
+ destroyGridMaterials();
+ }
+}
+
+void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store)
+{
+ const ESM::Pathgrid *pathgrid =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*store->mCell);
+ if (!pathgrid) return;
+
+ Vector3 cellPathGridPos(0, 0, 0);
+ if (store->mCell->isExterior())
+ {
+ cellPathGridPos.x = store->mCell->mData.mX * ESM::Land::REAL_SIZE;
+ cellPathGridPos.y = store->mCell->mData.mY * ESM::Land::REAL_SIZE;
+ }
+ SceneNode *cellPathGrid = mPathGridRoot->createChildSceneNode(cellPathGridPos);
+ cellPathGrid->attachObject(createPathgridLines(pathgrid));
+ cellPathGrid->attachObject(createPathgridPoints(pathgrid));
+
+ if (store->mCell->isExterior())
+ {
+ mExteriorPathgridNodes[std::make_pair(store->mCell->getGridX(), store->mCell->getGridY())] = cellPathGrid;
+ }
+ else
+ {
+ assert(mInteriorPathgridNode == NULL);
+ mInteriorPathgridNode = cellPathGrid;
+ }
+}
+
+void Debugging::disableCellPathgrid(MWWorld::Ptr::CellStore *store)
+{
+ if (store->mCell->isExterior())
+ {
+ ExteriorPathgridNodes::iterator it =
+ mExteriorPathgridNodes.find(std::make_pair(store->mCell->getGridX(), store->mCell->getGridY()));
+ if (it != mExteriorPathgridNodes.end())
+ {
+ destroyCellPathgridNode(it->second);
+ mExteriorPathgridNodes.erase(it);
+ }
+ }
+ else
+ {
+ if (mInteriorPathgridNode)
+ {
+ destroyCellPathgridNode(mInteriorPathgridNode);
+ mInteriorPathgridNode = NULL;
+ }
+ }
+}
+
+void Debugging::destroyCellPathgridNode(SceneNode *node)
+{
+ mPathGridRoot->removeChild(node);
+ destroyAttachedObjects(node);
+ mSceneMgr->destroySceneNode(node);
+}
+
+void Debugging::destroyAttachedObjects(SceneNode *node)
+{
+ SceneNode::ObjectIterator objIt = node->getAttachedObjectIterator();
+ while (objIt.hasMoreElements())
+ {
+ MovableObject *mesh = static_cast<MovableObject *>(objIt.getNext());
+ mSceneMgr->destroyMovableObject(mesh);
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp
new file mode 100644
index 0000000000..4a574017ce
--- /dev/null
+++ b/apps/openmw/mwrender/debugging.hpp
@@ -0,0 +1,90 @@
+#ifndef _GAME_RENDER_MWSCENE_H
+#define _GAME_RENDER_MWSCENE_H
+
+#include <utility>
+#include <openengine/ogre/renderer.hpp>
+
+#include <vector>
+#include <string>
+
+namespace ESM
+{
+ struct Pathgrid;
+}
+
+namespace OEngine
+{
+ namespace Physic
+ {
+ class PhysicEngine;
+ }
+}
+
+namespace Ogre
+{
+ class Camera;
+ class Viewport;
+ class SceneManager;
+ class SceneNode;
+ class RaySceneQuery;
+ class Quaternion;
+ class Vector3;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace MWRender
+{
+ class Debugging
+ {
+ OEngine::Physic::PhysicEngine* mEngine;
+ Ogre::SceneManager *mSceneMgr;
+
+ // Path grid stuff
+ bool mPathgridEnabled;
+
+ void togglePathgrid();
+
+ typedef std::vector<MWWorld::CellStore *> CellList;
+ CellList mActiveCells;
+
+ Ogre::SceneNode *mRootNode;
+
+ Ogre::SceneNode *mPathGridRoot;
+
+ typedef std::map<std::pair<int,int>, Ogre::SceneNode *> ExteriorPathgridNodes;
+ ExteriorPathgridNodes mExteriorPathgridNodes;
+ Ogre::SceneNode *mInteriorPathgridNode;
+
+ void enableCellPathgrid(MWWorld::CellStore *store);
+ void disableCellPathgrid(MWWorld::CellStore *store);
+
+ // utility
+ void destroyCellPathgridNode(Ogre::SceneNode *node);
+ void destroyAttachedObjects(Ogre::SceneNode *node);
+
+ // materials
+ bool mGridMatsCreated;
+ void createGridMaterials();
+ void destroyGridMaterials();
+
+ // path grid meshes
+ Ogre::ManualObject *createPathgridLines(const ESM::Pathgrid *pathgrid);
+ Ogre::ManualObject *createPathgridPoints(const ESM::Pathgrid *pathgrid);
+ public:
+ Debugging(Ogre::SceneNode* root, OEngine::Physic::PhysicEngine *engine);
+ ~Debugging();
+ bool toggleRenderMode (int mode);
+
+ void cellAdded(MWWorld::CellStore* store);
+ void cellRemoved(MWWorld::CellStore* store);
+ };
+
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/externalrendering.hpp b/apps/openmw/mwrender/externalrendering.hpp
new file mode 100644
index 0000000000..33c9afd875
--- /dev/null
+++ b/apps/openmw/mwrender/externalrendering.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_RENDERING_EXTERNALRENDERING_H
+#define GAME_RENDERING_EXTERNALRENDERING_H
+
+namespace Ogre
+{
+ class SceneManager;
+}
+
+namespace MWRender
+{
+ /// \brief Base class for out of world rendering
+ class ExternalRendering
+ {
+ public:
+
+ virtual void setup (Ogre::SceneManager *sceneManager) = 0;
+
+ virtual ~ExternalRendering() {}
+ };
+}
+
+#endif
+
diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp
new file mode 100644
index 0000000000..120a83fae7
--- /dev/null
+++ b/apps/openmw/mwrender/globalmap.cpp
@@ -0,0 +1,235 @@
+#include "globalmap.hpp"
+
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <OgreImage.h>
+#include <OgreTextureManager.h>
+#include <OgreColourValue.h>
+#include <OgreHardwareVertexBuffer.h>
+#include <OgreRoot.h>
+#include <OgreHardwarePixelBuffer.h>
+
+#include <components/loadinglistener/loadinglistener.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+namespace MWRender
+{
+
+ GlobalMap::GlobalMap(const std::string &cacheDir)
+ : mCacheDir(cacheDir)
+ , mMinX(0), mMaxX(0)
+ , mMinY(0), mMaxY(0)
+ , mWidth(0)
+ , mHeight(0)
+ {
+ }
+
+
+ void GlobalMap::render (Loading::Listener* loadingListener)
+ {
+ Ogre::TexturePtr tex;
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ // get the size of the world
+ MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
+ for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
+ {
+ if (it->getGridX() < mMinX)
+ mMinX = it->getGridX();
+ if (it->getGridX() > mMaxX)
+ mMaxX = it->getGridX();
+ if (it->getGridY() < mMinY)
+ mMinY = it->getGridY();
+ if (it->getGridY() > mMaxY)
+ mMaxY = it->getGridY();
+ }
+
+ int cellSize = 24;
+ mWidth = cellSize*(mMaxX-mMinX+1);
+ mHeight = cellSize*(mMaxY-mMinY+1);
+
+ loadingListener->loadingOn();
+ loadingListener->setLabel("Creating map");
+ loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1));
+ loadingListener->setProgress(0);
+
+ mExploredBuffer.resize((mMaxX-mMinX+1) * (mMaxY-mMinY+1) * 4);
+
+ //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png"))
+ if (1)
+ {
+ std::vector<Ogre::uchar> data (mWidth * mHeight * 3);
+
+ for (int x = mMinX; x <= mMaxX; ++x)
+ {
+ for (int y = mMinY; y <= mMaxY; ++y)
+ {
+ ESM::Land* land = esmStore.get<ESM::Land>().search (x,y);
+
+ if (land)
+ {
+ if (!land->isDataLoaded(ESM::Land::DATA_VHGT))
+ {
+ land->loadData(ESM::Land::DATA_VHGT);
+ }
+ }
+
+ for (int cellY=0; cellY<cellSize; ++cellY)
+ {
+ for (int cellX=0; cellX<cellSize; ++cellX)
+ {
+ int vertexX = float(cellX)/float(cellSize) * ESM::Land::LAND_SIZE;
+ int vertexY = float(cellY)/float(cellSize) * ESM::Land::LAND_SIZE;
+
+
+ int texelX = (x-mMinX) * cellSize + cellX;
+ int texelY = (mHeight-1) - ((y-mMinY) * cellSize + cellY);
+
+ Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19);
+ Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13);
+ Ogre::ColourValue groundColour(0.254, 0.19, 0.13);
+ Ogre::ColourValue mountainColour(0.05, 0.05, 0.05);
+ Ogre::ColourValue hillColour(0.16, 0.12, 0.08);
+
+ unsigned char r,g,b;
+
+ if (land)
+ {
+ const float landHeight = land->mLandData->mHeights[vertexY * ESM::Land::LAND_SIZE + vertexX];
+ const float mountainHeight = 15000.f;
+ const float hillHeight = 2500.f;
+
+ if (landHeight >= 0)
+ {
+ if (landHeight >= hillHeight)
+ {
+ float factor = std::min(1.f, float(landHeight-hillHeight)/mountainHeight);
+ r = (hillColour.r * (1-factor) + mountainColour.r * factor) * 255;
+ g = (hillColour.g * (1-factor) + mountainColour.g * factor) * 255;
+ b = (hillColour.b * (1-factor) + mountainColour.b * factor) * 255;
+ }
+ else
+ {
+ float factor = std::min(1.f, float(landHeight)/hillHeight);
+ r = (groundColour.r * (1-factor) + hillColour.r * factor) * 255;
+ g = (groundColour.g * (1-factor) + hillColour.g * factor) * 255;
+ b = (groundColour.b * (1-factor) + hillColour.b * factor) * 255;
+ }
+ }
+ else
+ {
+ if (landHeight >= -100)
+ {
+ float factor = std::min(1.f, -1*landHeight/100.f);
+ r = (((waterShallowColour+groundColour)/2).r * (1-factor) + waterShallowColour.r * factor) * 255;
+ g = (((waterShallowColour+groundColour)/2).g * (1-factor) + waterShallowColour.g * factor) * 255;
+ b = (((waterShallowColour+groundColour)/2).b * (1-factor) + waterShallowColour.b * factor) * 255;
+ }
+ else
+ {
+ float factor = std::min(1.f, -1*(landHeight-100)/1000.f);
+ r = (waterShallowColour.r * (1-factor) + waterDeepColour.r * factor) * 255;
+ g = (waterShallowColour.g * (1-factor) + waterDeepColour.g * factor) * 255;
+ b = (waterShallowColour.b * (1-factor) + waterDeepColour.b * factor) * 255;
+ }
+ }
+
+ }
+ else
+ {
+ r = waterDeepColour.r * 255;
+ g = waterDeepColour.g * 255;
+ b = waterDeepColour.b * 255;
+ }
+
+ data[texelY * mWidth * 3 + texelX * 3] = r;
+ data[texelY * mWidth * 3 + texelX * 3+1] = g;
+ data[texelY * mWidth * 3 + texelX * 3+2] = b;
+ }
+ }
+ loadingListener->increaseProgress(1);
+ }
+ }
+
+ Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
+
+ tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC);
+ tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8);
+ }
+ else
+ tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png");
+
+ tex->load();
+
+
+
+
+ mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC_WRITE_ONLY);
+
+
+ std::vector<Ogre::uint32> buffer;
+ buffer.resize(mWidth * mHeight);
+
+ // initialize to (0, 0, 0, 0)
+ for (int p=0; p<mWidth * mHeight; ++p)
+ buffer[p] = 0;
+
+ memcpy(mOverlayTexture->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], mWidth*mHeight*4);
+ mOverlayTexture->getBuffer()->unlock();
+
+ loadingListener->loadingOff();
+ }
+
+ void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY)
+ {
+ imageX = float(x / 8192.f - mMinX) / (mMaxX - mMinX + 1);
+
+ imageY = 1.f-float(z / 8192.f - mMinY) / (mMaxY - mMinY + 1);
+ }
+
+ void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY)
+ {
+ imageX = float(x - mMinX) / (mMaxX - mMinX + 1);
+
+ // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is
+ imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1);
+ }
+
+ void GlobalMap::exploreCell(int cellX, int cellY)
+ {
+ float originX = (cellX - mMinX) * 24;
+ // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is
+ float originY = mHeight - (cellY+1 - mMinY) * 24;
+
+ if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY)
+ return;
+
+ Ogre::TexturePtr localMapTexture = Ogre::TextureManager::getSingleton().getByName("Cell_"
+ + boost::lexical_cast<std::string>(cellX) + "_" + boost::lexical_cast<std::string>(cellY));
+
+ // mipmap version - can't get ogre to generate automips..
+ /*if (!localMapTexture.isNull())
+ {
+ assert(localMapTexture->getBuffer(0, 4)->getWidth() == 64); // 1024 / 2^4
+
+ mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(0, 4), Ogre::Image::Box(0,0,64, 64),
+ Ogre::Image::Box(originX,originY,originX+24,originY+24));
+ }*/
+
+ if (!localMapTexture.isNull())
+ {
+
+ mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512),
+ Ogre::Image::Box(originX,originY,originX+24,originY+24));
+ }
+ }
+}
diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp
new file mode 100644
index 0000000000..dd3787b62b
--- /dev/null
+++ b/apps/openmw/mwrender/globalmap.hpp
@@ -0,0 +1,51 @@
+#ifndef _GAME_RENDER_GLOBALMAP_H
+#define _GAME_RENDER_GLOBALMAP_H
+
+#include <string>
+
+#include <OgreTexture.h>
+
+namespace Loading
+{
+ class Listener;
+}
+
+namespace MWRender
+{
+
+ class GlobalMap
+ {
+ public:
+ GlobalMap(const std::string& cacheDir);
+
+ void render(Loading::Listener* loadingListener);
+
+ int getWidth() { return mWidth; }
+ int getHeight() { return mHeight; }
+
+ void worldPosToImageSpace(float x, float z, float& imageX, float& imageY);
+ ///< @param x x ogre coords
+ /// @param z z ogre coords
+
+ void cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY);
+
+ void exploreCell (int cellX, int cellY);
+
+ private:
+ std::string mCacheDir;
+
+ std::vector< std::pair<int,int> > mExploredCells;
+
+ Ogre::TexturePtr mOverlayTexture;
+ std::vector<Ogre::uchar> mExploredBuffer;
+
+ int mWidth;
+ int mHeight;
+
+ int mMinX, mMaxX, mMinY, mMaxY;
+ };
+
+}
+
+#endif
+
diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp
new file mode 100644
index 0000000000..5f41289788
--- /dev/null
+++ b/apps/openmw/mwrender/localmap.cpp
@@ -0,0 +1,426 @@
+#include "localmap.hpp"
+
+#include <OgreMaterialManager.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreSceneManager.h>
+#include <OgreSceneNode.h>
+#include <OgreCamera.h>
+#include <OgreTextureManager.h>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "renderconst.hpp"
+#include "renderingmanager.hpp"
+
+using namespace MWRender;
+using namespace Ogre;
+
+LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManager* rendering) :
+ mInterior(false), mCellX(0), mCellY(0)
+{
+ mRendering = rend;
+ mRenderingManager = rendering;
+
+ mCameraPosNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
+ mCameraRotNode = mCameraPosNode->createChildSceneNode();
+ mCameraNode = mCameraRotNode->createChildSceneNode();
+
+ mCellCamera = mRendering->getScene()->createCamera("CellCamera");
+ mCellCamera->setProjectionType(PT_ORTHOGRAPHIC);
+
+ mCameraNode->attachObject(mCellCamera);
+
+ mLight = mRendering->getScene()->createLight();
+ mLight->setType (Ogre::Light::LT_DIRECTIONAL);
+ mLight->setDirection (Ogre::Vector3(0.3, 0.3, -0.7));
+ mLight->setVisible (false);
+ mLight->setDiffuseColour (ColourValue(0.7,0.7,0.7));
+}
+
+LocalMap::~LocalMap()
+{
+ deleteBuffers();
+}
+
+const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle)
+{
+ return Vector2( Math::Cos(angle) * (p.x - c.x) - Math::Sin(angle) * (p.y - c.y) + c.x,
+ Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y);
+}
+
+void LocalMap::deleteBuffers()
+{
+ mBuffers.clear();
+}
+
+void LocalMap::saveTexture(const std::string& texname, const std::string& filename)
+{
+ TexturePtr tex = TextureManager::getSingleton().getByName(texname);
+ if (tex.isNull()) return;
+ HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer();
+ readbuffer->lock(HardwareBuffer::HBL_NORMAL );
+ const PixelBox &readrefpb = readbuffer->getCurrentLock();
+ uchar *readrefdata = static_cast<uchar*>(readrefpb.data);
+
+ Image img;
+ img = img.loadDynamicImage (readrefdata, tex->getWidth(),
+ tex->getHeight(), tex->getFormat());
+ img.save("./" + filename);
+
+ readbuffer->unlock();
+}
+
+std::string LocalMap::coordStr(const int x, const int y)
+{
+ return StringConverter::toString(x) + "_" + StringConverter::toString(y);
+}
+
+void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
+{
+ if (!mInterior)
+ {
+ /*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog",
+ "Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/
+ }
+ else
+ {
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
+ Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y);
+ Vector2 length = max-min;
+
+ // divide into segments
+ const int segsX = std::ceil( length.x / sSize );
+ const int segsY = std::ceil( length.y / sSize );
+
+ for (int x=0; x<segsX; ++x)
+ {
+ for (int y=0; y<segsY; ++y)
+ {
+ /*saveTexture(
+ mInteriorName + "_" + coordStr(x,y) + "_fog",
+ mInteriorName + "_" + coordStr(x,y) + "_fog.png");*/
+ }
+ }
+ }
+}
+
+void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax)
+{
+ mInterior = false;
+
+ mCameraRotNode->setOrientation(Quaternion::IDENTITY);
+ mCellCamera->setOrientation(Quaternion(Ogre::Math::Cos(Ogre::Degree(0)/2.f), 0, 0, -Ogre::Math::Sin(Ogre::Degree(0)/2.f)));
+
+ int x = cell->mCell->getGridX();
+ int y = cell->mCell->getGridY();
+
+ std::string name = "Cell_"+coordStr(x, y);
+
+ mCameraPosNode->setPosition(Vector3(0,0,0));
+
+ render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
+}
+
+void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,
+ AxisAlignedBox bounds)
+{
+ // if we're in an empty cell, don't bother rendering anything
+ if (bounds.isNull ())
+ return;
+
+ mInterior = true;
+ mBounds = bounds;
+
+ float zMin = mBounds.getMinimum().z;
+ float zMax = mBounds.getMaximum().z;
+
+ const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell);
+ Radian angle = Ogre::Math::ATan2 (north.x, north.y);
+ mAngle = angle.valueRadians();
+
+ mCellCamera->setOrientation(Quaternion::IDENTITY);
+ mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f)));
+
+ // rotate the cell and merge the rotated corners to the bounding box
+ Vector2 _center(bounds.getCenter().x, bounds.getCenter().y);
+ Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM);
+ Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM);
+ Vector3 _c3 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_TOP);
+ Vector3 _c4 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_TOP);
+
+ Vector2 c1(_c1.x, _c1.y);
+ Vector2 c2(_c2.x, _c2.y);
+ Vector2 c3(_c3.x, _c3.y);
+ Vector2 c4(_c4.x, _c4.y);
+ c1 = rotatePoint(c1, _center, mAngle);
+ c2 = rotatePoint(c2, _center, mAngle);
+ c3 = rotatePoint(c3, _center, mAngle);
+ c4 = rotatePoint(c4, _center, mAngle);
+ mBounds.merge(Vector3(c1.x, c1.y, 0));
+ mBounds.merge(Vector3(c2.x, c2.y, 0));
+ mBounds.merge(Vector3(c3.x, c3.y, 0));
+ mBounds.merge(Vector3(c4.x, c4.y, 0));
+
+ // apply a little padding
+ mBounds.setMinimum (mBounds.getMinimum() - Vector3(500,500,0));
+ mBounds.setMaximum (mBounds.getMaximum() + Vector3(500,500,0));
+
+ Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y);
+
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
+ Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y);
+
+ Vector2 length = max-min;
+
+ mCameraPosNode->setPosition(Vector3(center.x, center.y, 0));
+
+ // divide into segments
+ const int segsX = std::ceil( length.x / sSize );
+ const int segsY = std::ceil( length.y / sSize );
+
+ mInteriorName = cell->mCell->mName;
+
+ for (int x=0; x<segsX; ++x)
+ {
+ for (int y=0; y<segsY; ++y)
+ {
+ Vector2 start = min + Vector2(sSize*x,sSize*y);
+ Vector2 newcenter = start + 4096;
+
+ render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize,
+ cell->mCell->mName + "_" + coordStr(x,y));
+ }
+ }
+}
+
+void LocalMap::render(const float x, const float y,
+ const float zlow, const float zhigh,
+ const float xw, const float yw, const std::string& texture)
+{
+ mCellCamera->setFarClipDistance( (zhigh-zlow) + 2000 );
+ mCellCamera->setNearClipDistance(50);
+
+ mCellCamera->setOrthoWindow(xw, yw);
+ mCameraNode->setPosition(Vector3(x, y, zhigh+1000));
+
+ // disable fog (only necessary for fixed function, the shader based
+ // materials already do this through local_map material configuration)
+ float oldFogStart = mRendering->getScene()->getFogStart();
+ float oldFogEnd = mRendering->getScene()->getFogEnd();
+ Ogre::ColourValue oldFogColour = mRendering->getScene()->getFogColour();
+ mRendering->getScene()->setFog(FOG_NONE);
+
+ // set up lighting
+ Ogre::ColourValue oldAmbient = mRendering->getScene()->getAmbientLight();
+ mRendering->getScene()->setAmbientLight(Ogre::ColourValue(0.3, 0.3, 0.3));
+ mRenderingManager->disableLights(true);
+ mLight->setVisible(true);
+
+ TexturePtr tex;
+ // try loading from memory
+ 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)
+ {
+ buffer[p] = (255 << 24);
+ }
+
+ memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
+ tex2->getBuffer()->unlock();
+
+ mBuffers[texture] = buffer;
+
+ // save to cache for next time
+ //rtt->writeContentsToFile("./" + texture + ".jpg");
+ }
+ }
+ mRenderingManager->enableLights(true);
+ mLight->setVisible(false);
+
+ // re-enable fog
+ mRendering->getScene()->setFog(FOG_LINEAR, oldFogColour, 0, oldFogStart, oldFogEnd);
+ mRendering->getScene()->setAmbientLight(oldAmbient);
+}
+
+void LocalMap::getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y)
+{
+ pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), mAngle);
+
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
+
+ x = std::ceil((pos.x - min.x)/sSize)-1;
+ y = std::ceil((pos.y - min.y)/sSize)-1;
+
+ nX = (pos.x - min.x - sSize*x)/sSize;
+ nY = 1.0-(pos.y - min.y - sSize*y)/sSize;
+}
+
+bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior)
+{
+ std::string texName = (interior ? mInteriorName + "_" : "Cell_") + coordStr(x, y);
+
+ if (mBuffers.find(texName) == mBuffers.end())
+ return false;
+
+ int texU = (sFogOfWarResolution-1) * nX;
+ int texV = (sFogOfWarResolution-1) * nY;
+
+ Ogre::uint32 clr = mBuffers[texName][texV * sFogOfWarResolution + texU];
+ uint8 alpha = (clr >> 24);
+ return alpha < 200;
+}
+
+void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation)
+{
+ if (sFogOfWarSkip != 0)
+ {
+ static int count=0;
+ if (++count % sFogOfWarSkip != 0)
+ return;
+ }
+
+ // retrieve the x,y grid coordinates the player is in
+ int x,y;
+ float u,v;
+
+ Vector2 pos(position.x, position.y);
+
+ if (mInterior)
+ getInteriorMapPosition(pos, u,v, x,y);
+
+ Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis();
+
+ Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y);
+
+ if (!mInterior)
+ {
+ x = std::ceil(pos.x / sSize)-1;
+ y = std::ceil(pos.y / sSize)-1;
+ mCellX = x;
+ mCellY = y;
+ }
+ else
+ {
+ MWBase::Environment::get().getWindowManager()->setInteriorMapTexture(x,y);
+ }
+
+ // convert from world coordinates to texture UV coordinates
+ std::string texBaseName;
+ if (!mInterior)
+ {
+ u = std::abs((pos.x - (sSize*x))/sSize);
+ v = 1.0-std::abs((pos.y - (sSize*y))/sSize);
+ texBaseName = "Cell_";
+ }
+ else
+ {
+ texBaseName = mInteriorName + "_";
+ }
+
+ MWBase::Environment::get().getWindowManager()->setPlayerPos(u, v);
+ MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, playerdirection.y);
+
+ // explore radius (squared)
+ const float exploreRadius = (mInterior ? 0.1 : 0.3) * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1
+ const float sqrExploreRadius = Math::Sqr(exploreRadius);
+ const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space)
+
+ // change the affected fog of war textures (in a 3x3 grid around the player)
+ for (int mx = -1; mx<2; ++mx)
+ {
+ for (int my = -1; my<2; ++my)
+ {
+
+ // is this texture affected at all?
+ bool affected = false;
+ if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid
+ affected = true;
+ else
+ {
+ bool affectsX = (mx > 0)? (u + exploreRadiusUV > 1) : (u - exploreRadiusUV < 0);
+ bool affectsY = (my > 0)? (v + exploreRadiusUV > 1) : (v - exploreRadiusUV < 0);
+ affected = (affectsX && (my == 0)) || (affectsY && mx == 0) || (affectsX && affectsY);
+ }
+
+ if (!affected)
+ continue;
+
+ std::string texName = texBaseName + coordStr(x+mx,y+my*-1);
+
+ TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog");
+ if (!tex.isNull())
+ {
+ // get its buffer
+ if (mBuffers.find(texName) == mBuffers.end()) return;
+ int i=0;
+ for (int texV = 0; texV<sFogOfWarResolution; ++texV)
+ {
+ for (int texU = 0; texU<sFogOfWarResolution; ++texU)
+ {
+ float sqrDist = Math::Sqr((texU + mx*(sFogOfWarResolution-1)) - u*(sFogOfWarResolution-1))
+ + Math::Sqr((texV + my*(sFogOfWarResolution-1)) - v*(sFogOfWarResolution-1));
+ uint32 clr = mBuffers[texName][i];
+ uint8 alpha = (clr >> 24);
+ alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
+ mBuffers[texName][i] = (uint32) (alpha << 24);
+
+ ++i;
+ }
+ }
+
+ // copy to the texture
+ memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4);
+ tex->getBuffer()->unlock();
+ }
+ }
+ }
+}
diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp
new file mode 100644
index 0000000000..5384896407
--- /dev/null
+++ b/apps/openmw/mwrender/localmap.hpp
@@ -0,0 +1,125 @@
+#ifndef _GAME_RENDER_LOCALMAP_H
+#define _GAME_RENDER_LOCALMAP_H
+
+#include <openengine/ogre/renderer.hpp>
+
+#include <OgreAxisAlignedBox.h>
+#include <OgreColourValue.h>
+
+namespace MWWorld
+{
+ class CellStore;
+}
+
+namespace MWRender
+{
+ class RenderingManager;
+
+ ///
+ /// \brief Local map rendering
+ ///
+ class LocalMap
+ {
+ public:
+ LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering);
+ ~LocalMap();
+
+ /**
+ * Request the local map for an exterior cell.
+ * @remarks It will either be loaded from a disk cache,
+ * or rendered if it is not already cached.
+ * @param cell exterior cell
+ * @param zMin min height of objects or terrain in cell
+ * @param zMax max height of objects or terrain in cell
+ */
+ void requestMap (MWWorld::CellStore* cell, float zMin, float zMax);
+
+ /**
+ * Request the local map for an interior cell.
+ * @remarks It will either be loaded from a disk cache,
+ * or rendered if it is not already cached.
+ * @param cell interior cell
+ * @param bounds bounding box of the cell
+ */
+ void requestMap (MWWorld::CellStore* cell,
+ Ogre::AxisAlignedBox bounds);
+
+ /**
+ * Set the position & direction of the player.
+ * @remarks This is used to draw a "fog of war" effect
+ * to hide areas on the map the player has not discovered yet.
+ * @param position (OGRE coordinates)
+ * @param camera orientation (OGRE coordinates)
+ */
+ void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation);
+
+ /**
+ * Save the fog of war for the current cell to disk.
+ * @remarks This should be called before loading a
+ * new cell, as well as when the game is quit.
+ * @param current cell
+ */
+ void saveFogOfWar(MWWorld::CellStore* cell);
+
+ /**
+ * Get the interior map texture index and normalized position
+ * on this texture, given a world position (in ogre coordinates)
+ */
+ void getInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y);
+
+ /**
+ * Check if a given position is explored by the player (i.e. not obscured by fog of war)
+ */
+ bool isPositionExplored (float nX, float nY, int x, int y, bool interior);
+
+ private:
+ OEngine::Render::OgreRenderer* mRendering;
+ MWRender::RenderingManager* mRenderingManager;
+
+ // 1024*1024 pixels for a cell
+ static const int sMapResolution = 512;
+
+ // the dynamic texture is a bottleneck, so don't set this too high
+ static const int sFogOfWarResolution = 32;
+
+ // frames to skip before rendering fog of war
+ static const int sFogOfWarSkip = 2;
+
+ // size of a map segment (for exteriors, 1 cell)
+ static const int sSize = 8192;
+
+ Ogre::Camera* mCellCamera;
+ Ogre::SceneNode* mCameraNode;
+ Ogre::SceneNode* mCameraPosNode;
+ Ogre::SceneNode* mCameraRotNode;
+
+ // directional light from a fixed angle
+ Ogre::Light* mLight;
+
+ float mAngle;
+ const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle);
+
+ void render(const float x, const float y,
+ const float zlow, const float zhigh,
+ const float xw, const float yw,
+ const std::string& texture);
+
+ void saveTexture(const std::string& texname, const std::string& filename);
+
+ std::string coordStr(const int x, const int y);
+
+ // a buffer for the "fog of war" texture of the current cell.
+ // interior cells could be divided into multiple textures,
+ // so we store in a map.
+ std::map <std::string, std::vector<Ogre::uint32> > mBuffers;
+
+ void deleteBuffers();
+
+ bool mInterior;
+ int mCellX, mCellY;
+ Ogre::AxisAlignedBox mBounds;
+ std::string mInteriorName;
+ };
+
+}
+#endif
diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp
new file mode 100644
index 0000000000..13b5971e2a
--- /dev/null
+++ b/apps/openmw/mwrender/npcanimation.cpp
@@ -0,0 +1,605 @@
+#include "npcanimation.hpp"
+
+#include <OgreSceneManager.h>
+#include <OgreEntity.h>
+#include <OgreParticleSystem.h>
+#include <OgreSubEntity.h>
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "renderconst.hpp"
+#include "camera.hpp"
+
+
+namespace MWRender
+{
+
+static NpcAnimation::PartBoneMap createPartListMap()
+{
+ NpcAnimation::PartBoneMap result;
+ result.insert(std::make_pair(ESM::PRT_Head, "Head"));
+ result.insert(std::make_pair(ESM::PRT_Hair, "Head"));
+ result.insert(std::make_pair(ESM::PRT_Neck, "Neck"));
+ result.insert(std::make_pair(ESM::PRT_Cuirass, "Chest"));
+ result.insert(std::make_pair(ESM::PRT_Groin, "Groin"));
+ result.insert(std::make_pair(ESM::PRT_Skirt, "Groin"));
+ result.insert(std::make_pair(ESM::PRT_RHand, "Right Hand"));
+ result.insert(std::make_pair(ESM::PRT_LHand, "Left Hand"));
+ result.insert(std::make_pair(ESM::PRT_RWrist, "Right Wrist"));
+ result.insert(std::make_pair(ESM::PRT_LWrist, "Left Wrist"));
+ result.insert(std::make_pair(ESM::PRT_Shield, "Shield Bone"));
+ result.insert(std::make_pair(ESM::PRT_RForearm, "Right Forearm"));
+ result.insert(std::make_pair(ESM::PRT_LForearm, "Left Forearm"));
+ result.insert(std::make_pair(ESM::PRT_RUpperarm, "Right Upper Arm"));
+ result.insert(std::make_pair(ESM::PRT_LUpperarm, "Left Upper Arm"));
+ result.insert(std::make_pair(ESM::PRT_RFoot, "Right Foot"));
+ result.insert(std::make_pair(ESM::PRT_LFoot, "Left Foot"));
+ result.insert(std::make_pair(ESM::PRT_RAnkle, "Right Ankle"));
+ result.insert(std::make_pair(ESM::PRT_LAnkle, "Left Ankle"));
+ result.insert(std::make_pair(ESM::PRT_RKnee, "Right Knee"));
+ result.insert(std::make_pair(ESM::PRT_LKnee, "Left Knee"));
+ result.insert(std::make_pair(ESM::PRT_RLeg, "Right Upper Leg"));
+ result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg"));
+ result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle"));
+ result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle"));
+ result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone"));
+ result.insert(std::make_pair(ESM::PRT_Tail, "Tail"));
+ return result;
+}
+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]);
+}
+
+
+NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags, 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()),
+ mViewMode(viewMode),
+ mShowWeapons(false)
+{
+ mNpc = mPtr.get<ESM::NPC>()->mBase;
+
+ for(size_t i = 0;i < ESM::PRT_Count;i++)
+ {
+ mPartslots[i] = -1; //each slot is empty
+ mPartPriorities[i] = 0;
+ }
+
+ updateNpcBase();
+}
+
+void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
+{
+ assert(viewMode != VM_HeadOnly);
+ mViewMode = viewMode;
+ rebuild();
+}
+
+void NpcAnimation::rebuild()
+{
+ updateNpcBase();
+
+ MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
+}
+
+void NpcAnimation::updateNpcBase()
+{
+ clearAnimSources();
+
+ 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();
+
+ if(!isWerewolf)
+ {
+ mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHead)->mModel;
+ mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHair)->mModel;
+ }
+ else
+ {
+ mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
+ mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
+ }
+
+ bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
+ std::string smodel = (mViewMode != VM_FirstPerson) ?
+ (!isWerewolf ? !isBeast ? "meshes\\base_anim.nif"
+ : "meshes\\base_animkna.nif"
+ : "meshes\\wolf\\skin.nif") :
+ (!isWerewolf ? !isBeast ? "meshes\\base_anim.1st.nif"
+ : "meshes\\base_animkna.1st.nif"
+ : "meshes\\wolf\\skin.1st.nif");
+ setObjectRoot(smodel, true);
+
+ if(mViewMode != VM_FirstPerson)
+ {
+ addAnimSource(smodel);
+ if(!isWerewolf)
+ {
+ if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos)
+ addAnimSource("meshes\\argonian_swimkna.nif");
+ else if(!mNpc->isMale() && !isBeast)
+ addAnimSource("meshes\\base_anim_female.nif");
+ if(mNpc->mModel.length() > 0)
+ addAnimSource("meshes\\"+mNpc->mModel);
+ }
+ }
+ else
+ {
+ if(isWerewolf)
+ addAnimSource(smodel);
+ else
+ {
+ /* A bit counter-intuitive, but unlike third-person anims, it seems
+ * beast races get both base_anim.1st.nif and base_animkna.1st.nif.
+ */
+ addAnimSource("meshes\\base_anim.1st.nif");
+ if(isBeast)
+ addAnimSource("meshes\\base_animkna.1st.nif");
+ if(!mNpc->isMale() && !isBeast)
+ addAnimSource("meshes\\base_anim_female.1st.nif");
+ }
+ }
+
+ for(size_t i = 0;i < ESM::PRT_Count;i++)
+ removeIndividualPart((ESM::PartReferenceType)i);
+ updateParts(true);
+}
+
+void NpcAnimation::updateParts(bool forceupdate)
+{
+ 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 }
+ };
+ 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())
+ continue;
+
+ if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet)
+ removeIndividualPart(ESM::PRT_Hair);
+
+ int prio = 1;
+ 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);
+ }
+ 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);
+ }
+
+ if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe)
+ {
+ ESM::PartReferenceType parts[] = {
+ ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
+ ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee,
+ ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_RPauldron, ESM::PRT_LPauldron
+ };
+ size_t parts_size = sizeof(parts)/sizeof(parts[0]);
+ for(size_t p = 0;p < parts_size;++p)
+ reserveIndividualPart(parts[p], slotlist[i].mSlot, prio);
+ }
+ else if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Skirt)
+ {
+ reserveIndividualPart(ESM::PRT_Groin, slotlist[i].mSlot, prio);
+ reserveIndividualPart(ESM::PRT_RLeg, slotlist[i].mSlot, prio);
+ reserveIndividualPart(ESM::PRT_LLeg, slotlist[i].mSlot, prio);
+ }
+ }
+
+ if(mViewMode != VM_FirstPerson)
+ {
+ if(mPartPriorities[ESM::PRT_Head] < 1)
+ addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel);
+ if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1)
+ addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel);
+ }
+ if(mViewMode == VM_HeadOnly)
+ return;
+
+ if(mPartPriorities[ESM::PRT_Shield] < 1)
+ {
+ MWWorld::ContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ MWWorld::Ptr part;
+ if(store != inv.end() && (part=*store).getTypeName() == typeid(ESM::Light).name())
+ {
+ const ESM::Light *light = part.get<ESM::Light>()->mBase;
+ addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft,
+ 1, "meshes\\"+light->mModel);
+ addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], light);
+ }
+ }
+
+ showWeapons(mShowWeapons);
+
+ // 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 const int Flag_Female = 1<<0;
+ static const int Flag_FirstPerson = 1<<1;
+
+ bool isWerewolf = cls.getNpcStats(mPtr).isWerewolf();
+ int flags = (isWerewolf ? -1 : 0);
+ if(!mNpc->isMale())
+ flags |= Flag_Female;
+ if(mViewMode == VM_FirstPerson)
+ flags |= Flag_FirstPerson;
+
+ std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace));
+ std::pair<std::string, int> thisCombination = std::make_pair(race, flags);
+ if (sRaceMapping.find(thisCombination) == sRaceMapping.end())
+ {
+ typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType;
+ static BodyPartMapType sBodyPartMap;
+ if(sBodyPartMap.empty())
+ {
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg));
+ sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail));
+ }
+
+ std::vector<const ESM::BodyPart*> &parts = sRaceMapping[thisCombination];
+ parts.resize(ESM::PRT_Count, NULL);
+
+ 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)
+ {
+ if(isWerewolf)
+ break;
+ const ESM::BodyPart& bodypart = *it;
+ if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
+ continue;
+ if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
+ continue;
+
+ if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
+ continue;
+ if (!Misc::StringUtils::ciEqual(bodypart.mRace, mNpc->mRace))
+ continue;
+
+ bool firstPerson = (bodypart.mId.size() >= 3)
+ && bodypart.mId[bodypart.mId.size()-3] == '1'
+ && bodypart.mId[bodypart.mId.size()-2] == 's'
+ && bodypart.mId[bodypart.mId.size()-1] == 't';
+ if(firstPerson != (mViewMode == VM_FirstPerson))
+ {
+ if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand ||
+ bodypart.mData.mPart == ESM::BodyPart::MP_Wrist ||
+ bodypart.mData.mPart == ESM::BodyPart::MP_Forearm ||
+ bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm))
+ {
+ /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */
+ BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart));
+ while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart)
+ {
+ if(!parts[bIt->second])
+ parts[bIt->second] = &*it;
+ ++bIt;
+ }
+ }
+ continue;
+ }
+ BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart));
+ while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart)
+ {
+ parts[bIt->second] = &*it;
+ ++bIt;
+ }
+ }
+ }
+
+ const std::vector<const ESM::BodyPart*> &parts = sRaceMapping[thisCombination];
+ for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part)
+ {
+ if(mPartPriorities[part] < 1)
+ {
+ const ESM::BodyPart* bodypart = parts[part];
+ if(bodypart)
+ addOrReplaceIndividualPart((ESM::PartReferenceType)part, -1, 1,
+ "meshes\\"+bodypart->mModel);
+ }
+ }
+}
+
+class SetObjectGroup {
+ int mGroup;
+
+public:
+ SetObjectGroup(int group) : mGroup(group) { }
+
+ void operator()(Ogre::MovableObject *obj) const
+ {
+ obj->getUserObjectBindings().setUserAny(Ogre::Any(mGroup));
+ }
+};
+
+NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename)
+{
+ NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
+ setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha);
+
+ 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)
+ {
+ Ogre::AnimationStateSet *aset = objects.mSkelBase->getAllAnimationStates();
+ Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator();
+ while(asiter.hasMoreElements())
+ {
+ Ogre::AnimationState *state = asiter.getNext();
+ state->setEnabled(false);
+ state->setLoop(false);
+ }
+ Ogre::SkeletonInstance *skelinst = objects.mSkelBase->getSkeleton();
+ Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
+ while(boneiter.hasMoreElements())
+ boneiter.getNext()->setManuallyControlled(true);
+ }
+
+ return objects;
+}
+
+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();
+ if(mViewMode == VM_FirstPerson && mCamera)
+ {
+ float pitch = mCamera->getPitch();
+ Ogre::Node *node = baseinst->getBone("Bip01 Neck");
+ node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD);
+ }
+
+ 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++)
+ ctrl->update();
+
+ Ogre::Entity *ent = mObjectParts[i].mSkelBase;
+ if(!ent) continue;
+ updateSkeletonInstance(baseinst, ent->getSkeleton());
+ ent->getAllAnimationStates()->_notifyDirty();
+ }
+
+ return ret;
+}
+
+void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type)
+{
+ mPartPriorities[type] = 0;
+ mPartslots[type] = -1;
+
+ destroyObjectList(mInsert->getCreator(), mObjectParts[type]);
+}
+
+void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority)
+{
+ if(priority > mPartPriorities[type])
+ {
+ removeIndividualPart(type);
+ mPartPriorities[type] = priority;
+ mPartslots[type] = group;
+ }
+}
+
+void NpcAnimation::removePartGroup(int group)
+{
+ for(int i = 0; i < ESM::PRT_Count; i++)
+ {
+ if(mPartslots[i] == group)
+ removeIndividualPart((ESM::PartReferenceType)i);
+ }
+}
+
+bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh)
+{
+ if(priority <= mPartPriorities[type])
+ return false;
+
+ removeIndividualPart(type);
+ mPartslots[type] = group;
+ mPartPriorities[type] = priority;
+
+ mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type));
+ if(mObjectParts[type].mSkelBase)
+ {
+ Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton();
+ if(mObjectParts[type].mSkelBase->isParentTagPoint())
+ {
+ 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?
+ root->pitch(Ogre::Degree(-90.0f));
+ root->scale(offset->getScale());
+ root->setInitialState();
+ }
+ }
+
+ updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
+ }
+
+ // 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++)
+ {
+ if(ctrl->getSource().isNull())
+ ctrl->setSource(mNullAnimationValuePtr);
+ }
+
+ return true;
+}
+
+void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts)
+{
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
+
+ const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
+ std::vector<ESM::PartReference>::const_iterator part(parts.begin());
+ for(;part != parts.end();part++)
+ {
+ const ESM::BodyPart *bodypart = 0;
+ if(!mNpc->isMale() && !part->mFemale.empty())
+ {
+ bodypart = partStore.search(part->mFemale+ext);
+ if(!bodypart && mViewMode == VM_FirstPerson)
+ {
+ bodypart = partStore.search(part->mFemale);
+ if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm))
+ bodypart = NULL;
+ }
+ }
+ if(!bodypart && !part->mMale.empty())
+ {
+ bodypart = partStore.search(part->mMale+ext);
+ if(!bodypart && mViewMode == VM_FirstPerson)
+ {
+ bodypart = partStore.search(part->mMale);
+ if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
+ bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm))
+ bodypart = NULL;
+ }
+ }
+
+ if(bodypart)
+ addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel);
+ else
+ reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority);
+ }
+}
+
+void NpcAnimation::showWeapons(bool showWeapon)
+{
+ mShowWeapons = 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::Ptr weapon = *mWeapon;
+ std::string mesh = MWWorld::Class::get(weapon).getModel(weapon);
+ addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh);
+ }
+ }
+ else
+ {
+ removeIndividualPart(ESM::PRT_Weapon);
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp
new file mode 100644
index 0000000000..24205acafd
--- /dev/null
+++ b/apps/openmw/mwrender/npcanimation.hpp
@@ -0,0 +1,98 @@
+#ifndef _GAME_RENDER_NPCANIMATION_H
+#define _GAME_RENDER_NPCANIMATION_H
+
+#include "animation.hpp"
+
+#include "../mwworld/containerstore.hpp"
+
+namespace ESM
+{
+ struct NPC;
+}
+
+namespace MWWorld
+{
+ class InventoryStore;
+}
+
+namespace MWRender
+{
+
+class NpcAnimation : public Animation
+{
+public:
+ typedef std::map<ESM::PartReferenceType,std::string> PartBoneMap;
+
+ enum ViewMode {
+ VM_Normal,
+ VM_FirstPerson,
+ VM_HeadOnly
+ };
+
+private:
+ static const PartBoneMap sPartList;
+
+ int mStateID;
+
+ // Bounded Parts
+ NifOgre::ObjectList 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;
+
+ int mVisibilityFlags;
+
+ int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty
+ int mPartPriorities[ESM::PRT_Count];
+
+ void updateNpcBase();
+
+ NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename);
+
+ 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);
+ void removePartGroup(int group);
+ void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts);
+
+public:
+ NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node,
+ MWWorld::InventoryStore& inv, int visibilityFlags,
+ ViewMode viewMode=VM_Normal);
+ virtual ~NpcAnimation();
+
+ virtual Ogre::Vector3 runAnimation(float timepassed);
+
+ virtual void showWeapons(bool showWeapon);
+
+ void setViewMode(ViewMode viewMode);
+
+ void updateParts(bool forceupdate = false);
+
+ /// Rebuilds the NPC, updating their root model, animation sources, and equipment.
+ void rebuild();
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp
new file mode 100644
index 0000000000..fd81baf6ed
--- /dev/null
+++ b/apps/openmw/mwrender/objects.cpp
@@ -0,0 +1,283 @@
+#include "objects.hpp"
+
+#include <cmath>
+
+#include <OgreSceneNode.h>
+#include <OgreSceneManager.h>
+#include <OgreEntity.h>
+#include <OgreLight.h>
+#include <OgreSubEntity.h>
+#include <OgreParticleSystem.h>
+#include <OgreParticleEmitter.h>
+#include <OgreStaticGeometry.h>
+
+#include <components/nifogre/ogrenifloader.hpp>
+#include <components/settings/settings.hpp>
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/class.hpp"
+
+#include "renderconst.hpp"
+#include "animation.hpp"
+
+using namespace MWRender;
+
+int Objects::uniqueID = 0;
+
+void Objects::setRootNode(Ogre::SceneNode* root)
+{
+ mRootNode = root;
+}
+
+void Objects::insertBegin(const MWWorld::Ptr& ptr)
+{
+ Ogre::SceneNode* root = mRootNode;
+ Ogre::SceneNode* cellnode;
+ if(mCellSceneNodes.find(ptr.getCell()) == mCellSceneNodes.end())
+ {
+ //Create the scenenode and put it in the map
+ cellnode = root->createChildSceneNode();
+ mCellSceneNodes[ptr.getCell()] = cellnode;
+ }
+ else
+ {
+ cellnode = mCellSceneNodes[ptr.getCell()];
+ }
+
+ Ogre::SceneNode* insert = cellnode->createChildSceneNode();
+ const float *f = ptr.getRefData().getPosition().pos;
+
+ insert->setPosition(f[0], f[1], f[2]);
+ insert->setScale(ptr.getCellRef().mScale, ptr.getCellRef().mScale, ptr.getCellRef().mScale);
+
+
+ // Convert MW rotation to a quaternion:
+ f = ptr.getCellRef().mPos.rot;
+
+ // Rotate around X axis
+ Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X);
+
+ // Rotate around Y axis
+ Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y);
+
+ // Rotate around Z axis
+ Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z);
+
+ // Rotates first around z, then y, then x
+ insert->setOrientation(xr*yr*zr);
+
+ ptr.getRefData().setBaseNode(insert);
+}
+
+void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh)
+{
+ insertBegin(ptr);
+
+ std::auto_ptr<ObjectAnimation> anim(new ObjectAnimation(ptr, mesh));
+
+ Ogre::AxisAlignedBox bounds = anim->getWorldBounds();
+ Ogre::Vector3 extents = bounds.getSize();
+ extents *= ptr.getRefData().getBaseNode()->getScale();
+ float size = std::max(std::max(extents.x, extents.y), extents.z);
+
+ bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) &&
+ Settings::Manager::getBool("limit small object distance", "Viewing distance");
+ // do not fade out doors. that will cause holes and look stupid
+ if(ptr.getTypeName().find("Door") != std::string::npos)
+ small = false;
+
+ if (mBounds.find(ptr.getCell()) == mBounds.end())
+ mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL;
+ mBounds[ptr.getCell()].merge(bounds);
+
+ if(ptr.getTypeName() == typeid(ESM::Light).name())
+ anim->addLight(ptr.get<ESM::Light>()->mBase);
+
+ if(ptr.getTypeName() == typeid(ESM::Static).name() &&
+ Settings::Manager::getBool("use static geometry", "Objects") &&
+ anim->canBatch())
+ {
+ Ogre::StaticGeometry* sg = 0;
+
+ if (small)
+ {
+ if(mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end())
+ {
+ uniqueID = uniqueID+1;
+ sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID));
+ mStaticGeometrySmall[ptr.getCell()] = sg;
+
+ sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance"));
+ }
+ else
+ sg = mStaticGeometrySmall[ptr.getCell()];
+ }
+ else
+ {
+ if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end())
+ {
+ uniqueID = uniqueID+1;
+ sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID));
+ mStaticGeometry[ptr.getCell()] = sg;
+ }
+ else
+ sg = mStaticGeometry[ptr.getCell()];
+ }
+
+ // This specifies the size of a single batch region.
+ // If it is set too high:
+ // - there will be problems choosing the correct lights
+ // - the culling will be more inefficient
+ // If it is set too low:
+ // - there will be too many batches.
+ if(ptr.getCell()->isExterior())
+ sg->setRegionDimensions(Ogre::Vector3(2048,2048,2048));
+ else
+ sg->setRegionDimensions(Ogre::Vector3(1024,1024,1024));
+
+ sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics);
+
+ sg->setCastShadows(true);
+
+ sg->setRenderQueueGroup(RQG_Main);
+
+ anim->fillBatch(sg);
+ /* TODO: We could hold on to this and just detach it from the scene graph, so if the Ptr
+ * ever needs to modify we can reattach it and rebuild the StaticGeometry object without
+ * it. Would require associating the Ptr with the StaticGeometry. */
+ anim.reset();
+ }
+
+ if(anim.get() != NULL)
+ mObjects.insert(std::make_pair(ptr, anim.release()));
+}
+
+bool Objects::deleteObject (const MWWorld::Ptr& ptr)
+{
+ if(!ptr.getRefData().getBaseNode())
+ return true;
+
+ PtrAnimationMap::iterator iter = mObjects.find(ptr);
+ if(iter != mObjects.end())
+ {
+ delete iter->second;
+ mObjects.erase(iter);
+
+ mRenderer.getScene()->destroySceneNode(ptr.getRefData().getBaseNode());
+ ptr.getRefData().setBaseNode(0);
+ return true;
+ }
+
+ return false;
+}
+
+
+void Objects::removeCell(MWWorld::Ptr::CellStore* store)
+{
+ for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();)
+ {
+ if(iter->first.getCell() == store)
+ {
+ delete iter->second;
+ mObjects.erase(iter++);
+ }
+ else
+ ++iter;
+ }
+
+ std::map<MWWorld::CellStore*,Ogre::StaticGeometry*>::iterator geom = mStaticGeometry.find(store);
+ if(geom != mStaticGeometry.end())
+ {
+ Ogre::StaticGeometry *sg = geom->second;
+ mStaticGeometry.erase(geom);
+ mRenderer.getScene()->destroyStaticGeometry(sg);
+ }
+
+ geom = mStaticGeometrySmall.find(store);
+ if(geom != mStaticGeometrySmall.end())
+ {
+ Ogre::StaticGeometry *sg = geom->second;
+ mStaticGeometrySmall.erase(store);
+ mRenderer.getScene()->destroyStaticGeometry(sg);
+ }
+
+ mBounds.erase(store);
+
+ std::map<MWWorld::CellStore*,Ogre::SceneNode*>::iterator cell = mCellSceneNodes.find(store);
+ if(cell != mCellSceneNodes.end())
+ {
+ cell->second->removeAndDestroyAllChildren();
+ mRenderer.getScene()->destroySceneNode(cell->second);
+ mCellSceneNodes.erase(cell);
+ }
+}
+
+void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell)
+{
+ if(mStaticGeometry.find(&cell) != mStaticGeometry.end())
+ {
+ Ogre::StaticGeometry* sg = mStaticGeometry[&cell];
+ sg->build();
+ }
+ if(mStaticGeometrySmall.find(&cell) != mStaticGeometrySmall.end())
+ {
+ Ogre::StaticGeometry* sg = mStaticGeometrySmall[&cell];
+ sg->build();
+ }
+}
+
+Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell)
+{
+ return mBounds[cell];
+}
+
+void Objects::enableLights()
+{
+ PtrAnimationMap::const_iterator it = mObjects.begin();
+ for(;it != mObjects.end();it++)
+ it->second->enableLights(true);
+}
+
+void Objects::disableLights()
+{
+ PtrAnimationMap::const_iterator it = mObjects.begin();
+ for(;it != mObjects.end();it++)
+ it->second->enableLights(false);
+}
+
+void Objects::update(const float dt)
+{
+ PtrAnimationMap::const_iterator it = mObjects.begin();
+ for(;it != mObjects.end();it++)
+ it->second->runAnimation(dt);
+}
+
+void Objects::rebuildStaticGeometry()
+{
+ for (std::map<MWWorld::CellStore *, Ogre::StaticGeometry*>::iterator it = mStaticGeometry.begin(); it != mStaticGeometry.end(); ++it)
+ {
+ it->second->destroy();
+ it->second->build();
+ }
+
+ for (std::map<MWWorld::CellStore *, Ogre::StaticGeometry*>::iterator it = mStaticGeometrySmall.begin(); it != mStaticGeometrySmall.end(); ++it)
+ {
+ it->second->destroy();
+ it->second->build();
+ }
+}
+
+void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur)
+{
+ Ogre::SceneNode *node;
+ MWWorld::CellStore *newCell = cur.getCell();
+
+ if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) {
+ node = mRootNode->createChildSceneNode();
+ mCellSceneNodes[newCell] = node;
+ } else {
+ node = mCellSceneNodes[newCell];
+ }
+ node->addChild(cur.getRefData().getBaseNode());
+}
+
diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp
new file mode 100644
index 0000000000..22dd1e4f5d
--- /dev/null
+++ b/apps/openmw/mwrender/objects.hpp
@@ -0,0 +1,66 @@
+#ifndef _GAME_RENDER_OBJECTS_H
+#define _GAME_RENDER_OBJECTS_H
+
+#include <OgreColourValue.h>
+#include <OgreAxisAlignedBox.h>
+
+#include <openengine/ogre/renderer.hpp>
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace MWRender{
+
+class ObjectAnimation;
+
+class Objects{
+ typedef std::map<MWWorld::Ptr,ObjectAnimation*> PtrAnimationMap;
+
+ OEngine::Render::OgreRenderer &mRenderer;
+
+ std::map<MWWorld::CellStore*,Ogre::SceneNode*> mCellSceneNodes;
+ std::map<MWWorld::CellStore*,Ogre::StaticGeometry*> mStaticGeometry;
+ std::map<MWWorld::CellStore*,Ogre::StaticGeometry*> mStaticGeometrySmall;
+ std::map<MWWorld::CellStore*,Ogre::AxisAlignedBox> mBounds;
+ PtrAnimationMap mObjects;
+
+ Ogre::SceneNode* mRootNode;
+
+ static int uniqueID;
+
+ void insertBegin(const MWWorld::Ptr& ptr);
+
+public:
+ Objects(OEngine::Render::OgreRenderer &renderer)
+ : mRenderer(renderer)
+ , mRootNode(NULL)
+ {}
+ ~Objects(){}
+ void insertModel(const MWWorld::Ptr& ptr, const std::string &model);
+
+ void enableLights();
+ void disableLights();
+
+ void update (const float dt);
+ ///< per-frame update
+
+ Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*);
+ ///< get a bounding box that encloses all objects in the specified cell
+
+ bool deleteObject (const MWWorld::Ptr& ptr);
+ ///< \return found?
+
+ void removeCell(MWWorld::CellStore* store);
+ void buildStaticGeometry(MWWorld::CellStore &cell);
+ void setRootNode(Ogre::SceneNode* root);
+
+ void rebuildStaticGeometry();
+
+ /// Updates containing cell for object rendering data
+ void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur);
+};
+}
+#endif
diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp
new file mode 100644
index 0000000000..a69511acd7
--- /dev/null
+++ b/apps/openmw/mwrender/occlusionquery.cpp
@@ -0,0 +1,205 @@
+#include "occlusionquery.hpp"
+
+#include <OgreRenderSystem.h>
+#include <OgreRoot.h>
+#include <OgreBillboardSet.h>
+#include <OgreHardwareOcclusionQuery.h>
+#include <OgreEntity.h>
+#include <OgreSubEntity.h>
+#include <OgreMeshManager.h>
+#include <OgreMaterialManager.h>
+
+#include "renderconst.hpp"
+
+using namespace MWRender;
+using namespace Ogre;
+
+OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) :
+ mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0),
+ mDoQuery(0), mSunVisibility(0),
+ mWasVisible(false),
+ mActive(false),
+ mFirstFrame(true)
+{
+ mRendering = renderer;
+ mSunNode = sunNode;
+
+ try {
+ RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
+
+ mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery();
+ mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery();
+
+ mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0);
+ }
+ catch (Ogre::Exception& e)
+ {
+ mSupported = false;
+ }
+
+ if (!mSupported)
+ {
+ std::cout << "Hardware occlusion queries not supported." << std::endl;
+ return;
+ }
+
+ mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
+
+ static Ogre::Mesh* plane = MeshManager::getSingleton().createPlane("occlusionbillboard",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::Plane(Ogre::Vector3(0,0,1), 0), 1, 1, 1, 1, true, 1, 1, 1, Vector3::UNIT_Y).get();
+ plane->_setBounds(Ogre::AxisAlignedBox::BOX_INFINITE);
+
+ mBBQueryTotal = mRendering->getScene()->createEntity("occlusionbillboard");
+ mBBQueryTotal->setCastShadows(false);
+ mBBQueryTotal->setVisibilityFlags(RV_OcclusionQuery);
+ mBBQueryTotal->setRenderQueueGroup(RQG_OcclusionQuery+1);
+ mBBQueryTotal->setMaterialName("QueryTotalPixels");
+ mBBNodeReal->attachObject(mBBQueryTotal);
+
+ mBBQueryVisible = mRendering->getScene()->createEntity("occlusionbillboard");
+ mBBQueryVisible->setCastShadows(false);
+ mBBQueryVisible->setVisibilityFlags(RV_OcclusionQuery);
+ mBBQueryVisible->setRenderQueueGroup(RQG_OcclusionQuery+1);
+ mBBQueryVisible->setMaterialName("QueryVisiblePixels");
+ mBBNodeReal->attachObject(mBBQueryVisible);
+
+ mRendering->getScene()->addRenderObjectListener(this);
+ mRendering->getScene()->addRenderQueueListener(this);
+ mDoQuery = true;
+}
+
+OcclusionQuery::~OcclusionQuery()
+{
+ mRendering->getScene()->removeRenderObjectListener (this);
+ mRendering->getScene()->removeRenderQueueListener(this);
+
+ RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
+ if (mSunTotalAreaQuery)
+ renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery);
+ if (mSunVisibleAreaQuery)
+ renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery);
+}
+
+bool OcclusionQuery::supported()
+{
+ return mSupported;
+}
+
+void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source,
+ const LightList* pLightList, bool suppressRenderStateChanges)
+{
+ if (!mActive) return;
+
+ // The following code activates and deactivates the occlusion queries
+ // so that the queries only include the rendering of the intended meshes
+
+ // Close the last occlusion query
+ // Each occlusion query should only last a single rendering
+ if (mActiveQuery != NULL)
+ {
+ mActiveQuery->endOcclusionQuery();
+ mActiveQuery = NULL;
+ }
+
+ // Open a new occlusion query
+ if (mDoQuery == true)
+ {
+ if (rend == mBBQueryTotal->getSubEntity(0))
+ {
+ mActiveQuery = mSunTotalAreaQuery;
+ mWasVisible = true;
+ }
+ else if (rend == mBBQueryVisible->getSubEntity(0))
+ {
+ mActiveQuery = mSunVisibleAreaQuery;
+ }
+ }
+
+ if (mActiveQuery != NULL)
+ mActiveQuery->beginOcclusionQuery();
+}
+
+void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation)
+{
+ if (!mActive) return;
+
+ if (mActiveQuery != NULL)
+ {
+ mActiveQuery->endOcclusionQuery();
+ mActiveQuery = NULL;
+ }
+ /**
+ * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa
+ * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called
+ * this can happen for example if the object that is tested is outside of the view frustum
+ * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually
+ */
+ if (queueGroupId == RQG_SkiesLate)
+ {
+ if (mWasVisible == false && mDoQuery)
+ {
+ mSunTotalAreaQuery->beginOcclusionQuery();
+ mSunTotalAreaQuery->endOcclusionQuery();
+ mSunVisibleAreaQuery->beginOcclusionQuery();
+ mSunVisibleAreaQuery->endOcclusionQuery();
+ }
+ }
+}
+
+void OcclusionQuery::update(float duration)
+{
+ if (mFirstFrame)
+ {
+ // GLHardwareOcclusionQuery::isStillOutstanding doesn't seem to like getting called when nothing has been rendered yet
+ mFirstFrame = false;
+ return;
+ }
+ if (!mSupported) return;
+
+ mWasVisible = false;
+
+ // Adjust the position of the sun billboards according to camera viewing distance
+ // we need to do this to make sure that _everything_ can occlude the sun
+ float dist = mRendering->getCamera()->getFarClipDistance();
+ if (dist==0) dist = 10000000;
+ dist -= 1000; // bias
+ dist /= 1000.f;
+ if (mSunNode)
+ {
+ mBBNodeReal->setPosition(mSunNode->getPosition() * dist);
+ mBBNodeReal->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-mBBNodeReal->getPosition().normalisedCopy()));
+ mBBNodeReal->setScale(150.f*dist, 150.f*dist, 150.f*dist);
+ }
+
+ // Stop occlusion queries until we get their information
+ // (may not happen on the same frame they are requested in)
+ mDoQuery = false;
+
+ if (!mSunTotalAreaQuery->isStillOutstanding()
+ && !mSunVisibleAreaQuery->isStillOutstanding())
+ {
+ unsigned int totalPixels;
+ unsigned int visiblePixels;
+
+ mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels);
+ mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels);
+
+ if (totalPixels == 0)
+ {
+ // probably outside of the view frustum
+ mSunVisibility = 0;
+ }
+ else
+ {
+ mSunVisibility = float(visiblePixels) / float(totalPixels);
+ if (mSunVisibility > 1) mSunVisibility = 1;
+ }
+
+ mDoQuery = true;
+ }
+}
+
+void OcclusionQuery::setSunNode(Ogre::SceneNode* node)
+{
+ mSunNode = node;
+}
diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp
new file mode 100644
index 0000000000..983361c187
--- /dev/null
+++ b/apps/openmw/mwrender/occlusionquery.hpp
@@ -0,0 +1,77 @@
+#ifndef _GAME_OCCLUSION_QUERY_H
+#define _GAME_OCCLUSION_QUERY_H
+
+#include <OgreRenderObjectListener.h>
+#include <OgreRenderQueueListener.h>
+
+namespace Ogre
+{
+ class HardwareOcclusionQuery;
+ class Entity;
+ class SceneNode;
+}
+
+#include <openengine/ogre/renderer.hpp>
+
+namespace MWRender
+{
+ ///
+ /// \brief Implements hardware occlusion queries on the GPU
+ ///
+ class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener
+ {
+ public:
+ OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode);
+ ~OcclusionQuery();
+
+ /**
+ * @return true if occlusion queries are supported on the user's hardware
+ */
+ bool supported();
+
+ /**
+ * make sure to disable occlusion queries before updating unrelated render targets
+ * @param active
+ */
+ void setActive (bool active) { mActive = active; }
+
+ /**
+ * per-frame update
+ */
+ void update(float duration);
+
+ float getSunVisibility() const {return mSunVisibility;};
+
+ void setSunNode(Ogre::SceneNode* node);
+
+ private:
+ Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery;
+ Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery;
+ Ogre::HardwareOcclusionQuery* mActiveQuery;
+
+ Ogre::Entity* mBBQueryVisible;
+ Ogre::Entity* mBBQueryTotal;
+
+ Ogre::SceneNode* mSunNode;
+ Ogre::SceneNode* mBBNodeReal;
+ float mSunVisibility;
+
+ bool mWasVisible;
+
+ bool mActive;
+ bool mFirstFrame;
+
+ bool mSupported;
+ bool mDoQuery;
+
+ OEngine::Render::OgreRenderer* mRendering;
+
+ protected:
+ virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source,
+ const Ogre::LightList* pLightList, bool suppressRenderStateChanges);
+
+ virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp
new file mode 100644
index 0000000000..d590dbf4c3
--- /dev/null
+++ b/apps/openmw/mwrender/refraction.cpp
@@ -0,0 +1,107 @@
+#include "refraction.hpp"
+
+#include <OgreCamera.h>
+#include <OgreTextureManager.h>
+#include <OgreSceneManager.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRenderTarget.h>
+#include <OgreViewport.h>
+#include <OgreRoot.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+#include "renderconst.hpp"
+
+namespace MWRender
+{
+
+ Refraction::Refraction(Ogre::Camera *parentCamera)
+ : mParentCamera(parentCamera)
+ , mRenderActive(false)
+ , mIsUnderwater(false)
+ {
+ mCamera = mParentCamera->getSceneManager()->createCamera("RefractionCamera");
+
+ mParentCamera->getSceneManager()->addRenderQueueListener(this);
+
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual("WaterRefraction",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, 512, 512, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
+
+ mRenderTarget = texture->getBuffer()->getRenderTarget();
+ Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera);
+ vp->setOverlaysEnabled(false);
+ vp->setShadowsEnabled(false);
+ vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky);
+ vp->setMaterialScheme("water_refraction");
+ vp->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490));
+ mRenderTarget->setAutoUpdated(true);
+ mRenderTarget->addListener(this);
+ }
+
+ Refraction::~Refraction()
+ {
+ mRenderTarget->removeListener(this);
+ Ogre::TextureManager::getSingleton().remove("WaterRefraction");
+ mParentCamera->getSceneManager()->destroyCamera(mCamera);
+ mParentCamera->getSceneManager()->removeRenderQueueListener(this);
+ }
+
+ void Refraction::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
+ {
+ mParentCamera->getParentSceneNode ()->needUpdate ();
+ mCamera->setOrientation(mParentCamera->getDerivedOrientation());
+ mCamera->setPosition(mParentCamera->getDerivedPosition());
+ mCamera->setNearClipDistance(mParentCamera->getNearClipDistance());
+ mCamera->setFarClipDistance(mParentCamera->getFarClipDistance());
+ mCamera->setAspectRatio(mParentCamera->getAspectRatio());
+ mCamera->setFOVy(mParentCamera->getFOVy());
+
+ // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane.
+ // since all we are interested in is depth, we only need the third row of the matrix.
+ Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix ();
+ sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]);
+ sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty<sh::Vector4> (row3));
+
+ // enable clip plane here to take advantage of CPU culling for overwater or underwater objects
+ mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane);
+
+ mRenderActive = true;
+ }
+
+ void Refraction::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
+ {
+ mCamera->disableCustomNearClipPlane ();
+ mRenderActive = false;
+ }
+
+ void Refraction::setHeight(float height)
+ {
+ mNearClipPlane = Ogre::Plane( -Ogre::Vector3(0,0,1), -(height + 5));
+ mNearClipPlaneUnderwater = Ogre::Plane( Ogre::Vector3(0,0,1), height - 5);
+ }
+
+ void Refraction::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation)
+ {
+ // We don't want the sky to get clipped by custom near clip plane (the water plane)
+ if (queueGroupId < 20 && mRenderActive)
+ {
+ mCamera->disableCustomNearClipPlane();
+ Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS());
+ }
+ }
+
+ void Refraction::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation)
+ {
+ if (queueGroupId < 20 && mRenderActive)
+ {
+ mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane);
+ Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS());
+ }
+ }
+
+ void Refraction::setActive(bool active)
+ {
+ mRenderTarget->setActive(active);
+ }
+
+}
diff --git a/apps/openmw/mwrender/refraction.hpp b/apps/openmw/mwrender/refraction.hpp
new file mode 100644
index 0000000000..b9ab8deac2
--- /dev/null
+++ b/apps/openmw/mwrender/refraction.hpp
@@ -0,0 +1,45 @@
+#ifndef MWRENDER_REFRACTION_H
+#define MWRENDER_REFRACTION_H
+
+#include <OgrePlane.h>
+#include <OgreRenderTargetListener.h>
+#include <OgreRenderQueueListener.h>
+
+namespace Ogre
+{
+ class Camera;
+ class RenderTarget;
+}
+
+namespace MWRender
+{
+
+ class Refraction : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener
+ {
+
+ public:
+ Refraction(Ogre::Camera* parentCamera);
+ ~Refraction();
+
+ void setHeight (float height);
+ void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+ void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+ void setUnderwater(bool underwater) {mIsUnderwater = underwater;}
+ void setActive (bool active);
+
+ void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation);
+ void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation);
+
+ private:
+ Ogre::Camera* mParentCamera;
+ Ogre::Camera* mCamera;
+ Ogre::RenderTarget* mRenderTarget;
+ Ogre::Plane mNearClipPlane;
+ Ogre::Plane mNearClipPlaneUnderwater;
+ bool mRenderActive;
+ bool mIsUnderwater;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp
new file mode 100644
index 0000000000..44599ebee2
--- /dev/null
+++ b/apps/openmw/mwrender/renderconst.hpp
@@ -0,0 +1,70 @@
+#ifndef GAME_RENDER_CONST_H
+#define GAME_RENDER_CONST_H
+
+#include <OgreRenderQueue.h>
+
+namespace MWRender
+{
+
+// Render queue groups
+enum RenderQueueGroups
+{
+ // Sky early (atmosphere, clouds, moons)
+ RQG_SkiesEarly = Ogre::RENDER_QUEUE_SKIES_EARLY,
+
+ RQG_Main = Ogre::RENDER_QUEUE_MAIN,
+
+ RQG_Alpha = Ogre::RENDER_QUEUE_MAIN+1,
+
+ RQG_OcclusionQuery = Ogre::RENDER_QUEUE_6,
+
+ RQG_UnderWater = Ogre::RENDER_QUEUE_4,
+
+ RQG_Water = RQG_Alpha,
+
+ // Sky late (sun & sun flare)
+ RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE
+};
+
+// Visibility flags
+enum VisibilityFlags
+{
+ // Terrain
+ RV_Terrain = 1,
+
+ // Statics (e.g. trees, houses)
+ RV_Statics = 2,
+
+ // Small statics
+ RV_StaticsSmall = 4,
+
+ // Water
+ RV_Water = 8,
+
+ // Actors (npcs, creatures)
+ RV_Actors = 16,
+
+ // Misc objects (containers, dynamic objects)
+ RV_Misc = 32,
+
+ RV_Sky = 64,
+
+ // not visible in reflection
+ RV_NoReflection = 128,
+
+ RV_OcclusionQuery = 256,
+
+ RV_Debug = 512,
+
+ // overlays, we only want these on the main render target
+ RV_Overlay = 1024,
+
+ // First person meshes do not cast shadows
+ RV_FirstPerson = 2048,
+
+ RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp
new file mode 100644
index 0000000000..8ae2c0f8f9
--- /dev/null
+++ b/apps/openmw/mwrender/renderinginterface.hpp
@@ -0,0 +1,17 @@
+#ifndef _GAME_RENDERING_INTERFACE_H
+#define _GAME_RENDERING_INTERFACE_H
+
+namespace MWRender
+{
+ class Objects;
+ class Actors;
+
+ class RenderingInterface
+ {
+ public:
+ virtual MWRender::Objects& getObjects() = 0;
+ virtual MWRender::Actors& getActors() = 0;
+ virtual ~RenderingInterface(){};
+ };
+}
+#endif
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
new file mode 100644
index 0000000000..e03b2ccfcf
--- /dev/null
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -0,0 +1,1038 @@
+#include "renderingmanager.hpp"
+
+#include <cassert>
+
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+#include <OgreSceneManager.h>
+#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>
+
+#include <SDL_video.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+#include <extern/shiny/Platforms/Ogre/OgrePlatform.hpp>
+
+#include <openengine/bullet/physic.hpp>
+
+#include <components/esm/loadstat.hpp>
+#include <components/settings/settings.hpp>
+#include <components/terrain/world.hpp>
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp" // FIXME
+#include "../mwbase/windowmanager.hpp" // FIXME
+
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/player.hpp"
+
+#include "shadows.hpp"
+#include "localmap.hpp"
+#include "water.hpp"
+#include "compositors.hpp"
+#include "npcanimation.hpp"
+#include "externalrendering.hpp"
+#include "globalmap.hpp"
+#include "videoplayer.hpp"
+#include "terrainstorage.hpp"
+
+using namespace MWRender;
+using namespace Ogre;
+
+namespace MWRender {
+
+RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir,
+ const boost::filesystem::path& cacheDir, OEngine::Physic::PhysicEngine* engine,
+ MWWorld::Fallback* fallback)
+ : mRendering(_rend)
+ , mFallback(fallback)
+ , mObjects(mRendering)
+ , mActors(mRendering, this)
+ , mPlayerAnimation(NULL)
+ , mAmbientMode(0)
+ , mSunEnabled(0)
+ , mPhysicsEngine(engine)
+ , mTerrain(NULL)
+{
+ // 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);
+
+ // glsl is only supported in opengl mode and hlsl only in direct3d mode.
+ std::string currentMode = Settings::Manager::getString("shader mode", "General");
+ if (currentMode == ""
+ || (openGL && currentMode == "hlsl")
+ || (!openGL && currentMode == "glsl")
+ || (glES && currentMode != "glsles"))
+ {
+ Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl");
+ }
+
+ mRendering.adjustCamera(Settings::Manager::getFloat("field of view", "General"), 5);
+
+ mRendering.getWindow()->addListener(this);
+ mRendering.setWindowListener(this);
+
+ mCompositors = new Compositors(mRendering.getViewport());
+
+ mWater = 0;
+
+ // material system
+ sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string());
+ if (!boost::filesystem::exists (cacheDir))
+ boost::filesystem::create_directories (cacheDir);
+ platform->setCacheFolder (cacheDir.string());
+ mFactory = new sh::Factory(platform);
+
+ sh::Language lang;
+ std::string l = Settings::Manager::getString("shader mode", "General");
+ if (l == "glsl")
+ lang = sh::Language_GLSL;
+ else if (l == "glsles")
+ lang = sh::Language_GLSLES;
+ else if (l == "hlsl")
+ lang = sh::Language_HLSL;
+ else
+ lang = sh::Language_CG;
+ mFactory->setCurrentLanguage (lang);
+ mFactory->setWriteSourceCache (true);
+ mFactory->setReadSourceCache (true);
+ mFactory->setReadMicrocodeCache (true);
+ mFactory->setWriteMicrocodeCache (true);
+
+ 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"));
+ TextureManager::getSingleton().setDefaultNumMipmaps(0);
+
+ // Set default texture filtering options
+ TextureFilterOptions tfo;
+ std::string filter = Settings::Manager::getString("texture filtering", "General");
+ if (filter == "anisotropic") tfo = TFO_ANISOTROPIC;
+ else if (filter == "trilinear") tfo = TFO_TRILINEAR;
+ else if (filter == "bilinear") tfo = TFO_BILINEAR;
+ else /*if (filter == "none")*/ tfo = TFO_NONE;
+
+ MaterialManager::getSingleton().setDefaultTextureFiltering(tfo);
+ MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 );
+
+ Ogre::TextureManager::getSingleton().setMemoryBudget(126*1024*1024);
+ Ogre::MeshManager::getSingleton().setMemoryBudget(64*1024*1024);
+
+ Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
+
+ // disable unsupported effects
+ if (!Settings::Manager::getBool("shaders", "Objects"))
+ Settings::Manager::setBool("enabled", "Shadows", false);
+
+ sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects"));
+
+ sh::Factory::getInstance ().setGlobalSetting ("fog", "true");
+ sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects"));
+ sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true");
+ sh::Factory::getInstance ().setGlobalSetting ("render_refraction", "false");
+
+ sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty<sh::FloatValue> (new sh::FloatValue(0.0)));
+ sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty<sh::FloatValue>(new sh::FloatValue(0)));
+ sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty<sh::FloatValue>(new sh::FloatValue(0)));
+ sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty<sh::Vector3>(new sh::Vector3(0.5, -0.8, 0.2)));
+ sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty<sh::Vector2>(new sh::Vector2(1, 0.6)));
+ sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false");
+ 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);
+
+ mCamera = new MWRender::Camera(mRendering.getCamera());
+
+ mShadows = new Shadows(&mRendering);
+
+ mSkyManager = new SkyManager(mRootNode, mRendering.getCamera());
+
+ mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode());
+
+ mVideoPlayer = new VideoPlayer(mRendering.getScene (), mRendering.getWindow());
+ mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video"));
+
+ mSun = 0;
+
+ mDebugging = new Debugging(mRootNode, engine);
+ mLocalMap = new MWRender::LocalMap(&mRendering, this);
+
+ mWater = new MWRender::Water(mRendering.getCamera(), this);
+
+ setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI"));
+}
+
+RenderingManager::~RenderingManager ()
+{
+ mRendering.getWindow()->removeListener(this);
+
+ delete mPlayerAnimation;
+ delete mCamera;
+ delete mSkyManager;
+ delete mDebugging;
+ delete mShadows;
+ delete mTerrain;
+ delete mLocalMap;
+ delete mOcclusionQuery;
+ delete mCompositors;
+ delete mWater;
+ delete mVideoPlayer;
+ delete mFactory;
+}
+
+MWRender::SkyManager* RenderingManager::getSkyManager()
+{
+ return mSkyManager;
+}
+
+MWRender::Objects& RenderingManager::getObjects(){
+ return mObjects;
+}
+MWRender::Actors& RenderingManager::getActors(){
+ return mActors;
+}
+
+OEngine::Render::Fader* RenderingManager::getFader()
+{
+ return mRendering.getFader();
+}
+
+void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store)
+{
+ mObjects.removeCell(store);
+ mActors.removeCell(store);
+ mDebugging->cellRemoved(store);
+}
+
+void RenderingManager::removeWater ()
+{
+ mWater->setActive(false);
+}
+
+void RenderingManager::toggleWater()
+{
+ mWater->toggle();
+}
+
+void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
+{
+ mObjects.buildStaticGeometry (*store);
+ sh::Factory::getInstance().unloadUnreferencedMaterials();
+ mDebugging->cellAdded(store);
+ waterAdded(store);
+}
+
+void RenderingManager::addObject (const MWWorld::Ptr& ptr){
+ const MWWorld::Class& class_ =
+ MWWorld::Class::get (ptr);
+ class_.insertObjectRendering(ptr, *this);
+}
+
+void RenderingManager::removeObject (const MWWorld::Ptr& ptr)
+{
+ if (!mObjects.deleteObject (ptr))
+ mActors.deleteObject (ptr);
+}
+
+void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position)
+{
+ /// \todo move this to the rendering-subsystems
+ ptr.getRefData().getBaseNode()->setPosition(position);
+}
+
+void RenderingManager::scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale)
+{
+ ptr.getRefData().getBaseNode()->setScale(scale);
+}
+
+void RenderingManager::rotateObject(const MWWorld::Ptr &ptr)
+{
+ Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
+
+ if(ptr.getRefData().getHandle() == mCamera->getHandle() &&
+ !mCamera->isVanityOrPreviewModeEnabled())
+ mCamera->rotateCamera(rot, false);
+
+ Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z);
+ if(!MWWorld::Class::get(ptr).isActor())
+ newo = Ogre::Quaternion(Ogre::Radian(-rot.x), Ogre::Vector3::UNIT_X) *
+ Ogre::Quaternion(Ogre::Radian(-rot.y), Ogre::Vector3::UNIT_Y) * newo;
+
+ ptr.getRefData().getBaseNode()->setOrientation(newo);
+}
+
+void
+RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur)
+{
+ Ogre::SceneNode *child =
+ mRendering.getScene()->getSceneNode(old.getRefData().getHandle());
+
+ Ogre::SceneNode *parent = child->getParentSceneNode();
+ parent->removeChild(child);
+
+ if (MWWorld::Class::get(old).isActor()) {
+ mActors.updateObjectCell(old, cur);
+ } else {
+ mObjects.updateObjectCell(old, cur);
+ }
+}
+
+void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr)
+{
+ if(mPlayerAnimation)
+ mPlayerAnimation->updatePtr(ptr);
+ if(mCamera->getHandle() == ptr.getRefData().getHandle())
+ mCamera->attachTo(ptr);
+}
+
+void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr)
+{
+ NpcAnimation *anim = NULL;
+ if(ptr.getRefData().getHandle() == "player")
+ anim = mPlayerAnimation;
+ else if(MWWorld::Class::get(ptr).isActor())
+ anim = dynamic_cast<NpcAnimation*>(mActors.getAnimation(ptr));
+ if(anim)
+ {
+ anim->rebuild();
+ if(mCamera->getHandle() == ptr.getRefData().getHandle())
+ {
+ mCamera->attachTo(ptr);
+ mCamera->setAnimation(anim);
+ }
+ }
+}
+
+void RenderingManager::update (float duration, bool paused)
+{
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+
+ int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude;
+ mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f)));
+ setAmbientMode();
+
+ // player position
+ MWWorld::RefData &data = player.getRefData();
+ Ogre::Vector3 playerPos(data.getPosition().pos);
+
+ mCamera->setCameraDistance();
+ if(!mCamera->isFirstPerson())
+ {
+ Ogre::Vector3 orig, dest;
+ mCamera->getPosition(orig, dest);
+
+ btVector3 btOrig(orig.x, orig.y, orig.z);
+ btVector3 btDest(dest.x, dest.y, dest.z);
+ std::pair<bool,float> test = mPhysicsEngine->sphereCast(mRendering.getCamera()->getNearClipDistance()*2.5, btOrig, btDest);
+ if(test.first)
+ mCamera->setCameraDistance(test.second * orig.distance(dest), false, false);
+ }
+
+ mOcclusionQuery->update(duration);
+
+ mVideoPlayer->update ();
+
+ mRendering.update(duration);
+
+ Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f);
+
+ Ogre::Vector3 cam = mRendering.getCamera()->getRealPosition();
+
+ applyFog(world->isUnderwater(player.getCell(), cam));
+
+ mCamera->update(duration, paused);
+
+ if(paused)
+ return;
+
+ mActors.update (duration);
+ mObjects.update (duration);
+
+
+ mSkyManager->update(duration);
+
+ mSkyManager->setGlare(mOcclusionQuery->getSunVisibility());
+
+ Ogre::SceneNode *node = data.getBaseNode();
+ Ogre::Quaternion orient = node->_getDerivedOrientation();
+
+ mLocalMap->updatePlayer(playerPos, orient);
+
+ mWater->updateUnderwater(world->isUnderwater(player.getCell(), cam));
+
+ mWater->update(duration, playerPos);
+}
+
+void RenderingManager::preRenderTargetUpdate(const RenderTargetEvent &evt)
+{
+ mOcclusionQuery->setActive(true);
+}
+
+void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt)
+{
+ // deactivate queries to make sure we aren't getting false results from several misc render targets
+ // (will be reactivated at the bottom of this method)
+ mOcclusionQuery->setActive(false);
+}
+
+void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store)
+{
+ const MWWorld::Store<ESM::Land> &lands =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>();
+
+ if(store->mCell->mData.mFlags & ESM::Cell::HasWater
+ || ((store->mCell->isExterior())
+ && !lands.search(store->mCell->getGridX(),store->mCell->getGridY()) )) // always use water, if the cell does not have land.
+ {
+ mWater->changeCell(store->mCell);
+ mWater->setActive(true);
+ }
+ else
+ removeWater();
+}
+
+void RenderingManager::setWaterHeight(const float height)
+{
+ mWater->setHeight(height);
+}
+
+void RenderingManager::skyEnable ()
+{
+ mSkyManager->enable();
+ mOcclusionQuery->setSunNode(mSkyManager->getSunNode());
+}
+
+void RenderingManager::skyDisable ()
+{
+ mSkyManager->disable();
+}
+
+void RenderingManager::skySetHour (double hour)
+{
+ mSkyManager->setHour(hour);
+}
+
+
+void RenderingManager::skySetDate (int day, int month)
+{
+ mSkyManager->setDate(day, month);
+}
+
+int RenderingManager::skyGetMasserPhase() const
+{
+
+ return mSkyManager->getMasserPhase();
+}
+
+int RenderingManager::skyGetSecundaPhase() const
+{
+ return mSkyManager->getSecundaPhase();
+}
+
+void RenderingManager::skySetMoonColour (bool red){
+ mSkyManager->setMoonColour(red);
+}
+
+bool RenderingManager::toggleRenderMode(int mode)
+{
+ if (mode == MWBase::World::Render_CollisionDebug || mode == MWBase::World::Render_Pathgrid)
+ return mDebugging->toggleRenderMode(mode);
+ else if (mode == MWBase::World::Render_Wireframe)
+ {
+ 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)
+ {
+ 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)
+{
+ Ogre::ColourValue color;
+ color.setAsABGR (mCell.mCell->mAmbi.mFog);
+
+ configureFog(mCell.mCell->mAmbi.mFogDensity, color);
+}
+
+void RenderingManager::configureFog(const float density, const Ogre::ColourValue& colour)
+{
+ mFogColour = colour;
+ float max = Settings::Manager::getFloat("max viewing distance", "Viewing distance");
+
+ mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance");
+ mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance");
+
+ mRendering.getCamera()->setFarClipDistance ( Settings::Manager::getFloat("max viewing distance", "Viewing distance") / density );
+}
+
+void RenderingManager::applyFog (bool underwater)
+{
+ if (!underwater)
+ {
+ mRendering.getScene()->setFog (FOG_LINEAR, mFogColour, 0, mFogStart, mFogEnd);
+ mRendering.getViewport()->setBackgroundColour (mFogColour);
+ mWater->setViewportBackground (mFogColour);
+ }
+ else
+ {
+ mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(0.18039, 0.23137, 0.25490), 0, 0, 1000);
+ mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490));
+ mWater->setViewportBackground (Ogre::ColourValue(0.18039, 0.23137, 0.25490));
+ }
+}
+
+void RenderingManager::setAmbientMode()
+{
+ switch (mAmbientMode)
+ {
+ case 0:
+ setAmbientColour(mAmbientColor);
+ break;
+
+ case 1:
+ setAmbientColour(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1));
+ break;
+
+ case 2:
+ setAmbientColour(ColourValue(1,1,1));
+ break;
+ }
+}
+
+void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell)
+{
+ if (mCell.mCell->mData.mFlags & ESM::Cell::Interior)
+ mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient);
+ setAmbientMode();
+
+ // Create a "sun" that shines light downwards. It doesn't look
+ // completely right, but leave it for now.
+ if(!mSun)
+ {
+ mSun = mRendering.getScene()->createLight();
+ mSun->setType(Ogre::Light::LT_DIRECTIONAL);
+ }
+ if (mCell.mCell->mData.mFlags & ESM::Cell::Interior)
+ {
+ Ogre::ColourValue colour;
+ colour.setAsABGR (mCell.mCell->mAmbi.mSunlight);
+ mSun->setDiffuseColour (colour);
+ mSun->setDirection(0,-1,0);
+ }
+}
+// Switch through lighting modes.
+
+void RenderingManager::toggleLight()
+{
+ if (mAmbientMode==2)
+ mAmbientMode = 0;
+ else
+ ++mAmbientMode;
+
+ switch (mAmbientMode)
+ {
+ case 0: std::cout << "Setting lights to normal\n"; break;
+ case 1: std::cout << "Turning the lights up\n"; break;
+ case 2: std::cout << "Turning the lights to full\n"; break;
+ }
+
+ setAmbientMode();
+}
+
+void RenderingManager::setSunColour(const Ogre::ColourValue& colour)
+{
+ if (!mSunEnabled) return;
+ mSun->setDiffuseColour(colour);
+ mSun->setSpecularColour(colour);
+}
+
+void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour)
+{
+ mAmbientColor = colour;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::NightEye)).mMagnitude;
+ Ogre::ColourValue final = colour;
+ final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f));
+
+ mRendering.getScene()->setAmbientLight(final);
+}
+
+void RenderingManager::sunEnable(bool real)
+{
+ if (real && mSun) mSun->setVisible(true);
+ else
+ {
+ // Don't disable the light, as the shaders assume the first light to be directional.
+ mSunEnabled = true;
+ }
+}
+
+void RenderingManager::sunDisable(bool real)
+{
+ if (real && mSun) mSun->setVisible(false);
+ else
+ {
+ // Don't disable the light, as the shaders assume the first light to be directional.
+ mSunEnabled = false;
+ if (mSun)
+ {
+ mSun->setDiffuseColour(ColourValue(0,0,0));
+ mSun->setSpecularColour(ColourValue(0,0,0));
+ }
+ }
+}
+
+void RenderingManager::setSunDirection(const Ogre::Vector3& direction)
+{
+ // direction * -1 (because 'direction' is camera to sun vector and not sun to camera),
+ if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z));
+
+ mSkyManager->setSunDirection(direction);
+}
+
+void RenderingManager::setGlare(bool glare)
+{
+ mSkyManager->setGlare(glare);
+}
+
+void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
+{
+ if (cell->mCell->isExterior())
+ {
+ assert(mTerrain);
+
+ Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell);
+ Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5);
+ dims.merge(mTerrain->getWorldBoundingBox(center));
+
+ if (dims.isFinite())
+ mTerrain->update(dims.getCenter());
+
+ mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
+ }
+ else
+ mLocalMap->requestMap(cell, mObjects.getDimensions(cell));
+}
+
+void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell)
+{
+ mLocalMap->saveFogOfWar(cell);
+}
+
+void RenderingManager::disableLights(bool sun)
+{
+ mObjects.disableLights();
+ sunDisable(sun);
+}
+
+void RenderingManager::enableLights(bool sun)
+{
+ mObjects.enableLights();
+ sunEnable(sun);
+}
+
+Shadows* RenderingManager::getShadows()
+{
+ return mShadows;
+}
+
+void RenderingManager::switchToInterior()
+{
+ // causes light flicker in opengl when moving..
+ //mRendering.getScene()->setCameraRelativeRendering(false);
+}
+
+void RenderingManager::switchToExterior()
+{
+ // causes light flicker in opengl when moving..
+ //mRendering.getScene()->setCameraRelativeRendering(true);
+}
+
+Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds)
+{
+ Ogre::Matrix4 mat = mRendering.getCamera()->getViewMatrix();
+
+ const Ogre::Vector3* corners = bounds.getAllCorners();
+
+ float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f;
+
+ // expand the screen-space bounding-box so that it completely encloses
+ // the object's AABB
+ for (int i=0; i<8; i++)
+ {
+ Ogre::Vector3 corner = corners[i];
+
+ // multiply the AABB corner vertex by the view matrix to
+ // get a camera-space vertex
+ corner = mat * corner;
+
+ // make 2D relative/normalized coords from the view-space vertex
+ // by dividing out the Z (depth) factor -- this is an approximation
+ float x = corner.x / corner.z + 0.5;
+ float y = corner.y / corner.z + 0.5;
+
+ if (x < min_x)
+ min_x = x;
+
+ if (x > max_x)
+ max_x = x;
+
+ if (y < min_y)
+ min_y = y;
+
+ if (y > max_y)
+ max_y = y;
+ }
+
+ 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;
+ bool rebuild = false; // rebuild static geometry (necessary after any material changes)
+ for (Settings::CategorySettingVector::const_iterator it=settings.begin();
+ it != settings.end(); ++it)
+ {
+ if (it->second == "menu transparency" && it->first == "GUI")
+ {
+ setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI"));
+ }
+ else if (it->second == "max viewing distance" && it->first == "Viewing distance")
+ {
+ if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior())
+ configureFog(*MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell());
+ }
+ else if (it->first == "Video" && (
+ it->second == "resolution x"
+ || it->second == "resolution y"
+ || it->second == "fullscreen"))
+ changeRes = true;
+ else if (it->second == "field of view" && it->first == "General")
+ mRendering.setFov(Settings::Manager::getFloat("field of view", "General"));
+ else if ((it->second == "texture filtering" && it->first == "General")
+ || (it->second == "anisotropy" && it->first == "General"))
+ {
+ TextureFilterOptions tfo;
+ std::string filter = Settings::Manager::getString("texture filtering", "General");
+ if (filter == "anisotropic") tfo = TFO_ANISOTROPIC;
+ else if (filter == "trilinear") tfo = TFO_TRILINEAR;
+ else if (filter == "bilinear") tfo = TFO_BILINEAR;
+ else /*if (filter == "none")*/ tfo = TFO_NONE;
+
+ MaterialManager::getSingleton().setDefaultTextureFiltering(tfo);
+ MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 );
+ }
+ 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);
+ }
+ else if (it->second == "refraction" && it->first == "Water")
+ {
+ sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false");
+ rebuild = true;
+ }
+ else if (it->second == "shaders" && it->first == "Objects")
+ {
+ sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects"));
+ rebuild = true;
+ }
+ else if (it->second == "shader mode" && it->first == "General")
+ {
+ sh::Language lang;
+ std::string l = Settings::Manager::getString("shader mode", "General");
+ if (l == "glsl")
+ lang = sh::Language_GLSL;
+ else if (l == "hlsl")
+ lang = sh::Language_HLSL;
+ else
+ lang = sh::Language_CG;
+ sh::Factory::getInstance ().setCurrentLanguage (lang);
+ rebuild = true;
+ }
+ else if (it->first == "Shadows")
+ {
+ mShadows->recreate ();
+
+ rebuild = true;
+ }
+ }
+
+ if (changeRes)
+ {
+ unsigned int x = Settings::Manager::getInt("resolution x", "Video");
+ unsigned int y = Settings::Manager::getInt("resolution y", "Video");
+ bool fullscreen = Settings::Manager::getBool("fullscreen", "Video");
+
+ SDL_Window* window = mRendering.getSDLWindow();
+
+ SDL_SetWindowFullscreen(window, 0);
+
+ if (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED)
+ SDL_RestoreWindow(window);
+
+ if (fullscreen)
+ {
+ SDL_DisplayMode mode;
+ SDL_GetWindowDisplayMode(window, &mode);
+ mode.w = x;
+ mode.h = y;
+ SDL_SetWindowDisplayMode(window, &mode);
+ SDL_SetWindowFullscreen(window, fullscreen);
+ }
+ else
+ SDL_SetWindowSize(window, x, y);
+ }
+
+ mWater->processChangedSettings(settings);
+
+ if (rebuild)
+ {
+ mObjects.rebuildStaticGeometry();
+ if (mTerrain)
+ mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
+ Settings::Manager::getBool("split", "Shadows"));
+ }
+}
+
+void RenderingManager::setMenuTransparency(float val)
+{
+ Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName("transparent.png");
+ std::vector<Ogre::uint32> buffer;
+ buffer.resize(1);
+ buffer[0] = (int(255*val) << 24);
+ memcpy(tex->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], 1*4);
+ tex->getBuffer()->unlock();
+}
+
+void RenderingManager::windowResized(int x, int 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();
+ }
+}
+
+void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr)
+{
+ ptr.getRefData().setBaseNode(mRendering.getScene()->getSceneNode("player"));
+ mCamera->attachTo(ptr);
+}
+
+void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
+{
+ if(!mPlayerAnimation)
+ {
+ mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(),
+ MWWorld::Class::get(ptr).getInventoryStore(ptr),
+ 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);
+ }
+ mCamera->setAnimation(mPlayerAnimation);
+ mWater->removeEmitter(ptr);
+ mWater->addEmitter(ptr);
+ // apply race height
+ MWBase::Environment::get().getWorld()->scaleObject(ptr, 1.f);
+}
+
+bool RenderingManager::vanityRotateCamera(const float *rot)
+{
+ if(!mCamera->isVanityOrPreviewModeEnabled())
+ return false;
+
+ Ogre::Vector3 vRot(rot);
+ mCamera->rotateCamera(vRot, true);
+ return true;
+}
+
+void RenderingManager::setCameraDistance(float dist, bool adjust, bool override)
+{
+ if(!mCamera->isVanityOrPreviewModeEnabled() && !mCamera->isFirstPerson())
+ {
+ if(mCamera->isNearest() && dist > 0.f)
+ mCamera->toggleViewMode();
+ else
+ mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override);
+ }
+ else if(mCamera->isFirstPerson() && dist < 0.f)
+ {
+ mCamera->toggleViewMode();
+ mCamera->setCameraDistance(0.f, false, override);
+ }
+}
+
+void RenderingManager::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y)
+{
+ return mLocalMap->getInteriorMapPosition (position, nX, nY, x, y);
+}
+
+bool RenderingManager::isPositionExplored (float nX, float nY, int x, int y, bool interior)
+{
+ return mLocalMap->isPositionExplored(nX, nY, x, y, interior);
+}
+
+void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rendering)
+{
+ rendering.setup (mRendering.getScene());
+}
+
+Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
+{
+ Animation *anim = mActors.getAnimation(ptr);
+ if(!anim && ptr.getRefData().getHandle() == "player")
+ anim = mPlayerAnimation;
+ return anim;
+}
+
+
+void RenderingManager::playVideo(const std::string& name, bool allowSkipping)
+{
+ mVideoPlayer->playVideo ("video/" + name, allowSkipping);
+}
+
+void RenderingManager::stopVideo()
+{
+ mVideoPlayer->stopVideo ();
+}
+
+void RenderingManager::addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale, float force)
+{
+ mWater->addEmitter (ptr, scale, force);
+}
+
+void RenderingManager::removeWaterRippleEmitter (const MWWorld::Ptr& ptr)
+{
+ mWater->removeEmitter (ptr);
+}
+
+void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr)
+{
+ mWater->updateEmitterPtr(old, ptr);
+}
+
+void RenderingManager::frameStarted(float dt, bool paused)
+{
+ if (mTerrain)
+ mTerrain->update(mRendering.getCamera()->getRealPosition());
+
+ if (!paused)
+ mWater->frameStarted(dt);
+}
+
+void RenderingManager::resetCamera()
+{
+ mCamera->reset();
+}
+
+float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
+{
+ if (!mTerrain || !mTerrain->getVisible())
+ return -std::numeric_limits<float>::max();
+ return mTerrain->getHeightAt(worldPos);
+}
+
+void RenderingManager::enableTerrain(bool enable)
+{
+ if (enable)
+ {
+ if (!mTerrain)
+ {
+ Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
+ Loading::ScopedLoad load(listener);
+ mTerrain = new Terrain::World(listener, mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
+ Settings::Manager::getBool("distant land", "Terrain"),
+ Settings::Manager::getBool("shader", "Terrain"));
+ mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
+ Settings::Manager::getBool("split", "Shadows"));
+ mTerrain->update(mRendering.getCamera()->getRealPosition());
+ mTerrain->setLoadingListener(NULL);
+ }
+ mTerrain->setVisible(true);
+ }
+ else
+ if (mTerrain)
+ mTerrain->setVisible(false);
+}
+
+} // namespace
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
new file mode 100644
index 0000000000..2d08139128
--- /dev/null
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -0,0 +1,279 @@
+#ifndef _GAME_RENDERING_MANAGER_H
+#define _GAME_RENDERING_MANAGER_H
+
+#include "sky.hpp"
+#include "debugging.hpp"
+
+#include <openengine/ogre/fader.hpp>
+
+#include <components/settings/settings.hpp>
+
+#include <boost/filesystem.hpp>
+
+#include <OgreRenderTargetListener.h>
+
+#include "renderinginterface.hpp"
+
+#include "objects.hpp"
+#include "actors.hpp"
+#include "camera.hpp"
+#include "occlusionquery.hpp"
+
+namespace Ogre
+{
+ class SceneManager;
+ class SceneNode;
+ class Quaternion;
+ class Vector3;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+ class CellStore;
+}
+
+namespace sh
+{
+ class Factory;
+}
+
+namespace Terrain
+{
+ class World;
+}
+
+namespace MWRender
+{
+ class Shadows;
+ class LocalMap;
+ class Water;
+ class Compositors;
+ class ExternalRendering;
+ class GlobalMap;
+ class VideoPlayer;
+ class Animation;
+
+class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener
+{
+private:
+ virtual MWRender::Objects& getObjects();
+ virtual MWRender::Actors& getActors();
+
+public:
+ RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir,
+ const boost::filesystem::path& cacheDir, OEngine::Physic::PhysicEngine* engine,
+ MWWorld::Fallback* fallback);
+ virtual ~RenderingManager();
+
+ void togglePOV()
+ { mCamera->toggleViewMode(); }
+
+ void togglePreviewMode(bool enable)
+ { mCamera->togglePreviewMode(enable); }
+
+ bool toggleVanityMode(bool enable)
+ { return mCamera->toggleVanityMode(enable); }
+
+ void allowVanityMode(bool allow)
+ { mCamera->allowVanityMode(allow); }
+
+ void togglePlayerLooking(bool enable)
+ { mCamera->togglePlayerLooking(enable); }
+
+ void changeVanityModeScale(float factor)
+ {
+ if(mCamera->isVanityOrPreviewModeEnabled())
+ mCamera->setCameraDistance(-factor/120.f*10, true, true);
+ }
+
+ void resetCamera();
+
+ bool vanityRotateCamera(const float *rot);
+ void setCameraDistance(float dist, bool adjust = false, bool override = true);
+
+ void setupPlayer(const MWWorld::Ptr &ptr);
+ void renderPlayer(const MWWorld::Ptr &ptr);
+
+ SkyManager* getSkyManager();
+ Compositors* getCompositors();
+
+ void toggleLight();
+ bool toggleRenderMode(int mode);
+
+ OEngine::Render::Fader* getFader();
+
+ void removeCell (MWWorld::CellStore *store);
+
+ /// \todo this function should be removed later. Instead the rendering subsystems should track
+ /// when rebatching is needed and update automatically at the end of each frame.
+ void cellAdded (MWWorld::CellStore *store);
+ void waterAdded(MWWorld::CellStore *store);
+
+ void enableTerrain(bool enable);
+
+ void removeWater();
+
+ void preCellChange (MWWorld::CellStore* store);
+ ///< this event is fired immediately before changing cell
+
+ void addObject (const MWWorld::Ptr& ptr);
+ void removeObject (const MWWorld::Ptr& ptr);
+
+ void moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position);
+ void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale);
+
+ /// Updates an object's rotation
+ void rotateObject (const MWWorld::Ptr& ptr);
+
+ void setWaterHeight(const float height);
+ void toggleWater();
+
+ /// Updates object rendering after cell change
+ /// \param old Object reference in previous cell
+ /// \param cur Object reference in new cell
+ void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur);
+
+ /// Specifies an updated Ptr object for the player (used on cell change).
+ void updatePlayerPtr(const MWWorld::Ptr &ptr);
+
+ /// Currently for NPCs only. Rebuilds the NPC, updating their root model, animation sources,
+ /// and equipment.
+ void rebuildPtr(const MWWorld::Ptr &ptr);
+
+ void update (float duration, bool paused);
+
+ void setAmbientColour(const Ogre::ColourValue& colour);
+ void setSunColour(const Ogre::ColourValue& colour);
+ void setSunDirection(const Ogre::Vector3& direction);
+ void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0)
+ void sunDisable(bool real);
+
+ void disableLights(bool sun); ///< @param sun whether or not to really disable the sunlight (otherwise just set diffuse to 0)
+ void enableLights(bool sun);
+
+
+ void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+ void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+
+ bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }
+ OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }
+
+ float getTerrainHeightAt (Ogre::Vector3 worldPos);
+
+ Shadows* getShadows();
+
+ void switchToInterior();
+ void switchToExterior();
+
+ void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches);
+
+ void setGlare(bool glare);
+ void skyEnable ();
+ void skyDisable ();
+ void skySetHour (double hour);
+ void skySetDate (int day, int month);
+ int skyGetMasserPhase() const;
+ int skyGetSecundaPhase() const;
+ void skySetMoonColour (bool red);
+ void configureAmbient(MWWorld::CellStore &mCell);
+
+ void addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f);
+ void removeWaterRippleEmitter (const MWWorld::Ptr& ptr);
+ void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr);
+
+ void requestMap (MWWorld::CellStore* cell);
+ ///< request the local map for a cell
+
+ /// configure fog according to cell
+ void configureFog(MWWorld::CellStore &mCell);
+
+ /// configure fog manually
+ void configureFog(const float density, const Ogre::ColourValue& colour);
+
+ Ogre::Vector4 boundingBoxToScreen(Ogre::AxisAlignedBox bounds);
+ ///< transform the specified bounding box (in world coordinates) into screen coordinates.
+ /// @return packed vector4 (min_x, min_y, max_x, max_y)
+
+ void processChangedSettings(const Settings::CategorySettingVector& settings);
+
+ Ogre::Viewport* getViewport() { return mRendering.getViewport(); }
+
+ void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y);
+ ///< see MWRender::LocalMap::getInteriorMapPosition
+
+ bool isPositionExplored (float nX, float nY, int x, int y, bool interior);
+ ///< see MWRender::LocalMap::isPositionExplored
+
+ void setupExternalRendering (MWRender::ExternalRendering& rendering);
+
+ Animation* getAnimation(const MWWorld::Ptr &ptr);
+
+ void playVideo(const std::string& name, bool allowSkipping);
+ void stopVideo();
+ void frameStarted(float dt, bool paused);
+
+protected:
+ virtual void windowResized(int x, int y);
+
+private:
+ sh::Factory* mFactory;
+
+ void setAmbientMode();
+ void applyFog(bool underwater);
+
+ void setMenuTransparency(float val);
+
+ void applyCompositors();
+
+ bool mSunEnabled;
+
+ MWWorld::Fallback* mFallback;
+
+ SkyManager* mSkyManager;
+
+ OcclusionQuery* mOcclusionQuery;
+
+ Terrain::World* mTerrain;
+
+ MWRender::Water *mWater;
+
+ GlobalMap* mGlobalMap;
+
+ OEngine::Render::OgreRenderer &mRendering;
+
+ MWRender::Objects mObjects;
+ MWRender::Actors mActors;
+
+ MWRender::NpcAnimation *mPlayerAnimation;
+
+ // 0 normal, 1 more bright, 2 max
+ int mAmbientMode;
+
+ Ogre::ColourValue mAmbientColor;
+ Ogre::Light* mSun;
+
+ Ogre::SceneNode *mRootNode;
+
+ Ogre::ColourValue mFogColour;
+ float mFogStart;
+ float mFogEnd;
+
+ OEngine::Physic::PhysicEngine* mPhysicsEngine;
+
+ MWRender::Camera *mCamera;
+
+ MWRender::Debugging *mDebugging;
+
+ MWRender::LocalMap* mLocalMap;
+
+ MWRender::Shadows* mShadows;
+
+ MWRender::Compositors* mCompositors;
+
+ VideoPlayer* mVideoPlayer;
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp
new file mode 100644
index 0000000000..47fbc8ddf6
--- /dev/null
+++ b/apps/openmw/mwrender/ripplesimulation.cpp
@@ -0,0 +1,263 @@
+#include "ripplesimulation.hpp"
+
+#include <OgreTextureManager.h>
+#include <OgreStringConverter.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRoot.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/player.hpp"
+
+namespace MWRender
+{
+
+
+RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager)
+ : mMainSceneMgr(mainSceneManager),
+ mTime(0),
+ mCurrentFrameOffset(0,0),
+ mPreviousFrameOffset(0,0),
+ mRippleCenter(0,0),
+ mTextureSize(512),
+ mRippleAreaLength(1000),
+ mImpulseSize(20),
+ mTexelOffset(0,0),
+ mFirstUpdate(true)
+{
+ Ogre::AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+
+ mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
+
+ mCamera = mSceneMgr->createCamera("RippleCamera");
+
+ mRectangle = new Ogre::Rectangle2D(true);
+ mRectangle->setBoundingBox(aabInf);
+ mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0, false);
+ Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ node->attachObject(mRectangle);
+
+ mImpulse = new Ogre::Rectangle2D(true);
+ mImpulse->setCorners(-0.1, 0.1, 0.1, -0.1, false);
+ mImpulse->setBoundingBox(aabInf);
+ mImpulse->setMaterial("AddImpulse");
+ Ogre::SceneNode* impulseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ impulseNode->attachObject(mImpulse);
+
+ //float w=0.05;
+ for (int i=0; i<4; ++i)
+ {
+ Ogre::TexturePtr texture;
+ if (i != 3)
+ texture = Ogre::TextureManager::getSingleton().createManual("RippleHeight" + Ogre::StringConverter::toString(i),
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
+ else
+ texture = Ogre::TextureManager::getSingleton().createManual("RippleNormal",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
+
+
+ Ogre::RenderTexture* rt = texture->getBuffer()->getRenderTarget();
+ rt->removeAllViewports();
+ rt->addViewport(mCamera);
+ rt->setAutoUpdated(false);
+ rt->getViewport(0)->setClearEveryFrame(false);
+
+ // debug overlay
+ /*
+ Ogre::Rectangle2D* debugOverlay = new Ogre::Rectangle2D(true);
+ debugOverlay->setCorners(w*2-1, 0.9, (w+0.18)*2-1, 0.4, false);
+ w += 0.2;
+ debugOverlay->setBoundingBox(aabInf);
+ Ogre::SceneNode* debugNode = mMainSceneMgr->getRootSceneNode()->createChildSceneNode();
+ debugNode->attachObject(debugOverlay);
+
+ Ogre::MaterialPtr debugMaterial = Ogre::MaterialManager::getSingleton().create("RippleDebug" + Ogre::StringConverter::toString(i),
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+
+ if (i != 3)
+ debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleHeight" + Ogre::StringConverter::toString(i));
+ else
+ debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleNormal");
+ debugMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+
+ debugOverlay->setMaterial("RippleDebug" + Ogre::StringConverter::toString(i));
+ */
+
+ mRenderTargets[i] = rt;
+ mTextures[i] = texture;
+ }
+
+ sh::Factory::getInstance().setSharedParameter("rippleTextureSize", sh::makeProperty<sh::Vector4>(
+ new sh::Vector4(1.0/512, 1.0/512, 512, 512)));
+ sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
+ new sh::Vector3(0, 0, 0)));
+ sh::Factory::getInstance().setSharedParameter("rippleAreaLength", sh::makeProperty<sh::FloatValue>(
+ new sh::FloatValue(mRippleAreaLength)));
+
+}
+
+RippleSimulation::~RippleSimulation()
+{
+ delete mRectangle;
+
+ Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
+}
+
+void RippleSimulation::update(float dt, Ogre::Vector2 position)
+{
+ // try to keep 20 fps
+ mTime += dt;
+
+ while (mTime >= 1/20.0 || mFirstUpdate)
+ {
+ mPreviousFrameOffset = mCurrentFrameOffset;
+
+ mCurrentFrameOffset = position - mRippleCenter;
+ // add texel offsets from previous frame.
+ mCurrentFrameOffset += mTexelOffset;
+
+ mTexelOffset = Ogre::Vector2(std::fmod(mCurrentFrameOffset.x, 1.0f/mTextureSize),
+ std::fmod(mCurrentFrameOffset.y, 1.0f/mTextureSize));
+
+ // now subtract new offset in order to snap to texels
+ mCurrentFrameOffset -= mTexelOffset;
+
+ // texture coordinate space
+ mCurrentFrameOffset /= mRippleAreaLength;
+
+ mRippleCenter = position;
+
+ addImpulses();
+ waterSimulation();
+ heightMapToNormalMap();
+
+ swapHeightMaps();
+ if (!mFirstUpdate)
+ mTime -= 1/20.0;
+ else
+ mFirstUpdate = false;
+ }
+
+ sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
+ new sh::Vector3(mRippleCenter.x + mTexelOffset.x, mRippleCenter.y + mTexelOffset.y, 0)));
+}
+
+void RippleSimulation::addImpulses()
+{
+ mRectangle->setVisible(false);
+ mImpulse->setVisible(true);
+
+ /// \todo it should be more efficient to render all emitters at once
+ for (std::vector<Emitter>::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it)
+ {
+ if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ())
+ {
+ // fetch a new ptr (to handle cell change etc)
+ // for non-player actors this is done in updateObjectCell
+ it->mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ }
+ float* _currentPos = it->mPtr.getRefData().getPosition().pos;
+ Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]);
+
+ if ( (currentPos - it->mLastEmitPosition).length() > 2
+ && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), currentPos))
+ {
+ it->mLastEmitPosition = currentPos;
+
+ Ogre::Vector2 pos (currentPos.x, currentPos.y);
+ pos -= mRippleCenter;
+ pos /= mRippleAreaLength;
+ float size = mImpulseSize / mRippleAreaLength;
+ mImpulse->setCorners(pos.x-size, pos.y+size, pos.x+size, pos.y-size, false);
+
+ // don't render if we are offscreen
+ if (pos.x - size >= 1.0 || pos.y+size <= -1.0 || pos.x+size <= -1.0 || pos.y-size >= 1.0)
+ continue;
+ mRenderTargets[1]->update();
+ }
+ }
+
+ mImpulse->setVisible(false);
+ mRectangle->setVisible(true);
+}
+
+void RippleSimulation::waterSimulation()
+{
+ mRectangle->setMaterial("HeightmapSimulation");
+
+ sh::Factory::getInstance().setTextureAlias("Heightmap0", mTextures[0]->getName());
+ sh::Factory::getInstance().setTextureAlias("Heightmap1", mTextures[1]->getName());
+
+ sh::Factory::getInstance().setSharedParameter("currentFrameOffset", sh::makeProperty<sh::Vector3>(
+ new sh::Vector3(mCurrentFrameOffset.x, mCurrentFrameOffset.y, 0)));
+ sh::Factory::getInstance().setSharedParameter("previousFrameOffset", sh::makeProperty<sh::Vector3>(
+ new sh::Vector3(mPreviousFrameOffset.x, mPreviousFrameOffset.y, 0)));
+
+ mRenderTargets[2]->update();
+}
+
+void RippleSimulation::heightMapToNormalMap()
+{
+ mRectangle->setMaterial("HeightToNormalMap");
+
+ sh::Factory::getInstance().setTextureAlias("Heightmap2", mTextures[2]->getName());
+
+ mRenderTargets[TEX_NORMAL]->update();
+}
+
+void RippleSimulation::swapHeightMaps()
+{
+ // 0 -> 1 -> 2 to 2 -> 0 ->1
+ Ogre::RenderTexture* tmp = mRenderTargets[0];
+ Ogre::TexturePtr tmp2 = mTextures[0];
+
+ mRenderTargets[0] = mRenderTargets[1];
+ mTextures[0] = mTextures[1];
+
+ mRenderTargets[1] = mRenderTargets[2];
+ mTextures[1] = mTextures[2];
+
+ mRenderTargets[2] = tmp;
+ mTextures[2] = tmp2;
+}
+
+void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force)
+{
+ Emitter newEmitter;
+ newEmitter.mPtr = ptr;
+ newEmitter.mScale = scale;
+ newEmitter.mForce = force;
+ newEmitter.mLastEmitPosition = Ogre::Vector3(0,0,0);
+ mEmitters.push_back (newEmitter);
+}
+
+void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr)
+{
+ for (std::vector<Emitter>::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it)
+ {
+ if (it->mPtr == ptr)
+ {
+ mEmitters.erase(it);
+ return;
+ }
+ }
+}
+
+void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr)
+{
+ for (std::vector<Emitter>::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it)
+ {
+ if (it->mPtr == old)
+ {
+ it->mPtr = ptr;
+ return;
+ }
+ }
+}
+
+
+}
diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp
new file mode 100644
index 0000000000..7e7eebc1cf
--- /dev/null
+++ b/apps/openmw/mwrender/ripplesimulation.hpp
@@ -0,0 +1,85 @@
+#ifndef RIPPLE_SIMULATION_H
+#define RIPPLE_SIMULATION_H
+
+#include <OgreTexture.h>
+#include <OgreMaterial.h>
+#include <OgreVector2.h>
+#include <OgreVector3.h>
+
+#include "../mwworld/ptr.hpp"
+
+namespace Ogre
+{
+ class RenderTexture;
+ class Camera;
+ class SceneManager;
+ class Rectangle2D;
+}
+
+namespace MWRender
+{
+
+struct Emitter
+{
+ MWWorld::Ptr mPtr;
+ Ogre::Vector3 mLastEmitPosition;
+ float mScale;
+ float mForce;
+};
+
+class RippleSimulation
+{
+public:
+ RippleSimulation(Ogre::SceneManager* mainSceneManager);
+ ~RippleSimulation();
+
+ void update(float dt, Ogre::Vector2 position);
+
+ /// adds an emitter, position will be tracked automatically
+ void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f);
+ void removeEmitter (const MWWorld::Ptr& ptr);
+ void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr);
+
+private:
+ std::vector<Emitter> mEmitters;
+
+ Ogre::RenderTexture* mRenderTargets[4];
+ Ogre::TexturePtr mTextures[4];
+
+ int mTextureSize;
+ float mRippleAreaLength;
+ float mImpulseSize;
+
+ bool mFirstUpdate;
+
+ Ogre::Camera* mCamera;
+
+ // own scenemanager to render our simulation
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::Rectangle2D* mRectangle;
+
+ // scenemanager to create the debug overlays on
+ Ogre::SceneManager* mMainSceneMgr;
+
+ static const int TEX_NORMAL = 3;
+
+ Ogre::Rectangle2D* mImpulse;
+
+ void addImpulses();
+ void heightMapToNormalMap();
+ void waterSimulation();
+ void swapHeightMaps();
+
+ float mTime;
+
+ Ogre::Vector2 mRippleCenter;
+
+ Ogre::Vector2 mTexelOffset;
+
+ Ogre::Vector2 mCurrentFrameOffset;
+ Ogre::Vector2 mPreviousFrameOffset;
+};
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp
new file mode 100644
index 0000000000..21bbe51b63
--- /dev/null
+++ b/apps/openmw/mwrender/shadows.cpp
@@ -0,0 +1,195 @@
+#include "shadows.hpp"
+
+#include <components/settings/settings.hpp>
+#include <openengine/ogre/renderer.hpp>
+
+#include <OgreSceneManager.h>
+#include <OgreColourValue.h>
+#include <OgreShadowCameraSetupLiSPSM.h>
+#include <OgreShadowCameraSetupPSSM.h>
+#include <OgreHardwarePixelBuffer.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+#include "renderconst.hpp"
+
+using namespace Ogre;
+using namespace MWRender;
+
+Shadows::Shadows(OEngine::Render::OgreRenderer* rend) :
+ mShadowFar(1000), mFadeStart(0.9)
+{
+ mRendering = rend;
+ mSceneMgr = mRendering->getScene();
+ recreate();
+}
+
+void Shadows::recreate()
+{
+ bool enabled = Settings::Manager::getBool("enabled", "Shadows");
+
+ bool split = Settings::Manager::getBool("split", "Shadows");
+
+ sh::Factory::getInstance ().setGlobalSetting ("shadows", enabled && !split ? "true" : "false");
+ sh::Factory::getInstance ().setGlobalSetting ("shadows_pssm", enabled && split ? "true" : "false");
+
+ if (!enabled)
+ {
+ mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE);
+ return;
+ }
+
+ int texsize = Settings::Manager::getInt("texture size", "Shadows");
+ mSceneMgr->setShadowTextureSize(texsize);
+
+ mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED);
+
+ // no point light shadows, i'm afraid. might revisit this with Deferred Shading
+ mSceneMgr->setShadowTextureCountPerLightType(Light::LT_POINT, 0);
+
+ mSceneMgr->setShadowTextureCountPerLightType(Light::LT_DIRECTIONAL, split ? 3 : 1);
+ mSceneMgr->setShadowTextureCount(split ? 3 : 1);
+
+ mSceneMgr->setShadowTextureSelfShadow(true);
+ mSceneMgr->setShadowCasterRenderBackFaces(true);
+ mSceneMgr->setShadowTextureCasterMaterial("openmw_shadowcaster_default");
+ mSceneMgr->setShadowTexturePixelFormat(PF_FLOAT32_R);
+ mSceneMgr->setShadowDirectionalLightExtrusionDistance(1000000);
+
+ mShadowFar = split ? Settings::Manager::getInt("split shadow distance", "Shadows") : Settings::Manager::getInt("shadow distance", "Shadows");
+ mSceneMgr->setShadowFarDistance(mShadowFar);
+
+ mFadeStart = Settings::Manager::getFloat("fade start", "Shadows");
+
+ ShadowCameraSetupPtr shadowCameraSetup;
+ if (split)
+ {
+ mPSSMSetup = new PSSMShadowCameraSetup();
+
+ // Make sure to keep this in sync with the camera's near clip distance!
+ mPSSMSetup->setSplitPadding(mRendering->getCamera()->getNearClipDistance());
+
+ mPSSMSetup->calculateSplitPoints(3, mRendering->getCamera()->getNearClipDistance(), mShadowFar);
+
+ const Real adjustFactors[3] = {64, 64, 64};
+ for (int i=0; i < 3; ++i)
+ {
+ mPSSMSetup->setOptimalAdjustFactor(i, adjustFactors[i]);
+ /*if (i==0)
+ mSceneMgr->setShadowTextureConfig(i, texsize, texsize, Ogre::PF_FLOAT32_R);
+ else if (i ==1)
+ mSceneMgr->setShadowTextureConfig(i, texsize/2, texsize/2, Ogre::PF_FLOAT32_R);
+ else if (i ==2)
+ mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/
+ }
+
+ // Populate from split point 1, not 0, since split 0 isn't useful (usually 0)
+ const PSSMShadowCameraSetup::SplitPointList& splitPointList = getPSSMSetup()->getSplitPoints();
+ sh::Vector3* splitPoints = new sh::Vector3(splitPointList[1], splitPointList[2], splitPointList[3]);
+
+ sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty<sh::Vector3>(splitPoints));
+
+ shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup);
+ }
+ else
+ {
+ LiSPSMShadowCameraSetup* lispsmSetup = new LiSPSMShadowCameraSetup();
+ lispsmSetup->setOptimalAdjustFactor(64);
+ //lispsmSetup->setCameraLightDirectionThreshold(Degree(0));
+ //lispsmSetup->setUseAggressiveFocusRegion(false);
+ shadowCameraSetup = ShadowCameraSetupPtr(lispsmSetup);
+ }
+ mSceneMgr->setShadowCameraSetup(shadowCameraSetup);
+
+ sh::Vector4* shadowFar_fadeStart = new sh::Vector4(mShadowFar, mFadeStart * mShadowFar, 0, 0);
+ sh::Factory::getInstance ().setSharedParameter ("shadowFar_fadeStart", sh::makeProperty<sh::Vector4>(shadowFar_fadeStart));
+
+ // Set visibility mask for the shadow render textures
+ int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows")
+ + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows")
+ + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows")
+ + RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows"));
+ for (int i = 0; i < (split ? 3 : 1); ++i)
+ {
+ TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i);
+ Viewport* vp = shadowTexture->getBuffer()->getRenderTarget()->getViewport(0);
+ vp->setVisibilityMask(visibilityMask);
+ }
+
+ // --------------------------------------------------------------------------------------------------------------------
+ // --------------------------- Debug overlays to display the content of shadow maps -----------------------------------
+ // --------------------------------------------------------------------------------------------------------------------
+ /*
+ if (Settings::Manager::getBool("debug", "Shadows"))
+ {
+ OverlayManager& mgr = OverlayManager::getSingleton();
+ Overlay* overlay;
+
+ // destroy if already exists
+ if ((overlay = mgr.getByName("DebugOverlay")))
+ mgr.destroy(overlay);
+
+ overlay = mgr.create("DebugOverlay");
+ for (size_t i = 0; i < (split ? 3 : 1); ++i) {
+ TexturePtr tex = mRendering->getScene()->getShadowTexture(i);
+
+ // Set up a debug panel to display the shadow
+
+ if (MaterialManager::getSingleton().resourceExists("Ogre/DebugTexture" + StringConverter::toString(i)))
+ MaterialManager::getSingleton().remove("Ogre/DebugTexture" + StringConverter::toString(i));
+ MaterialPtr debugMat = MaterialManager::getSingleton().create(
+ "Ogre/DebugTexture" + StringConverter::toString(i),
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+
+ debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName());
+ t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
+
+ OverlayContainer* debugPanel;
+
+ // destroy container if exists
+ try
+ {
+ if ((debugPanel =
+ static_cast<OverlayContainer*>(
+ mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i)
+ ))))
+ mgr.destroyOverlayElement(debugPanel);
+ }
+ catch (Ogre::Exception&) {}
+
+ debugPanel = (OverlayContainer*)
+ (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/DebugTexPanel" + StringConverter::toString(i)));
+ debugPanel->_setPosition(0.8, i*0.25);
+ debugPanel->_setDimensions(0.2, 0.24);
+ debugPanel->setMaterialName(debugMat->getName());
+ debugPanel->show();
+ overlay->add2D(debugPanel);
+ overlay->show();
+ }
+ }
+ else
+ {
+ OverlayManager& mgr = OverlayManager::getSingleton();
+ Overlay* overlay;
+
+ if ((overlay = mgr.getByName("DebugOverlay")))
+ mgr.destroy(overlay);
+ }
+ */
+}
+
+PSSMShadowCameraSetup* Shadows::getPSSMSetup()
+{
+ return mPSSMSetup;
+}
+
+float Shadows::getShadowFar() const
+{
+ return mShadowFar;
+}
+
+float Shadows::getFadeStart() const
+{
+ return mFadeStart;
+}
diff --git a/apps/openmw/mwrender/shadows.hpp b/apps/openmw/mwrender/shadows.hpp
new file mode 100644
index 0000000000..bc2b141f70
--- /dev/null
+++ b/apps/openmw/mwrender/shadows.hpp
@@ -0,0 +1,39 @@
+#ifndef GAME_SHADOWS_H
+#define GAME_SHADOWS_H
+
+// forward declares
+namespace Ogre
+{
+ class SceneManager;
+ class PSSMShadowCameraSetup;
+}
+namespace OEngine{
+ namespace Render{
+ class OgreRenderer;
+ }
+}
+
+namespace MWRender
+{
+ class Shadows
+ {
+ public:
+ Shadows(OEngine::Render::OgreRenderer* rend);
+
+ void recreate();
+
+ Ogre::PSSMShadowCameraSetup* getPSSMSetup();
+ float getShadowFar() const;
+ float getFadeStart() const;
+
+ protected:
+ OEngine::Render::OgreRenderer* mRendering;
+ Ogre::SceneManager* mSceneMgr;
+
+ Ogre::PSSMShadowCameraSetup* mPSSMSetup;
+ float mShadowFar;
+ float mFadeStart;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp
new file mode 100644
index 0000000000..03e14dc07d
--- /dev/null
+++ b/apps/openmw/mwrender/sky.cpp
@@ -0,0 +1,646 @@
+#include "sky.hpp"
+
+#include <OgreCamera.h>
+#include <OgreRenderWindow.h>
+#include <OgreSceneNode.h>
+#include <OgreMesh.h>
+#include <OgreSubMesh.h>
+#include <OgreSceneManager.h>
+#include <OgreHardwareVertexBuffer.h>
+#include <OgreHighLevelGpuProgramManager.h>
+#include <OgreBillboardSet.h>
+#include <OgreEntity.h>
+#include <OgreSubEntity.h>
+
+#include <OgreMeshManager.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <components/nifogre/ogrenifloader.hpp>
+
+#include <extern/shiny/Platforms/Ogre/OgreMaterial.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/fallback.hpp"
+
+#include "renderconst.hpp"
+#include "renderingmanager.hpp"
+
+using namespace MWRender;
+using namespace Ogre;
+
+BillboardObject::BillboardObject( const String& textureName,
+ const float initialSize,
+ const Vector3& position,
+ SceneNode* rootNode,
+ const std::string& material)
+{
+ SceneManager* sceneMgr = rootNode->getCreator();
+
+ Vector3 finalPosition = position.normalisedCopy() * 1000.f;
+
+ static unsigned int bodyCount=0;
+
+ mMaterial = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), material);
+ mMaterial->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName)));
+
+ static Ogre::Mesh* plane = MeshManager::getSingleton().createPlane("billboard",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::Plane(Ogre::Vector3(0,0,1), 0), 1, 1, 1, 1, true, 1, 1, 1, Vector3::UNIT_Y).get();
+ plane->_setBounds(Ogre::AxisAlignedBox::BOX_INFINITE);
+ mEntity = sceneMgr->createEntity("billboard");
+ mEntity->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount));
+ mEntity->setVisibilityFlags(RV_Sky);
+ mEntity->setCastShadows(false);
+
+ mNode = rootNode->createChildSceneNode();
+ mNode->setPosition(finalPosition);
+ mNode->attachObject(mEntity);
+ mNode->setScale(Ogre::Vector3(450.f*initialSize));
+ mNode->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-position.normalisedCopy()));
+
+ sh::Factory::getInstance().getMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount))->setListener(this);
+
+ bodyCount++;
+}
+
+BillboardObject::BillboardObject()
+{
+}
+
+void BillboardObject::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration)
+{
+}
+
+void BillboardObject::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration)
+{
+ setVisibility(mVisibility);
+ setColour(mColour);
+}
+
+void BillboardObject::setVisible(const bool visible)
+{
+ mEntity->setVisible(visible);
+}
+
+void BillboardObject::setSize(const float size)
+{
+ mNode->setScale(450.f*size, 450.f*size, 450.f*size);
+}
+
+void BillboardObject::setVisibility(const float visibility)
+{
+ mVisibility = visibility;
+ Ogre::MaterialPtr m = static_cast<sh::OgreMaterial*>(mMaterial->getMaterial ())->getOgreMaterial ();
+ for (int i=0; i<m->getNumTechniques(); ++i)
+ {
+ Ogre::Technique* t = m->getTechnique(i);
+ if (t->getNumPasses ())
+ t->getPass(0)->setDiffuse (0,0,0, visibility);
+ }
+}
+
+void BillboardObject::setPosition(const Vector3& pPosition)
+{
+ Vector3 normalised = pPosition.normalisedCopy();
+ Vector3 finalPosition = normalised * 1000.f;
+ mNode->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-normalised));
+ mNode->setPosition(finalPosition);
+}
+
+Vector3 BillboardObject::getPosition() const
+{
+ return mNode->getPosition();
+}
+
+void BillboardObject::setVisibilityFlags(int flags)
+{
+ mEntity->setVisibilityFlags(flags);
+}
+
+void BillboardObject::setColour(const ColourValue& pColour)
+{
+ mColour = pColour;
+ Ogre::MaterialPtr m = static_cast<sh::OgreMaterial*>(mMaterial->getMaterial ())->getOgreMaterial ();
+ for (int i=0; i<m->getNumTechniques(); ++i)
+ {
+ Ogre::Technique* t = m->getTechnique(i);
+ if (t->getNumPasses ())
+ t->getPass(0)->setSelfIllumination (pColour);
+ }
+}
+
+void BillboardObject::setRenderQueue(unsigned int id)
+{
+ mEntity->setRenderQueueGroup(id);
+}
+
+SceneNode* BillboardObject::getNode()
+{
+ return mNode;
+}
+
+Moon::Moon( const String& textureName,
+ const float initialSize,
+ const Vector3& position,
+ SceneNode* rootNode,
+ const std::string& material)
+ : BillboardObject(textureName, initialSize, position, rootNode, material)
+{
+ setVisibility(1.0);
+
+ mPhase = Moon::Phase_Full;
+}
+
+void Moon::setType(const Moon::Type& type)
+{
+ mType = type;
+}
+
+void Moon::setPhase(const Moon::Phase& phase)
+{
+ // Colour texture
+ Ogre::String textureName = "textures\\tx_";
+
+ if (mType == Moon::Type_Secunda) textureName += "secunda_";
+ else textureName += "masser_";
+
+ if (phase == Moon::Phase_New) textureName += "new";
+ else if (phase == Moon::Phase_WaxingCrescent) textureName += "one_wax";
+ else if (phase == Moon::Phase_WaxingHalf) textureName += "half_wax";
+ else if (phase == Moon::Phase_WaxingGibbous) textureName += "three_wax";
+ else if (phase == Moon::Phase_WaningCrescent) textureName += "one_wan";
+ else if (phase == Moon::Phase_WaningHalf) textureName += "half_wan";
+ else if (phase == Moon::Phase_WaningGibbous) textureName += "three_wan";
+ else if (phase == Moon::Phase_Full) textureName += "full";
+
+ textureName += ".dds";
+
+ if (mType == Moon::Type_Secunda)
+ sh::Factory::getInstance ().setTextureAlias ("secunda_texture", textureName);
+ else
+ sh::Factory::getInstance ().setTextureAlias ("masser_texture", textureName);
+
+ mPhase = phase;
+}
+
+Moon::Phase Moon::getPhase() const
+{
+ return mPhase;
+}
+
+unsigned int Moon::getPhaseInt() const
+{
+ if (mPhase == Moon::Phase_New) return 0;
+ else if (mPhase == Moon::Phase_WaxingCrescent) return 1;
+ else if (mPhase == Moon::Phase_WaningCrescent) return 1;
+ else if (mPhase == Moon::Phase_WaxingHalf) return 2;
+ else if (mPhase == Moon::Phase_WaningHalf) return 2;
+ else if (mPhase == Moon::Phase_WaxingGibbous) return 3;
+ else if (mPhase == Moon::Phase_WaningGibbous) return 3;
+ else if (mPhase == Moon::Phase_Full) return 4;
+
+ return 0;
+}
+
+SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera)
+ : mHour(0.0f)
+ , mDay(0)
+ , mMonth(0)
+ , mSun(NULL)
+ , mSunGlare(NULL)
+ , mMasser(NULL)
+ , mSecunda(NULL)
+ , mCamera(pCamera)
+ , mRootNode(NULL)
+ , mSceneMgr(NULL)
+ , mAtmosphereDay(NULL)
+ , mAtmosphereNight(NULL)
+ , mClouds()
+ , mNextClouds()
+ , mCloudBlendFactor(0.0f)
+ , mCloudOpacity(0.0f)
+ , mCloudSpeed(0.0f)
+ , mStarsOpacity(0.0f)
+ , mRemainingTransitionTime(0.0f)
+ , mGlareFade(0.0f)
+ , mGlare(0.0f)
+ , mEnabled(true)
+ , mSunEnabled(true)
+ , mMasserEnabled(true)
+ , mSecundaEnabled(true)
+ , mCreated(false)
+ , mCloudAnimationTimer(0.f)
+ , mMoonRed(false)
+{
+ mSceneMgr = root->getCreator();
+ mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ mRootNode->setInheritOrientation(false);
+}
+
+void SkyManager::create()
+{
+ assert(!mCreated);
+
+ sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(0)));
+ sh::Factory::getInstance().setSharedParameter ("cloudOpacity",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(1)));
+ sh::Factory::getInstance().setSharedParameter ("cloudColour",
+ sh::makeProperty<sh::Vector3>(new sh::Vector3(1,1,1)));
+ sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(0)));
+ sh::Factory::getInstance().setSharedParameter ("nightFade",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(0)));
+ sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty<sh::Vector4>(new sh::Vector4(0,0,0,1)));
+ sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty<sh::Vector4>(new sh::Vector4(0,0,0,1)));
+
+ sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "");
+ sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", "");
+
+ // Create light used for thunderstorm
+ mLightning = mSceneMgr->createLight();
+ mLightning->setType (Ogre::Light::LT_DIRECTIONAL);
+ mLightning->setDirection (Ogre::Vector3(0.3, -0.7, 0.3));
+ mLightning->setVisible (false);
+ mLightning->setDiffuseColour (ColourValue(3,3,3));
+
+ const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback();
+ mSecunda = new Moon("secunda_texture", fallback->getFallbackFloat("Moons_Secunda_Size")/100, Vector3(-0.4, 0.4, 0.5), mRootNode, "openmw_moon");
+ mSecunda->setType(Moon::Type_Secunda);
+ mSecunda->setRenderQueue(RQG_SkiesEarly+4);
+
+ mMasser = new Moon("masser_texture", fallback->getFallbackFloat("Moons_Masser_Size")/100, Vector3(-0.4, 0.4, 0.5), mRootNode, "openmw_moon");
+ mMasser->setRenderQueue(RQG_SkiesEarly+3);
+ mMasser->setType(Moon::Type_Masser);
+
+ mSun = new BillboardObject("textures\\tx_sun_05.dds", 1, Vector3(0.4, 0.4, 0.4), mRootNode, "openmw_sun");
+ mSun->setRenderQueue(RQG_SkiesEarly+4);
+ mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4, 0.4, 0.4), mRootNode, "openmw_sun");
+ mSunGlare->setRenderQueue(RQG_SkiesLate);
+ mSunGlare->setVisibilityFlags(RV_NoReflection);
+
+ Ogre::AxisAlignedBox aabInf = Ogre::AxisAlignedBox::BOX_INFINITE;
+
+ // 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++)
+ {
+ Entity* night1_ent = objects.mEntities[i];
+ night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1);
+ night1_ent->setVisibilityFlags(RV_Sky);
+ night1_ent->setCastShadows(false);
+ night1_ent->getMesh()->_setBounds (aabInf);
+
+ for (unsigned int j=0; j<night1_ent->getNumSubEntities(); ++j)
+ {
+ std::string matName = "openmw_stars_" + boost::lexical_cast<std::string>(matidx++);
+ sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance(matName, "openmw_stars");
+
+ std::string textureName = sh::retrieveValue<sh::StringValue>(
+ sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity(j)->getMaterialName())->getProperty("diffuseMap"), NULL).get();
+
+ m->setProperty("texture", sh::makeProperty<sh::StringValue>(new sh::StringValue(textureName)));
+
+ night1_ent->getSubEntity(j)->setMaterialName(matName);
+ }
+ }
+
+
+ // Atmosphere (day)
+ mAtmosphereDay = mRootNode->createChildSceneNode();
+ objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif");
+ for(size_t i = 0;i < objects.mEntities.size();i++)
+ {
+ Entity* atmosphere_ent = objects.mEntities[i];
+ atmosphere_ent->setCastShadows(false);
+ atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly);
+ atmosphere_ent->setVisibilityFlags(RV_Sky);
+
+ for(unsigned int j = 0;j < atmosphere_ent->getNumSubEntities();j++)
+ atmosphere_ent->getSubEntity (j)->setMaterialName("openmw_atmosphere");
+
+ // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions
+ atmosphere_ent->getMesh()->_setBounds (aabInf);
+ }
+
+
+ // 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++)
+ {
+ 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++)
+ clouds_ent->getSubEntity(j)->setMaterialName("openmw_clouds");
+ clouds_ent->setCastShadows(false);
+ // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions
+ clouds_ent->getMesh()->_setBounds (aabInf);
+ }
+
+ mCreated = true;
+}
+
+SkyManager::~SkyManager()
+{
+ delete mSun;
+ delete mSunGlare;
+ delete mMasser;
+ delete mSecunda;
+}
+
+int SkyManager::getMasserPhase() const
+{
+ if (!mCreated) return 0;
+ return mMasser->getPhaseInt();
+}
+
+int SkyManager::getSecundaPhase() const
+{
+ if (!mCreated) return 0;
+ return mSecunda->getPhaseInt();
+}
+
+void SkyManager::update(float duration)
+{
+ if (!mEnabled) return;
+ const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback();
+
+ // UV Scroll the clouds
+ mCloudAnimationTimer += duration * mCloudSpeed;
+ sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(mCloudAnimationTimer)));
+
+ /// \todo improve this
+ mMasser->setPhase( static_cast<Moon::Phase>( (int) ((mDay % 32)/4.f)) );
+ mSecunda->setPhase ( static_cast<Moon::Phase>( (int) ((mDay % 32)/4.f)) );
+
+ mSecunda->setColour ( mMoonRed ? fallback->getFallbackColour("Moons_Script_Color") : ColourValue(1,1,1,1));
+ mMasser->setColour (ColourValue(1,1,1,1));
+
+ if (mSunEnabled)
+ {
+ // take 1/10 sec for fading the glare effect from invisible to full
+ if (mGlareFade > mGlare)
+ {
+ mGlareFade -= duration*10;
+ if (mGlareFade < mGlare) mGlareFade = mGlare;
+ }
+ else if (mGlareFade < mGlare)
+ {
+ mGlareFade += duration*10;
+ if (mGlareFade > mGlare) mGlareFade = mGlare;
+ }
+
+ // increase the strength of the sun glare effect depending
+ // on how directly the player is looking at the sun
+ Vector3 sun = mSunGlare->getPosition();
+ Vector3 cam = mCamera->getRealDirection();
+ const Degree angle = sun.angleBetween( cam );
+ float val = 1- (angle.valueDegrees() / 180.f);
+ val = (val*val*val*val)*6;
+ mSunGlare->setSize(val * mGlareFade);
+ }
+
+ mSunGlare->setVisible(mSunEnabled);
+ mSun->setVisible(mSunEnabled);
+ mMasser->setVisible(mMasserEnabled);
+ mSecunda->setVisible(mSecundaEnabled);
+
+ // rotate the stars by 360 degrees every 4 days
+ mAtmosphereNight->roll(Degree(MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*360 / (3600*96.f)));
+}
+
+void SkyManager::enable()
+{
+ if (!mCreated)
+ create();
+
+ mRootNode->setVisible(true);
+ mEnabled = true;
+}
+
+void SkyManager::disable()
+{
+ mRootNode->setVisible(false);
+ mEnabled = false;
+}
+
+void SkyManager::setMoonColour (bool red)
+{
+ mMoonRed = red;
+}
+
+void SkyManager::setWeather(const MWWorld::WeatherResult& weather)
+{
+ if (!mCreated) return;
+
+ if (mClouds != weather.mCloudTexture)
+ {
+ sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture);
+ mClouds = weather.mCloudTexture;
+ }
+
+ if (mNextClouds != weather.mNextCloudTexture)
+ {
+ sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", "textures\\"+weather.mNextCloudTexture);
+ mNextClouds = weather.mNextCloudTexture;
+ }
+
+ if (mCloudBlendFactor != weather.mCloudBlendFactor)
+ {
+ mCloudBlendFactor = weather.mCloudBlendFactor;
+ sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(weather.mCloudBlendFactor)));
+ }
+
+ if (mCloudOpacity != weather.mCloudOpacity)
+ {
+ mCloudOpacity = weather.mCloudOpacity;
+ sh::Factory::getInstance().setSharedParameter ("cloudOpacity",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(weather.mCloudOpacity)));
+ }
+
+ if (mCloudColour != weather.mSunColor)
+ {
+ ColourValue clr( weather.mSunColor.r*0.7 + weather.mAmbientColor.r*0.7,
+ weather.mSunColor.g*0.7 + weather.mAmbientColor.g*0.7,
+ weather.mSunColor.b*0.7 + weather.mAmbientColor.b*0.7);
+
+ sh::Factory::getInstance().setSharedParameter ("cloudColour",
+ sh::makeProperty<sh::Vector3>(new sh::Vector3(clr.r, clr.g, clr.b)));
+
+ mCloudColour = weather.mSunColor;
+ }
+
+ if (mSkyColour != weather.mSkyColor)
+ {
+ mSkyColour = weather.mSkyColor;
+ sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty<sh::Vector4>(new sh::Vector4(
+ weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, weather.mSkyColor.a)));
+ }
+
+ if (mFogColour != weather.mFogColor)
+ {
+ mFogColour = weather.mFogColor;
+ sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty<sh::Vector4>(new sh::Vector4(
+ weather.mFogColor.r, weather.mFogColor.g, weather.mFogColor.b, weather.mFogColor.a)));
+ }
+
+ mCloudSpeed = weather.mCloudSpeed;
+
+ if (weather.mNight && mStarsOpacity != weather.mNightFade)
+ {
+ if (weather.mNightFade == 0)
+ mAtmosphereNight->setVisible(false);
+ else
+ {
+ mAtmosphereNight->setVisible(true);
+
+ sh::Factory::getInstance().setSharedParameter ("nightFade",
+ sh::makeProperty<sh::FloatValue>(new sh::FloatValue(weather.mNightFade)));
+
+ mStarsOpacity = weather.mNightFade;
+ }
+ }
+
+
+ float strength;
+ float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length());
+ if (timeofday_angle <= 0.44)
+ strength = timeofday_angle/0.44f;
+ else
+ strength = 1.f;
+
+ mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength);
+
+ mSun->setVisibility(weather.mGlareView * strength);
+
+ mAtmosphereNight->setVisible(weather.mNight && mEnabled);
+}
+
+void SkyManager::setGlare(const float glare)
+{
+ mGlare = glare;
+}
+
+Vector3 SkyManager::getRealSunPos()
+{
+ if (!mCreated) return Vector3(0,0,0);
+ return mSun->getNode()->getPosition() + mCamera->getRealPosition();
+}
+
+void SkyManager::sunEnable()
+{
+ mSunEnabled = true;
+}
+
+void SkyManager::sunDisable()
+{
+ mSunEnabled = false;
+}
+
+void SkyManager::setSunDirection(const Vector3& direction)
+{
+ if (!mCreated) return;
+ mSun->setPosition(direction);
+ mSunGlare->setPosition(direction);
+
+ float height = direction.z;
+ float fade = ( height > 0.5) ? 1.0 : height * 2;
+ sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty<sh::Vector2>(new sh::Vector2(fade, height)));
+}
+
+void SkyManager::setMasserDirection(const Vector3& direction)
+{
+ if (!mCreated) return;
+ mMasser->setPosition(direction);
+}
+
+void SkyManager::setSecundaDirection(const Vector3& direction)
+{
+ if (!mCreated) return;
+ mSecunda->setPosition(direction);
+}
+
+void SkyManager::masserEnable()
+{
+ mMasserEnabled = true;
+}
+
+void SkyManager::secundaEnable()
+{
+ mSecundaEnabled = true;
+}
+
+void SkyManager::masserDisable()
+{
+ mMasserEnabled = false;
+}
+
+void SkyManager::secundaDisable()
+{
+ mSecundaEnabled = false;
+}
+
+void SkyManager::setLightningStrength(const float factor)
+{
+ if (!mCreated) return;
+ if (factor > 0.f)
+ {
+ mLightning->setDiffuseColour (ColourValue(2*factor, 2*factor, 2*factor));
+ mLightning->setVisible(true);
+ }
+ else
+ mLightning->setVisible(false);
+}
+void SkyManager::setLightningEnabled(bool enabled)
+{
+ /// \todo
+}
+
+void SkyManager::setLightningDirection(const Ogre::Vector3& dir)
+{
+ if (!mCreated) return;
+ mLightning->setDirection (dir);
+}
+
+void SkyManager::setMasserFade(const float fade)
+{
+ if (!mCreated) return;
+ mMasser->setVisibility(fade);
+}
+
+void SkyManager::setSecundaFade(const float fade)
+{
+ if (!mCreated) return;
+ mSecunda->setVisibility(fade);
+}
+
+void SkyManager::setHour(double hour)
+{
+ mHour = hour;
+}
+
+void SkyManager::setDate(int day, int month)
+{
+ mDay = day;
+ mMonth = month;
+}
+
+Ogre::SceneNode* SkyManager::getSunNode()
+{
+ if (!mCreated) return 0;
+ return mSun->getNode();
+}
+
+void SkyManager::setGlareEnabled (bool enabled)
+{
+ if (!mCreated || !mEnabled)
+ return;
+ mSunGlare->setVisible (mSunEnabled && enabled);
+}
diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp
new file mode 100644
index 0000000000..3df8846cd4
--- /dev/null
+++ b/apps/openmw/mwrender/sky.hpp
@@ -0,0 +1,224 @@
+#ifndef GAME_RENDER_SKY_H
+#define GAME_RENDER_SKY_H
+
+#include <vector>
+
+#include <OgreVector3.h>
+#include <OgreString.h>
+#include <OgreMaterial.h>
+#include <OgreColourValue.h>
+#include <OgreHighLevelGpuProgram.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+
+#include "../mwworld/weather.hpp"
+
+namespace Ogre
+{
+ class RenderWindow;
+ class SceneNode;
+ class Camera;
+ class Viewport;
+ class SceneManager;
+ class Entity;
+ class BillboardSet;
+ class TextureUnitState;
+}
+
+namespace MWRender
+{
+ class BillboardObject : public sh::MaterialInstanceListener
+ {
+ public:
+ BillboardObject( const Ogre::String& textureName,
+ const float size,
+ const Ogre::Vector3& position,
+ Ogre::SceneNode* rootNode,
+ const std::string& material
+ );
+ BillboardObject();
+
+ void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration);
+ void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration);
+
+ virtual ~BillboardObject() {}
+
+ void setColour(const Ogre::ColourValue& pColour);
+ void setPosition(const Ogre::Vector3& pPosition);
+ void setVisible(const bool visible);
+ void setRenderQueue(unsigned int id);
+ void setVisibilityFlags(int flags);
+ void setSize(const float size);
+ Ogre::Vector3 getPosition() const;
+
+ void setVisibility(const float visibility);
+
+ Ogre::SceneNode* getNode();
+
+ protected:
+ float mVisibility;
+ Ogre::ColourValue mColour;
+ Ogre::SceneNode* mNode;
+ sh::MaterialInstance* mMaterial;
+ Ogre::Entity* mEntity;
+ };
+
+
+ /*
+ * The moons need a seperate class because of their shader (which allows them to be partially transparent)
+ */
+ class Moon : public BillboardObject
+ {
+ public:
+ Moon( const Ogre::String& textureName,
+ const float size,
+ const Ogre::Vector3& position,
+ Ogre::SceneNode* rootNode,
+ const std::string& material
+ );
+
+ virtual ~Moon() {}
+
+ enum Phase
+ {
+ Phase_New = 0,
+ Phase_WaxingCrescent,
+ Phase_WaxingHalf,
+ Phase_WaxingGibbous,
+ Phase_Full,
+ Phase_WaningGibbous,
+ Phase_WaningHalf,
+ Phase_WaningCrescent
+ };
+
+ enum Type
+ {
+ Type_Masser = 0,
+ Type_Secunda
+ };
+
+ void setPhase(const Phase& phase);
+ void setType(const Type& type);
+
+ Phase getPhase() const;
+ unsigned int getPhaseInt() const;
+
+ private:
+ Type mType;
+ Phase mPhase;
+ };
+
+ class SkyManager
+ {
+ public:
+ SkyManager(Ogre::SceneNode* root, Ogre::Camera* pCamera);
+ ~SkyManager();
+
+ void update(float duration);
+
+ void enable();
+
+ void disable();
+
+ void setHour (double hour);
+ ///< will be called even when sky is disabled.
+
+ void setDate (int day, int month);
+ ///< will be called even when sky is disabled.
+
+ int getMasserPhase() const;
+ ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half,
+ /// 3 waxing or waning gibbous, 4 full moon
+
+ int getSecundaPhase() const;
+ ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half,
+ /// 3 waxing or waning gibbous, 4 full moon
+
+ void setMoonColour (bool red);
+ ///< change Secunda colour to red
+
+ void setWeather(const MWWorld::WeatherResult& weather);
+
+ Ogre::SceneNode* getSunNode();
+
+ void sunEnable();
+
+ void sunDisable();
+
+ void setSunDirection(const Ogre::Vector3& direction);
+
+ void setMasserDirection(const Ogre::Vector3& direction);
+
+ void setSecundaDirection(const Ogre::Vector3& direction);
+
+ void setMasserFade(const float fade);
+
+ void setSecundaFade(const float fade);
+
+ void masserEnable();
+ void masserDisable();
+
+ void secundaEnable();
+ void secundaDisable();
+
+ void setLightningStrength(const float factor);
+ void setLightningDirection(const Ogre::Vector3& dir);
+ void setLightningEnabled(bool enabled); ///< disable prior to map render
+
+ void setGlare(const float glare);
+ void setGlareEnabled(bool enabled);
+ Ogre::Vector3 getRealSunPos();
+
+ private:
+ void create();
+ ///< no need to call this, automatically done on first enable()
+
+ bool mCreated;
+
+ bool mMoonRed;
+
+ float mHour;
+ int mDay;
+ int mMonth;
+
+ float mCloudAnimationTimer;
+
+ BillboardObject* mSun;
+ BillboardObject* mSunGlare;
+ Moon* mMasser;
+ Moon* mSecunda;
+
+ Ogre::Camera* mCamera;
+ Ogre::SceneNode* mRootNode;
+ Ogre::SceneManager* mSceneMgr;
+
+ Ogre::SceneNode* mAtmosphereDay;
+ Ogre::SceneNode* mAtmosphereNight;
+
+ // remember some settings so we don't have to apply them again if they didnt change
+ Ogre::String mClouds;
+ Ogre::String mNextClouds;
+ float mCloudBlendFactor;
+ float mCloudOpacity;
+ float mCloudSpeed;
+ float mStarsOpacity;
+ Ogre::ColourValue mCloudColour;
+ Ogre::ColourValue mSkyColour;
+ Ogre::ColourValue mFogColour;
+
+ Ogre::Light* mLightning;
+
+ float mRemainingTransitionTime;
+
+ float mGlare; // target
+ float mGlareFade; // actual
+
+ bool mEnabled;
+ bool mSunEnabled;
+ bool mMasserEnabled;
+ bool mSecundaEnabled;
+ };
+}
+
+#endif // GAME_RENDER_SKY_H
diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp
new file mode 100644
index 0000000000..318627fc70
--- /dev/null
+++ b/apps/openmw/mwrender/terrainstorage.cpp
@@ -0,0 +1,56 @@
+#include "terrainstorage.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwworld/esmstore.hpp"
+
+namespace MWRender
+{
+
+ Ogre::AxisAlignedBox TerrainStorage::getBounds()
+ {
+ int minX = 0, minY = 0, maxX = 0, maxY = 0;
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
+ for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
+ {
+ if (it->getGridX() < minX)
+ minX = it->getGridX();
+ if (it->getGridX() > maxX)
+ maxX = it->getGridX();
+ if (it->getGridY() < minY)
+ minY = it->getGridY();
+ if (it->getGridY() > maxY)
+ maxY = it->getGridY();
+ }
+
+ // since grid coords are at cell origin, we need to add 1 cell
+ maxX += 1;
+ maxY += 1;
+
+ return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0);
+ }
+
+ ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
+ {
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+ ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
+ // Load the data we are definitely going to need
+ int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
+ if (land && !land->isDataLoaded(mask))
+ land->loadData(mask);
+ return land;
+ }
+
+ const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
+ {
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+ return esmStore.get<ESM::LandTexture>().find(index, plugin);
+ }
+
+}
diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp
new file mode 100644
index 0000000000..ebf5e26ab7
--- /dev/null
+++ b/apps/openmw/mwrender/terrainstorage.hpp
@@ -0,0 +1,22 @@
+#ifndef MWRENDER_TERRAINSTORAGE_H
+#define MWRENDER_TERRAINSTORAGE_H
+
+#include <components/terrain/storage.hpp>
+
+namespace MWRender
+{
+
+ class TerrainStorage : public Terrain::Storage
+ {
+ private:
+ virtual ESM::Land* getLand (int cellX, int cellY);
+ virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
+ public:
+ virtual Ogre::AxisAlignedBox getBounds();
+ ///< Get bounds in cell units
+ };
+
+}
+
+
+#endif
diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp
new file mode 100644
index 0000000000..ee2b80f731
--- /dev/null
+++ b/apps/openmw/mwrender/videoplayer.cpp
@@ -0,0 +1,1191 @@
+#include "videoplayer.hpp"
+
+#define __STDC_CONSTANT_MACROS
+#include <stdint.h>
+
+#include <cstdio>
+#include <cmath>
+
+#include <OgreRoot.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRenderWindow.h>
+
+#include <boost/thread.hpp>
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwsound/sound_decoder.hpp"
+#include "../mwsound/sound.hpp"
+#include "../mwbase/inputmanager.hpp"
+
+#include "renderconst.hpp"
+
+#ifdef _WIN32
+#include <BaseTsd.h>
+
+typedef SSIZE_T ssize_t;
+#endif
+
+namespace MWRender
+{
+
+#ifdef OPENMW_USE_FFMPEG
+
+extern "C"
+{
+ #include <libavcodec/avcodec.h>
+ #include <libavformat/avformat.h>
+ #include <libswscale/swscale.h>
+
+ // From libavformat version 55.0.100 and onward the declaration of av_gettime() is removed from libavformat/avformat.h and moved
+ // to libavutil/time.h
+ // https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e
+ #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO)
+ #include <libavutil/time.h>
+ #endif
+
+ // From libavutil version 52.2.0 and onward the declaration of
+ // AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to
+ // libavutil/channel_layout.h
+ #if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+ LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
+ #include <libavutil/channel_layout.h>
+ #endif
+}
+
+#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
+#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
+#define AV_SYNC_THRESHOLD 0.01
+#define AUDIO_DIFF_AVG_NB 20
+#define VIDEO_PICTURE_QUEUE_SIZE 1
+
+enum {
+ AV_SYNC_AUDIO_MASTER,
+ AV_SYNC_VIDEO_MASTER,
+ AV_SYNC_EXTERNAL_MASTER,
+
+ AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER
+};
+
+
+struct PacketQueue {
+ PacketQueue()
+ : first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0)
+ { }
+ ~PacketQueue()
+ { clear(); }
+
+ AVPacketList *first_pkt, *last_pkt;
+ volatile bool flushing;
+ int nb_packets;
+ int size;
+
+ boost::mutex mutex;
+ boost::condition_variable cond;
+
+ void put(AVPacket *pkt);
+ int get(AVPacket *pkt, VideoState *is);
+
+ void flush();
+ void clear();
+};
+
+struct VideoPicture {
+ VideoPicture() : pts(0.0)
+ { }
+
+ std::vector<uint8_t> data;
+ double pts;
+};
+
+struct VideoState {
+ VideoState()
+ : format_ctx(NULL), av_sync_type(AV_SYNC_DEFAULT)
+ , external_clock_base(0.0)
+ , audio_st(NULL)
+ , video_st(NULL), frame_last_pts(0.0), frame_last_delay(0.0),
+ video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0),
+ pictq_rindex(0), pictq_windex(0)
+ , refresh_rate_ms(10), refresh(false), quit(false), display_ready(false)
+ {
+ // Register all formats and codecs
+ av_register_all();
+ }
+
+ ~VideoState()
+ { deinit(); }
+
+ void init(const std::string& resourceName);
+ void deinit();
+
+ int stream_open(int stream_index, AVFormatContext *pFormatCtx);
+
+ bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height);
+
+ static void video_thread_loop(VideoState *is);
+ static void decode_thread_loop(VideoState *is);
+
+ void video_display();
+ void video_refresh_timer();
+
+ int queue_picture(AVFrame *pFrame, double pts);
+ double synchronize_video(AVFrame *src_frame, double pts);
+
+ static void video_refresh(VideoState *is);
+
+
+ double get_audio_clock()
+ { return this->AudioTrack->getTimeOffset(); }
+
+ double get_video_clock()
+ { return this->frame_last_pts; }
+
+ double get_external_clock()
+ { return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0; }
+
+ double get_master_clock()
+ {
+ if(this->av_sync_type == AV_SYNC_VIDEO_MASTER)
+ return this->get_video_clock();
+ if(this->av_sync_type == AV_SYNC_AUDIO_MASTER)
+ return this->get_audio_clock();
+ return this->get_external_clock();
+ }
+
+
+ static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size);
+ static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size);
+ static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence);
+
+
+ Ogre::DataStreamPtr stream;
+ AVFormatContext* format_ctx;
+
+ int av_sync_type;
+ uint64_t external_clock_base;
+
+ AVStream** audio_st;
+ PacketQueue audioq;
+ MWBase::SoundPtr AudioTrack;
+
+ AVStream** video_st;
+ double frame_last_pts;
+ double frame_last_delay;
+ double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
+ PacketQueue videoq;
+ SwsContext* sws_context;
+ VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
+ AVFrame* rgbaFrame; // used as buffer for the frame converted from its native format to RGBA
+ int pictq_size, pictq_rindex, pictq_windex;
+ boost::mutex pictq_mutex;
+ boost::condition_variable pictq_cond;
+
+
+ boost::thread parse_thread;
+ boost::thread video_thread;
+
+ boost::thread refresh_thread;
+ volatile int refresh_rate_ms;
+
+ volatile bool refresh;
+ volatile bool quit;
+ volatile bool display_ready;
+};
+
+
+void PacketQueue::put(AVPacket *pkt)
+{
+ AVPacketList *pkt1;
+ pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
+ if(!pkt1) throw std::bad_alloc();
+ pkt1->pkt = *pkt;
+ pkt1->next = NULL;
+
+ if(pkt1->pkt.destruct == NULL)
+ {
+ if(av_dup_packet(&pkt1->pkt) < 0)
+ {
+ av_free(pkt1);
+ throw std::runtime_error("Failed to duplicate packet");
+ }
+ av_free_packet(pkt);
+ }
+
+ this->mutex.lock ();
+
+ if(!last_pkt)
+ this->first_pkt = pkt1;
+ else
+ this->last_pkt->next = pkt1;
+ this->last_pkt = pkt1;
+ this->nb_packets++;
+ this->size += pkt1->pkt.size;
+ this->cond.notify_one();
+
+ this->mutex.unlock();
+}
+
+int PacketQueue::get(AVPacket *pkt, VideoState *is)
+{
+ boost::unique_lock<boost::mutex> lock(this->mutex);
+ while(!is->quit)
+ {
+ AVPacketList *pkt1 = this->first_pkt;
+ if(pkt1)
+ {
+ this->first_pkt = pkt1->next;
+ if(!this->first_pkt)
+ this->last_pkt = NULL;
+ this->nb_packets--;
+ this->size -= pkt1->pkt.size;
+
+ *pkt = pkt1->pkt;
+ av_free(pkt1);
+
+ return 1;
+ }
+
+ if(this->flushing)
+ break;
+ this->cond.wait(lock);
+ }
+
+ return -1;
+}
+
+void PacketQueue::flush()
+{
+ this->flushing = true;
+ this->cond.notify_one();
+}
+
+void PacketQueue::clear()
+{
+ AVPacketList *pkt, *pkt1;
+
+ this->mutex.lock();
+ for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1)
+ {
+ pkt1 = pkt->next;
+ av_free_packet(&pkt->pkt);
+ av_freep(&pkt);
+ }
+ this->last_pkt = NULL;
+ this->first_pkt = NULL;
+ this->nb_packets = 0;
+ this->size = 0;
+ this->mutex.unlock ();
+}
+
+
+class MovieAudioDecoder : public MWSound::Sound_Decoder
+{
+ static void fail(const std::string &str)
+ {
+ throw std::runtime_error(str);
+ }
+
+ struct AutoAVPacket : public AVPacket {
+ AutoAVPacket(int size=0)
+ {
+ if(av_new_packet(this, size) < 0)
+ throw std::bad_alloc();
+ }
+ ~AutoAVPacket()
+ { av_free_packet(this); }
+ };
+
+ VideoState *mVideoState;
+ AVStream *mAVStream;
+
+ AutoAVPacket mPacket;
+ AVFrame *mFrame;
+ ssize_t mFramePos;
+ ssize_t mFrameSize;
+
+ double mAudioClock;
+
+ /* averaging filter for audio sync */
+ double mAudioDiffAccum;
+ const double mAudioDiffAvgCoef;
+ const double mAudioDiffThreshold;
+ int mAudioDiffAvgCount;
+
+ /* Add or subtract samples to get a better sync, return number of bytes to
+ * skip (negative means to duplicate). */
+ int synchronize_audio()
+ {
+ if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER)
+ return 0;
+
+ int sample_skip = 0;
+
+ // accumulate the clock difference
+ double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock();
+ mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum;
+ if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB)
+ mAudioDiffAvgCount++;
+ else
+ {
+ double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef);
+ if(fabs(avg_diff) >= mAudioDiffThreshold)
+ {
+ int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) *
+ mAVStream->codec->channels;
+ sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n);
+ }
+ }
+
+ return sample_skip;
+ }
+
+ int audio_decode_frame(AVFrame *frame)
+ {
+ AVPacket *pkt = &mPacket;
+
+ for(;;)
+ {
+ while(pkt->size > 0)
+ {
+ int len1, got_frame;
+
+ len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt);
+ if(len1 < 0) break;
+
+ if(len1 <= pkt->size)
+ {
+ /* Move the unread data to the front and clear the end bits */
+ int remaining = pkt->size - len1;
+ memmove(pkt->data, &pkt->data[len1], remaining);
+ av_shrink_packet(pkt, remaining);
+ }
+
+ /* No data yet? Look for more frames */
+ if(!got_frame || frame->nb_samples <= 0)
+ continue;
+
+ mAudioClock += (double)frame->nb_samples /
+ (double)mAVStream->codec->sample_rate;
+
+ /* We have data, return it and come back for more later */
+ return frame->nb_samples * mAVStream->codec->channels *
+ av_get_bytes_per_sample(mAVStream->codec->sample_fmt);
+ }
+ av_free_packet(pkt);
+
+ /* next packet */
+ if(mVideoState->audioq.get(pkt, mVideoState) < 0)
+ return -1;
+
+ /* if update, update the audio clock w/pts */
+ if((uint64_t)pkt->pts != AV_NOPTS_VALUE)
+ mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts;
+ }
+ }
+
+ void open(const std::string&)
+#ifdef _WIN32
+ { fail(std::string("Invalid call to ")+__FUNCSIG__); }
+#else
+ { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); }
+#endif
+
+ void close() { }
+
+ std::string getName()
+ { return mVideoState->stream->getName(); }
+
+ void rewind() { }
+
+public:
+ MovieAudioDecoder(VideoState *is)
+ : mVideoState(is)
+ , mAVStream(*is->audio_st)
+ , mFrame(avcodec_alloc_frame())
+ , mFramePos(0)
+ , mFrameSize(0)
+ , mAudioClock(0.0)
+ , mAudioDiffAccum(0.0)
+ , mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB)))
+ /* Correct audio only if larger error than this */
+ , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */)
+ , mAudioDiffAvgCount(0)
+ { }
+ virtual ~MovieAudioDecoder()
+ {
+ av_freep(&mFrame);
+ }
+
+ void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type)
+ {
+ if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8)
+ *type = MWSound::SampleType_UInt8;
+ else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16)
+ *type = MWSound::SampleType_Int16;
+ else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
+ *type = MWSound::SampleType_Float32;
+ else
+ fail(std::string("Unsupported sample format: ")+
+ av_get_sample_fmt_name(mAVStream->codec->sample_fmt));
+
+ if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO)
+ *chans = MWSound::ChannelConfig_Mono;
+ else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO)
+ *chans = MWSound::ChannelConfig_Stereo;
+ else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_QUAD)
+ *chans = MWSound::ChannelConfig_Quad;
+ else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_5POINT1)
+ *chans = MWSound::ChannelConfig_5point1;
+ else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_7POINT1)
+ *chans = MWSound::ChannelConfig_7point1;
+ else if(mAVStream->codec->channel_layout == 0)
+ {
+ /* Unknown channel layout. Try to guess. */
+ if(mAVStream->codec->channels == 1)
+ *chans = MWSound::ChannelConfig_Mono;
+ else if(mAVStream->codec->channels == 2)
+ *chans = MWSound::ChannelConfig_Stereo;
+ else
+ {
+ std::stringstream sstr("Unsupported raw channel count: ");
+ sstr << mAVStream->codec->channels;
+ fail(sstr.str());
+ }
+ }
+ else
+ {
+ char str[1024];
+ av_get_channel_layout_string(str, sizeof(str), mAVStream->codec->channels,
+ mAVStream->codec->channel_layout);
+ fail(std::string("Unsupported channel layout: ")+str);
+ }
+
+ *samplerate = mAVStream->codec->sample_rate;
+ }
+
+ size_t read(char *stream, size_t len)
+ {
+ int sample_skip = synchronize_audio();
+ size_t total = 0;
+
+ while(total < len)
+ {
+ if(mFramePos >= mFrameSize)
+ {
+ /* We have already sent all our data; get more */
+ mFrameSize = audio_decode_frame(mFrame);
+ if(mFrameSize < 0)
+ {
+ /* If error, we're done */
+ break;
+ }
+
+ mFramePos = std::min<ssize_t>(mFrameSize, sample_skip);
+ sample_skip -= mFramePos;
+ continue;
+ }
+
+ size_t len1 = len - total;
+ if(mFramePos >= 0)
+ {
+ len1 = std::min<size_t>(len1, mFrameSize-mFramePos);
+ memcpy(stream, mFrame->data[0]+mFramePos, len1);
+ }
+ else
+ {
+ len1 = std::min<size_t>(len1, -mFramePos);
+
+ int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) *
+ mAVStream->codec->channels;
+
+ /* add samples by copying the first sample*/
+ if(n == 1)
+ memset(stream, *mFrame->data[0], len1);
+ else if(n == 2)
+ {
+ const int16_t val = *((int16_t*)mFrame->data[0]);
+ for(size_t nb = 0;nb < len1;nb += n)
+ *((int16_t*)(stream+nb)) = val;
+ }
+ else if(n == 4)
+ {
+ const int32_t val = *((int32_t*)mFrame->data[0]);
+ for(size_t nb = 0;nb < len1;nb += n)
+ *((int32_t*)(stream+nb)) = val;
+ }
+ else if(n == 8)
+ {
+ const int64_t val = *((int64_t*)mFrame->data[0]);
+ for(size_t nb = 0;nb < len1;nb += n)
+ *((int64_t*)(stream+nb)) = val;
+ }
+ else
+ {
+ for(size_t nb = 0;nb < len1;nb += n)
+ memcpy(stream+nb, mFrame->data[0], n);
+ }
+ }
+
+ total += len1;
+ stream += len1;
+ mFramePos += len1;
+ }
+
+ return total;
+ }
+
+ size_t getSampleOffset()
+ {
+ ssize_t clock_delay = (mFrameSize-mFramePos) / mAVStream->codec->channels /
+ av_get_bytes_per_sample(mAVStream->codec->sample_fmt);
+ return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay;
+ }
+};
+
+
+int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size)
+{
+ Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
+ return stream->read(buf, buf_size);
+}
+
+int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size)
+{
+ Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
+ return stream->write(buf, buf_size);
+}
+
+int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence)
+{
+ Ogre::DataStreamPtr stream = static_cast<VideoState*>(user_data)->stream;
+
+ whence &= ~AVSEEK_FORCE;
+ if(whence == AVSEEK_SIZE)
+ return stream->size();
+ if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+
+void VideoState::video_refresh(VideoState* is)
+{
+ boost::system_time t = boost::get_system_time();
+ while(!is->quit)
+ {
+ t += boost::posix_time::milliseconds(is->refresh_rate_ms);
+ boost::this_thread::sleep(t);
+ is->refresh = true;
+ }
+}
+
+
+void VideoState::video_display()
+{
+ VideoPicture *vp = &this->pictq[this->pictq_rindex];
+
+ if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0)
+ {
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("VideoTexture");
+ if(texture.isNull() || static_cast<int>(texture->getWidth()) != (*this->video_st)->codec->width
+ || static_cast<int>(texture->getHeight()) != (*this->video_st)->codec->height)
+ {
+ Ogre::TextureManager::getSingleton ().remove ("VideoTexture");
+ texture = Ogre::TextureManager::getSingleton().createManual(
+ "VideoTexture",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D,
+ (*this->video_st)->codec->width, (*this->video_st)->codec->height,
+ 0,
+ Ogre::PF_BYTE_RGBA,
+ Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
+ }
+ Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]);
+ Ogre::HardwarePixelBufferSharedPtr buffer = texture->getBuffer();
+ buffer->blitFromMemory(pb);
+ this->display_ready = true;
+ }
+}
+
+void VideoState::video_refresh_timer()
+{
+ VideoPicture *vp;
+ double delay;
+
+ if(this->pictq_size == 0)
+ return;
+
+ vp = &this->pictq[this->pictq_rindex];
+
+ delay = vp->pts - this->frame_last_pts; /* the pts from last time */
+ if(delay <= 0 || delay >= 1.0) {
+ /* if incorrect delay, use previous one */
+ delay = this->frame_last_delay;
+ }
+ /* save for next time */
+ this->frame_last_delay = delay;
+ this->frame_last_pts = vp->pts;
+
+ /* FIXME: Syncing should be done in the decoding stage, where frames can be
+ * skipped or duplicated as needed. */
+ /* update delay to sync to audio if not master source */
+ if(this->av_sync_type != AV_SYNC_VIDEO_MASTER)
+ {
+ double diff = this->get_video_clock() - this->get_master_clock();
+
+ /* Skip or repeat the frame. Take delay into account
+ * FFPlay still doesn't "know if this is the best guess." */
+ double sync_threshold = std::max(delay, AV_SYNC_THRESHOLD);
+ if(diff <= -sync_threshold)
+ delay = 0;
+ else if(diff >= sync_threshold)
+ delay = 2 * delay;
+ }
+
+ this->refresh_rate_ms = std::max<int>(1, (int)(delay*1000.0));
+ /* show the picture! */
+ this->video_display();
+
+ /* update queue for next picture! */
+ this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE;
+ this->pictq_mutex.lock();
+ this->pictq_size--;
+ this->pictq_cond.notify_one();
+ this->pictq_mutex.unlock();
+}
+
+
+int VideoState::queue_picture(AVFrame *pFrame, double pts)
+{
+ VideoPicture *vp;
+
+ /* wait until we have a new pic */
+ {
+ boost::unique_lock<boost::mutex> lock(this->pictq_mutex);
+ while(this->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !this->quit)
+ this->pictq_cond.timed_wait(lock, boost::posix_time::milliseconds(1));
+ }
+ if(this->quit)
+ return -1;
+
+ // windex is set to 0 initially
+ vp = &this->pictq[this->pictq_windex];
+
+ // Convert the image into RGBA format for Ogre
+ if(this->sws_context == NULL)
+ {
+ int w = (*this->video_st)->codec->width;
+ int h = (*this->video_st)->codec->height;
+ this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt,
+ w, h, PIX_FMT_RGBA, SWS_BICUBIC,
+ NULL, NULL, NULL);
+ if(this->sws_context == NULL)
+ throw std::runtime_error("Cannot initialize the conversion context!\n");
+ }
+
+ vp->pts = pts;
+ vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4);
+
+ uint8_t *dst = &vp->data[0];
+ sws_scale(this->sws_context, pFrame->data, pFrame->linesize,
+ 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize);
+
+ // now we inform our display thread that we have a pic ready
+ this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE;
+ this->pictq_mutex.lock();
+ this->pictq_size++;
+ this->pictq_mutex.unlock();
+
+ return 0;
+}
+
+double VideoState::synchronize_video(AVFrame *src_frame, double pts)
+{
+ double frame_delay;
+
+ /* if we have pts, set video clock to it */
+ if(pts != 0)
+ this->video_clock = pts;
+ else
+ pts = this->video_clock;
+
+ /* update the video clock */
+ frame_delay = av_q2d((*this->video_st)->codec->time_base);
+
+ /* if we are repeating a frame, adjust clock accordingly */
+ frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
+ this->video_clock += frame_delay;
+
+ return pts;
+}
+
+
+/* These are called whenever we allocate a frame
+ * buffer. We use this to store the global_pts in
+ * a frame at the time it is allocated.
+ */
+static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE;
+static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic)
+{
+ int ret = avcodec_default_get_buffer(c, pic);
+ uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t));
+ *pts = global_video_pkt_pts;
+ pic->opaque = pts;
+ return ret;
+}
+static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic)
+{
+ if(pic) av_freep(&pic->opaque);
+ avcodec_default_release_buffer(c, pic);
+}
+
+
+void VideoState::video_thread_loop(VideoState *self)
+{
+ AVPacket pkt1, *packet = &pkt1;
+ int frameFinished;
+ AVFrame *pFrame;
+ double pts;
+
+ pFrame = avcodec_alloc_frame();
+
+ self->rgbaFrame = avcodec_alloc_frame();
+ avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height);
+
+ while(self->videoq.get(packet, self) >= 0)
+ {
+ // Save global pts to be stored in pFrame
+ global_video_pkt_pts = packet->pts;
+ // Decode video frame
+ if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0)
+ throw std::runtime_error("Error decoding video frame");
+
+ pts = 0;
+ if((uint64_t)packet->dts != AV_NOPTS_VALUE)
+ pts = packet->dts;
+ else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE)
+ pts = *(uint64_t*)pFrame->opaque;
+ pts *= av_q2d((*self->video_st)->time_base);
+
+ av_free_packet(packet);
+
+ // Did we get a video frame?
+ if(frameFinished)
+ {
+ pts = self->synchronize_video(pFrame, pts);
+ if(self->queue_picture(pFrame, pts) < 0)
+ break;
+ }
+ }
+
+ av_free(pFrame);
+
+ avpicture_free((AVPicture*)self->rgbaFrame);
+ av_free(self->rgbaFrame);
+}
+
+void VideoState::decode_thread_loop(VideoState *self)
+{
+ AVFormatContext *pFormatCtx = self->format_ctx;
+ AVPacket pkt1, *packet = &pkt1;
+
+ try
+ {
+ if(!self->video_st && !self->audio_st)
+ throw std::runtime_error("No streams to decode");
+
+ // main decode loop
+ while(!self->quit)
+ {
+ if((self->audio_st && self->audioq.size > MAX_AUDIOQ_SIZE) ||
+ (self->video_st && self->videoq.size > MAX_VIDEOQ_SIZE))
+ {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+ continue;
+ }
+
+ if(av_read_frame(pFormatCtx, packet) < 0)
+ break;
+
+ // Is this a packet from the video stream?
+ if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams)
+ self->videoq.put(packet);
+ else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams)
+ self->audioq.put(packet);
+ else
+ av_free_packet(packet);
+ }
+
+ /* all done - wait for it */
+ self->videoq.flush();
+ self->audioq.flush();
+ while(!self->quit)
+ {
+ // EOF reached, all packets processed, we can exit now
+ if(self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0)
+ break;
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+ }
+ }
+ catch(std::runtime_error& e) {
+ std::cerr << "An error occured playing the video: " << e.what () << std::endl;
+ }
+ catch(Ogre::Exception& e) {
+ std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl;
+ }
+
+ self->quit = true;
+}
+
+
+bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height)
+{
+ if(this->quit)
+ return false;
+
+ if(this->refresh)
+ {
+ this->refresh = false;
+ this->video_refresh_timer();
+ // Would be nice not to do this all the time...
+ if(this->display_ready)
+ mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("VideoTexture");
+
+ // Correct aspect ratio by adding black bars
+ double videoaspect = av_q2d((*this->video_st)->codec->sample_aspect_ratio);
+ if(videoaspect == 0.0)
+ videoaspect = 1.0;
+ videoaspect *= static_cast<double>((*this->video_st)->codec->width) / (*this->video_st)->codec->height;
+
+ double screenaspect = static_cast<double>(screen_width) / screen_height;
+ double aspect_correction = videoaspect / screenaspect;
+
+ rect->setCorners(std::max(-1.0, -1.0 * aspect_correction), std::min( 1.0, 1.0 / aspect_correction),
+ std::min( 1.0, 1.0 * aspect_correction), std::max(-1.0, -1.0 / aspect_correction));
+ }
+ return true;
+}
+
+
+int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx)
+{
+ MWSound::DecoderPtr decoder;
+ AVCodecContext *codecCtx;
+ AVCodec *codec;
+
+ if(stream_index < 0 || stream_index >= static_cast<int>(pFormatCtx->nb_streams))
+ return -1;
+
+ // Get a pointer to the codec context for the video stream
+ codecCtx = pFormatCtx->streams[stream_index]->codec;
+ codec = avcodec_find_decoder(codecCtx->codec_id);
+ if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0))
+ {
+ fprintf(stderr, "Unsupported codec!\n");
+ return -1;
+ }
+
+ switch(codecCtx->codec_type)
+ {
+ case AVMEDIA_TYPE_AUDIO:
+ this->audio_st = pFormatCtx->streams + stream_index;
+
+ decoder.reset(new MovieAudioDecoder(this));
+ this->AudioTrack = MWBase::Environment::get().getSoundManager()->playTrack(decoder, MWBase::SoundManager::Play_TypeMovie);
+ if(!this->AudioTrack)
+ {
+ avcodec_close((*this->audio_st)->codec);
+ this->audio_st = NULL;
+ return -1;
+ }
+ break;
+
+ case AVMEDIA_TYPE_VIDEO:
+ this->video_st = pFormatCtx->streams + stream_index;
+
+ this->frame_last_delay = 40e-3;
+
+ codecCtx->get_buffer = our_get_buffer;
+ codecCtx->release_buffer = our_release_buffer;
+ this->video_thread = boost::thread(video_thread_loop, this);
+ this->refresh_thread = boost::thread(video_refresh, this);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void VideoState::init(const std::string& resourceName)
+{
+ int video_index = -1;
+ int audio_index = -1;
+ unsigned int i;
+
+ this->av_sync_type = AV_SYNC_DEFAULT;
+ this->refresh_rate_ms = 10;
+ this->refresh = false;
+ this->quit = false;
+
+ this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName);
+ if(this->stream.isNull())
+ throw std::runtime_error("Failed to open video resource");
+
+ AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek);
+ if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext");
+
+ this->format_ctx = avformat_alloc_context();
+ if(this->format_ctx)
+ this->format_ctx->pb = ioCtx;
+
+ // Open video file
+ /// \todo leak here, ffmpeg or valgrind bug ?
+ if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL))
+ {
+ // "Note that a user-supplied AVFormatContext will be freed on failure."
+ this->format_ctx = NULL;
+ av_free(ioCtx);
+ throw std::runtime_error("Failed to open video input");
+ }
+
+ // Retrieve stream information
+ if(avformat_find_stream_info(this->format_ctx, NULL) < 0)
+ throw std::runtime_error("Failed to retrieve stream information");
+
+ // Dump information about file onto standard error
+ av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0);
+
+ for(i = 0;i < this->format_ctx->nb_streams;i++)
+ {
+ if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0)
+ video_index = i;
+ if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0)
+ audio_index = i;
+ }
+
+ this->external_clock_base = av_gettime();
+ if(audio_index >= 0)
+ this->stream_open(audio_index, this->format_ctx);
+ if(video_index >= 0)
+ this->stream_open(video_index, this->format_ctx);
+
+ this->parse_thread = boost::thread(decode_thread_loop, this);
+}
+
+void VideoState::deinit()
+{
+ this->quit = true;
+
+ this->audioq.cond.notify_one();
+ this->videoq.cond.notify_one();
+
+ this->parse_thread.join();
+ this->video_thread.join();
+ this->refresh_thread.join();
+
+ if(this->audio_st)
+ avcodec_close((*this->audio_st)->codec);
+ this->audio_st = NULL;
+ if(this->video_st)
+ avcodec_close((*this->video_st)->codec);
+ this->video_st = NULL;
+
+ if(this->sws_context)
+ sws_freeContext(this->sws_context);
+ this->sws_context = NULL;
+
+ if(this->format_ctx)
+ {
+ AVIOContext *ioContext = this->format_ctx->pb;
+ avformat_close_input(&this->format_ctx);
+ av_free(ioContext);
+ }
+}
+
+#else // defined OPENMW_USE_FFMPEG
+
+class VideoState
+{
+public:
+ VideoState() { }
+
+ void init(const std::string& resourceName)
+ {
+ throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\"");
+ }
+ void deinit() { }
+
+ void close() { }
+
+ bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height)
+ { return false; }
+};
+
+#endif // defined OPENMW_USE_FFMPEG
+
+
+VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window)
+ : mState(NULL)
+ , mSceneMgr(sceneMgr)
+ , mRectangle(NULL)
+ , mNode(NULL)
+ , mAllowSkipping(false)
+ , mWindow(window)
+ , mWidth(0)
+ , mHeight(0)
+{
+ mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General");
+ if (mVideoMaterial.isNull ())
+ {
+ mVideoMaterial = Ogre::MaterialManager::getSingleton().create("VideoMaterial", "General");
+ mVideoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
+ mVideoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
+ mVideoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ mVideoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState();
+ mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
+ }
+ mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png");
+
+ Ogre::MaterialPtr blackMaterial = Ogre::MaterialManager::getSingleton().getByName("BlackBarsMaterial", "General");
+ if (blackMaterial.isNull ())
+ {
+ blackMaterial = Ogre::MaterialManager::getSingleton().create("BlackBarsMaterial", "General");
+ blackMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
+ blackMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
+ blackMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ blackMaterial->getTechnique(0)->getPass(0)->createTextureUnitState()->setTextureName("black.png");
+ }
+
+ mRectangle = new Ogre::Rectangle2D(true);
+ mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
+ mRectangle->setMaterial("VideoMaterial");
+ mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+2);
+ mBackgroundRectangle = new Ogre::Rectangle2D(true);
+ mBackgroundRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
+ mBackgroundRectangle->setMaterial("BlackBarsMaterial");
+ mBackgroundRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1);
+
+ // Use infinite AAB to always stay visible
+ Ogre::AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+ mRectangle->setBoundingBox(aabInf);
+ mBackgroundRectangle->setBoundingBox(aabInf);
+
+ // Attach background to the scene
+ mNode = sceneMgr->getRootSceneNode()->createChildSceneNode();
+ mNode->attachObject(mRectangle);
+ mBackgroundNode = sceneMgr->getRootSceneNode()->createChildSceneNode();
+ mBackgroundNode->attachObject(mBackgroundRectangle);
+
+ mRectangle->setVisible(false);
+ mRectangle->setVisibilityFlags(RV_Overlay);
+ mBackgroundRectangle->setVisible(false);
+ mBackgroundRectangle->setVisibilityFlags(RV_Overlay);
+}
+
+VideoPlayer::~VideoPlayer()
+{
+ if(mState)
+ close();
+
+ mSceneMgr->destroySceneNode(mNode);
+ mSceneMgr->destroySceneNode(mBackgroundNode);
+
+ delete mRectangle;
+ delete mBackgroundRectangle;
+}
+
+void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping)
+{
+ mAllowSkipping = allowSkipping;
+
+ if(mState)
+ close();
+
+ mRectangle->setVisible(true);
+ mBackgroundRectangle->setVisible(true);
+ mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png");
+
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Video);
+
+ // Turn off rendering except the GUI
+ mSceneMgr->clearSpecialCaseRenderQueues();
+ // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work.
+ for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i)
+ {
+ if(i > 0 && i < 96)
+ mSceneMgr->addSpecialCaseRenderQueue(i);
+ }
+ mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);
+
+ MWBase::Environment::get().getSoundManager()->pauseSounds();
+
+ try {
+ mState = new VideoState;
+ mState->init(resourceName);
+
+ while (isPlaying())
+ {
+ MWBase::Environment::get().getInputManager()->update(0, false);
+ update();
+ mWindow->update();
+ }
+
+ }
+ catch(std::exception& e) {
+ std::cerr<< "Failed to play video: "<<e.what() <<std::endl;
+ close();
+ }
+}
+
+void VideoPlayer::update ()
+{
+ if(mState)
+ {
+ if(!mState->update(mVideoMaterial, mRectangle, mWidth, mHeight))
+ close();
+ }
+}
+
+void VideoPlayer::stopVideo ()
+{
+ if (mAllowSkipping)
+ close();
+}
+
+void VideoPlayer::close()
+{
+ if(mState)
+ {
+ mState->deinit();
+
+ delete mState;
+ mState = NULL;
+ }
+
+ MWBase::Environment::get().getSoundManager()->resumeSounds();
+
+ mRectangle->setVisible(false);
+ mBackgroundRectangle->setVisible(false);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Video);
+
+ mSceneMgr->clearSpecialCaseRenderQueues();
+ mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE);
+}
+
+bool VideoPlayer::isPlaying ()
+{
+ return mState != NULL;
+}
+
+}
diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp
new file mode 100644
index 0000000000..0e548e23e4
--- /dev/null
+++ b/apps/openmw/mwrender/videoplayer.hpp
@@ -0,0 +1,54 @@
+#ifndef VIDEOPLAYER_H
+#define VIDEOPLAYER_H
+
+#include <OgreMaterial.h>
+
+namespace Ogre
+{
+ class SceneManager;
+ class SceneNode;
+ class Rectangle2D;
+ class RenderWindow;
+}
+
+namespace MWRender
+{
+ struct VideoState;
+
+ class VideoPlayer
+ {
+ public:
+ VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window);
+ ~VideoPlayer();
+
+ void playVideo (const std::string& resourceName, bool allowSkipping);
+
+ void update();
+
+ void close();
+ void stopVideo();
+
+ bool isPlaying();
+
+ void setResolution (int w, int h) { mWidth = w; mHeight = h; }
+
+
+ private:
+ VideoState* mState;
+
+ bool mAllowSkipping;
+
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::MaterialPtr mVideoMaterial;
+ Ogre::Rectangle2D* mRectangle;
+ Ogre::Rectangle2D* mBackgroundRectangle;
+ Ogre::SceneNode* mNode;
+ Ogre::SceneNode* mBackgroundNode;
+ Ogre::RenderWindow* mWindow;
+
+ int mWidth;
+ int mHeight;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp
new file mode 100644
index 0000000000..082551f371
--- /dev/null
+++ b/apps/openmw/mwrender/water.cpp
@@ -0,0 +1,496 @@
+#include "water.hpp"
+
+#include <OgreRenderTarget.h>
+#include <OgreEntity.h>
+#include <OgreMeshManager.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRoot.h>
+
+#include "sky.hpp"
+#include "renderingmanager.hpp"
+#include "ripplesimulation.hpp"
+#include "refraction.hpp"
+
+#include <extern/shiny/Main/Factory.hpp>
+#include <extern/shiny/Platforms/Ogre/OgreMaterial.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+using namespace Ogre;
+
+namespace MWRender
+{
+
+CubeReflection::CubeReflection(Ogre::SceneManager* sceneManager)
+ : Reflection(sceneManager)
+{
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton ().createManual("CubeReflection",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_CUBE_MAP,
+ 512,512, 0, PF_R8G8B8, TU_RENDERTARGET);
+
+ mCamera = mSceneMgr->createCamera ("CubeCamera");
+ mCamera->setNearClipDistance (5);
+ mCamera->setFarClipDistance (1000);
+
+ for (int face = 0; face < 6; ++face)
+ {
+ mRenderTargets[face] = texture->getBuffer (face)->getRenderTarget();
+ mRenderTargets[face]->removeAllViewports ();
+ Viewport* vp = mRenderTargets[face]->addViewport (mCamera);
+ vp->setOverlaysEnabled(false);
+ vp->setShadowsEnabled(false);
+ vp->setMaterialScheme ("water_reflection");
+ mRenderTargets[face]->setAutoUpdated(false);
+
+ /*
+ Vector3 lookAt(0,0,0), up(0,0,0), right(0,0,0);
+ switch(face)
+ {
+ case 0: lookAt.x =-1; up.y = 1; right.z = 1; break; // +X
+ case 1: lookAt.x = 1; up.y = 1; right.z =-1; break; // -X
+ case 2: lookAt.y =-1; up.z = 1; right.x = 1; break; // +Y
+ case 3: lookAt.y = 1; up.z =-1; right.x = 1; break; // -Y
+ case 4: lookAt.z = 1; up.y = 1; right.x =-1; break; // +Z
+ case 5: lookAt.z =-1; up.y = 1; right.x =-1; break; // -Z
+ }
+ Quaternion orient(right, up, lookAt);
+ mCamera->setOrientation(orient);
+ */
+ }
+}
+
+CubeReflection::~CubeReflection ()
+{
+ Ogre::TextureManager::getSingleton ().remove("CubeReflection");
+ mSceneMgr->destroyCamera (mCamera);
+}
+
+void CubeReflection::update ()
+{
+ mParentCamera->getParentSceneNode ()->needUpdate ();
+ mCamera->setPosition(mParentCamera->getDerivedPosition());
+}
+
+// --------------------------------------------------------------------------------------------------------------------------------
+
+PlaneReflection::PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky)
+ : Reflection(sceneManager)
+ , mSky(sky)
+ , mRenderActive(false)
+{
+ mCamera = mSceneMgr->createCamera ("PlaneReflectionCamera");
+ mSceneMgr->addRenderQueueListener(this);
+
+ mTexture = TextureManager::getSingleton().createManual("WaterReflection",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET);
+
+ mRenderTarget = mTexture->getBuffer()->getRenderTarget();
+ Viewport* vp = mRenderTarget->addViewport(mCamera);
+ vp->setOverlaysEnabled(false);
+ vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f));
+ vp->setShadowsEnabled(false);
+ vp->setMaterialScheme("water_reflection");
+ mRenderTarget->addListener(this);
+ mRenderTarget->setActive(true);
+ mRenderTarget->setAutoUpdated(true);
+
+ sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mTexture->getName());
+}
+
+PlaneReflection::~PlaneReflection ()
+{
+ mRenderTarget->removeListener (this);
+ mSceneMgr->destroyCamera (mCamera);
+ mSceneMgr->removeRenderQueueListener(this);
+ TextureManager::getSingleton ().remove("WaterReflection");
+}
+
+void PlaneReflection::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation)
+{
+ // We don't want the sky to get clipped by custom near clip plane (the water plane)
+ if (queueGroupId < 20 && mRenderActive)
+ {
+ mCamera->disableCustomNearClipPlane();
+ Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS());
+ }
+}
+
+void PlaneReflection::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation)
+{
+ if (queueGroupId < 20 && mRenderActive)
+ {
+ mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane);
+ Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS());
+ }
+}
+
+void PlaneReflection::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
+{
+ mParentCamera->getParentSceneNode ()->needUpdate ();
+ mCamera->setOrientation(mParentCamera->getDerivedOrientation());
+ mCamera->setPosition(mParentCamera->getDerivedPosition());
+ mCamera->setNearClipDistance(mParentCamera->getNearClipDistance());
+ mCamera->setFarClipDistance(mParentCamera->getFarClipDistance());
+ mCamera->setAspectRatio(mParentCamera->getAspectRatio());
+ mCamera->setFOVy(mParentCamera->getFOVy());
+ mRenderActive = true;
+
+ mCamera->enableReflection(mWaterPlane);
+
+ // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane.
+ // since all we are interested in is depth, we only need the third row of the matrix.
+ Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix ();
+ sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]);
+ sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty<sh::Vector4> (row3));
+
+ // enable clip plane here to take advantage of CPU culling for overwater or underwater objects
+ mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane);
+}
+
+void PlaneReflection::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
+{
+ mCamera->disableReflection();
+ mCamera->disableCustomNearClipPlane();
+ mRenderActive = false;
+}
+
+void PlaneReflection::setHeight (float height)
+{
+ mWaterPlane = Plane(Ogre::Vector3(0,0,1), height);
+ mErrorPlane = Plane(Ogre::Vector3(0,0,1), height - 5);
+ mErrorPlaneUnderwater = Plane(Ogre::Vector3(0,0,-1), -height - 5);
+}
+
+void PlaneReflection::setActive (bool active)
+{
+ mRenderTarget->setActive(active);
+}
+
+void PlaneReflection::setViewportBackground(Ogre::ColourValue colour)
+{
+ mRenderTarget->getViewport (0)->setBackgroundColour (colour);
+}
+
+void PlaneReflection::setVisibilityMask (int flags)
+{
+ mRenderTarget->getViewport (0)->setVisibilityMask (flags);
+}
+
+// --------------------------------------------------------------------------------------------------------------------------------
+
+Water::Water (Ogre::Camera *camera, RenderingManager* rend) :
+ mCamera (camera), mSceneMgr (camera->getSceneManager()),
+ mIsUnderwater(false), mVisibilityFlags(0),
+ mActive(1), mToggled(1),
+ mRendering(rend),
+ mWaterTimer(0.f),
+ mReflection(NULL),
+ mRefraction(NULL),
+ mSimulation(NULL),
+ mPlayer(0,0)
+{
+ mSimulation = new RippleSimulation(mSceneMgr);
+
+ mSky = rend->getSkyManager();
+
+ mMaterial = MaterialManager::getSingleton().getByName("Water");
+
+ mTop = 0;
+
+ mIsUnderwater = false;
+
+ mWaterPlane = Plane(Vector3::UNIT_Z, 0);
+
+ int waterScale = 300;
+
+ MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane,
+ CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y);
+
+ mWater = mSceneMgr->createEntity("water");
+ mWater->setVisibilityFlags(RV_Water);
+ mWater->setCastShadows(false);
+ mWater->setRenderQueueGroup(RQG_Alpha);
+
+ mWaterNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+
+ mWaterNode->attachObject(mWater);
+
+ applyRTT();
+ applyVisibilityMask();
+
+ mWater->setMaterial(mMaterial);
+
+ setHeight(mTop);
+
+ sh::MaterialInstance* m = sh::Factory::getInstance ().getMaterialInstance ("Water");
+ m->setListener (this);
+
+ // ----------------------------------------------------------------------------------------------
+ // ---------------------------------- reflection debug overlay ----------------------------------
+ // ----------------------------------------------------------------------------------------------
+/*
+ if (Settings::Manager::getBool("shader", "Water"))
+ {
+ OverlayManager& mgr = OverlayManager::getSingleton();
+ Overlay* overlay;
+ // destroy if already exists
+ if ((overlay = mgr.getByName("ReflectionDebugOverlay")))
+ mgr.destroy(overlay);
+
+ overlay = mgr.create("ReflectionDebugOverlay");
+
+ if (MaterialManager::getSingleton().resourceExists("Ogre/ReflectionDebugTexture"))
+ MaterialManager::getSingleton().remove("Ogre/ReflectionDebugTexture");
+ MaterialPtr debugMat = MaterialManager::getSingleton().create(
+ "Ogre/ReflectionDebugTexture",
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+
+ debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName());
+
+ OverlayContainer* debugPanel;
+
+ // destroy container if exists
+ try
+ {
+ if ((debugPanel =
+ static_cast<OverlayContainer*>(
+ mgr.getOverlayElement("Ogre/ReflectionDebugTexPanel"
+ ))))
+ mgr.destroyOverlayElement(debugPanel);
+ }
+ catch (Ogre::Exception&) {}
+
+ debugPanel = (OverlayContainer*)
+ (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/ReflectionDebugTexPanel"));
+ debugPanel->_setPosition(0, 0.55);
+ debugPanel->_setDimensions(0.3, 0.3);
+ debugPanel->setMaterialName(debugMat->getName());
+ debugPanel->show();
+ overlay->add2D(debugPanel);
+ overlay->show();
+ }
+*/
+}
+
+void Water::setActive(bool active)
+{
+ mActive = active;
+ updateVisible();
+
+ sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty<sh::FloatValue> (new sh::FloatValue(active ? 1.0 : 0.0)));
+}
+
+Water::~Water()
+{
+ MeshManager::getSingleton().remove("water");
+
+ mWaterNode->detachObject(mWater);
+ mSceneMgr->destroyEntity(mWater);
+ mSceneMgr->destroySceneNode(mWaterNode);
+
+ delete mReflection;
+ delete mRefraction;
+}
+
+void Water::changeCell(const ESM::Cell* cell)
+{
+ mTop = cell->mWater;
+
+ setHeight(mTop);
+
+ if(!(cell->mData.mFlags & cell->Interior))
+ mWaterNode->setPosition(getSceneNodeCoordinates(cell->mData.mX, cell->mData.mY));
+}
+
+void Water::setHeight(const float height)
+{
+ mTop = height;
+
+ mWaterPlane = Plane(Vector3::UNIT_Z, -height);
+
+ if (mReflection)
+ mReflection->setHeight(height);
+ if (mRefraction)
+ mRefraction->setHeight(height);
+
+ mWaterNode->setPosition(0, 0, height);
+ sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty<sh::FloatValue>(new sh::FloatValue(height)));
+}
+
+void Water::toggle()
+{
+ mToggled = !mToggled;
+ updateVisible();
+}
+
+void
+Water::updateUnderwater(bool underwater)
+{
+ if (!mActive) {
+ return;
+ }
+ mIsUnderwater =
+ underwater &&
+ mWater->isVisible() &&
+ mCamera->getPolygonMode() == Ogre::PM_SOLID;
+
+ if (mReflection)
+ mReflection->setUnderwater (mIsUnderwater);
+ if (mRefraction)
+ mRefraction->setUnderwater (mIsUnderwater);
+
+ updateVisible();
+}
+
+Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY)
+{
+ return Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), gridY * CELL_SIZE + (CELL_SIZE / 2), mTop);
+}
+
+void Water::setViewportBackground(const ColourValue& bg)
+{
+ if (mReflection)
+ mReflection->setViewportBackground(bg);
+}
+
+void Water::updateVisible()
+{
+ mWater->setVisible(mToggled && mActive);
+ if (mReflection)
+ mReflection->setActive(mToggled && mActive);
+ if (mRefraction)
+ mRefraction->setActive(mToggled && mActive);
+}
+
+void Water::update(float dt, Ogre::Vector3 player)
+{
+ mWaterTimer += dt;
+ sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty<sh::FloatValue>(new sh::FloatValue(mWaterTimer)));
+
+ mRendering->getSkyManager ()->setGlareEnabled (!mIsUnderwater);
+
+ mPlayer = Ogre::Vector2(player.x, player.y);
+}
+
+void Water::frameStarted(float dt)
+{
+ mSimulation->update(dt, mPlayer);
+
+ if (mReflection)
+ mReflection->update();
+}
+
+void Water::applyRTT()
+{
+ delete mReflection;
+ mReflection = NULL;
+ delete mRefraction;
+ mRefraction = NULL;
+
+ // Create rendertarget for reflection
+ //int rttsize = Settings::Manager::getInt("rtt size", "Water");
+
+ if (Settings::Manager::getBool("shader", "Water"))
+ {
+ mReflection = new PlaneReflection(mSceneMgr, mSky);
+ mReflection->setParentCamera (mCamera);
+ mReflection->setHeight(mTop);
+
+ if (Settings::Manager::getBool("refraction", "Water"))
+ {
+ mRefraction = new Refraction(mCamera);
+ mRefraction->setHeight(mTop);
+ }
+ }
+
+ updateVisible();
+}
+
+void Water::applyVisibilityMask()
+{
+ mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water")
+ + RV_Statics * Settings::Manager::getBool("reflect statics", "Water")
+ + RV_StaticsSmall * Settings::Manager::getBool("reflect small statics", "Water")
+ + RV_Actors * Settings::Manager::getBool("reflect actors", "Water")
+ + RV_Misc * Settings::Manager::getBool("reflect misc", "Water")
+ + RV_Sky;
+
+ if (mReflection)
+ mReflection->setVisibilityMask(mVisibilityFlags);
+}
+
+void Water::processChangedSettings(const Settings::CategorySettingVector& settings)
+{
+ bool applyRT = false;
+ bool applyVisMask = false;
+ for (Settings::CategorySettingVector::const_iterator it=settings.begin();
+ it != settings.end(); ++it)
+ {
+ if ( it->first == "Water" && (
+ it->second == "shader"
+ || it->second == "refraction"
+ || it->second == "rtt size"))
+ applyRT = true;
+
+ if ( it->first == "Water" && (
+ it->second == "reflect actors"
+ || it->second == "reflect terrain"
+ || it->second == "reflect misc"
+ || it->second == "reflect small statics"
+ || it->second == "reflect statics"))
+ applyVisMask = true;
+ }
+
+ if(applyRT)
+ {
+ applyRTT();
+ applyVisibilityMask();
+ mWater->setMaterial(mMaterial);
+ }
+ if (applyVisMask)
+ applyVisibilityMask();
+}
+
+void Water::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration)
+{
+}
+
+void Water::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration)
+{
+ if (configuration == "local_map" || !Settings::Manager::getBool("shader", "Water"))
+ {
+ // for simple water, set animated texture names
+ // these have to be set in code
+ std::string textureNames[32];
+ for (int i=0; i<32; ++i)
+ {
+ textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds";
+ }
+
+ Ogre::Technique* t = static_cast<sh::OgreMaterial*>(m->getMaterial())->getOgreTechniqueForConfiguration(configuration);
+ if (t->getPass(0)->getNumTextureUnitStates () == 0)
+ return;
+ t->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2);
+ t->getPass(0)->setDepthWriteEnabled (false);
+ t->getPass(0)->setSceneBlending (Ogre::SBT_TRANSPARENT_ALPHA);
+ }
+}
+
+void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force)
+{
+ mSimulation->addEmitter (ptr, scale, force);
+}
+
+void Water::removeEmitter (const MWWorld::Ptr& ptr)
+{
+ mSimulation->removeEmitter (ptr);
+}
+
+void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr)
+{
+ mSimulation->updateEmitterPtr(old, ptr);
+}
+
+} // namespace
diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp
new file mode 100644
index 0000000000..bc15b4980a
--- /dev/null
+++ b/apps/openmw/mwrender/water.hpp
@@ -0,0 +1,180 @@
+#ifndef GAME_MWRENDER_WATER_H
+#define GAME_MWRENDER_WATER_H
+
+#include <OgrePlane.h>
+#include <OgreRenderQueue.h>
+#include <OgreRenderQueueListener.h>
+#include <OgreRenderTargetListener.h>
+#include <OgreMaterial.h>
+#include <OgreTexture.h>
+#include <OgreVector2.h>
+
+#include <components/esm/loadcell.hpp>
+#include <components/settings/settings.hpp>
+
+#include <extern/shiny/Main/MaterialInstance.hpp>
+
+
+#include "renderconst.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+namespace Ogre
+{
+ class Camera;
+ class SceneManager;
+ class SceneNode;
+ class Entity;
+ class Vector3;
+ class Rectangle2D;
+ struct RenderTargetEvent;
+}
+
+namespace MWRender {
+
+ class SkyManager;
+ class RenderingManager;
+ class RippleSimulation;
+ class Refraction;
+
+ class Reflection
+ {
+ public:
+ Reflection(Ogre::SceneManager* sceneManager)
+ : mSceneMgr(sceneManager)
+ , mIsUnderwater(false)
+ , mCamera(NULL)
+ , mParentCamera(NULL)
+ {}
+ virtual ~Reflection() {}
+
+ virtual void setHeight (float height) {}
+ virtual void setParentCamera (Ogre::Camera* parent) { mParentCamera = parent; }
+ void setUnderwater(bool underwater) { mIsUnderwater = underwater; }
+ virtual void setActive (bool active) {}
+ virtual void setViewportBackground(Ogre::ColourValue colour) {}
+ virtual void update() {}
+ virtual void setVisibilityMask (int flags) {}
+
+ protected:
+ Ogre::Camera* mCamera;
+ Ogre::Camera* mParentCamera;
+ Ogre::TexturePtr mTexture;
+ Ogre::SceneManager* mSceneMgr;
+ bool mIsUnderwater;
+ };
+
+ class CubeReflection : public Reflection
+ {
+ public:
+ CubeReflection(Ogre::SceneManager* sceneManager);
+ virtual ~CubeReflection();
+
+ virtual void update();
+ protected:
+ Ogre::RenderTarget* mRenderTargets[6];
+ };
+
+ class PlaneReflection : public Reflection, public Ogre::RenderQueueListener, public Ogre::RenderTargetListener
+ {
+ public:
+ PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky);
+ virtual ~PlaneReflection();
+
+ virtual void setHeight (float height);
+ virtual void setActive (bool active);
+ virtual void setVisibilityMask (int flags);
+
+ void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+ void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
+
+ void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation);
+ void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation);
+
+ virtual void setViewportBackground(Ogre::ColourValue colour);
+
+ protected:
+ Ogre::RenderTarget* mRenderTarget;
+ SkyManager* mSky;
+ Ogre::Plane mWaterPlane;
+ Ogre::Plane mErrorPlane;
+ Ogre::Plane mErrorPlaneUnderwater;
+ bool mRenderActive;
+ };
+
+ /// Water rendering
+ class Water : public sh::MaterialInstanceListener
+ {
+ static const int CELL_SIZE = 8192;
+ Ogre::Camera *mCamera;
+ Ogre::SceneManager *mSceneMgr;
+
+ Ogre::Plane mWaterPlane;
+
+ Ogre::SceneNode *mWaterNode;
+ Ogre::Entity *mWater;
+
+ bool mIsUnderwater;
+ bool mActive;
+ bool mToggled;
+ int mTop;
+
+ float mWaterTimer;
+
+
+ Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY);
+
+ protected:
+ void applyRTT();
+ void applyVisibilityMask();
+
+ void updateVisible();
+
+ RenderingManager* mRendering;
+ SkyManager* mSky;
+
+ std::string mCompositorName;
+
+ Ogre::MaterialPtr mMaterial;
+
+ bool mUnderwaterEffect;
+ int mVisibilityFlags;
+
+ Reflection* mReflection;
+ Refraction* mRefraction;
+ RippleSimulation* mSimulation;
+
+ Ogre::Vector2 mPlayer;
+
+ public:
+ Water (Ogre::Camera *camera, RenderingManager* rend);
+ ~Water();
+
+ void setActive(bool active);
+
+ void toggle();
+ void update(float dt, Ogre::Vector3 player);
+ void frameStarted(float dt);
+
+ /// adds an emitter, position will be tracked automatically using its scene node
+ void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f);
+ void removeEmitter (const MWWorld::Ptr& ptr);
+ void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr);
+
+ void setViewportBackground(const Ogre::ColourValue& bg);
+
+ void processChangedSettings(const Settings::CategorySettingVector& settings);
+
+ /// Updates underwater state accordingly
+ void updateUnderwater(bool underwater);
+ void changeCell(const ESM::Cell* cell);
+ void setHeight(const float height);
+
+ virtual void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration);
+ virtual void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration);
+
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp
new file mode 100644
index 0000000000..fac44c08f3
--- /dev/null
+++ b/apps/openmw/mwscript/aiextensions.cpp
@@ -0,0 +1,420 @@
+
+#include "aiextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/aiactivate.hpp"
+#include "../mwmechanics/aiescort.hpp"
+#include "../mwmechanics/aifollow.hpp"
+#include "../mwmechanics/aitravel.hpp"
+#include "../mwmechanics/aiwander.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+#include <iostream>
+
+namespace MWScript
+{
+ namespace Ai
+ {
+ template<class R>
+ class OpAiActivate : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string objectID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiActivate activatePackage(objectID);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage);
+ std::cout << "AiActivate" << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpAiTravel : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiTravel travelPackage(x, y, z);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(travelPackage);
+
+ std::cout << "AiTravel: " << x << ", " << y << ", " << z << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpAiEscort : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float duration = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiEscort escortPackage(actorID, duration, x, y, z);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage);
+
+ std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
+ << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpAiEscortCell : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::string cellID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float duration = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiEscort escortPackage(actorID, cellID, duration, x, y, z);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(escortPackage);
+
+ std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration
+ << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpGetAiPackageDone : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().isPackageDone();
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpAiWander : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer range = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Integer duration = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Integer time = runtime[0].mFloat;
+ runtime.pop();
+
+ std::vector<int> idleList;
+ bool repeat = false;
+
+ for(int i=1; i < 10 && arg0; ++i)
+ {
+ if(!repeat)
+ repeat = true;
+ Interpreter::Type_Integer idleValue = runtime[0].mInteger;
+ idleList.push_back(idleValue);
+ runtime.pop();
+ --arg0;
+ }
+
+ if(arg0)
+ {
+ repeat = runtime[0].mInteger != 0;
+ runtime.pop();
+ --arg0;
+ }
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiWander wanderPackage(range, duration, time, idleList, repeat);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(wanderPackage);
+ }
+ };
+
+ template<class R>
+ class OpGetAiSetting : public Interpreter::Opcode0
+ {
+ int mIndex;
+ public:
+ OpGetAiSetting(int index) : mIndex(index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex));
+ }
+ };
+ template<class R>
+ class OpModAiSetting : public Interpreter::Opcode0
+ {
+ int mIndex;
+ public:
+ OpModAiSetting(int index) : mIndex(index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex,
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex) + value);
+ }
+ };
+ template<class R>
+ class OpSetAiSetting : public Interpreter::Opcode0
+ {
+ int mIndex;
+ public:
+ OpSetAiSetting(int index) : mIndex(index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex,
+ value);
+ }
+ };
+
+ template<class R>
+ class OpAiFollow : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float duration = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiFollow followPackage(actorID, duration, x, y ,z);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage);
+
+ std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
+ << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpAiFollowCell : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::string cellID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float duration = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+
+ // discard additional arguments (reset), because we have no idea what they mean.
+ for (unsigned int i=0; i<arg0; ++i) runtime.pop();
+
+ MWMechanics::AiFollow followPackage(actorID, cellID, duration, x, y ,z);
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(followPackage);
+ std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration
+ << std::endl;
+ }
+ };
+
+ template<class R>
+ class OpGetCurrentAIPackage : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getTypeId ();
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpGetDetected : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+
+ std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer value = false; // TODO replace with implementation
+
+ std::cout << "AiGetDetected: " << actorID << ", " << value << std::endl;
+
+ runtime.push (value);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAIActivateExplicit, new OpAiActivate<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiTravel, new OpAiTravel<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiTravelExplicit, new OpAiTravel<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiEscort, new OpAiEscort<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortExplicit, new OpAiEscort<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCell, new OpAiEscortCell<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCellExplicit, new OpAiEscortCell<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiWander, new OpAiWander<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiWanderExplicit, new OpAiWander<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiFollow, new OpAiFollow<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowExplicit, new OpAiFollow<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCell, new OpAiFollowCell<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCellExplicit, new OpAiFollowCell<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDone, new OpGetAiPackageDone<ImplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDoneExplicit,
+ new OpGetAiPackageDone<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage<ImplicitRef>);
+ 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::opcodeSetHello, new OpSetAiSetting<ImplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting<ExplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting<ImplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting<ExplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting<ImplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting<ExplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting<ImplicitRef>(3));
+ interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting<ExplicitRef>(3));
+
+ interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting<ImplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting<ExplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting<ImplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting<ExplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting<ImplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting<ExplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting<ImplicitRef>(3));
+ interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting<ExplicitRef>(3));
+
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting<ImplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting<ExplicitRef>(0));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting<ImplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting<ExplicitRef>(1));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting<ImplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting<ExplicitRef>(2));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting<ImplicitRef>(3));
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting<ExplicitRef>(3));
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/aiextensions.hpp b/apps/openmw/mwscript/aiextensions.hpp
new file mode 100644
index 0000000000..e9e36113cf
--- /dev/null
+++ b/apps/openmw/mwscript/aiextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_AIEXTENSIONS_H
+#define GAME_SCRIPT_AIEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief AI-related script functionality
+ namespace Ai
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp
new file mode 100644
index 0000000000..52de8e0421
--- /dev/null
+++ b/apps/openmw/mwscript/animationextensions.cpp
@@ -0,0 +1,106 @@
+
+#include "animationextensions.hpp"
+
+#include <stdexcept>
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Animation
+ {
+ template<class R>
+ class OpSkipAnim : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWBase::Environment::get().getMechanicsManager()->skipAnimation (ptr);
+ }
+ };
+
+ template<class R>
+ class OpPlayAnim : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string group = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer mode = 0;
+
+ if (arg0==1)
+ {
+ mode = runtime[0].mInteger;
+ runtime.pop();
+
+ if (mode<0 || mode>2)
+ throw std::runtime_error ("animation mode out of range");
+ }
+
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, 1);
+ }
+ };
+
+ template<class R>
+ class OpLoopAnim : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string group = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer loops = runtime[0].mInteger;
+ runtime.pop();
+
+ if (loops<0)
+ throw std::runtime_error ("number of animation loops must be non-negative");
+
+ Interpreter::Type_Integer mode = 0;
+
+ if (arg0==1)
+ {
+ mode = runtime[0].mInteger;
+ runtime.pop();
+
+ if (mode<0 || mode>2)
+ throw std::runtime_error ("animation mode out of range");
+ }
+
+ MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnim, new OpSkipAnim<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnimExplicit, new OpSkipAnim<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Animation::opcodePlayAnim, new OpPlayAnim<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Animation::opcodePlayAnimExplicit, new OpPlayAnim<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnim, new OpLoopAnim<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnimExplicit, new OpLoopAnim<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/animationextensions.hpp b/apps/openmw/mwscript/animationextensions.hpp
new file mode 100644
index 0000000000..ff619ab73a
--- /dev/null
+++ b/apps/openmw/mwscript/animationextensions.hpp
@@ -0,0 +1,24 @@
+#ifndef GAME_SCRIPT_ANIMATIONEXTENSIONS_H
+#define GAME_SCRIPT_ANIMATIONEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ namespace Animation
+ {
+ void registerExtensions (Compiler::Extensions& extensions);
+
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp
new file mode 100644
index 0000000000..316f912dad
--- /dev/null
+++ b/apps/openmw/mwscript/cellextensions.cpp
@@ -0,0 +1,182 @@
+
+#include "cellextensions.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/player.hpp"
+
+#include "interpretercontext.hpp"
+
+namespace MWScript
+{
+ namespace Cell
+ {
+ class OpCellChanged : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWorld()->hasCellChanged() ? 1 : 0);
+ }
+ };
+
+ class OpCOC : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string cell = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ ESM::Position pos;
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+
+ if (world->findExteriorPosition(cell, pos)) {
+ world->changeToExteriorCell(pos);
+ }
+ else {
+ // Change to interior even if findInteriorPosition()
+ // yields false. In this case position will be zero-point.
+ world->findInteriorPosition(cell, pos);
+ world->changeToInteriorCell(cell, pos);
+ }
+ }
+ };
+
+ class OpCOE : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Integer x = runtime[0].mInteger;
+ runtime.pop();
+
+ Interpreter::Type_Integer y = runtime[0].mInteger;
+ runtime.pop();
+
+ ESM::Position pos;
+
+ MWBase::Environment::get().getWorld()->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);
+ }
+ };
+
+ class OpGetInterior : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ bool interior =
+ !MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->isExterior();
+
+ runtime.push (interior ? 1 : 0);
+ }
+ };
+
+ class OpGetPCCell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell;
+
+ std::string current = cell->mName;
+
+ if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty())
+ {
+ const ESM::Region *region =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().find (cell->mRegion);
+
+ current = region->mName;
+ }
+
+ bool match = current.length()>=name.length() &&
+ current.substr (0, name.length())==name;
+
+ runtime.push (match ? 1 : 0);
+ }
+ };
+
+ class OpGetWaterLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+ runtime.push (cell->mWaterLevel);
+ }
+ };
+
+ class OpSetWaterLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Float level = runtime[0].mFloat;
+
+ MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+
+ if (cell->mCell->isExterior())
+ throw std::runtime_error("Can't set water level in exterior cell");
+
+ cell->mWaterLevel = level;
+ MWBase::Environment::get().getWorld()->setWaterHeight(cell->mWaterLevel);
+ }
+ };
+
+ class OpModWaterLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Float level = runtime[0].mFloat;
+
+ MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+
+ if (cell->mCell->isExterior())
+ throw std::runtime_error("Can't set water level in exterior cell");
+
+ cell->mWaterLevel +=level;
+ MWBase::Environment::get().getWorld()->setWaterHeight(cell->mWaterLevel);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Cell::opcodeCellChanged, new OpCellChanged);
+ interpreter.installSegment5 (Compiler::Cell::opcodeCOC, new OpCOC);
+ interpreter.installSegment5 (Compiler::Cell::opcodeCOE, new OpCOE);
+ interpreter.installSegment5 (Compiler::Cell::opcodeGetInterior, new OpGetInterior);
+ interpreter.installSegment5 (Compiler::Cell::opcodeGetPCCell, new OpGetPCCell);
+ interpreter.installSegment5 (Compiler::Cell::opcodeGetWaterLevel, new OpGetWaterLevel);
+ interpreter.installSegment5 (Compiler::Cell::opcodeSetWaterLevel, new OpSetWaterLevel);
+ interpreter.installSegment5 (Compiler::Cell::opcodeModWaterLevel, new OpModWaterLevel);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/cellextensions.hpp b/apps/openmw/mwscript/cellextensions.hpp
new file mode 100644
index 0000000000..0891cb9dc8
--- /dev/null
+++ b/apps/openmw/mwscript/cellextensions.hpp
@@ -0,0 +1,25 @@
+#ifndef GAME_SCRIPT_CELLEXTENSIONS_H
+#define GAME_SCRIPT_CELLEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief cell-related script functionality
+ namespace Cell
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
+
+
diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp
new file mode 100644
index 0000000000..7e63a33b25
--- /dev/null
+++ b/apps/openmw/mwscript/compilercontext.cpp
@@ -0,0 +1,70 @@
+
+#include "compilercontext.hpp"
+
+#include "../mwworld/esmstore.hpp"
+
+#include <components/compiler/locals.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/class.hpp"
+
+namespace MWScript
+{
+ CompilerContext::CompilerContext (Type type)
+ : mType (type)
+ {}
+
+ bool CompilerContext::canDeclareLocals() const
+ {
+ return mType==Type_Full;
+ }
+
+ char CompilerContext::getGlobalType (const std::string& name) const
+ {
+ return MWBase::Environment::get().getWorld()->getGlobalVariableType (name);
+ }
+
+ char CompilerContext::getMemberType (const std::string& name, const std::string& id) const
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false);
+
+ std::string script = MWWorld::Class::get (ptr).getScript (ptr);
+
+ if (script.empty())
+ return ' ';
+
+ return MWBase::Environment::get().getScriptManager()->getLocals (script).getType (name);
+ }
+
+ bool CompilerContext::isId (const std::string& name) const
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ return
+ store.get<ESM::Activator>().search (name) ||
+ store.get<ESM::Potion>().search (name) ||
+ store.get<ESM::Apparatus>().search (name) ||
+ store.get<ESM::Armor>().search (name) ||
+ store.get<ESM::Book>().search (name) ||
+ store.get<ESM::Clothing>().search (name) ||
+ store.get<ESM::Container>().search (name) ||
+ store.get<ESM::Creature>().search (name) ||
+ store.get<ESM::Door>().search (name) ||
+ store.get<ESM::Ingredient>().search (name) ||
+ store.get<ESM::CreatureLevList>().search (name) ||
+ store.get<ESM::ItemLevList>().search (name) ||
+ store.get<ESM::Light>().search (name) ||
+ store.get<ESM::Lockpick>().search (name) ||
+ store.get<ESM::Miscellaneous>().search (name) ||
+ store.get<ESM::NPC>().search (name) ||
+ store.get<ESM::Probe>().search (name) ||
+ store.get<ESM::Repair>().search (name) ||
+ store.get<ESM::Static>().search (name) ||
+ store.get<ESM::Weapon>().search (name);
+ }
+}
diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp
new file mode 100644
index 0000000000..5ec98e09a7
--- /dev/null
+++ b/apps/openmw/mwscript/compilercontext.hpp
@@ -0,0 +1,41 @@
+#ifndef GAME_SCRIPT_COMPILERCONTEXT_H
+#define GAME_SCRIPT_COMPILERCONTEXT_H
+
+#include <components/compiler/context.hpp>
+
+namespace MWScript
+{
+ class CompilerContext : public Compiler::Context
+ {
+ public:
+
+ enum Type
+ {
+ Type_Full, // global, local, targetted
+ Type_Dialgoue,
+ Type_Console
+ };
+
+ private:
+
+ Type mType;
+
+ public:
+
+ CompilerContext (Type type);
+
+ /// Is the compiler allowed to declare local variables?
+ virtual bool canDeclareLocals() const;
+
+ /// 'l: long, 's': short, 'f': float, ' ': does not exist.
+ virtual char getGlobalType (const std::string& name) const;
+
+ virtual char getMemberType (const std::string& name, const std::string& id) const;
+ ///< 'l: long, 's': short, 'f': float, ' ': does not exist.
+
+ virtual bool isId (const std::string& name) const;
+ ///< Does \a name match an ID, that can be referenced?
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp
new file mode 100644
index 0000000000..30956d429f
--- /dev/null
+++ b/apps/openmw/mwscript/consoleextensions.cpp
@@ -0,0 +1,20 @@
+
+#include "consoleextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+namespace MWScript
+{
+ namespace Console
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/consoleextensions.hpp b/apps/openmw/mwscript/consoleextensions.hpp
new file mode 100644
index 0000000000..5571a54694
--- /dev/null
+++ b/apps/openmw/mwscript/consoleextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_CONSOLEEXTENSIONS_H
+#define GAME_SCRIPT_CONSOLEEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief Script functionality limited to the console
+ namespace Console
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp
new file mode 100644
index 0000000000..2f3ef2d792
--- /dev/null
+++ b/apps/openmw/mwscript/containerextensions.cpp
@@ -0,0 +1,385 @@
+
+#include "containerextensions.hpp"
+
+#include <stdexcept>
+
+#include <boost/format.hpp>
+
+#include <MyGUI_LanguageManager.h>
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
+#include "../mwworld/actionequip.hpp"
+#include "../mwworld/inventorystore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Container
+ {
+ template<class R>
+ class OpAddItem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer count = runtime[0].mInteger;
+ runtime.pop();
+
+ if (count<0)
+ throw std::runtime_error ("second argument for AddItem must be non-negative");
+
+ // no-op
+ 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);
+
+ // 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());
+ if (count == 1)
+ {
+ msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}");
+ msgBox = boost::str(boost::format(msgBox) % itemName);
+ }
+ else
+ {
+ msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}");
+ msgBox = boost::str(boost::format(msgBox) % count % itemName);
+ }
+ std::vector <std::string> noButtons;
+ MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
+ }
+ }
+ };
+
+ template<class R>
+ class OpGetItemCount : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
+
+ Interpreter::Type_Integer sum = 0;
+
+ for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter)
+ if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
+ sum += iter->getRefData().getCount();
+
+ runtime.push (sum);
+ }
+ };
+
+ template<class R>
+ class OpRemoveItem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer count = runtime[0].mInteger;
+ runtime.pop();
+
+ if (count<0)
+ throw std::runtime_error ("second argument for RemoveItem must be non-negative");
+
+ // no-op
+ if (count == 0)
+ 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)
+ {
+ 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;
+ }
+ }
+ }
+
+ // Spawn a messagebox (only for items removed from player's inventory)
+ 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 removed from their inventory
+ std::string msgBox;
+ int numRemoved = (originalCount - count);
+ if (numRemoved == 0)
+ return;
+
+ if(numRemoved > 1)
+ {
+ msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}");
+ msgBox = boost::str (boost::format(msgBox) % numRemoved % itemName);
+ }
+ else
+ {
+ msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}");
+ msgBox = boost::str (boost::format(msgBox) % itemName);
+ }
+ std::vector <std::string> noButtons;
+ MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
+ }
+ }
+ };
+
+ template <class R>
+ class OpEquip : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+ MWWorld::ContainerStoreIterator it = invStore.begin();
+ for (; it != invStore.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
+ break;
+ }
+ if (it == invStore.end())
+ throw std::runtime_error("Item to equip not found");
+
+ MWWorld::ActionEquip action (*it);
+ action.execute(ptr);
+ }
+ };
+
+ template <class R>
+ class OpGetArmorType : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer location = runtime[0].mInteger;
+ runtime.pop();
+
+ int slot;
+ switch (location)
+ {
+ case 0:
+ slot = MWWorld::InventoryStore::Slot_Helmet;
+ break;
+ case 1:
+ slot = MWWorld::InventoryStore::Slot_Cuirass;
+ break;
+ case 2:
+ slot = MWWorld::InventoryStore::Slot_LeftPauldron;
+ break;
+ case 3:
+ slot = MWWorld::InventoryStore::Slot_RightPauldron;
+ break;
+ case 4:
+ slot = MWWorld::InventoryStore::Slot_Greaves;
+ break;
+ case 5:
+ slot = MWWorld::InventoryStore::Slot_Boots;
+ break;
+ case 6:
+ slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
+ break;
+ case 7:
+ slot = MWWorld::InventoryStore::Slot_RightGauntlet;
+ break;
+ case 8:
+ slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield
+ break;
+ case 9:
+ slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
+ break;
+ case 10:
+ slot = MWWorld::InventoryStore::Slot_RightGauntlet;
+ break;
+ default:
+ throw std::runtime_error ("armor index out of range");
+ }
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+
+ MWWorld::ContainerStoreIterator it = invStore.getSlot (slot);
+ if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name())
+ {
+ runtime.push(-1);
+ return;
+ }
+
+ int skill = MWWorld::Class::get(*it).getEquipmentSkill (*it) ;
+ if (skill == ESM::Skill::HeavyArmor)
+ runtime.push(2);
+ else if (skill == ESM::Skill::MediumArmor)
+ runtime.push(1);
+ else if (skill == ESM::Skill::LightArmor)
+ runtime.push(0);
+ else
+ runtime.push(-1);
+ }
+ };
+
+ template <class R>
+ class OpHasItemEquipped : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+ for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
+ {
+ MWWorld::ContainerStoreIterator it = invStore.getSlot (slot);
+ if (it != invStore.end() && Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
+ {
+ runtime.push(1);
+ return;
+ }
+ }
+ runtime.push(0);
+ }
+ };
+
+ template <class R>
+ class OpHasSoulGem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ const std::string &name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+ for (MWWorld::ContainerStoreIterator it = invStore.begin(MWWorld::ContainerStore::Type_Miscellaneous);
+ it != invStore.end(); ++it)
+ {
+
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name))
+ {
+ runtime.push(1);
+ return;
+ }
+ }
+ runtime.push(0);
+ }
+ };
+
+ template <class R>
+ class OpGetWeaponType : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+ MWWorld::ContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight);
+ if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name())
+ {
+ runtime.push(-1);
+ return;
+ }
+
+ runtime.push(it->get<ESM::Weapon>()->mBase->mData.mType);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Container::opcodeAddItem, new OpAddItem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/containerextensions.hpp b/apps/openmw/mwscript/containerextensions.hpp
new file mode 100644
index 0000000000..d5be8fb2a6
--- /dev/null
+++ b/apps/openmw/mwscript/containerextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_CONTAINEREXTENSIONS_H
+#define GAME_SCRIPT_CONTAINEREXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief Container-related script functionality (chests, NPCs, creatures)
+ namespace Container
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp
new file mode 100644
index 0000000000..e46302b183
--- /dev/null
+++ b/apps/openmw/mwscript/controlextensions.cpp
@@ -0,0 +1,200 @@
+
+#include "controlextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/ptr.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Control
+ {
+ class OpSetControl : public Interpreter::Opcode0
+ {
+ std::string mControl;
+ bool mEnable;
+
+ public:
+
+ OpSetControl (const std::string& control, bool enable)
+ : mControl (control), mEnable (enable)
+ {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get()
+ .getInputManager()
+ ->toggleControlSwitch(mControl, mEnable);
+ }
+ };
+
+ class OpGetDisabled : public Interpreter::Opcode0
+ {
+ std::string mControl;
+
+ public:
+
+ OpGetDisabled (const std::string& control)
+ : mControl (control)
+ {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push(!MWBase::Environment::get().getInputManager()->getControlSwitch (mControl));
+ }
+ };
+
+ class OpToggleCollision : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context
+ = static_cast<InterpreterContext&> (runtime.getContext());
+
+ bool enabled = MWBase::Environment::get().getWorld()->toggleCollisionMode();
+
+ context.report (enabled ? "Collision -> On" : "Collision -> Off");
+ }
+ };
+
+ template<class R>
+ class OpClearMovementFlag : public Interpreter::Opcode0
+ {
+ MWMechanics::NpcStats::Flag mFlag;
+
+ public:
+
+ OpClearMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, false);
+ }
+ };
+
+ template<class R>
+ class OpSetMovementFlag : public Interpreter::Opcode0
+ {
+ MWMechanics::NpcStats::Flag mFlag;
+
+ public:
+
+ OpSetMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, true);
+ }
+ };
+
+ template <class R>
+ class OpGetForceRun : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr);
+
+ runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun));
+ }
+ };
+
+ template <class R>
+ class OpGetForceSneak : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr);
+
+ runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak));
+ }
+ };
+
+ class OpGetPcRunning : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Run));
+ }
+ };
+
+ class OpGetPcSneaking : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ runtime.push (MWWorld::Class::get(ptr).getStance (ptr, MWWorld::Class::Sneak));
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ for (int i=0; i<Compiler::Control::numberOfControls; ++i)
+ {
+ interpreter.installSegment5 (Compiler::Control::opcodeEnable+i, new OpSetControl (Compiler::Control::controls[i], true));
+ interpreter.installSegment5 (Compiler::Control::opcodeDisable+i, new OpSetControl (Compiler::Control::controls[i], false));
+ interpreter.installSegment5 (Compiler::Control::opcodeGetDisabled+i, new OpGetDisabled (Compiler::Control::controls[i]));
+ }
+
+ interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision);
+
+ interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun,
+ new OpClearMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ interpreter.installSegment5 (Compiler::Control::opcodeForceRun,
+ new OpSetMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak,
+ new OpClearMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ interpreter.installSegment5 (Compiler::Control::opcodeForceSneak,
+ new OpSetMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+
+ interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit,
+ new OpClearMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit,
+ new OpSetMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit,
+ new OpClearMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit,
+ new OpSetMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning);
+ interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking);
+ interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/controlextensions.hpp b/apps/openmw/mwscript/controlextensions.hpp
new file mode 100644
index 0000000000..b9c6654fea
--- /dev/null
+++ b/apps/openmw/mwscript/controlextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_CONTROLEXTENSIONS_H
+#define GAME_SCRIPT_CONTROLEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief player controls-related script functionality
+ namespace Control
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp
new file mode 100644
index 0000000000..5e797ee589
--- /dev/null
+++ b/apps/openmw/mwscript/dialogueextensions.cpp
@@ -0,0 +1,214 @@
+
+#include "dialogueextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/journal.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Dialogue
+ {
+ class OpJournal : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string quest = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer index = runtime[0].mInteger;
+ runtime.pop();
+
+ MWBase::Environment::get().getJournal()->addEntry (quest, index);
+ }
+ };
+
+ class OpSetJournalIndex : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string quest = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer index = runtime[0].mInteger;
+ runtime.pop();
+
+ MWBase::Environment::get().getJournal()->setJournalIndex (quest, index);
+ }
+ };
+
+ class OpGetJournalIndex : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string quest = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ int index = MWBase::Environment::get().getJournal()->getJournalIndex (quest);
+
+ runtime.push (index);
+
+ }
+ };
+
+ class OpAddTopic : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string topic = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getDialogueManager()->addTopic(topic);
+ }
+ };
+
+ class OpChoice : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWBase::DialogueManager* dialogue = MWBase::Environment::get().getDialogueManager();
+ while(arg0>0)
+ {
+ std::string question = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ arg0 = arg0 -1;
+ Interpreter::Type_Integer choice = 1;
+ if(arg0>0)
+ {
+ choice = runtime[0].mInteger;
+ runtime.pop();
+ arg0 = arg0 -1;
+ }
+ dialogue->askQuestion(question,choice);
+ }
+ }
+ };
+
+ template<class R>
+ class OpForceGreeting : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWBase::Environment::get().getDialogueManager()->startDialogue (ptr);
+ }
+ };
+
+ class OpGoodbye : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute(Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getDialogueManager()->goodbye();
+ }
+ };
+
+ template<class R>
+ class OpModReputation : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get(ptr).getNpcStats (ptr).setReputation (MWWorld::Class::get(ptr).getNpcStats (ptr).getReputation () + value);
+ }
+ };
+
+ template<class R>
+ class OpSetReputation : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get(ptr).getNpcStats (ptr).setReputation (value);
+ }
+ };
+
+ template<class R>
+ class OpGetReputation : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getReputation ());
+ }
+ };
+
+ template<class R>
+ class OpSameFaction : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+
+ runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).isSameFaction (MWWorld::Class::get(player).getNpcStats (player)));
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic);
+ interpreter.installSegment3 (Compiler::Dialogue::opcodeChoice,new OpChoice);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreeting, new OpForceGreeting<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreetingExplicit, new OpForceGreeting<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeGoodbye, new OpGoodbye);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputation, new OpGetReputation<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputation, new OpSetReputation<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputation, new OpModReputation<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputationExplicit, new OpSetReputation<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputationExplicit, new OpModReputation<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputationExplicit, new OpGetReputation<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction<ExplicitRef>);
+ }
+ }
+
+}
diff --git a/apps/openmw/mwscript/dialogueextensions.hpp b/apps/openmw/mwscript/dialogueextensions.hpp
new file mode 100644
index 0000000000..7b03154dfb
--- /dev/null
+++ b/apps/openmw/mwscript/dialogueextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_DIALOGUEEXTENSIONS_H
+#define GAME_SCRIPT_DIALOGUEEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief Dialogue/Journal-related script functionality
+ namespace Dialogue
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt
new file mode 100644
index 0000000000..08b4991753
--- /dev/null
+++ b/apps/openmw/mwscript/docs/vmformat.txt
@@ -0,0 +1,355 @@
+OpenMW Extensions:
+
+Segment 0:
+(not implemented yet)
+opcodes 0x20-0x3f unused
+
+Segment 1:
+(not implemented yet)
+opcodes 0x20-0x3f unused
+
+Segment 2:
+(not implemented yet)
+opcodes 0x200-0x3ff unused
+
+Segment 3:
+op 0x20000: AiTravel
+op 0x20001: AiTravel, explicit reference
+op 0x20002: AiEscort
+op 0x20003: AiEscort, explicit reference
+op 0x20004: Lock
+op 0x20005: Lock, explicit reference
+op 0x20006: PlayAnim
+op 0x20007: PlayAnim, explicit reference
+op 0x20008: LoopAnim
+op 0x20009: LoopAnim, explicit reference
+op 0x2000a: Choice
+op 0x2000b: PCRaiseRank
+op 0x2000c: PCLowerRank
+op 0x2000d: PCJoinFaction
+op 0x2000e: PCGetRank implicit
+op 0x2000f: PCGetRank explicit
+op 0x20010: AiWander
+op 0x20011: AiWander, explicit reference
+op 0x20012: GetPCFacRep
+op 0x20013: GetPCFacRep, explicit reference
+op 0x20014: SetPCFacRep
+op 0x20015: SetPCFacRep, explicit reference
+op 0x20016: ModPCFacRep
+op 0x20017: ModPCFacRep, explicit reference
+op 0x20018: PcExpelled
+op 0x20019: PcExpelled, explicit
+op 0x2001a: PcExpell
+op 0x2001b: PcExpell, explicit
+op 0x2001c: PcClearExpelled
+op 0x2001d: PcClearExpelled, explicit
+op 0x2001e: AIActivate
+op 0x2001f: AIActivate, explicit reference
+op 0x20020: AiEscortCell
+op 0x20021: AiEscortCell, explicit reference
+op 0x20022: AiFollow
+op 0x20023: AiFollow, explicit reference
+op 0x20024: AiFollowCell
+op 0x20025: AiFollowCell, explicit reference
+op 0x20026: ModRegion
+opcodes 0x20027-0x3ffff unused
+
+Segment 4:
+(not implemented yet)
+opcodes 0x200-0x3ff unused
+
+Segment 5:
+op 0x2000000: CellChanged
+op 0x2000001: Say
+op 0x2000002: SayDone
+op 0x2000003: StreamMusic
+op 0x2000004: PlaySound
+op 0x2000005: PlaySoundVP
+op 0x2000006: PlaySound3D
+op 0x2000007: PlaySound3DVP
+op 0x2000008: PlayLoopSound3D
+op 0x2000009: PlayLoopSound3DVP
+op 0x200000a: StopSound
+op 0x200000b: GetSoundPlaying
+op 0x200000c: XBox (always 0)
+op 0x200000d: OnActivate
+op 0x200000e: EnableBirthMenu
+op 0x200000f: EnableClassMenu
+op 0x2000010: EnableNameMenu
+op 0x2000011: EnableRaceMenu
+op 0x2000012: EnableStatsReviewMenu
+op 0x2000013: EnableInventoryMenu
+op 0x2000014: EnableMagicMenu
+op 0x2000015: EnableMapMenu
+op 0x2000016: EnableStatsMenu
+op 0x2000017: EnableRest
+op 0x2000018: ShowRestMenu
+op 0x2000019: Say, explicit reference
+op 0x200001a: SayDone, explicit reference
+op 0x200001b: PlaySound3D, explicit reference
+op 0x200001c: PlaySound3DVP, explicit reference
+op 0x200001d: PlayLoopSound3D, explicit reference
+op 0x200001e: PlayLoopSound3DVP, explicit reference
+op 0x200001f: StopSound, explicit reference
+op 0x2000020: GetSoundPlaying, explicit reference
+op 0x2000021: ToggleSky
+op 0x2000022: TurnMoonWhite
+op 0x2000023: TurnMoonRed
+op 0x2000024: GetMasserPhase
+op 0x2000025: GetSecundaPhase
+op 0x2000026: COC
+op 0x2000027-0x200002e: GetAttribute
+op 0x200002f-0x2000036: GetAttribute, explicit reference
+op 0x2000037-0x200003e: SetAttribute
+op 0x200003f-0x2000046: SetAttribute, explicit reference
+op 0x2000047-0x200004e: ModAttribute
+op 0x200004f-0x2000056: ModAttribute, explicit reference
+op 0x2000057-0x2000059: GetDynamic (health, magicka, fatigue)
+op 0x200005a-0x200005c: GetDynamic (health, magicka, fatigue), explicit reference
+op 0x200005d-0x200005f: SetDynamic (health, magicka, fatigue)
+op 0x2000060-0x2000062: SetDynamic (health, magicka, fatigue), explicit reference
+op 0x2000063-0x2000065: ModDynamic (health, magicka, fatigue)
+op 0x2000066-0x2000068: ModDynamic (health, magicka, fatigue), explicit reference
+op 0x2000069-0x200006b: ModDynamic (health, magicka, fatigue)
+op 0x200006c-0x200006e: ModDynamic (health, magicka, fatigue), explicit reference
+op 0x200006f-0x2000071: GetDynamic (health, magicka, fatigue)
+op 0x2000072-0x2000074: GetDynamic (health, magicka, fatigue), explicit reference
+op 0x2000075: Activate
+op 0x2000076: AddItem
+op 0x2000077: AddItem, explicit reference
+op 0x2000078: GetItemCount
+op 0x2000079: GetItemCount, explicit reference
+op 0x200007a: RemoveItem
+op 0x200007b: RemoveItem, explicit reference
+op 0x200007c: GetAiPackageDone
+op 0x200007d: GetAiPackageDone, explicit reference
+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
+op 0x20000df-0x20000f9: SetSkill, explicit reference
+op 0x20000fa-0x2000114: ModSkill
+op 0x2000115-0x200012f: ModSKill, explicit reference
+op 0x2000130: ToggleCollision
+op 0x2000131: GetInterior
+op 0x2000132: ToggleCollsionDebug
+op 0x2000133: Journal
+op 0x2000134: SetJournalIndex
+op 0x2000135: GetJournalIndex
+op 0x2000136: GetPCCell
+op 0x2000137: GetButtonPressed
+op 0x2000138: SkipAnim
+op 0x2000139: SkipAnim, expplicit reference
+op 0x200013a: AddTopic
+op 0x200013b: twf
+op 0x200013c: FadeIn
+op 0x200013d: FadeOut
+op 0x200013e: FadeTo
+op 0x200013f: GetCurrentWeather
+op 0x2000140: ChangeWeather
+op 0x2000141: GetWaterLevel
+op 0x2000142: SetWaterLevel
+op 0x2000143: ModWaterLevel
+op 0x2000144: ToggleWater, twa
+op 0x2000145: ToggleFogOfWar (tfow)
+op 0x2000146: TogglePathgrid
+op 0x2000147: AddSpell
+op 0x2000148: AddSpell, explicit reference
+op 0x2000149: RemoveSpell
+op 0x200014a: RemoveSpell, explicit reference
+op 0x200014b: GetSpell
+op 0x200014c: GetSpell, explicit reference
+op 0x200014d: ModDisposition
+op 0x200014e: ModDisposition, explicit reference
+op 0x200014f: ForceGreeting
+op 0x2000150: ForceGreeting, explicit reference
+op 0x2000151: ToggleFullHelp
+op 0x2000152: Goodbye
+op 0x2000153: DontSaveObject (left unimplemented)
+op 0x2000154: ClearForceRun
+op 0x2000155: ClearForceRun, explicit reference
+op 0x2000156: ForceRun
+op 0x2000157: ForceRun, explicit reference
+op 0x2000158: ClearForceSneak
+op 0x2000159: ClearForceSneak, explicit reference
+op 0x200015a: ForceSneak
+op 0x200015b: ForceSneak, explicit reference
+op 0x200015c: SetHello
+op 0x200015d: SetHello, explicit reference
+op 0x200015e: SetFight
+op 0x200015f: SetFight, explicit reference
+op 0x2000160: SetFlee
+op 0x2000161: SetFlee, explicit reference
+op 0x2000162: SetAlarm
+op 0x2000163: SetAlarm, explicit reference
+op 0x2000164: SetScale
+op 0x2000165: SetScale, explicit reference
+op 0x2000166: SetAngle
+op 0x2000167: SetAngle, explicit reference
+op 0x2000168: GetScale
+op 0x2000169: GetScale, explicit reference
+op 0x200016a: GetAngle
+op 0x200016b: GetAngle, explicit reference
+op 0x200016c: user1 (console only, requires --script-console switch)
+op 0x200016d: user2 (console only, requires --script-console switch)
+op 0x200016e: user3, explicit reference (console only, requires --script-console switch)
+op 0x200016f: user3 (implicit reference, console only, requires --script-console switch)
+op 0x2000170: user4, explicit reference (console only, requires --script-console switch)
+op 0x2000171: user4 (implicit reference, console only, requires --script-console switch)
+op 0x2000172: GetStartingAngle
+op 0x2000173: GetStartingAngle, explicit reference
+op 0x2000174: ToggleVanityMode
+op 0x2000175-0x200018B: Get controls disabled
+op 0x200018C: GetLevel
+op 0x200018D: GetLevel, explicit reference
+op 0x200018E: SetLevel
+op 0x200018F: SetLevel, explicit reference
+op 0x2000190: GetPos
+op 0x2000191: GetPosExplicit
+op 0x2000192: SetPos
+op 0x2000193: SetPosExplicit
+op 0x2000194: GetStartingPos
+op 0x2000195: GetStartingPosExplicit
+op 0x2000196: Position
+op 0x2000197: Position Explicit
+op 0x2000198: PositionCell
+op 0x2000199: PositionCell Explicit
+op 0x200019a: PlaceItemCell
+op 0x200019b: PlaceItem
+op 0x200019c: PlaceAtPc
+op 0x200019d: PlaceAtMe
+op 0x200019e: PlaceAtMe Explicit
+op 0x200019f: GetPcSleep
+op 0x20001a0: ShowMap
+op 0x20001a1: FillMap
+op 0x20001a2: WakeUpPc
+op 0x20001a3: GetDeadCount
+op 0x20001a4: SetDisposition
+op 0x20001a5: SetDisposition, Explicit
+op 0x20001a6: GetDisposition
+op 0x20001a7: GetDisposition, Explicit
+op 0x20001a8: CommonDisease
+op 0x20001a9: CommonDisease, explicit reference
+op 0x20001aa: BlightDisease
+op 0x20001ab: BlightDisease, explicit reference
+op 0x20001ac: ToggleCollisionBoxes
+op 0x20001ad: SetReputation
+op 0x20001ae: ModReputation
+op 0x20001af: SetReputation, explicit
+op 0x20001b0: ModReputation, explicit
+op 0x20001b1: GetReputation
+op 0x20001b2: GetReputation, explicit
+op 0x20001b3: Equip
+op 0x20001b4: Equip, explicit
+op 0x20001b5: SameFaction
+op 0x20001b6: SameFaction, explicit
+op 0x20001b7: ModHello
+op 0x20001b8: ModHello, explicit reference
+op 0x20001b9: ModFight
+op 0x20001ba: ModFight, explicit reference
+op 0x20001bb: ModFlee
+op 0x20001bc: ModFlee, explicit reference
+op 0x20001bd: ModAlarm
+op 0x20001be: ModAlarm, explicit reference
+op 0x20001bf: GetHello
+op 0x20001c0: GetHello, explicit reference
+op 0x20001c1: GetFight
+op 0x20001c2: GetFight, explicit reference
+op 0x20001c3: GetFlee
+op 0x20001c4: GetFlee, explicit reference
+op 0x20001c5: GetAlarm
+op 0x20001c6: GetAlarm, explicit reference
+op 0x20001c7: GetLocked
+op 0x20001c8: GetLocked, explicit reference
+op 0x20001c9: GetPcRunning
+op 0x20001ca: GetPcSneaking
+op 0x20001cb: GetForceRun
+op 0x20001cc: GetForceSneak
+op 0x20001cd: GetForceRun, explicit
+op 0x20001ce: GetForceSneak, explicit
+op 0x20001cf: GetEffect
+op 0x20001d0: GetEffect, explicit
+op 0x20001d1: GetArmorType
+op 0x20001d2: GetArmorType, explicit
+op 0x20001d3: GetAttacked
+op 0x20001d4: GetAttacked, explicit
+op 0x20001d5: HasItemEquipped
+op 0x20001d6: HasItemEquipped, explicit
+op 0x20001d7: GetWeaponDrawn
+op 0x20001d8: GetWeaponDrawn, explicit
+op 0x20001d9: GetRace
+op 0x20001da: GetRace, explicit
+op 0x20001db: GetSpellEffects
+op 0x20001dc: GetSpellEffects, explicit
+op 0x20001dd: GetCurrentTime
+op 0x20001de: HasSoulGem
+op 0x20001df: HasSoulGem, explicit
+op 0x20001e0: GetWeaponType
+op 0x20001e1: GetWeaponType, explicit
+op 0x20001e2: GetWerewolfKills
+op 0x20001e3: ModScale
+op 0x20001e4: ModScale, explicit
+op 0x20001e5: SetDelete
+op 0x20001e6: SetDelete, explicit
+op 0x20001e7: GetSquareRoot
+op 0x20001e8: RaiseRank
+op 0x20001e9: RaiseRank, explicit
+op 0x20001ea: LowerRank
+op 0x20001eb: LowerRank, explicit
+op 0x20001ec: GetPCCrimeLevel
+op 0x20001ed: SetPCCrimeLevel
+op 0x20001ee: ModPCCrimeLevel
+op 0x20001ef: GetCurrentAIPackage
+op 0x20001f0: GetCurrentAIPackage, explicit reference
+op 0x20001f1: GetDetected
+op 0x20001f2: GetDetected, explicit reference
+op 0x20001f3: AddSoulGem
+op 0x20001f4: AddSoulGem, explicit reference
+op 0x20001f5: RemoveSoulGem
+op 0x20001f6: RemoveSoulGem, explicit reference
+op 0x20001f7: PlayBink
+op 0x20001f8: Drop
+op 0x20001f9: Drop, explicit reference
+op 0x20001fa: DropSoulGem
+op 0x20001fb: DropSoulGem, explicit reference
+op 0x20001fc: OnDeath
+op 0x20001fd: IsWerewolf
+op 0x20001fe: IsWerewolf, explicit reference
+op 0x20001ff: Rotate
+op 0x2000200: Rotate, explicit reference
+op 0x2000201: RotateWorld
+op 0x2000202: RotateWorld, explicit reference
+op 0x2000203: SetAtStart
+op 0x2000204: SetAtStart, explicit
+op 0x2000205: OnDeath, explicit
+op 0x2000206: Move
+op 0x2000207: Move, explicit
+op 0x2000208: MoveWorld
+op 0x2000209: MoveWorld, explicit
+op 0x200020a: Fall
+op 0x200020b: Fall, explicit
+op 0x200020c: GetStandingPC
+op 0x200020d: GetStandingPC, explicit
+op 0x200020e: GetStandingActor
+op 0x200020f: GetStandingActor, explicit
+op 0x2000210: GetStartingAngle
+op 0x2000211: GetStartingAngle, explicit
+op 0x2000212: GetWindSpeed
+op 0x2000213: HitOnMe
+op 0x2000214: HitOnMe, explicit
+op 0x2000215: DisableTeleporting
+op 0x2000216: EnableTeleporting
+op 0x2000217: BecomeWerewolf
+op 0x2000218: BecomeWerewolfExplicit
+op 0x2000219: UndoWerewolf
+op 0x200021a: UndoWerewolfExplicit
+op 0x200021b: SetWerewolfAcrobatics
+op 0x200021c: SetWerewolfAcrobaticsExplicit
+op 0x200021d: ShowVars
+op 0x200021e: ShowVarsExplicit
+
+opcodes 0x200021f-0x3ffffff unused
diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp
new file mode 100644
index 0000000000..2170ba4fb3
--- /dev/null
+++ b/apps/openmw/mwscript/extensions.cpp
@@ -0,0 +1,46 @@
+
+#include "extensions.hpp"
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/installopcodes.hpp>
+
+#include "soundextensions.hpp"
+#include "cellextensions.hpp"
+#include "miscextensions.hpp"
+#include "guiextensions.hpp"
+#include "skyextensions.hpp"
+#include "statsextensions.hpp"
+#include "containerextensions.hpp"
+#include "aiextensions.hpp"
+#include "controlextensions.hpp"
+#include "dialogueextensions.hpp"
+#include "animationextensions.hpp"
+#include "transformationextensions.hpp"
+#include "consoleextensions.hpp"
+#include "userextensions.hpp"
+
+namespace MWScript
+{
+ void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly)
+ {
+ Interpreter::installOpcodes (interpreter);
+ Cell::installOpcodes (interpreter);
+ Misc::installOpcodes (interpreter);
+ Gui::installOpcodes (interpreter);
+ Sound::installOpcodes (interpreter);
+ Sky::installOpcodes (interpreter);
+ Stats::installOpcodes (interpreter);
+ Container::installOpcodes (interpreter);
+ Ai::installOpcodes (interpreter);
+ Control::installOpcodes (interpreter);
+ Dialogue::installOpcodes (interpreter);
+ Animation::installOpcodes (interpreter);
+ Transformation::installOpcodes (interpreter);
+
+ if (consoleOnly)
+ {
+ Console::installOpcodes (interpreter);
+ User::installOpcodes (interpreter);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/extensions.hpp b/apps/openmw/mwscript/extensions.hpp
new file mode 100644
index 0000000000..67f6de5c58
--- /dev/null
+++ b/apps/openmw/mwscript/extensions.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_SCRIPT_EXTENSIONS_H
+#define GAME_SCRIPT_EXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ void installOpcodes (Interpreter::Interpreter& interpreter, bool consoleOnly = false);
+ ///< \param consoleOnly include console only opcodes
+}
+
+#endif
diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp
new file mode 100644
index 0000000000..608725ae6f
--- /dev/null
+++ b/apps/openmw/mwscript/globalscripts.cpp
@@ -0,0 +1,79 @@
+
+#include "globalscripts.hpp"
+
+#include <cassert>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+#include "interpretercontext.hpp"
+
+namespace MWScript
+{
+ GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store)
+ : mStore (store)
+ {
+ reset();
+ }
+
+ void GlobalScripts::reset()
+ {
+ mScripts.clear();
+ addScript ("Main");
+
+ MWWorld::Store<ESM::StartScript>::iterator iter =
+ mStore.get<ESM::StartScript>().begin();
+
+ for (; iter != mStore.get<ESM::StartScript>().end(); ++iter) {
+ addScript (iter->mScript);
+ }
+ }
+
+ void GlobalScripts::addScript (const std::string& name)
+ {
+ if (mScripts.find (name)==mScripts.end())
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
+ {
+ Locals locals;
+
+ locals.configure (*script);
+
+ mScripts.insert (std::make_pair (name, std::make_pair (true, locals)));
+ }
+ }
+
+ void GlobalScripts::removeScript (const std::string& name)
+ {
+ std::map<std::string, std::pair<bool, Locals> >::iterator iter = mScripts.find (name);
+
+ if (iter!=mScripts.end())
+ iter->second.first = false;
+ }
+
+ bool GlobalScripts::isRunning (const std::string& name) const
+ {
+ std::map<std::string, std::pair<bool, Locals> >::const_iterator iter =
+ mScripts.find (name);
+
+ if (iter==mScripts.end())
+ return false;
+
+ return iter->second.first;
+ }
+
+ void GlobalScripts::run()
+ {
+ for (std::map<std::string, std::pair<bool, Locals> >::iterator iter (mScripts.begin());
+ iter!=mScripts.end(); ++iter)
+ {
+ if (iter->second.first)
+ {
+ MWScript::InterpreterContext interpreterContext (
+ &iter->second.second, MWWorld::Ptr());
+ MWBase::Environment::get().getScriptManager()->run (iter->first, interpreterContext);
+ }
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp
new file mode 100644
index 0000000000..628919d1d2
--- /dev/null
+++ b/apps/openmw/mwscript/globalscripts.hpp
@@ -0,0 +1,38 @@
+#ifndef GAME_SCRIPT_GLOBALSCRIPTS_H
+#define GAME_SCRIPT_GLOBALSCRIPTS_H
+
+#include <string>
+#include <map>
+
+#include "locals.hpp"
+
+namespace MWWorld
+{
+ struct ESMStore;
+}
+
+namespace MWScript
+{
+ class GlobalScripts
+ {
+ const MWWorld::ESMStore& mStore;
+ std::map<std::string, std::pair<bool, Locals> > mScripts; // running, local variables
+
+ public:
+
+ GlobalScripts (const MWWorld::ESMStore& store);
+
+ void reset();
+
+ void addScript (const std::string& name);
+
+ void removeScript (const std::string& name);
+
+ bool isRunning (const std::string& name) const;
+
+ void run();
+ ///< run all active global scripts
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp
new file mode 100644
index 0000000000..6c89a0d1ce
--- /dev/null
+++ b/apps/openmw/mwscript/guiextensions.cpp
@@ -0,0 +1,187 @@
+
+#include "guiextensions.hpp"
+
+#include <boost/algorithm/string.hpp>
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "interpretercontext.hpp"
+
+namespace MWScript
+{
+ namespace Gui
+ {
+ class OpEnableWindow : public Interpreter::Opcode0
+ {
+ MWGui::GuiWindow mWindow;
+
+ public:
+
+ OpEnableWindow (MWGui::GuiWindow window) : mWindow (window) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager()->allow (mWindow);
+ }
+ };
+
+ class OpEnableRest : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager()->enableRest();
+ }
+ };
+
+ class OpShowDialogue : public Interpreter::Opcode0
+ {
+ MWGui::GuiMode mDialogue;
+
+ public:
+
+ OpShowDialogue (MWGui::GuiMode dialogue)
+ : mDialogue (dialogue)
+ {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(mDialogue);
+ }
+ };
+
+ class OpGetButtonPressed : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWindowManager()->readPressedButton());
+ }
+ };
+
+ class OpToggleFogOfWar : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager()->toggleFogOfWar();
+ }
+ };
+
+ class OpToggleFullHelp : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager()->toggleFullHelp();
+ }
+ };
+
+ class OpShowMap : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string cell = (runtime.getStringLiteral (runtime[0].mInteger));
+ Misc::StringUtils::toLower(cell);
+ runtime.pop();
+
+ // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well."
+ // http://www.uesp.net/wiki/Tes3Mod:ShowMap
+
+ const MWWorld::Store<ESM::Cell> &cells =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Cell>();
+
+ MWWorld::Store<ESM::Cell>::iterator it = cells.extBegin();
+ for (; it != cells.extEnd(); ++it)
+ {
+ std::string name = it->mName;
+ Misc::StringUtils::toLower(name);
+ if (name.find(cell) != std::string::npos)
+ MWBase::Environment::get().getWindowManager()->addVisitedLocation (
+ it->mName,
+ it->getGridX(),
+ it->getGridY()
+ );
+ }
+ }
+ };
+
+ class OpFillMap : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ const MWWorld::Store<ESM::Cell> &cells =
+ MWBase::Environment::get().getWorld ()->getStore().get<ESM::Cell>();
+
+ MWWorld::Store<ESM::Cell>::iterator it = cells.extBegin();
+ for (; it != cells.extEnd(); ++it)
+ {
+ std::string name = it->mName;
+ if (name != "")
+ MWBase::Environment::get().getWindowManager()->addVisitedLocation (
+ name,
+ it->getGridX(),
+ it->getGridY()
+ );
+ }
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableBirthMenu,
+ new OpShowDialogue (MWGui::GM_Birth));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableClassMenu,
+ new OpShowDialogue (MWGui::GM_Class));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableNameMenu,
+ new OpShowDialogue (MWGui::GM_Name));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableRaceMenu,
+ new OpShowDialogue (MWGui::GM_Race));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsReviewMenu,
+ new OpShowDialogue (MWGui::GM_Review));
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableInventoryMenu,
+ new OpEnableWindow (MWGui::GW_Inventory));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableMagicMenu,
+ new OpEnableWindow (MWGui::GW_Magic));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableMapMenu,
+ new OpEnableWindow (MWGui::GW_Map));
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsMenu,
+ new OpEnableWindow (MWGui::GW_Stats));
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeEnableRest,
+ new OpEnableRest ());
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu,
+ new OpShowDialogue (MWGui::GM_RestBed));
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed);
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeToggleFogOfWar, new OpToggleFogOfWar);
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeToggleFullHelp, new OpToggleFullHelp);
+
+ interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap);
+ interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/guiextensions.hpp b/apps/openmw/mwscript/guiextensions.hpp
new file mode 100644
index 0000000000..ec775a51c9
--- /dev/null
+++ b/apps/openmw/mwscript/guiextensions.hpp
@@ -0,0 +1,24 @@
+#ifndef GAME_SCRIPT_GUIEXTENSIONS_H
+#define GAME_SCRIPT_GUIEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief GUI-related script functionality
+ namespace Gui
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
+
diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp
new file mode 100644
index 0000000000..b8fc9ed477
--- /dev/null
+++ b/apps/openmw/mwscript/interpretercontext.cpp
@@ -0,0 +1,506 @@
+
+#include "interpretercontext.hpp"
+
+#include <cmath>
+#include <stdexcept>
+
+#include <components/interpreter/types.hpp>
+#include "../mwworld/esmstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/scriptmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/inputmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "locals.hpp"
+#include "globalscripts.hpp"
+
+namespace MWScript
+{
+ MWWorld::Ptr InterpreterContext::getReference (
+ const std::string& id, bool activeOnly)
+ {
+ if (!id.empty())
+ {
+ return MWBase::Environment::get().getWorld()->getPtr (id, activeOnly);
+ }
+ else
+ {
+ if (mReference.isEmpty())
+ throw std::runtime_error ("no implicit reference");
+
+ return mReference;
+ }
+ }
+
+ const MWWorld::Ptr InterpreterContext::getReference (
+ const std::string& id, bool activeOnly) const
+ {
+ if (!id.empty())
+ {
+ return MWBase::Environment::get().getWorld()->getPtr (id, activeOnly);
+ }
+ else
+ {
+ if (mReference.isEmpty())
+ throw std::runtime_error ("no implicit reference");
+
+ return mReference;
+ }
+ }
+
+ InterpreterContext::InterpreterContext (
+ MWScript::Locals *locals, MWWorld::Ptr reference)
+ : mLocals (locals), mReference (reference),
+ mActivationHandled (false)
+ {}
+
+ int InterpreterContext::getLocalShort (int index) const
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ return mLocals->mShorts.at (index);
+ }
+
+ int InterpreterContext::getLocalLong (int index) const
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ return mLocals->mLongs.at (index);
+ }
+
+ float InterpreterContext::getLocalFloat (int index) const
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ return mLocals->mFloats.at (index);
+ }
+
+ void InterpreterContext::setLocalShort (int index, int value)
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ mLocals->mShorts.at (index) = value;
+ }
+
+ void InterpreterContext::setLocalLong (int index, int value)
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ mLocals->mLongs.at (index) = value;
+ }
+
+ void InterpreterContext::setLocalFloat (int index, float value)
+ {
+ if (!mLocals)
+ throw std::runtime_error ("local variables not available in this context");
+
+ mLocals->mFloats.at (index) = value;
+ }
+
+ void InterpreterContext::messageBox (const std::string& message,
+ const std::vector<std::string>& buttons)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox (message, buttons);
+ }
+
+ void InterpreterContext::report (const std::string& message)
+ {
+ messageBox (message);
+ }
+
+ bool InterpreterContext::menuMode()
+ {
+ return MWBase::Environment::get().getWindowManager()->isGuiMode();
+ }
+
+ int InterpreterContext::getGlobalShort (const std::string& name) const
+ {
+ return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort;
+ }
+
+ int InterpreterContext::getGlobalLong (const std::string& name) const
+ {
+ // a global long is internally a float.
+ return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong;
+ }
+
+ float InterpreterContext::getGlobalFloat (const std::string& name) const
+ {
+ return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat;
+ }
+
+ void InterpreterContext::setGlobalShort (const std::string& name, int value)
+ {
+ if (name=="gamehour")
+ MWBase::Environment::get().getWorld()->setHour (value);
+ else if (name=="day")
+ MWBase::Environment::get().getWorld()->setDay (value);
+ else if (name=="month")
+ MWBase::Environment::get().getWorld()->setMonth (value);
+ else
+ MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort = value;
+ }
+
+ void InterpreterContext::setGlobalLong (const std::string& name, int value)
+ {
+ if (name=="gamehour")
+ MWBase::Environment::get().getWorld()->setHour (value);
+ else if (name=="day")
+ MWBase::Environment::get().getWorld()->setDay (value);
+ else if (name=="month")
+ MWBase::Environment::get().getWorld()->setMonth (value);
+ else
+ MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong = value;
+ }
+
+ void InterpreterContext::setGlobalFloat (const std::string& name, float value)
+ {
+ if (name=="gamehour")
+ MWBase::Environment::get().getWorld()->setHour (value);
+ else if (name=="day")
+ MWBase::Environment::get().getWorld()->setDay (value);
+ else if (name=="month")
+ MWBase::Environment::get().getWorld()->setMonth (value);
+ else
+ MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat = value;
+ }
+
+ std::vector<std::string> InterpreterContext::getGlobals () const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ return world->getGlobals();
+
+ }
+
+ char InterpreterContext::getGlobalType (const std::string& name) const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ return world->getGlobalVariableType(name);
+ }
+
+ std::string InterpreterContext::getActionBinding(const std::string& action) const
+ {
+ std::vector<int> actions = MWBase::Environment::get().getInputManager()->getActionSorting ();
+ for (std::vector<int>::const_iterator it = actions.begin(); it != actions.end(); ++it)
+ {
+ std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (*it);
+ if(desc == "")
+ continue;
+
+ if(desc == action)
+ return MWBase::Environment::get().getInputManager()->getActionBindingName (*it);
+ }
+
+ return "None";
+ }
+
+ std::string InterpreterContext::getNPCName() const
+ {
+ ESM::NPC npc = *mReference.get<ESM::NPC>()->mBase;
+ return npc.mName;
+ }
+
+ std::string InterpreterContext::getNPCRace() const
+ {
+ ESM::NPC npc = *mReference.get<ESM::NPC>()->mBase;
+ const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc.mRace);
+ return race->mName;
+ }
+
+ std::string InterpreterContext::getNPCClass() const
+ {
+ ESM::NPC npc = *mReference.get<ESM::NPC>()->mBase;
+ const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc.mClass);
+ return class_->mName;
+ }
+
+ std::string InterpreterContext::getNPCFaction() const
+ {
+ ESM::NPC npc = *mReference.get<ESM::NPC>()->mBase;
+ const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(npc.mFaction);
+ return faction->mName;
+ }
+
+ std::string InterpreterContext::getNPCRank() const
+ {
+ std::map<std::string, int> ranks = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks();
+ std::map<std::string, int>::const_iterator it = ranks.begin();
+
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::ESMStore &store = world->getStore();
+ const ESM::Faction *faction = store.get<ESM::Faction>().find(it->first);
+
+ return faction->mRanks[it->second];
+ }
+
+ std::string InterpreterContext::getPCName() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ ESM::NPC player = *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ return player.mName;
+ }
+
+ std::string InterpreterContext::getPCRace() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ std::string race = world->getPlayer().getPlayer().get<ESM::NPC>()->mBase->mRace;
+ return world->getStore().get<ESM::Race>().find(race)->mName;
+ }
+
+ std::string InterpreterContext::getPCClass() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ std::string class_ = world->getPlayer().getPlayer().get<ESM::NPC>()->mBase->mClass;
+ return world->getStore().get<ESM::Class>().find(class_)->mName;
+ }
+
+ std::string InterpreterContext::getPCRank() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+
+ std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
+
+ std::map<std::string, int> ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks();
+ std::map<std::string, int>::const_iterator it = ranks.begin();
+
+ const MWWorld::ESMStore &store = world->getStore();
+ const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
+
+ if(it->second < 0 || it->second > 9) // there are only 10 ranks
+ return "";
+
+ return faction->mRanks[it->second];
+ }
+
+ std::string InterpreterContext::getPCNextRank() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+
+ std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
+
+ const MWWorld::ESMStore &store = world->getStore();
+ const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId);
+
+ std::map<std::string, int> ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks();
+
+ if (!ranks.empty())
+ {
+ std::map<std::string, int>::const_iterator it = ranks.begin();
+
+ if(it->second < -1 || it->second > 9)
+ return "";
+
+ if(it->second <= 8) // If player is at max rank, there is no next rank
+ return faction->mRanks[it->second + 1];
+ else
+ return faction->mRanks[it->second];
+ }
+ else
+ return faction->mRanks[0];
+ }
+
+ int InterpreterContext::getPCBounty() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ return MWWorld::Class::get (player).getNpcStats (player).getBounty();
+ }
+
+ std::string InterpreterContext::getCurrentCellName() const
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ return world->getCurrentCellName();
+ }
+
+ bool InterpreterContext::isScriptRunning (const std::string& name) const
+ {
+ return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name);
+ }
+
+ void InterpreterContext::startScript (const std::string& name)
+ {
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name);
+ }
+
+ void InterpreterContext::stopScript (const std::string& name)
+ {
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript (name);
+ }
+
+ float InterpreterContext::getDistance (const std::string& name, const std::string& id) const
+ {
+ // TODO handle exterior cells (when ref and ref2 are located in different cells)
+ const MWWorld::Ptr ref2 = getReference (id, false);
+
+ const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr (name, true);
+
+ double diff[3];
+
+ const float* const pos1 = ref.getRefData().getPosition().pos;
+ const float* const pos2 = ref2.getRefData().getPosition().pos;
+ for (int i=0; i<3; ++i)
+ diff[i] = pos1[i] - pos2[i];
+
+ return std::sqrt (diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2]);
+ }
+
+ bool InterpreterContext::hasBeenActivated (const MWWorld::Ptr& ptr)
+ {
+ if (!mActivated.isEmpty() && mActivated==ptr)
+ {
+ mActivationHandled = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool InterpreterContext::hasActivationBeenHandled() const
+ {
+ return mActivationHandled;
+ }
+
+ void InterpreterContext::activate (const MWWorld::Ptr& ptr,
+ boost::shared_ptr<MWWorld::Action> action)
+ {
+ mActivated = ptr;
+ mActivationHandled = false;
+ mAction = action;
+ }
+
+ void InterpreterContext::executeActivation()
+ {
+ if (!mAction.get())
+ throw std::runtime_error ("activation failed, because no action to perform");
+
+ mAction->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mActivationHandled = true;
+ }
+
+ void InterpreterContext::clearActivation()
+ {
+ mActivated = MWWorld::Ptr();
+ mActivationHandled = false;
+ mAction.reset();
+ }
+
+ float InterpreterContext::getSecondsPassed() const
+ {
+ return MWBase::Environment::get().getFrameDuration();
+ }
+
+ bool InterpreterContext::isDisabled (const std::string& id) const
+ {
+ const MWWorld::Ptr ref = getReference (id, false);
+ return !ref.getRefData().isEnabled();
+ }
+
+ void InterpreterContext::enable (const std::string& id)
+ {
+ MWWorld::Ptr ref = getReference (id, false);
+ MWBase::Environment::get().getWorld()->enable (ref);
+ }
+
+ void InterpreterContext::disable (const std::string& id)
+ {
+ MWWorld::Ptr ref = getReference (id, false);
+ MWBase::Environment::get().getWorld()->disable (ref);
+ }
+
+ int InterpreterContext::getMemberShort (const std::string& id, const std::string& name) const
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ return ptr.getRefData().getLocals().mShorts[index];
+ }
+
+ int InterpreterContext::getMemberLong (const std::string& id, const std::string& name) const
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ return ptr.getRefData().getLocals().mLongs[index];
+ }
+
+ float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name) const
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ return ptr.getRefData().getLocals().mFloats[index];
+ }
+
+ void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, int value)
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ ptr.getRefData().getLocals().mShorts[index] = value;
+ }
+
+ void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value)
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ ptr.getRefData().getLocals().mLongs[index] = value;
+ }
+
+ void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value)
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+
+ int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f');
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (scriptId));
+ ptr.getRefData().getLocals().mFloats[index] = value;
+ }
+
+ MWWorld::Ptr InterpreterContext::getReference()
+ {
+ return getReference ("", true);
+ }
+}
diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp
new file mode 100644
index 0000000000..9c7b443ae1
--- /dev/null
+++ b/apps/openmw/mwscript/interpretercontext.hpp
@@ -0,0 +1,158 @@
+#ifndef GAME_SCRIPT_INTERPRETERCONTEXT_H
+#define GAME_SCRIPT_INTERPRETERCONTEXT_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <components/interpreter/context.hpp>
+
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/action.hpp"
+
+namespace MWSound
+{
+ class SoundManager;
+}
+
+namespace MWInput
+{
+ struct MWInputManager;
+}
+
+namespace MWScript
+{
+ struct Locals;
+
+ class InterpreterContext : public Interpreter::Context
+ {
+ Locals *mLocals;
+ MWWorld::Ptr mReference;
+
+ MWWorld::Ptr mActivated;
+ bool mActivationHandled;
+ boost::shared_ptr<MWWorld::Action> mAction;
+
+ MWWorld::Ptr getReference (const std::string& id, bool activeOnly);
+
+ const MWWorld::Ptr getReference (const std::string& id, bool activeOnly) const;
+
+ public:
+
+ InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference);
+ ///< The ownership of \a locals is not transferred. 0-pointer allowed.
+
+ virtual int getLocalShort (int index) const;
+
+ virtual int getLocalLong (int index) const;
+
+ virtual float getLocalFloat (int index) const;
+
+ virtual void setLocalShort (int index, int value);
+
+ virtual void setLocalLong (int index, int value);
+
+ virtual void setLocalFloat (int index, float value);
+
+ using Interpreter::Context::messageBox;
+
+ virtual void messageBox (const std::string& message,
+ const std::vector<std::string>& buttons);
+
+ virtual void report (const std::string& message);
+ ///< By default echo via messageBox.
+
+ virtual bool menuMode();
+
+ virtual int getGlobalShort (const std::string& name) const;
+
+ virtual int getGlobalLong (const std::string& name) const;
+
+ virtual float getGlobalFloat (const std::string& name) const;
+
+ virtual void setGlobalShort (const std::string& name, int value);
+
+ virtual void setGlobalLong (const std::string& name, int value);
+
+ virtual void setGlobalFloat (const std::string& name, float value);
+
+ virtual std::vector<std::string> getGlobals () const;
+
+ virtual char getGlobalType (const std::string& name) const;
+
+ virtual std::string getActionBinding(const std::string& action) const;
+
+ virtual std::string getNPCName() const;
+
+ virtual std::string getNPCRace() const;
+
+ virtual std::string getNPCClass() const;
+
+ virtual std::string getNPCFaction() const;
+
+ virtual std::string getNPCRank() const;
+
+ virtual std::string getPCName() const;
+
+ virtual std::string getPCRace() const;
+
+ virtual std::string getPCClass() const;
+
+ virtual std::string getPCRank() const;
+
+ virtual std::string getPCNextRank() const;
+
+ virtual int getPCBounty() const;
+
+ virtual std::string getCurrentCellName() const;
+
+ virtual bool isScriptRunning (const std::string& name) const;
+
+ virtual void startScript (const std::string& name);
+
+ virtual void stopScript (const std::string& name);
+
+ virtual float getDistance (const std::string& name, const std::string& id = "") const;
+
+ bool hasBeenActivated (const MWWorld::Ptr& ptr);
+ ///< \attention Calling this function for the right reference will mark the action as
+ /// been handled.
+
+ bool hasActivationBeenHandled() const;
+
+ void activate (const MWWorld::Ptr& ptr, boost::shared_ptr<MWWorld::Action> action);
+ ///< Store reference acted upon and action. The actual execution of the action does not
+ /// take place here.
+
+ void executeActivation();
+ ///< Execute the action defined by the last activate call.
+
+ void clearActivation();
+ ///< Discard the action defined by the last activate call.
+
+ virtual float getSecondsPassed() const;
+
+ virtual bool isDisabled (const std::string& id = "") const;
+
+ virtual void enable (const std::string& id = "");
+
+ virtual void disable (const std::string& id = "");
+
+ virtual int getMemberShort (const std::string& id, const std::string& name) const;
+
+ virtual int getMemberLong (const std::string& id, const std::string& name) const;
+
+ virtual float getMemberFloat (const std::string& id, const std::string& name) const;
+
+ virtual void setMemberShort (const std::string& id, const std::string& name, int value);
+
+ virtual void setMemberLong (const std::string& id, const std::string& name, int value);
+
+ virtual void setMemberFloat (const std::string& id, const std::string& name, float value);
+
+ MWWorld::Ptr getReference();
+ ///< Reference, that the script is running from (can be empty)
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp
new file mode 100644
index 0000000000..180a2791bc
--- /dev/null
+++ b/apps/openmw/mwscript/locals.cpp
@@ -0,0 +1,68 @@
+#include "locals.hpp"
+
+#include <components/esm/loadscpt.hpp>
+
+#include <components/compiler/locals.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+namespace MWScript
+{
+ void Locals::configure (const ESM::Script& script)
+ {
+ mShorts.clear();
+ mShorts.resize (script.mData.mNumShorts, 0);
+ mLongs.clear();
+ mLongs.resize (script.mData.mNumLongs, 0);
+ mFloats.clear();
+ mFloats.resize (script.mData.mNumFloats, 0);
+ }
+
+ int Locals::getIntVar(const std::string &script, const std::string &var)
+ {
+ Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
+ int index = locals.getIndex(var);
+ char type = locals.getType(var);
+ if(index != -1)
+ {
+ switch(type)
+ {
+ case 's':
+ return mShorts.at (index);
+
+ case 'l':
+ return mLongs.at (index);
+
+ case 'f':
+ return mFloats.at (index);
+ default:
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ bool Locals::setVarByInt(const std::string& script, const std::string& var, int val)
+ {
+ Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
+ int index = locals.getIndex(var);
+ char type = locals.getType(var);
+ if(index != -1)
+ {
+ switch(type)
+ {
+ case 's':
+ mShorts.at (index) = val; break;
+
+ case 'l':
+ mLongs.at (index) = val; break;
+
+ case 'f':
+ mFloats.at (index) = val; break;
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp
new file mode 100644
index 0000000000..deae0d44ea
--- /dev/null
+++ b/apps/openmw/mwscript/locals.hpp
@@ -0,0 +1,30 @@
+#ifndef GAME_SCRIPT_LOCALS_H
+#define GAME_SCRIPT_LOCALS_H
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+namespace ESM
+{
+ struct Script;
+}
+
+namespace MWScript
+{
+ class Locals
+ {
+ public:
+ std::vector<Interpreter::Type_Short> mShorts;
+ std::vector<Interpreter::Type_Integer> mLongs;
+ std::vector<Interpreter::Type_Float> mFloats;
+
+ void configure (const ESM::Script& script);
+ bool setVarByInt(const std::string& script, const std::string& var, int val);
+ int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0
+
+ };
+}
+
+#endif
+
diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp
new file mode 100644
index 0000000000..3141f13a25
--- /dev/null
+++ b/apps/openmw/mwscript/miscextensions.cpp
@@ -0,0 +1,780 @@
+
+#include "miscextensions.hpp"
+
+#include <cstdlib>
+
+#include <libs/openengine/ogre/fader.hpp>
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+#include <components/compiler/locals.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/containerstore.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Misc
+ {
+ class OpPlayBink : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ bool allowSkipping = runtime[0].mInteger;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld ()->playVideo (name, allowSkipping);
+ }
+ };
+
+ class OpGetPcSleep : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWindowManager ()->getPlayerSleeping());
+ }
+ };
+
+ class OpWakeUpPc : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWindowManager ()->wakeUpPlayer();
+ }
+ };
+
+ class OpXBox : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (0);
+ }
+ };
+
+ class OpOnActivate : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ MWWorld::Ptr ptr = context.getReference();
+
+ runtime.push (context.hasBeenActivated (ptr));
+ }
+ };
+
+ class OpActivate : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ context.executeActivation();
+ }
+ };
+
+ template<class R>
+ class OpLock : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer lockLevel = 100;
+
+ if (arg0==1)
+ {
+ lockLevel = runtime[0].mInteger;
+ runtime.pop();
+ }
+
+ MWWorld::Class::get (ptr).lock (ptr, lockLevel);
+ }
+ };
+
+ template<class R>
+ class OpUnlock : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWWorld::Class::get (ptr).unlock (ptr);
+ }
+ };
+
+ class OpToggleCollisionDebug : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ bool enabled =
+ MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_CollisionDebug);
+
+ context.report (enabled ?
+ "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off");
+ }
+ };
+
+
+ class OpToggleCollisionBoxes : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ bool enabled =
+ MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_BoundingBoxes);
+
+ context.report (enabled ?
+ "Bounding Box Rendering -> On" : "Bounding Box Rendering -> Off");
+ }
+ };
+
+ class OpToggleWireframe : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ bool enabled =
+ MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_Wireframe);
+
+ context.report (enabled ?
+ "Wireframe Rendering -> On" : "Wireframe Rendering -> Off");
+ }
+ };
+
+ class OpTogglePathgrid : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ bool enabled =
+ MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_Pathgrid);
+
+ context.report (enabled ?
+ "Path Grid rendering -> On" : "Path Grid Rendering -> Off");
+ }
+ };
+
+ class OpFadeIn : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Float time = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld()->getFader()->fadeIn(time);
+ }
+ };
+
+ class OpFadeOut : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Float time = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld()->getFader()->fadeOut(time);
+ }
+ };
+
+ class OpFadeTo : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ Interpreter::Type_Float alpha = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float time = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld()->getFader()->fadeTo(alpha, time);
+ }
+ };
+
+ class OpToggleWater : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWorld()->toggleWater();
+ }
+ };
+
+ class OpDontSaveObject : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ // We are ignoring the DontSaveObject statement for now. Probably not worth
+ /// bothering with. The incompatibility we are creating should be marginal at most.
+ }
+ };
+
+ class OpToggleVanityMode : public Interpreter::Opcode0
+ {
+ static bool sActivate;
+
+ public:
+
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ MWBase::World *world =
+ MWBase::Environment::get().getWorld();
+
+ if (world->toggleVanityMode(sActivate)) {
+ context.report(sActivate ? "Vanity Mode -> On" : "Vanity Mode -> Off");
+ sActivate = !sActivate;
+ } else {
+ context.report("Vanity Mode -> No");
+ }
+ }
+ };
+ bool OpToggleVanityMode::sActivate = true;
+
+ template <class R>
+ class OpGetLocked : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (ptr.getCellRef ().mLockLevel > 0);
+ }
+ };
+
+ template <class R>
+ class OpGetEffect : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string effect = runtime.getStringLiteral(runtime[0].mInteger);
+ runtime.pop();
+
+ char *end;
+ long key = strtol(effect.c_str(), &end, 10);
+ if(key < 0 || key > 32767 || *end != '\0')
+ key = ESM::MagicEffect::effectStringToId(effect);
+
+ runtime.push(MWWorld::Class::get(ptr).getCreatureStats(ptr).getMagicEffects().get(
+ MWMechanics::EffectKey(key)).mMagnitude > 0);
+ }
+ };
+
+ template<class R>
+ class OpAddSoulGem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string creature = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::string gem = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ 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);
+
+ }
+ };
+
+ template<class R>
+ class OpRemoveSoulGem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string soul = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ 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;
+ }
+ }
+ }
+ };
+
+ template<class R>
+ class OpDrop : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string item = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer amount = runtime[0].mInteger;
+ runtime.pop();
+
+ if (amount<0)
+ throw std::runtime_error ("amount must be non-negative");
+
+ // no-op
+ if (amount == 0)
+ return;
+
+ 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().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);
+ }
+
+ break;
+ }
+ }
+ }
+ };
+
+ template<class R>
+ class OpDropSoulGem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string soul = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ 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)
+ {
+ 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);
+ }
+
+ break;
+ }
+ }
+ }
+ };
+
+ template <class R>
+ class OpGetAttacked : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push(MWWorld::Class::get(ptr).getCreatureStats (ptr).getAttacked ());
+ }
+ };
+
+ template <class R>
+ class OpGetWeaponDrawn : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push(MWWorld::Class::get(ptr).getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon);
+ }
+ };
+
+ template <class R>
+ class OpGetSpellEffects : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ std::string id = runtime.getStringLiteral(runtime[0].mInteger);
+ runtime.pop();
+
+ runtime.push(MWWorld::Class::get(ptr).getCreatureStats(ptr).getActiveSpells().isSpellActive(id));
+ }
+ };
+
+ class OpGetCurrentTime : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push(MWBase::Environment::get().getWorld()->getTimeStamp().getHour());
+ }
+ };
+
+ template <class R>
+ class OpSetDelete : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ int parameter = runtime[0].mInteger;
+ runtime.pop();
+
+ if (parameter == 1)
+ {
+ if (ptr.isInCell())
+ MWBase::Environment::get().getWorld()->deleteObject (ptr);
+ else
+ ptr.getRefData().setCount(0);
+ }
+ }
+ };
+
+ class OpGetSquareRoot : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ float param = runtime[0].mFloat;
+ runtime.pop();
+
+ runtime.push(std::sqrt (param));
+ }
+ };
+
+ template <class R>
+ class OpFall : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ }
+ };
+
+ template <class R>
+ class OpGetStandingPc : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ runtime.push (MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr));
+ }
+ };
+
+ template <class R>
+ class OpGetStandingActor : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ runtime.push (MWBase::Environment::get().getWorld()->getActorStandingOn(ptr));
+ }
+ };
+
+ class OpGetWindSpeed : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push(MWBase::Environment::get().getWorld()->getWindSpeed());
+ }
+ };
+
+ template <class R>
+ class OpHitOnMe : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string objectID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWMechanics::CreatureStats &stats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
+ runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject()));
+ }
+ };
+
+ template <bool Enable>
+ class OpEnableTeleporting : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ world->enableTeleporting(Enable);
+ }
+ };
+
+
+ template <class R>
+ class OpShowVars : public Interpreter::Opcode0
+ {
+ void printLocalVars(Interpreter::Runtime &runtime, const MWWorld::Ptr &ptr)
+ {
+ std::stringstream str;
+
+ const std::string script = MWWorld::Class::get(ptr).getScript(ptr);
+ if(script.empty())
+ str<< ptr.getCellRef().mRefID<<" ("<<ptr.getRefData().getHandle()<<") does not have a script.";
+ else
+ {
+ str<< "Local variables for "<<ptr.getCellRef().mRefID<<" ("<<ptr.getRefData().getHandle()<<")";
+
+ const Locals &locals = ptr.getRefData().getLocals();
+ const Compiler::Locals &complocals = MWBase::Environment::get().getScriptManager()->getLocals(script);
+
+ const std::vector<std::string> *names = &complocals.get('s');
+ for(size_t i = 0;i < names->size();++i)
+ {
+ if(i >= locals.mShorts.size())
+ break;
+ str<<std::endl<< " "<<(*names)[i]<<" = "<<locals.mShorts[i]<<" (short)";
+ }
+ names = &complocals.get('l');
+ for(size_t i = 0;i < names->size();++i)
+ {
+ if(i >= locals.mLongs.size())
+ break;
+ str<<std::endl<< " "<<(*names)[i]<<" = "<<locals.mLongs[i]<<" (long)";
+ }
+ names = &complocals.get('f');
+ for(size_t i = 0;i < names->size();++i)
+ {
+ if(i >= locals.mFloats.size())
+ break;
+ str<<std::endl<< " "<<(*names)[i]<<" = "<<locals.mFloats[i]<<" (float)";
+ }
+ }
+
+ runtime.getContext().report(str.str());
+ }
+
+ void printGlobalVars(Interpreter::Runtime &runtime)
+ {
+ std::stringstream str;
+ str<< "Global variables:";
+
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ std::vector<std::string> names = world->getGlobals();
+ for(size_t i = 0;i < names.size();++i)
+ {
+ char type = world->getGlobalVariableType(names[i]);
+ if(type == 's')
+ str<<std::endl<< " "<<names[i]<<" = "<<world->getGlobalVariable(names[i]).mShort<<" (short)";
+ else if(type == 'l')
+ str<<std::endl<< " "<<names[i]<<" = "<<world->getGlobalVariable(names[i]).mLong<<" (long)";
+ else if(type == 'f')
+ str<<std::endl<< " "<<names[i]<<" = "<<world->getGlobalVariable(names[i]).mFloat<<" (float)";
+ }
+
+ runtime.getContext().report(str.str());
+ }
+
+ public:
+ virtual void execute(Interpreter::Runtime& runtime)
+ {
+ // No way to tell if we have a reference before trying to get it, and it will
+ // cause an exception is there isn't one :(
+ try {
+ MWWorld::Ptr ptr = R()(runtime);
+ printLocalVars(runtime, ptr);
+ }
+ catch(std::runtime_error&) {
+ // No reference, no problem.
+ printGlobalVars(runtime);
+ }
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
+ interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate);
+ interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate);
+ interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Misc::opcodeLockExplicit, new OpLock<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeUnlock, new OpUnlock<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeUnlockExplicit, new OpUnlock<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionDebug, new OpToggleCollisionDebug);
+ interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionBoxes, new OpToggleCollisionBoxes);
+ interpreter.installSegment5 (Compiler::Misc::opcodeToggleWireframe, new OpToggleWireframe);
+ interpreter.installSegment5 (Compiler::Misc::opcodeFadeIn, new OpFadeIn);
+ interpreter.installSegment5 (Compiler::Misc::opcodeFadeOut, new OpFadeOut);
+ interpreter.installSegment5 (Compiler::Misc::opcodeFadeTo, new OpFadeTo);
+ interpreter.installSegment5 (Compiler::Misc::opcodeTogglePathgrid, new OpTogglePathgrid);
+ interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject);
+ interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep);
+ interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc);
+ interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetLockedExplicit, new OpGetLocked<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetEffect, new OpGetEffect<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetEffectExplicit, new OpGetEffect<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGem, new OpAddSoulGem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGemExplicit, new OpAddSoulGem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeRemoveSoulGem, new OpRemoveSoulGem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDrop, new OpDrop<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDropExplicit, new OpDrop<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGem, new OpDropSoulGem<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGemExplicit, new OpDropSoulGem<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetAttacked, new OpGetAttacked<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime);
+ interpreter.installSegment5 (Compiler::Misc::opcodeSetDelete, new OpSetDelete<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeSetDeleteExplicit, new OpSetDelete<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetSquareRoot, new OpGetSquareRoot);
+ interpreter.installSegment5 (Compiler::Misc::opcodeFall, new OpFall<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeFallExplicit, new OpFall<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPc, new OpGetStandingPc<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed);
+ interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting<false>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting<true>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/miscextensions.hpp b/apps/openmw/mwscript/miscextensions.hpp
new file mode 100644
index 0000000000..16ed9301ed
--- /dev/null
+++ b/apps/openmw/mwscript/miscextensions.hpp
@@ -0,0 +1,24 @@
+#ifndef GAME_SCRIPT_MISCEXTENSIONS_H
+#define GAME_SCRIPT_MISCEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ namespace Misc
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
+
+
diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp
new file mode 100644
index 0000000000..81b1d5ef99
--- /dev/null
+++ b/apps/openmw/mwscript/ref.hpp
@@ -0,0 +1,40 @@
+#ifndef GAME_MWSCRIPT_REF_H
+#define GAME_MWSCRIPT_REF_H
+
+#include <string>
+
+#include <components/interpreter/runtime.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+#include "interpretercontext.hpp"
+
+namespace MWScript
+{
+ struct ExplicitRef
+ {
+ MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const
+ {
+ std::string id = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ return MWBase::Environment::get().getWorld()->getPtr (id, false);
+ }
+ };
+
+ struct ImplicitRef
+ {
+ MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const
+ {
+ MWScript::InterpreterContext& context
+ = static_cast<MWScript::InterpreterContext&> (runtime.getContext());
+
+ return context.getReference();
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp
new file mode 100644
index 0000000000..14fe5b7fd6
--- /dev/null
+++ b/apps/openmw/mwscript/scriptmanagerimp.cpp
@@ -0,0 +1,230 @@
+
+#include "scriptmanagerimp.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <exception>
+
+#include <components/esm/loadscpt.hpp>
+#include "../mwworld/esmstore.hpp"
+
+#include <components/compiler/scanner.hpp>
+#include <components/compiler/context.hpp>
+#include <components/compiler/exception.hpp>
+
+#include "extensions.hpp"
+
+namespace MWScript
+{
+ ScriptManager::ScriptManager (const MWWorld::ESMStore& store, bool verbose,
+ Compiler::Context& compilerContext)
+ : mErrorHandler (std::cerr), mStore (store), mVerbose (verbose),
+ mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext),
+ mOpcodesInstalled (false), mGlobalScripts (store)
+ {}
+
+ bool ScriptManager::compile (const std::string& name)
+ {
+ mParser.reset();
+ mErrorHandler.reset();
+
+ bool Success = true;
+
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
+ {
+ if (mVerbose)
+ std::cout << "compiling script: " << name << std::endl;
+
+ try
+ {
+ std::istringstream input (script->mScriptText);
+
+ Compiler::Scanner scanner (mErrorHandler, input, mCompilerContext.getExtensions());
+
+ scanner.scan (mParser);
+
+ if (!mErrorHandler.isGood())
+ Success = false;
+ }
+ catch (const Compiler::SourceException&)
+ {
+ // error has already been reported via error handler
+ Success = false;
+ }
+ catch (const std::exception& error)
+ {
+ std::cerr << "An exception has been thrown: " << error.what() << std::endl;
+ Success = false;
+ }
+
+ if (!Success && mVerbose)
+ {
+ std::cerr
+ << "compiling failed: " << name << std::endl
+ << script->mScriptText
+ << std::endl << std::endl;
+ }
+
+ if (Success)
+ {
+ std::vector<Interpreter::Type_Code> code;
+ mParser.getCode (code);
+ mScripts.insert (std::make_pair (name, std::make_pair (code, mParser.getLocals())));
+
+ // TODO sanity check on generated locals
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void ScriptManager::run (const std::string& name, Interpreter::Context& interpreterContext)
+ {
+ // compile script
+ ScriptCollection::iterator iter = mScripts.find (name);
+
+ if (iter==mScripts.end())
+ {
+ if (!compile (name))
+ {
+ // failed -> ignore script from now on.
+ std::vector<Interpreter::Type_Code> empty;
+ mScripts.insert (std::make_pair (name, std::make_pair (empty, Compiler::Locals())));
+ return;
+ }
+
+ iter = mScripts.find (name);
+ assert (iter!=mScripts.end());
+ }
+
+ // execute script
+ if (!iter->second.first.empty())
+ try
+ {
+ if (!mOpcodesInstalled)
+ {
+ installOpcodes (mInterpreter);
+ mOpcodesInstalled = true;
+ }
+
+ mInterpreter.run (&iter->second.first[0], iter->second.first.size(), interpreterContext);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "execution of script " << name << " failed." << std::endl;
+
+ if (mVerbose)
+ std::cerr << "(" << e.what() << ")" << std::endl;
+
+ iter->second.first.clear(); // don't execute again.
+ }
+ }
+
+ std::pair<int, int> ScriptManager::compileAll()
+ {
+ int count = 0;
+ int success = 0;
+
+ const MWWorld::Store<ESM::Script>& scripts = mStore.get<ESM::Script>();
+ MWWorld::Store<ESM::Script>::iterator it = scripts.begin();
+
+ for (; it != scripts.end(); ++it, ++count)
+ if (compile (it->mId))
+ ++success;
+
+ return std::make_pair (count, success);
+ }
+
+ Compiler::Locals& ScriptManager::getLocals (const std::string& name)
+ {
+ {
+ ScriptCollection::iterator iter = mScripts.find (name);
+
+ if (iter!=mScripts.end())
+ return iter->second.second;
+ }
+
+ {
+ std::map<std::string, Compiler::Locals>::iterator iter = mOtherLocals.find (name);
+
+ if (iter!=mOtherLocals.end())
+ return iter->second;
+ }
+
+ Compiler::Locals locals;
+
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
+ {
+ int index = 0;
+
+ for (int i=0; i<script->mData.mNumShorts; ++i)
+ locals.declare ('s', script->mVarNames[index++]);
+
+ for (int i=0; i<script->mData.mNumLongs; ++i)
+ locals.declare ('l', script->mVarNames[index++]);
+
+ for (int i=0; i<script->mData.mNumFloats; ++i)
+ locals.declare ('f', script->mVarNames[index++]);
+
+ std::map<std::string, Compiler::Locals>::iterator iter =
+ mOtherLocals.insert (std::make_pair (name, locals)).first;
+
+ return iter->second;
+ }
+
+ throw std::logic_error ("script " + name + " does not exist");
+ }
+
+ GlobalScripts& ScriptManager::getGlobalScripts()
+ {
+ return mGlobalScripts;
+ }
+
+ int ScriptManager::getLocalIndex (const std::string& scriptId, const std::string& variable,
+ char type)
+ {
+ const ESM::Script *script = mStore.get<ESM::Script>().find (scriptId);
+
+ int offset = 0;
+ int size = 0;
+
+ switch (type)
+ {
+ case 's':
+
+ offset = 0;
+ size = script->mData.mNumShorts;
+ break;
+
+ case 'l':
+
+ offset = script->mData.mNumShorts;
+ size = script->mData.mNumLongs;
+ break;
+
+ case 'f':
+
+ offset = script->mData.mNumShorts+script->mData.mNumLongs;
+ size = script->mData.mNumFloats;
+ break;
+
+ default:
+
+ throw std::runtime_error ("invalid variable type");
+ }
+
+ for (int i=0; i<size; ++i)
+ if (script->mVarNames.at (i+offset)==variable)
+ return i;
+
+ throw std::runtime_error ("unable to access local variable " + variable + " of " + scriptId);
+ }
+
+ void ScriptManager::resetGlobalScripts()
+ {
+ mGlobalScripts.reset();
+ }
+}
diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp
new file mode 100644
index 0000000000..7bb98ffbd3
--- /dev/null
+++ b/apps/openmw/mwscript/scriptmanagerimp.hpp
@@ -0,0 +1,82 @@
+#ifndef GAME_SCRIPT_SCRIPTMANAGER_H
+#define GAME_SCRIPT_SCRIPTMANAGER_H
+
+#include <map>
+#include <string>
+
+#include <components/compiler/streamerrorhandler.hpp>
+#include <components/compiler/fileparser.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/types.hpp>
+
+#include "../mwbase/scriptmanager.hpp"
+
+#include "globalscripts.hpp"
+
+namespace MWWorld
+{
+ struct ESMStore;
+}
+
+namespace Compiler
+{
+ class Context;
+}
+
+namespace Interpreter
+{
+ class Context;
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ class ScriptManager : public MWBase::ScriptManager
+ {
+ Compiler::StreamErrorHandler mErrorHandler;
+ const MWWorld::ESMStore& mStore;
+ bool mVerbose;
+ Compiler::Context& mCompilerContext;
+ Compiler::FileParser mParser;
+ Interpreter::Interpreter mInterpreter;
+ bool mOpcodesInstalled;
+
+ typedef std::pair<std::vector<Interpreter::Type_Code>, Compiler::Locals> CompiledScript;
+ typedef std::map<std::string, CompiledScript> ScriptCollection;
+
+ ScriptCollection mScripts;
+ GlobalScripts mGlobalScripts;
+ std::map<std::string, Compiler::Locals> mOtherLocals;
+
+ public:
+
+ ScriptManager (const MWWorld::ESMStore& store, bool verbose,
+ Compiler::Context& compilerContext);
+
+ virtual void run (const std::string& name, Interpreter::Context& interpreterContext);
+ ///< Run the script with the given name (compile first, if not compiled yet)
+
+ virtual bool compile (const std::string& name);
+ ///< Compile script with the given namen
+ /// \return Success?
+
+ virtual void resetGlobalScripts();
+
+ virtual std::pair<int, int> compileAll();
+ ///< Compile all scripts
+ /// \return count, success
+
+ virtual Compiler::Locals& getLocals (const std::string& name);
+ ///< Return locals for script \a name.
+
+ virtual GlobalScripts& getGlobalScripts();
+
+ virtual int getLocalIndex (const std::string& scriptId, const std::string& variable,
+ char type);
+ ///< Return index of the variable of the given name and type in the given script. Will
+ /// throw an exception, if there is no such script or variable or the type does not match.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp
new file mode 100644
index 0000000000..8b9efd74e1
--- /dev/null
+++ b/apps/openmw/mwscript/skyextensions.cpp
@@ -0,0 +1,135 @@
+
+#include "skyextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+
+#include "interpretercontext.hpp"
+
+namespace MWScript
+{
+ namespace Sky
+ {
+ class OpToggleSky : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ bool enabled = MWBase::Environment::get().getWorld()->toggleSky();
+
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
+ context.report (enabled ? "Sky -> On" : "Sky -> Off");
+ }
+ };
+
+ class OpTurnMoonWhite : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWorld()->setMoonColour (false);
+ }
+ };
+
+ class OpTurnMoonRed : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::Environment::get().getWorld()->setMoonColour (true);
+ }
+ };
+
+ class OpGetMasserPhase : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWorld()->getMasserPhase());
+ }
+ };
+
+ class OpGetSecundaPhase : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWorld()->getSecundaPhase());
+ }
+ };
+
+ class OpGetCurrentWeather : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.push (MWBase::Environment::get().getWorld()->getCurrentWeather());
+ }
+ };
+
+ class OpChangeWeather : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string region = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer id = runtime[0].mInteger;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld()->changeWeather(region, id);
+ }
+ };
+
+ class OpModRegion : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ std::string region = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::vector<char> chances;
+ chances.reserve(10);
+ while(arg0 > 0)
+ {
+ chances.push_back(std::max(0, std::min(127, runtime[0].mInteger)));
+ runtime.pop();
+ arg0--;
+ }
+
+ MWBase::Environment::get().getWorld()->modRegion(region, chances);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Sky::opcodeToggleSky, new OpToggleSky);
+ interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonWhite, new OpTurnMoonWhite);
+ interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonRed, new OpTurnMoonRed);
+ interpreter.installSegment5 (Compiler::Sky::opcodeGetMasserPhase, new OpGetMasserPhase);
+ interpreter.installSegment5 (Compiler::Sky::opcodeGetSecundaPhase, new OpGetSecundaPhase);
+ interpreter.installSegment5 (Compiler::Sky::opcodeGetCurrentWeather, new OpGetCurrentWeather);
+ interpreter.installSegment5 (Compiler::Sky::opcodeChangeWeather, new OpChangeWeather);
+ interpreter.installSegment3 (Compiler::Sky::opcodeModRegion, new OpModRegion);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/skyextensions.hpp b/apps/openmw/mwscript/skyextensions.hpp
new file mode 100644
index 0000000000..003f2fb190
--- /dev/null
+++ b/apps/openmw/mwscript/skyextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_SKYEXTENSIONS_H
+#define GAME_SCRIPT_SKYEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief sky-related script functionality
+ namespace Sky
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp
new file mode 100644
index 0000000000..73c3ec93a5
--- /dev/null
+++ b/apps/openmw/mwscript/soundextensions.cpp
@@ -0,0 +1,223 @@
+
+#include "extensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Sound
+ {
+ template<class R>
+ class OpSay : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWScript::InterpreterContext& context
+ = static_cast<MWScript::InterpreterContext&> (runtime.getContext());
+
+ std::string file = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::string text = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->say (ptr, file);
+
+ if (MWBase::Environment::get().getWindowManager ()->getSubtitlesEnabled())
+ context.messageBox (text);
+ }
+ };
+
+ template<class R>
+ class OpSayDone : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (MWBase::Environment::get().getSoundManager()->sayDone (ptr));
+ }
+ };
+
+ class OpStreamMusic : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->streamMusic (sound);
+ }
+ };
+
+ class OpPlaySound : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
+ }
+ };
+
+ class OpPlaySoundVP : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float volume = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float pitch = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->playSound (sound, volume, pitch);
+ }
+ };
+
+ template<class R>
+ class OpPlaySound3D : public Interpreter::Opcode0
+ {
+ bool mLoop;
+
+ public:
+
+ OpPlaySound3D (bool loop) : mLoop (loop) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0,
+ MWBase::SoundManager::Play_TypeSfx,
+ mLoop ? MWBase::SoundManager::Play_Loop :
+ MWBase::SoundManager::Play_Normal);
+ }
+ };
+
+ template<class R>
+ class OpPlaySoundVP3D : public Interpreter::Opcode0
+ {
+ bool mLoop;
+
+ public:
+
+ OpPlaySoundVP3D (bool loop) : mLoop (loop) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float volume = runtime[0].mFloat;
+ runtime.pop();
+
+ Interpreter::Type_Float pitch = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch,
+ MWBase::SoundManager::Play_TypeSfx,
+ mLoop ? MWBase::SoundManager::Play_Loop :
+ MWBase::SoundManager::Play_Normal);
+
+ }
+ };
+
+ template<class R>
+ class OpStopSound : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string sound = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWBase::Environment::get().getSoundManager()->stopSound3D (ptr, sound);
+ }
+ };
+
+ template<class R>
+ class OpGetSoundPlaying : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ int index = runtime[0].mInteger;
+ runtime.pop();
+
+ runtime.push (MWBase::Environment::get().getSoundManager()->getSoundPlaying (
+ ptr, runtime.getStringLiteral (index)));
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::Sound::opcodeSay, new OpSay<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodeSayDone, new OpSayDone<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodeStreamMusic, new OpStreamMusic);
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySound, new OpPlaySound);
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySoundVP, new OpPlaySoundVP);
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3D, new OpPlaySound3D<ImplicitRef> (false));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVP, new OpPlaySoundVP3D<ImplicitRef> (false));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3D, new OpPlaySound3D<ImplicitRef> (true));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVP,
+ new OpPlaySoundVP3D<ImplicitRef> (true));
+ interpreter.installSegment5 (Compiler::Sound::opcodeStopSound, new OpStopSound<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlaying, new OpGetSoundPlaying<ImplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Sound::opcodeSayExplicit, new OpSay<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodeSayDoneExplicit, new OpSayDone<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DExplicit,
+ new OpPlaySound3D<ExplicitRef> (false));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVPExplicit,
+ new OpPlaySoundVP3D<ExplicitRef> (false));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DExplicit,
+ new OpPlaySound3D<ExplicitRef> (true));
+ interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVPExplicit,
+ new OpPlaySoundVP3D<ExplicitRef> (true));
+ interpreter.installSegment5 (Compiler::Sound::opcodeStopSoundExplicit, new OpStopSound<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlayingExplicit,
+ new OpGetSoundPlaying<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/soundextensions.hpp b/apps/openmw/mwscript/soundextensions.hpp
new file mode 100644
index 0000000000..b92d7ea1bb
--- /dev/null
+++ b/apps/openmw/mwscript/soundextensions.hpp
@@ -0,0 +1,24 @@
+#ifndef GAME_SCRIPT_SOUNDEXTENSIONS_H
+#define GAME_SCRIPT_SOUNDEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ namespace Sound
+ {
+ // Script-extensions related to sound
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
+
diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp
new file mode 100644
index 0000000000..eca9d279b2
--- /dev/null
+++ b/apps/openmw/mwscript/statsextensions.cpp
@@ -0,0 +1,1213 @@
+
+#include "statsextensions.hpp"
+
+#include <cmath>
+
+#include <boost/algorithm/string.hpp>
+
+#include <components/esm/loadnpc.hpp>
+
+#include "../mwworld/esmstore.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace
+{
+ std::string getDialogueActorFaction()
+ {
+ MWWorld::Ptr actor = MWBase::Environment::get().getDialogueManager()->getActor();
+
+ const MWMechanics::NpcStats &stats = MWWorld::Class::get (actor).getNpcStats (actor);
+
+ if (stats.getFactionRanks().empty())
+ throw std::runtime_error (
+ "failed to determine dialogue actors faction (because actor is factionless)");
+
+ return stats.getFactionRanks().begin()->first;
+ }
+}
+
+namespace MWScript
+{
+ namespace Stats
+ {
+ template<class R>
+ class OpGetLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value =
+ MWWorld::Class::get (ptr)
+ .getCreatureStats (ptr)
+ .getLevel();
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpSetLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr)
+ .getCreatureStats (ptr)
+ .setLevel(value);
+ }
+ };
+
+ template<class R>
+ class OpGetAttribute : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpGetAttribute (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value =
+ MWWorld::Class::get (ptr)
+ .getCreatureStats (ptr)
+ .getAttribute(mIndex)
+ .getModified();
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpSetAttribute : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpSetAttribute (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get(ptr)
+ .getCreatureStats(ptr)
+ .getAttribute(mIndex)
+ .setModified (value, 0);
+ }
+ };
+
+ template<class R>
+ class OpModAttribute : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpModAttribute (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ value +=
+ MWWorld::Class::get(ptr)
+ .getCreatureStats(ptr)
+ .getAttribute(mIndex)
+ .getModified();
+
+ MWWorld::Class::get(ptr)
+ .getCreatureStats(ptr)
+ .getAttribute(mIndex)
+ .setModified (value, 0, 100);
+ }
+ };
+
+ template<class R>
+ class OpGetDynamic : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpGetDynamic (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ Interpreter::Type_Float value;
+
+ if (mIndex==0 && MWWorld::Class::get (ptr).hasItemHealth (ptr))
+ {
+ // health is a special case
+ value = MWWorld::Class::get (ptr).getItemMaxHealth (ptr);
+ } else {
+ value =
+ MWWorld::Class::get(ptr)
+ .getCreatureStats(ptr)
+ .getDynamic(mIndex)
+ .getCurrent();
+ }
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpSetDynamic : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpSetDynamic (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float value = runtime[0].mFloat;
+ runtime.pop();
+
+ MWMechanics::DynamicStat<float> stat (MWWorld::Class::get (ptr).getCreatureStats (ptr)
+ .getDynamic (mIndex));
+
+ stat.setModified (value, 0);
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat);
+ }
+ };
+
+ template<class R>
+ class OpModDynamic : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpModDynamic (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float diff = runtime[0].mFloat;
+ runtime.pop();
+
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+
+ Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent();
+
+ MWMechanics::DynamicStat<float> stat (MWWorld::Class::get (ptr).getCreatureStats (ptr)
+ .getDynamic (mIndex));
+
+ stat.setModified (diff + stat.getModified(), 0);
+
+ stat.setCurrent (diff + current);
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat);
+ }
+ };
+
+ template<class R>
+ class OpModCurrentDynamic : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpModCurrentDynamic (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float diff = runtime[0].mFloat;
+ runtime.pop();
+
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+
+ Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent();
+
+ MWMechanics::DynamicStat<float> stat (MWWorld::Class::get (ptr).getCreatureStats (ptr)
+ .getDynamic (mIndex));
+
+ stat.setCurrent (diff + current);
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat);
+ }
+ };
+
+ template<class R>
+ class OpGetDynamicGetRatio : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpGetDynamicGetRatio (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+
+ Interpreter::Type_Float value = 0;
+
+ Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified();
+
+ if (max>0)
+ value = stats.getDynamic(mIndex).getCurrent() / max;
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpGetSkill : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpGetSkill (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value =
+ MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
+ getModified();
+
+ runtime.push (value);
+ }
+ };
+
+ template<class R>
+ class OpSetSkill : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpSetSkill (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWMechanics::NpcStats& stats = MWWorld::Class::get (ptr).getNpcStats (ptr);
+
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+
+ assert (ref);
+
+ const ESM::Class& class_ =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (ref->mBase->mClass);
+
+ float level = 0;
+ float progress = std::modf (stats.getSkill (mIndex).getBase(), &level);
+
+ float modifier = stats.getSkill (mIndex).getModifier();
+
+ int newLevel = static_cast<int> (value-modifier);
+
+ 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);
+
+ if (progress>=1)
+ progress = 0.999999999;
+
+ stats.getSkill (mIndex).set (newLevel + progress);
+ stats.getSkill (mIndex).setModifier (modifier);
+ }
+ };
+
+ template<class R>
+ class OpModSkill : public Interpreter::Opcode0
+ {
+ int mIndex;
+
+ public:
+
+ OpModSkill (int index) : mIndex (index) {}
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ value += MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
+ getModified();
+
+ MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
+ setModified (value, 0, 100);
+ }
+ };
+
+ class OpGetPCCrimeLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ runtime.push (static_cast <Interpreter::Type_Float> (MWWorld::Class::get (player).getNpcStats (player).getBounty()));
+ }
+ };
+
+ class OpSetPCCrimeLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+
+ MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat);
+ runtime.pop();
+ }
+ };
+
+ class OpModPCCrimeLevel : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+
+ MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty());
+ runtime.pop();
+ }
+ };
+
+ template<class R>
+ class OpAddSpell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string id = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ // make sure a spell with this ID actually exists.
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (id);
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().add (id);
+ }
+ };
+
+ template<class R>
+ class OpRemoveSpell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string id = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id);
+ }
+ };
+
+ template<class R>
+ class OpGetSpell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string id = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Integer value = 0;
+
+ for (MWMechanics::Spells::TIterator iter (
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().begin());
+ iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().end(); ++iter)
+ if (iter->first==id)
+ {
+ value = 1;
+ break;
+ }
+
+ runtime.push (value);
+ }
+ };
+
+ class OpPCJoinFaction : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ std::string factionID = "";
+
+ if(arg0==0)
+ {
+ factionID = getDialogueActorFaction();
+ }
+ else
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ Misc::StringUtils::toLower(factionID);
+ if(factionID != "")
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end())
+ {
+ MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0;
+ }
+ }
+ }
+ };
+
+ class OpPCRaiseRank : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ std::string factionID = "";
+
+ if(arg0==0)
+ {
+ factionID = getDialogueActorFaction();
+ }
+ else
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ Misc::StringUtils::toLower(factionID);
+ if(factionID != "")
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end())
+ {
+ MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0;
+ }
+ else
+ {
+ MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] +1;
+ }
+ }
+ }
+ };
+
+ class OpPCLowerRank : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ std::string factionID = "";
+
+ if(arg0==0)
+ {
+ factionID = getDialogueActorFaction();
+ }
+ else
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ Misc::StringUtils::toLower(factionID);
+ if(factionID != "")
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end())
+ {
+ MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] -1;
+ }
+ }
+ }
+ };
+
+ template<class R>
+ class OpGetPCRank : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(arg0 >0)
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ {
+ factionID = "";
+ }
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ }
+ Misc::StringUtils::toLower(factionID);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(factionID!="")
+ {
+ if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end())
+ {
+ runtime.push(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID]);
+ }
+ else
+ {
+ runtime.push(-1);
+ }
+ }
+ else
+ {
+ runtime.push(-1);
+ }
+ }
+ };
+
+ template<class R>
+ class OpModDisposition : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getNpcStats (ptr).setBaseDisposition
+ (MWWorld::Class::get (ptr).getNpcStats (ptr).getBaseDisposition() + value);
+ }
+ };
+
+ template<class R>
+ class OpSetDisposition : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getNpcStats (ptr).setBaseDisposition (value);
+ }
+ };
+
+ template<class R>
+ class OpGetDisposition : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr));
+ }
+ };
+
+ class OpGetDeadCount : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string id = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime[0].mInteger = MWBase::Environment::get().getMechanicsManager()->countDeaths (id);
+ }
+ };
+
+ template<class R>
+ class OpGetPCFacRep : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ std::string factionId;
+
+ if (arg0==1)
+ {
+ factionId = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty())
+ factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first;
+ }
+
+ if (factionId.empty())
+ throw std::runtime_error ("failed to determine faction");
+
+ Misc::StringUtils::toLower (factionId);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ runtime.push (
+ MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId));
+ }
+ };
+
+ template<class R>
+ class OpSetPCFacRep : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ std::string factionId;
+
+ if (arg0==1)
+ {
+ factionId = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty())
+ factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first;
+ }
+
+ if (factionId.empty())
+ throw std::runtime_error ("failed to determine faction");
+
+ Misc::StringUtils::toLower (factionId);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value);
+ }
+ };
+
+ template<class R>
+ class OpModPCFacRep : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ Interpreter::Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ std::string factionId;
+
+ if (arg0==1)
+ {
+ factionId = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty())
+ factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first;
+ }
+
+ if (factionId.empty())
+ throw std::runtime_error ("failed to determine faction");
+
+ Misc::StringUtils::toLower (factionId);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId,
+ MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)+
+ value);
+ }
+ };
+
+ template<class R>
+ class OpGetCommonDisease : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (MWWorld::Class::get (ptr).getCreatureStats (ptr).hasCommonDisease());
+ }
+ };
+
+ template<class R>
+ class OpGetBlightDisease : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push (MWWorld::Class::get (ptr).getCreatureStats (ptr).hasBlightDisease());
+ }
+ };
+
+ template<class R>
+ class OpGetRace : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string race = runtime.getStringLiteral(runtime[0].mInteger);
+ Misc::StringUtils::toLower(race);
+ runtime.pop();
+
+ std::string npcRace = ptr.get<ESM::NPC>()->mBase->mRace;
+ Misc::StringUtils::toLower(npcRace);
+
+ runtime.push (npcRace == race);
+ }
+ };
+
+ class OpGetWerewolfKills : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+
+ runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getWerewolfKills ());
+ }
+ };
+
+ template <class R>
+ class OpPcExpelled : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(arg0 >0 )
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ {
+ factionID = "";
+ }
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ }
+ Misc::StringUtils::toLower(factionID);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(factionID!="")
+ {
+ std::set<std::string>& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled ();
+ if (expelled.find (factionID) != expelled.end())
+ {
+ runtime.push(1);
+ }
+ else
+ {
+ runtime.push(0);
+ }
+ }
+ else
+ {
+ runtime.push(0);
+ }
+ }
+ };
+
+ template <class R>
+ class OpPcExpell : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(arg0 >0 )
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ {
+ factionID = "";
+ }
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(factionID!="")
+ {
+ std::set<std::string>& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled ();
+ Misc::StringUtils::toLower(factionID);
+ expelled.insert(factionID);
+ }
+ }
+ };
+
+ template <class R>
+ class OpPcClearExpelled : public Interpreter::Opcode1
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(arg0 >0 )
+ {
+ factionID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ }
+ else
+ {
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ {
+ factionID = "";
+ }
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ if(factionID!="")
+ {
+ std::set<std::string>& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled ();
+ Misc::StringUtils::toLower(factionID);
+ expelled.erase (factionID);
+ }
+ }
+ };
+
+ template <class R>
+ class OpRaiseRank : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ return;
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ // no-op when executed on the player
+ if (ptr == player)
+ return;
+
+ std::map<std::string, int>& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks ();
+ ranks[factionID] = ranks[factionID]+1;
+ }
+ };
+
+ template <class R>
+ class OpLowerRank : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string factionID = "";
+ if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
+ return;
+ else
+ {
+ factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
+ }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ // no-op when executed on the player
+ if (ptr == player)
+ return;
+
+ std::map<std::string, int>& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks ();
+ ranks[factionID] = ranks[factionID]-1;
+ }
+ };
+
+ template <class R>
+ class OpOnDeath : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer value =
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).hasDied();
+
+ if (value)
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).clearHasDied();
+
+ runtime.push (value);
+ }
+ };
+
+ template <class R>
+ class OpIsWerewolf : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ runtime.push(MWWorld::Class::get(ptr).getNpcStats(ptr).isWerewolf());
+ }
+ };
+
+ template <class R, bool set>
+ class OpSetWerewolf : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ MWBase::Environment::get().getWorld()->setWerewolf(ptr, set);
+ }
+ };
+
+ template <class R>
+ class OpSetWerewolfAcrobatics : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ MWBase::Environment::get().getWorld()->applyWerewolfAcrobatics(ptr);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ for (int i=0; i<Compiler::Stats::numberOfAttributes; ++i)
+ {
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetAttribute+i, new OpGetAttribute<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetAttributeExplicit+i,
+ new OpGetAttribute<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetAttribute+i, new OpSetAttribute<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetAttributeExplicit+i,
+ new OpSetAttribute<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeModAttribute+i, new OpModAttribute<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeModAttributeExplicit+i,
+ new OpModAttribute<ExplicitRef> (i));
+ }
+
+ for (int i=0; i<Compiler::Stats::numberOfDynamics; ++i)
+ {
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamic+i, new OpGetDynamic<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicExplicit+i,
+ new OpGetDynamic<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamic+i, new OpSetDynamic<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamicExplicit+i,
+ new OpSetDynamic<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeModDynamic+i, new OpModDynamic<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeModDynamicExplicit+i,
+ new OpModDynamic<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamic+i,
+ new OpModCurrentDynamic<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamicExplicit+i,
+ new OpModCurrentDynamic<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatio+i,
+ new OpGetDynamicGetRatio<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatioExplicit+i,
+ new OpGetDynamicGetRatio<ExplicitRef> (i));
+ }
+
+ for (int i=0; i<Compiler::Stats::numberOfSkills; ++i)
+ {
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetSkill+i, new OpGetSkill<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetSkillExplicit+i, new OpGetSkill<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetSkill+i, new OpSetSkill<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetSkillExplicit+i, new OpSetSkill<ExplicitRef> (i));
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeModSkill+i, new OpModSkill<ImplicitRef> (i));
+ interpreter.installSegment5 (Compiler::Stats::opcodeModSkillExplicit+i, new OpModSkill<ExplicitRef> (i));
+ }
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel);
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel);
+ interpreter.installSegment5 (Compiler::Stats::opcodeModPCCrimeLevel, new OpModPCCrimeLevel);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeAddSpell, new OpAddSpell<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeAddSpellExplicit, new OpAddSpell<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpell, new OpRemoveSpell<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit,
+ new OpRemoveSpell<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell<ExplicitRef>);
+
+ interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank);
+ interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank);
+ interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction);
+ interpreter.installSegment3(Compiler::Stats::opcodeGetPCRank,new OpGetPCRank<ImplicitRef>);
+ interpreter.installSegment3(Compiler::Stats::opcodeGetPCRankExplicit,new OpGetPCRank<ExplicitRef>);
+
+ interpreter.installSegment5(Compiler::Stats::opcodeModDisposition,new OpModDisposition<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Stats::opcodeModDispositionExplicit,new OpModDisposition<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Stats::opcodeSetDisposition,new OpSetDisposition<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Stats::opcodeSetDispositionExplicit,new OpSetDisposition<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Stats::opcodeGetDisposition,new OpGetDisposition<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Stats::opcodeGetDispositionExplicit,new OpGetDisposition<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetLevel, new OpGetLevel<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetLevelExplicit, new OpGetLevel<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetLevel, new OpSetLevel<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetLevelExplicit, new OpSetLevel<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetDeadCount, new OpGetDeadCount);
+
+ interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRep, new OpGetPCFacRep<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRepExplicit, new OpGetPCFacRep<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRep, new OpSetPCFacRep<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRepExplicit, new OpSetPCFacRep<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRep, new OpModPCFacRep<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRepExplicit, new OpModPCFacRep<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDisease, new OpGetCommonDisease<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDiseaseExplicit, new OpGetCommonDisease<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDisease, new OpGetBlightDisease<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDiseaseExplicit, new OpGetBlightDisease<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetRace, new OpGetRace<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetRaceExplicit, new OpGetRace<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeGetWerewolfKills, new OpGetWerewolfKills);
+
+ interpreter.installSegment3 (Compiler::Stats::opcodePcExpelled, new OpPcExpelled<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodePcExpelledExplicit, new OpPcExpelled<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodePcExpell, new OpPcExpell<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodePcExpellExplicit, new OpPcExpell<ExplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelled, new OpPcClearExpelled<ImplicitRef>);
+ interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelledExplicit, new OpPcClearExpelled<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRank, new OpRaiseRank<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRankExplicit, new OpRaiseRank<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeLowerRank, new OpLowerRank<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeLowerRankExplicit, new OpLowerRank<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf<ExplicitRef>);
+
+ interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolf, new OpSetWerewolf<ImplicitRef, true>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolfExplicit, new OpSetWerewolf<ExplicitRef, true>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf<ImplicitRef, false>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf<ExplicitRef, false>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/statsextensions.hpp b/apps/openmw/mwscript/statsextensions.hpp
new file mode 100644
index 0000000000..213b549674
--- /dev/null
+++ b/apps/openmw/mwscript/statsextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_STATSEXTENSIONS_H
+#define GAME_SCRIPT_STATSEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief stats-related script functionality (creatures and NPCs)
+ namespace Stats
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp
new file mode 100644
index 0000000000..6246daee22
--- /dev/null
+++ b/apps/openmw/mwscript/transformationextensions.cpp
@@ -0,0 +1,752 @@
+#include <boost/algorithm/string.hpp>
+
+#include <OgreMath.h>
+#include <OgreSceneNode.h>
+
+#include "../mwworld/esmstore.hpp"
+#include <components/esm/loadcell.hpp>
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+
+#include "../mwbase/environment.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/manualref.hpp"
+
+#include "interpretercontext.hpp"
+#include "ref.hpp"
+
+namespace MWScript
+{
+ namespace Transformation
+ {
+ template<class R>
+ class OpSetScale : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float scale = runtime[0].mFloat;
+ runtime.pop();
+
+ MWBase::Environment::get().getWorld()->scaleObject(ptr,scale);
+ }
+ };
+
+ template<class R>
+ class OpGetScale : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ runtime.push(ptr.getCellRef().mScale);
+ }
+ };
+
+ template<class R>
+ class OpModScale : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float scale = runtime[0].mFloat;
+ runtime.pop();
+
+ // add the parameter to the object's scale.
+ MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().mScale + scale);
+ }
+ };
+
+ template<class R>
+ class OpSetAngle : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float angle = runtime[0].mFloat;
+ runtime.pop();
+
+ float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees();
+ float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees();
+ float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees();
+
+ float *objRot = ptr.getRefData().getPosition().rot;
+
+ float lx = Ogre::Radian(objRot[0]).valueDegrees();
+ float ly = Ogre::Radian(objRot[1]).valueDegrees();
+ float lz = Ogre::Radian(objRot[2]).valueDegrees();
+
+ if (axis == "x")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,angle-lx,ay,az);
+ }
+ else if (axis == "y")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,angle-ly,az);
+ }
+ else if (axis == "z")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,angle-lz);
+ }
+ else
+ throw std::runtime_error ("invalid ration axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpGetStartingAngle : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ if (axis == "x")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees());
+ }
+ else if (axis == "y")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees());
+ }
+ else if (axis == "z")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees());
+ }
+ else
+ throw std::runtime_error ("invalid ration axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpGetAngle : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ if (axis=="x")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees());
+ }
+ else if (axis=="y")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees());
+ }
+ else if (axis=="z")
+ {
+ runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees());
+ }
+ else
+ throw std::runtime_error ("invalid ration axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpGetPos : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ if(axis == "x")
+ {
+ runtime.push(ptr.getRefData().getPosition().pos[0]);
+ }
+ else if(axis == "y")
+ {
+ runtime.push(ptr.getRefData().getPosition().pos[1]);
+ }
+ else if(axis == "z")
+ {
+ runtime.push(ptr.getRefData().getPosition().pos[2]);
+ }
+ else
+ throw std::runtime_error ("invalid rotation axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpSetPos : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float pos = runtime[0].mFloat;
+ runtime.pop();
+
+ float ax = ptr.getRefData().getPosition().pos[0];
+ float ay = ptr.getRefData().getPosition().pos[1];
+ float az = ptr.getRefData().getPosition().pos[2];
+
+ if(axis == "x")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az);
+ }
+ else if(axis == "y")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az);
+ }
+ else if(axis == "z")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos);
+ }
+ else
+ throw std::runtime_error ("invalid axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpGetStartingPos : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ if(axis == "x")
+ {
+ runtime.push(ptr.getCellRef().mPos.pos[0]);
+ }
+ else if(axis == "y")
+ {
+ runtime.push(ptr.getCellRef().mPos.pos[1]);
+ }
+ else if(axis == "z")
+ {
+ runtime.push(ptr.getCellRef().mPos.pos[2]);
+ }
+ else
+ throw std::runtime_error ("invalid axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpPositionCell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float zRot = runtime[0].mFloat;
+ runtime.pop();
+ std::string cellID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::CellStore* store = 0;
+ try
+ {
+ store = MWBase::Environment::get().getWorld()->getInterior(cellID);
+ }
+ catch(std::exception &e)
+ {
+ const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID);
+ if(cell)
+ {
+ int cx,cy;
+ MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
+ store = MWBase::Environment::get().getWorld()->getExterior(cx,cy);
+ }
+ }
+ if(store)
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr,*store,x,y,z);
+ float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
+ float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
+ if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity
+ {
+ ax = ax/60.;
+ ay = ay/60.;
+ zRot = zRot/60.;
+ }
+ MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot);
+
+ MWWorld::Class::get(ptr).adjustPosition(ptr);
+ }
+ else
+ {
+ throw std::runtime_error ("unknown cell");
+ }
+ }
+ };
+
+ template<class R>
+ class OpPosition : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float zRot = runtime[0].mFloat;
+ runtime.pop();
+ int cx,cy;
+ MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
+ MWBase::Environment::get().getWorld()->moveObject(ptr,
+ *MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z);
+ float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
+ float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
+ if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity
+ {
+ ax = ax/60.;
+ ay = ay/60.;
+ zRot = zRot/60.;
+ }
+ MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot);
+ MWWorld::Class::get(ptr).adjustPosition(ptr);
+ }
+ };
+
+ template<class R>
+ class OpPlaceItemCell : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string itemID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ std::string cellID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float zRot = runtime[0].mFloat;
+ runtime.pop();
+
+ MWWorld::CellStore* store = 0;
+ try
+ {
+ store = MWBase::Environment::get().getWorld()->getInterior(cellID);
+ }
+ catch(std::exception &e)
+ {
+ const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID);
+ if(cell)
+ {
+ int cx,cy;
+ MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
+ store = MWBase::Environment::get().getWorld()->getExterior(cx,cy);
+ }
+ }
+ if(store)
+ {
+ ESM::Position pos;
+ pos.pos[0] = x;
+ pos.pos[1] = y;
+ pos.pos[2] = z;
+ pos.rot[0] = pos.rot[1] = 0;
+ pos.rot[2] = zRot;
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
+ ref.getPtr().getCellRef().mPos = pos;
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos);
+ }
+ else
+ {
+ throw std::runtime_error ("unknown cell");
+ }
+ }
+ };
+
+ template<class R>
+ class OpPlaceItem : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ std::string itemID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ Interpreter::Type_Float x = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float y = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float z = runtime[0].mFloat;
+ runtime.pop();
+ Interpreter::Type_Float zRot = runtime[0].mFloat;
+ runtime.pop();
+
+ int cx,cy;
+ MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
+ MWWorld::CellStore* store = MWBase::Environment::get().getWorld()->getExterior(cx,cy);
+ if(store)
+ {
+ ESM::Position pos;
+ pos.pos[0] = x;
+ pos.pos[1] = y;
+ pos.pos[2] = z;
+ pos.rot[0] = pos.rot[1] = 0;
+ pos.rot[2] = zRot;
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
+ ref.getPtr().getCellRef().mPos = pos;
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos);
+ }
+ else
+ {
+ throw std::runtime_error ("unknown cell");
+ }
+ }
+ };
+
+ 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
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr me = R()(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 = me.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 = me.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 OpRotate : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ const MWWorld::Ptr& ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
+ runtime.pop();
+
+ float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees();
+ float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees();
+ float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees();
+
+ if (axis == "x")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax+rotation,ay,az);
+ }
+ else if (axis == "y")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay+rotation,az);
+ }
+ else if (axis == "z")
+ {
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,az+rotation);
+ }
+ else
+ throw std::runtime_error ("invalid rotation axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpRotateWorld : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
+ runtime.pop();
+
+ float *objRot = ptr.getRefData().getPosition().rot;
+
+ float ax = Ogre::Radian(objRot[0]).valueDegrees();
+ float ay = Ogre::Radian(objRot[1]).valueDegrees();
+ float az = Ogre::Radian(objRot[2]).valueDegrees();
+
+ if (axis == "x")
+ {
+ MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az);
+ }
+ else if (axis == "y")
+ {
+ MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az);
+ }
+ else if (axis == "z")
+ {
+ MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation);
+ }
+ else
+ throw std::runtime_error ("invalid rotation axis: " + axis);
+ }
+ };
+
+ template<class R>
+ class OpSetAtStart : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ ptr.getRefData().getLocalRotation().rot[0] = 0;
+ ptr.getRefData().getLocalRotation().rot[1] = 0;
+ ptr.getRefData().getLocalRotation().rot[2] = 0;
+ MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true);
+ MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().mPos.pos[0],
+ ptr.getCellRef().mPos.pos[1], ptr.getCellRef().mPos.pos[2]);
+
+ }
+ };
+
+ template<class R>
+ class OpMove : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ const MWWorld::Ptr& ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
+ runtime.pop();
+
+ Ogre::Vector3 posChange;
+ if (axis == "x")
+ {
+ posChange=Ogre::Vector3(movement, 0, 0);
+ }
+ else if (axis == "y")
+ {
+ posChange=Ogre::Vector3(0, movement, 0);
+ }
+ else if (axis == "z")
+ {
+ posChange=Ogre::Vector3(0, 0, movement);
+ }
+ else
+ throw std::runtime_error ("invalid movement axis: " + axis);
+
+ Ogre::Vector3 worldPos = ptr.getRefData().getBaseNode()->convertLocalToWorldPosition(posChange);
+ MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x, worldPos.y, worldPos.z);
+ }
+ };
+
+ template<class R>
+ class OpMoveWorld : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string axis = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
+ runtime.pop();
+
+ float *objPos = ptr.getRefData().getPosition().pos;
+
+ if (axis == "x")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]);
+ }
+ else if (axis == "y")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]);
+ }
+ else if (axis == "z")
+ {
+ MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement);
+ }
+ else
+ throw std::runtime_error ("invalid movement axis: " + axis);
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetScaleExplicit,new OpSetScale<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetAngle,new OpSetAngle<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetAngleExplicit,new OpSetAngle<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetScale,new OpGetScale<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetScaleExplicit,new OpGetScale<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetAngle,new OpGetAngle<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetAngleExplicit,new OpGetAngle<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetPos,new OpGetPos<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetPosExplicit,new OpGetPos<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetPos,new OpSetPos<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetPosExplicit,new OpSetPos<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPos,new OpGetStartingPos<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPosExplicit,new OpGetStartingPos<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePosition,new OpPosition<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell<ImplicitRef>);
+ 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::opcodeModScale,new OpModScale<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeRotateExplicit,new OpRotate<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorld,new OpRotateWorld<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorldExplicit,new OpRotateWorld<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStart,new OpSetAtStart<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStartExplicit,new OpSetAtStart<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeMove,new OpMove<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeMoveExplicit,new OpMove<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorld,new OpMoveWorld<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld<ExplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle<ImplicitRef>);
+ interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/transformationextensions.hpp b/apps/openmw/mwscript/transformationextensions.hpp
new file mode 100644
index 0000000000..a25cc0c3d8
--- /dev/null
+++ b/apps/openmw/mwscript/transformationextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H
+#define GAME_SCRIPT_TRANSFORMATIONEXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief stats-related script functionality (creatures and NPCs)
+ namespace Transformation
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp
new file mode 100644
index 0000000000..538133c65d
--- /dev/null
+++ b/apps/openmw/mwscript/userextensions.cpp
@@ -0,0 +1,78 @@
+
+#include "userextensions.hpp"
+
+#include <components/compiler/extensions.hpp>
+#include <components/compiler/opcodes.hpp>
+
+#include <components/interpreter/interpreter.hpp>
+#include <components/interpreter/runtime.hpp>
+#include <components/interpreter/opcodes.hpp>
+#include <components/interpreter/context.hpp>
+
+#include "ref.hpp"
+
+namespace MWScript
+{
+ /// Temporary script extensions.
+ ///
+ /// \attention Do not commit changes to this file to a git repository!
+ namespace User
+ {
+ class OpUser1 : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.getContext().report ("user1: not in use");
+ }
+ };
+
+ class OpUser2 : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ runtime.getContext().report ("user2: not in use");
+ }
+ };
+
+ template<class R>
+ class OpUser3 : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+// MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.getContext().report ("user3: not in use");
+ }
+ };
+
+ template<class R>
+ class OpUser4 : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+// MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.getContext().report ("user4: not in use");
+ }
+ };
+
+
+ void installOpcodes (Interpreter::Interpreter& interpreter)
+ {
+ interpreter.installSegment5 (Compiler::User::opcodeUser1, new OpUser1);
+ interpreter.installSegment5 (Compiler::User::opcodeUser2, new OpUser2);
+ interpreter.installSegment5 (Compiler::User::opcodeUser3, new OpUser3<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::User::opcodeUser3Explicit, new OpUser3<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::User::opcodeUser4, new OpUser4<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::User::opcodeUser4Explicit, new OpUser4<ExplicitRef>);
+ }
+ }
+}
diff --git a/apps/openmw/mwscript/userextensions.hpp b/apps/openmw/mwscript/userextensions.hpp
new file mode 100644
index 0000000000..da6e0faa66
--- /dev/null
+++ b/apps/openmw/mwscript/userextensions.hpp
@@ -0,0 +1,23 @@
+#ifndef GAME_SCRIPT_USEREXTENSIONS_H
+#define GAME_SCRIPT_USEREXTENSIONS_H
+
+namespace Compiler
+{
+ class Extensions;
+}
+
+namespace Interpreter
+{
+ class Interpreter;
+}
+
+namespace MWScript
+{
+ /// \brief Temporary script functionality limited to the console
+ namespace User
+ {
+ void installOpcodes (Interpreter::Interpreter& interpreter);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp
new file mode 100644
index 0000000000..3f3e3a62d2
--- /dev/null
+++ b/apps/openmw/mwsound/audiere_decoder.cpp
@@ -0,0 +1,137 @@
+#ifdef OPENMW_USE_AUDIERE
+
+#include <stdexcept>
+#include <iostream>
+
+#include "audiere_decoder.hpp"
+
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("Audiere exception: "+msg); }
+
+namespace MWSound
+{
+
+class OgreFile : public audiere::File
+{
+ Ogre::DataStreamPtr mStream;
+
+ ADR_METHOD(int) read(void* buffer, int size)
+ {
+ return mStream->read(buffer, size);
+ }
+
+ ADR_METHOD(bool) seek(int position, SeekMode mode)
+ {
+ if(mode == CURRENT)
+ mStream->seek(mStream->tell()+position);
+ else if(mode == BEGIN)
+ mStream->seek(position);
+ else if(mode == END)
+ mStream->seek(mStream->size()+position);
+ else
+ return false;
+
+ return true;
+ }
+
+ ADR_METHOD(int) tell()
+ {
+ return mStream->tell();
+ }
+
+ size_t refs;
+ ADR_METHOD(void) ref() { ++refs; }
+ ADR_METHOD(void) unref()
+ {
+ if(--refs == 0)
+ delete this;
+ }
+
+public:
+ OgreFile(const Ogre::DataStreamPtr &stream)
+ : mStream(stream), refs(1)
+ { }
+ virtual ~OgreFile() { }
+
+ Ogre::String getName()
+ { return mStream->getName(); }
+};
+
+
+void Audiere_Decoder::open(const std::string &fname)
+{
+ close();
+
+ mSoundFile = audiere::FilePtr(new OgreFile(mResourceMgr.openResource(fname)));
+ mSoundSource = audiere::OpenSampleSource(mSoundFile);
+ mSoundFileName = fname;
+
+ int channels, srate;
+ audiere::SampleFormat format;
+
+ mSoundSource->getFormat(channels, srate, format);
+ if(format == audiere::SF_S16)
+ mSampleType = SampleType_Int16;
+ else if(format == audiere::SF_U8)
+ mSampleType = SampleType_UInt8;
+ else
+ fail("Unsupported sample type");
+
+ if(channels == 1)
+ mChannelConfig = ChannelConfig_Mono;
+ else if(channels == 2)
+ mChannelConfig = ChannelConfig_Stereo;
+ else
+ fail("Unsupported channel count");
+
+ mSampleRate = srate;
+}
+
+void Audiere_Decoder::close()
+{
+ mSoundFile = NULL;
+ mSoundSource = NULL;
+}
+
+std::string Audiere_Decoder::getName()
+{
+ return mSoundFileName;
+}
+
+void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
+{
+ *samplerate = mSampleRate;
+ *chans = mChannelConfig;
+ *type = mSampleType;
+}
+
+size_t Audiere_Decoder::read(char *buffer, size_t bytes)
+{
+ int size = bytesToFrames(bytes, mChannelConfig, mSampleType);
+ size = mSoundSource->read(size, buffer);
+ return framesToBytes(size, mChannelConfig, mSampleType);
+}
+
+void Audiere_Decoder::rewind()
+{
+ mSoundSource->reset();
+}
+
+size_t Audiere_Decoder::getSampleOffset()
+{
+ return 0;
+}
+
+Audiere_Decoder::Audiere_Decoder()
+{
+}
+
+Audiere_Decoder::~Audiere_Decoder()
+{
+ close();
+}
+
+}
+
+#endif
diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp
new file mode 100644
index 0000000000..f432c32ec2
--- /dev/null
+++ b/apps/openmw/mwsound/audiere_decoder.hpp
@@ -0,0 +1,46 @@
+#ifndef GAME_SOUND_AUDIERE_DECODER_H
+#define GAME_SOUND_AUDIERE_DECODER_H
+
+#include <OgreDataStream.h>
+
+#include "audiere.h"
+
+#include "sound_decoder.hpp"
+
+
+namespace MWSound
+{
+ class Audiere_Decoder : public Sound_Decoder
+ {
+ std::string mSoundFileName;
+ audiere::FilePtr mSoundFile;
+ audiere::SampleSourcePtr mSoundSource;
+ int mSampleRate;
+ SampleType mSampleType;
+ ChannelConfig mChannelConfig;
+
+ virtual void open(const std::string &fname);
+ virtual void close();
+
+ virtual std::string getName();
+ virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
+
+ virtual size_t read(char *buffer, size_t bytes);
+ virtual void rewind();
+ virtual size_t getSampleOffset();
+
+ Audiere_Decoder& operator=(const Audiere_Decoder &rhs);
+ Audiere_Decoder(const Audiere_Decoder &rhs);
+
+ Audiere_Decoder();
+ public:
+ virtual ~Audiere_Decoder();
+
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_DECODER
+#define DEFAULT_DECODER (::MWSound::Audiere_Decoder)
+#endif
+};
+
+#endif
diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp
new file mode 100644
index 0000000000..d5c382a419
--- /dev/null
+++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp
@@ -0,0 +1,343 @@
+#ifdef OPENMW_USE_FFMPEG
+
+
+#include "ffmpeg_decoder.hpp"
+
+// auto_ptr
+#include <memory>
+
+#include <stdexcept>
+
+namespace MWSound
+{
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("FFmpeg exception: "+msg); }
+
+
+int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
+{
+ Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
+ return stream->read(buf, buf_size);
+}
+
+int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size)
+{
+ Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
+ return stream->write(buf, buf_size);
+}
+
+int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence)
+{
+ Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
+
+ whence &= ~AVSEEK_FORCE;
+ if(whence == AVSEEK_SIZE)
+ return stream->size();
+ if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+
+/* Used by getAV*Data to search for more compressed data, and buffer it in the
+ * correct stream. It won't buffer data for streams that the app doesn't have a
+ * handle for. */
+bool FFmpeg_Decoder::getNextPacket()
+{
+ if(!mStream)
+ return false;
+
+ int stream_idx = mStream - mFormatCtx->streams;
+ while(av_read_frame(mFormatCtx, &mPacket) >= 0)
+ {
+ /* Check if the packet belongs to this stream */
+ if(stream_idx == mPacket.stream_index)
+ {
+ if((uint64_t)mPacket.pts != AV_NOPTS_VALUE)
+ mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts;
+ return true;
+ }
+
+ /* Free the packet and look for another */
+ av_free_packet(&mPacket);
+ }
+
+ return false;
+}
+
+bool FFmpeg_Decoder::getAVAudioData()
+{
+ int got_frame, len;
+
+ if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO)
+ return false;
+
+ do {
+ if(mPacket.size == 0 && !getNextPacket())
+ return false;
+
+ /* Decode some data, and check for errors */
+ if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0)
+ return false;
+
+ /* Move the unread data to the front and clear the end bits */
+ int remaining = mPacket.size - len;
+ if(remaining <= 0)
+ av_free_packet(&mPacket);
+ else
+ {
+ memmove(mPacket.data, &mPacket.data[len], remaining);
+ av_shrink_packet(&mPacket, remaining);
+ }
+ } while(got_frame == 0 || mFrame->nb_samples == 0);
+ mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate;
+
+ return true;
+}
+
+size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length)
+{
+ size_t dec = 0;
+
+ while(dec < length)
+ {
+ /* If there's no decoded data, find some */
+ if(mFramePos >= mFrameSize)
+ {
+ if(!getAVAudioData())
+ break;
+ mFramePos = 0;
+ mFrameSize = mFrame->nb_samples * (*mStream)->codec->channels *
+ av_get_bytes_per_sample((*mStream)->codec->sample_fmt);
+ }
+
+ /* Get the amount of bytes remaining to be written, and clamp to
+ * the amount of decoded data we have */
+ size_t rem = std::min<size_t>(length-dec, mFrameSize-mFramePos);
+
+ /* Copy the data to the app's buffer and increment */
+ memcpy(data, mFrame->data[0]+mFramePos, rem);
+ data = (char*)data + rem;
+ dec += rem;
+ mFramePos += rem;
+ }
+
+ /* Return the number of bytes we were able to get */
+ return dec;
+}
+
+static AVSampleFormat ffmpegNonPlanarSampleFormat (AVSampleFormat format)
+{
+ switch (format)
+ {
+ case AV_SAMPLE_FMT_U8P: return AV_SAMPLE_FMT_U8;
+ case AV_SAMPLE_FMT_S16P: return AV_SAMPLE_FMT_S16;
+ case AV_SAMPLE_FMT_S32P: return AV_SAMPLE_FMT_S32;
+ case AV_SAMPLE_FMT_FLTP: return AV_SAMPLE_FMT_FLT;
+ case AV_SAMPLE_FMT_DBLP: return AV_SAMPLE_FMT_DBL;
+ default:return format;
+ }
+}
+
+void FFmpeg_Decoder::open(const std::string &fname)
+{
+ close();
+ mDataStream = mResourceMgr.openResource(fname);
+
+ if((mFormatCtx=avformat_alloc_context()) == NULL)
+ fail("Failed to allocate context");
+
+ 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)
+ {
+ avformat_free_context(mFormatCtx);
+ mFormatCtx = NULL;
+ fail("Failed to allocate input stream");
+ }
+
+ try
+ {
+ if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
+ fail("Failed to find stream info in "+fname);
+
+ for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
+ {
+ if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
+ {
+ mStream = &mFormatCtx->streams[j];
+ break;
+ }
+ }
+ if(!mStream)
+ fail("No audio streams in "+fname);
+
+ (*mStream)->codec->request_sample_fmt = ffmpegNonPlanarSampleFormat ((*mStream)->codec->sample_fmt);
+
+ AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id);
+ if(!codec)
+ {
+ std::stringstream ss("No codec found for id ");
+ ss << (*mStream)->codec->codec_id;
+ fail(ss.str());
+ }
+ if(avcodec_open2((*mStream)->codec, codec, NULL) < 0)
+ fail("Failed to open audio codec " + std::string(codec->long_name));
+
+ mFrame = avcodec_alloc_frame();
+ }
+ catch(std::exception &e)
+ {
+ avformat_close_input(&mFormatCtx);
+ throw;
+ }
+}
+
+void FFmpeg_Decoder::close()
+{
+ if(mStream)
+ avcodec_close((*mStream)->codec);
+ mStream = NULL;
+
+ av_free_packet(&mPacket);
+ av_freep(&mFrame);
+
+ if(mFormatCtx)
+ {
+ AVIOContext* context = mFormatCtx->pb;
+ avformat_close_input(&mFormatCtx);
+ av_free(context);
+ }
+
+ mDataStream.setNull();
+}
+
+std::string FFmpeg_Decoder::getName()
+{
+ return mFormatCtx->filename;
+}
+
+void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
+{
+ if(!mStream)
+ fail("No audio stream info");
+
+ if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8)
+ *type = SampleType_UInt8;
+ else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16)
+ *type = SampleType_Int16;
+ else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT)
+ *type = SampleType_Float32;
+ else
+ fail(std::string("Unsupported sample format: ")+
+ av_get_sample_fmt_name((*mStream)->codec->sample_fmt));
+
+ if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO)
+ *chans = ChannelConfig_Mono;
+ else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO)
+ *chans = ChannelConfig_Stereo;
+ else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_QUAD)
+ *chans = ChannelConfig_Quad;
+ else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_5POINT1)
+ *chans = ChannelConfig_5point1;
+ else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_7POINT1)
+ *chans = ChannelConfig_7point1;
+ else if((*mStream)->codec->channel_layout == 0)
+ {
+ /* Unknown channel layout. Try to guess. */
+ if((*mStream)->codec->channels == 1)
+ *chans = ChannelConfig_Mono;
+ else if((*mStream)->codec->channels == 2)
+ *chans = ChannelConfig_Stereo;
+ else
+ {
+ std::stringstream sstr("Unsupported raw channel count: ");
+ sstr << (*mStream)->codec->channels;
+ fail(sstr.str());
+ }
+ }
+ else
+ {
+ char str[1024];
+ av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
+ (*mStream)->codec->channel_layout);
+ fail(std::string("Unsupported channel layout: ")+str);
+ }
+
+ *samplerate = (*mStream)->codec->sample_rate;
+}
+
+size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
+{
+ if(!mStream)
+ fail("No audio stream");
+ return readAVAudioData(buffer, bytes);
+}
+
+void FFmpeg_Decoder::readAll(std::vector<char> &output)
+{
+ if(!mStream)
+ fail("No audio stream");
+
+ while(getAVAudioData())
+ {
+ size_t got = mFrame->nb_samples * (*mStream)->codec->channels *
+ av_get_bytes_per_sample((*mStream)->codec->sample_fmt);
+ const char *inbuf = reinterpret_cast<char*>(mFrame->data[0]);
+ output.insert(output.end(), inbuf, inbuf+got);
+ }
+}
+
+void FFmpeg_Decoder::rewind()
+{
+ int stream_idx = mStream - mFormatCtx->streams;
+ if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0)
+ fail("Failed to seek in audio stream");
+ av_free_packet(&mPacket);
+ mFrameSize = mFramePos = 0;
+ mNextPts = 0.0;
+}
+
+size_t FFmpeg_Decoder::getSampleOffset()
+{
+ int delay = (mFrameSize-mFramePos) / (*mStream)->codec->channels /
+ av_get_bytes_per_sample((*mStream)->codec->sample_fmt);
+ return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay;
+}
+
+FFmpeg_Decoder::FFmpeg_Decoder()
+ : mFormatCtx(NULL)
+ , mStream(NULL)
+ , mFrame(NULL)
+ , mFrameSize(0)
+ , mFramePos(0)
+ , mNextPts(0.0)
+{
+ memset(&mPacket, 0, sizeof(mPacket));
+
+ /* We need to make sure ffmpeg is initialized. Optionally silence warning
+ * output from the lib */
+ static bool done_init = false;
+ if(!done_init)
+ {
+ av_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+ done_init = true;
+ }
+}
+
+FFmpeg_Decoder::~FFmpeg_Decoder()
+{
+ close();
+}
+
+}
+
+#endif
diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp
new file mode 100644
index 0000000000..d0d73379d3
--- /dev/null
+++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp
@@ -0,0 +1,78 @@
+#ifndef GAME_SOUND_FFMPEG_DECODER_H
+#define GAME_SOUND_FFMPEG_DECODER_H
+
+// FIXME: This can't be right? The headers refuse to build without UINT64_C,
+// which only gets defined in stdint.h in either C99 mode or with this macro
+// defined...
+#define __STDC_CONSTANT_MACROS
+#include <stdint.h>
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+// From libavutil version 52.2.0 and onward the declaration of
+// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to
+// libavutil/channel_layout.h
+#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
+ LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
+ #include <libavutil/channel_layout.h>
+#endif
+}
+
+#include <string>
+
+#include "sound_decoder.hpp"
+
+
+namespace MWSound
+{
+ class FFmpeg_Decoder : public Sound_Decoder
+ {
+ AVFormatContext *mFormatCtx;
+ AVStream **mStream;
+
+ AVPacket mPacket;
+ AVFrame *mFrame;
+
+ int mFrameSize;
+ int mFramePos;
+
+ double mNextPts;
+
+ bool getNextPacket();
+
+ Ogre::DataStreamPtr mDataStream;
+ static int readPacket(void *user_data, uint8_t *buf, int buf_size);
+ static int writePacket(void *user_data, uint8_t *buf, int buf_size);
+ static int64_t seek(void *user_data, int64_t offset, int whence);
+
+ bool getAVAudioData();
+ size_t readAVAudioData(void *data, size_t length);
+
+ virtual void open(const std::string &fname);
+ virtual void close();
+
+ virtual std::string getName();
+ virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
+
+ virtual size_t read(char *buffer, size_t bytes);
+ virtual void readAll(std::vector<char> &output);
+ virtual void rewind();
+ virtual size_t getSampleOffset();
+
+ FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
+ FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
+
+ FFmpeg_Decoder();
+ public:
+ virtual ~FFmpeg_Decoder();
+
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_DECODER
+#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
+#endif
+}
+
+#endif
diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp
new file mode 100644
index 0000000000..fb187f8442
--- /dev/null
+++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp
@@ -0,0 +1,247 @@
+#ifdef OPENMW_USE_MPG123
+
+#include <stdexcept>
+#include <iostream>
+
+#include "mpgsnd_decoder.hpp"
+
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("MpgSnd exception: "+msg); }
+
+namespace MWSound
+{
+
+//
+// libSndFile io callbacks
+//
+sf_count_t MpgSnd_Decoder::ogresf_get_filelen(void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+ return stream->size();
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_seek(sf_count_t offset, int whence, void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+
+ if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_read(void *ptr, sf_count_t count, void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+ return stream->read(ptr, count);
+}
+
+sf_count_t MpgSnd_Decoder::ogresf_write(const void*, sf_count_t, void*)
+{ return -1; }
+
+sf_count_t MpgSnd_Decoder::ogresf_tell(void *user_data)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+ return stream->tell();
+}
+
+//
+// libmpg13 io callbacks
+//
+ssize_t MpgSnd_Decoder::ogrempg_read(void *user_data, void *ptr, size_t count)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+ return stream->read(ptr, count);
+}
+
+off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence)
+{
+ Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
+
+ if(whence == SEEK_CUR)
+ stream->seek(stream->tell()+offset);
+ else if(whence == SEEK_SET)
+ stream->seek(offset);
+ else if(whence == SEEK_END)
+ stream->seek(stream->size()+offset);
+ else
+ return -1;
+
+ return stream->tell();
+}
+
+
+void MpgSnd_Decoder::open(const std::string &fname)
+{
+ close();
+ mDataStream = mResourceMgr.openResource(fname);
+
+ SF_VIRTUAL_IO streamIO = {
+ ogresf_get_filelen, ogresf_seek,
+ ogresf_read, ogresf_write, ogresf_tell
+ };
+ mSndFile = sf_open_virtual(&streamIO, SFM_READ, &mSndInfo, this);
+ if(mSndFile)
+ {
+ if(mSndInfo.channels == 1)
+ mChanConfig = ChannelConfig_Mono;
+ else if(mSndInfo.channels == 2)
+ mChanConfig = ChannelConfig_Stereo;
+ else
+ {
+ sf_close(mSndFile);
+ mSndFile = NULL;
+ fail("Unsupported channel count in "+fname);
+ }
+ mSampleRate = mSndInfo.samplerate;
+ return;
+ }
+ mDataStream->seek(0);
+
+ mMpgFile = mpg123_new(NULL, NULL);
+ if(mMpgFile && mpg123_replace_reader_handle(mMpgFile, ogrempg_read, ogrempg_lseek, NULL) == MPG123_OK &&
+ mpg123_open_handle(mMpgFile, this) == MPG123_OK)
+ {
+ try
+ {
+ int encoding, channels;
+ long rate;
+ if(mpg123_getformat(mMpgFile, &rate, &channels, &encoding) != MPG123_OK)
+ fail("Failed to get audio format");
+ if(encoding != MPG123_ENC_SIGNED_16)
+ fail("Unsupported encoding in "+fname);
+ if(channels != 1 && channels != 2)
+ fail("Unsupported channel count in "+fname);
+ mChanConfig = ((channels==2)?ChannelConfig_Stereo:ChannelConfig_Mono);
+ mSampleRate = rate;
+ return;
+ }
+ catch(std::exception &e)
+ {
+ mpg123_close(mMpgFile);
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+ throw;
+ }
+ mpg123_close(mMpgFile);
+ }
+ if(mMpgFile)
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+
+ fail("Unsupported file type: "+fname);
+}
+
+void MpgSnd_Decoder::close()
+{
+ if(mSndFile)
+ sf_close(mSndFile);
+ mSndFile = NULL;
+
+ if(mMpgFile)
+ {
+ mpg123_close(mMpgFile);
+ mpg123_delete(mMpgFile);
+ mMpgFile = NULL;
+ }
+
+ mDataStream.setNull();
+}
+
+std::string MpgSnd_Decoder::getName()
+{
+ return mDataStream->getName();
+}
+
+void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
+{
+ if(!mSndFile && !mMpgFile)
+ fail("No open file");
+
+ *samplerate = mSampleRate;
+ *chans = mChanConfig;
+ *type = SampleType_Int16;
+}
+
+size_t MpgSnd_Decoder::read(char *buffer, size_t bytes)
+{
+ size_t got = 0;
+
+ if(mSndFile)
+ {
+ got = sf_read_short(mSndFile, (short*)buffer, bytes/2)*2;
+ }
+ else if(mMpgFile)
+ {
+ int err;
+ err = mpg123_read(mMpgFile, (unsigned char*)buffer, bytes, &got);
+ if(err != MPG123_OK && err != MPG123_DONE)
+ fail("Failed to read from file");
+ }
+ return got;
+}
+
+void MpgSnd_Decoder::readAll(std::vector<char> &output)
+{
+ if(mSndFile && mSndInfo.frames > 0)
+ {
+ size_t pos = output.size();
+ output.resize(pos + mSndInfo.frames*mSndInfo.channels*2);
+ sf_readf_short(mSndFile, (short*)(&output[0]+pos), mSndInfo.frames);
+ return;
+ }
+ // Fallback in case we don't know the total already
+ Sound_Decoder::readAll(output);
+}
+
+void MpgSnd_Decoder::rewind()
+{
+ if(!mSndFile && !mMpgFile)
+ fail("No open file");
+
+ if(mSndFile)
+ {
+ if(sf_seek(mSndFile, 0, SEEK_SET) == -1)
+ fail("seek failed");
+ }
+ else if(mMpgFile)
+ {
+ if(mpg123_seek(mMpgFile, 0, SEEK_SET) < 0)
+ fail("seek failed");
+ }
+}
+
+size_t MpgSnd_Decoder::getSampleOffset()
+{
+ return 0;
+}
+
+MpgSnd_Decoder::MpgSnd_Decoder()
+ : mSndInfo()
+ , mSndFile(NULL)
+ , mMpgFile(NULL)
+ , mDataStream()
+ , mChanConfig(ChannelConfig_Stereo)
+ , mSampleRate(0)
+{
+ static bool initdone = false;
+ if(!initdone)
+ mpg123_init();
+ initdone = true;
+}
+
+MpgSnd_Decoder::~MpgSnd_Decoder()
+{
+ close();
+}
+
+}
+
+#endif
diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp
new file mode 100644
index 0000000000..be52f6f491
--- /dev/null
+++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp
@@ -0,0 +1,59 @@
+#ifndef GAME_SOUND_MPGSND_DECODER_H
+#define GAME_SOUND_MPGSND_DECODER_H
+
+#include <string>
+
+#include <OgreDataStream.h>
+
+#include "mpg123.h"
+#include "sndfile.h"
+
+#include "sound_decoder.hpp"
+
+
+namespace MWSound
+{
+ class MpgSnd_Decoder : public Sound_Decoder
+ {
+ SF_INFO mSndInfo;
+ SNDFILE *mSndFile;
+ mpg123_handle *mMpgFile;
+
+ Ogre::DataStreamPtr mDataStream;
+ static sf_count_t ogresf_get_filelen(void *user_data);
+ static sf_count_t ogresf_seek(sf_count_t offset, int whence, void *user_data);
+ static sf_count_t ogresf_read(void *ptr, sf_count_t count, void *user_data);
+ static sf_count_t ogresf_write(const void*, sf_count_t, void*);
+ static sf_count_t ogresf_tell(void *user_data);
+ static ssize_t ogrempg_read(void*, void*, size_t);
+ static off_t ogrempg_lseek(void*, off_t, int);
+
+ ChannelConfig mChanConfig;
+ int mSampleRate;
+
+ virtual void open(const std::string &fname);
+ virtual void close();
+
+ virtual std::string getName();
+ virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
+
+ virtual size_t read(char *buffer, size_t bytes);
+ virtual void readAll(std::vector<char> &output);
+ virtual void rewind();
+ virtual size_t getSampleOffset();
+
+ MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs);
+ MpgSnd_Decoder(const MpgSnd_Decoder &rhs);
+
+ MpgSnd_Decoder();
+ public:
+ virtual ~MpgSnd_Decoder();
+
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_DECODER
+#define DEFAULT_DECODER (::MWSound::MpgSnd_Decoder)
+#endif
+}
+
+#endif
diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp
new file mode 100644
index 0000000000..4ee754b35d
--- /dev/null
+++ b/apps/openmw/mwsound/openal_output.cpp
@@ -0,0 +1,1025 @@
+#include <algorithm>
+#include <stdexcept>
+#include <iostream>
+#include <vector>
+
+#include <boost/thread.hpp>
+
+#include "openal_output.hpp"
+#include "sound_decoder.hpp"
+#include "sound.hpp"
+#include "soundmanagerimp.hpp"
+
+#ifndef ALC_ALL_DEVICES_SPECIFIER
+#define ALC_ALL_DEVICES_SPECIFIER 0x1013
+#endif
+
+
+namespace MWSound
+{
+
+static void fail(const std::string &msg)
+{ throw std::runtime_error("OpenAL exception: " + msg); }
+
+static void throwALCerror(ALCdevice *device)
+{
+ ALCenum err = alcGetError(device);
+ if(err != ALC_NO_ERROR)
+ {
+ const ALCchar *errstring = alcGetString(device, err);
+ fail(errstring ? errstring : "");
+ }
+}
+
+static void throwALerror()
+{
+ ALenum err = alGetError();
+ if(err != AL_NO_ERROR)
+ {
+ const ALchar *errstring = alGetString(err);
+ fail(errstring ? errstring : "");
+ }
+}
+
+
+static ALenum getALFormat(ChannelConfig chans, SampleType type)
+{
+ static const struct {
+ ALenum format;
+ ChannelConfig chans;
+ SampleType type;
+ } fmtlist[] = {
+ { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 },
+ { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 },
+ { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 },
+ { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 },
+ };
+ static const size_t fmtlistsize = sizeof(fmtlist)/sizeof(fmtlist[0]);
+
+ for(size_t i = 0;i < fmtlistsize;i++)
+ {
+ if(fmtlist[i].chans == chans && fmtlist[i].type == type)
+ return fmtlist[i].format;
+ }
+
+ if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
+ {
+ static const struct {
+ char name[32];
+ ChannelConfig chans;
+ SampleType type;
+ } mcfmtlist[] = {
+ { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 },
+ { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 },
+ { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 },
+ { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 },
+ { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 },
+ { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 },
+ };
+ static const size_t mcfmtlistsize = sizeof(mcfmtlist)/sizeof(mcfmtlist[0]);
+
+ for(size_t i = 0;i < mcfmtlistsize;i++)
+ {
+ if(mcfmtlist[i].chans == chans && mcfmtlist[i].type == type)
+ {
+ ALenum format = alGetEnumValue(mcfmtlist[i].name);
+ if(format != 0 && format != -1)
+ return format;
+ }
+ }
+ }
+ if(alIsExtensionPresent("AL_EXT_FLOAT32"))
+ {
+ static const struct {
+ char name[32];
+ ChannelConfig chans;
+ SampleType type;
+ } fltfmtlist[] = {
+ { "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 },
+ { "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 },
+ };
+ static const size_t fltfmtlistsize = sizeof(fltfmtlist)/sizeof(fltfmtlist[0]);
+
+ for(size_t i = 0;i < fltfmtlistsize;i++)
+ {
+ if(fltfmtlist[i].chans == chans && fltfmtlist[i].type == type)
+ {
+ ALenum format = alGetEnumValue(fltfmtlist[i].name);
+ if(format != 0 && format != -1)
+ return format;
+ }
+ }
+ if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
+ {
+ static const struct {
+ char name[32];
+ ChannelConfig chans;
+ SampleType type;
+ } fltmcfmtlist[] = {
+ { "AL_FORMAT_QUAD32", ChannelConfig_Quad, SampleType_Float32 },
+ { "AL_FORMAT_51CHN32", ChannelConfig_5point1, SampleType_Float32 },
+ { "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 },
+ };
+ static const size_t fltmcfmtlistsize = sizeof(fltmcfmtlist)/sizeof(fltmcfmtlist[0]);
+
+ for(size_t i = 0;i < fltmcfmtlistsize;i++)
+ {
+ if(fltmcfmtlist[i].chans == chans && fltmcfmtlist[i].type == type)
+ {
+ ALenum format = alGetEnumValue(fltmcfmtlist[i].name);
+ if(format != 0 && format != -1)
+ return format;
+ }
+ }
+ }
+ }
+
+ fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")");
+ return AL_NONE;
+}
+
+static ALint getBufferSampleCount(ALuint buf)
+{
+ ALint size, bits, channels;
+
+ alGetBufferi(buf, AL_SIZE, &size);
+ alGetBufferi(buf, AL_BITS, &bits);
+ alGetBufferi(buf, AL_CHANNELS, &channels);
+ throwALerror();
+
+ return size / channels * 8 / bits;
+}
+
+//
+// A streaming OpenAL sound.
+//
+class OpenAL_SoundStream : public Sound
+{
+ static const ALuint sNumBuffers = 6;
+ static const ALfloat sBufferLength;
+
+ OpenAL_Output &mOutput;
+
+ ALuint mSource;
+ ALuint mBuffers[sNumBuffers];
+
+ ALenum mFormat;
+ ALsizei mSampleRate;
+ ALuint mBufferSize;
+
+ ALuint mSamplesQueued;
+
+ DecoderPtr mDecoder;
+
+ volatile bool mIsFinished;
+
+ void updateAll(bool local);
+
+ OpenAL_SoundStream(const OpenAL_SoundStream &rhs);
+ OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs);
+
+ friend class OpenAL_Output;
+
+public:
+ OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags);
+ virtual ~OpenAL_SoundStream();
+
+ virtual void stop();
+ virtual bool isPlaying();
+ virtual double getTimeOffset();
+ virtual void update();
+
+ void play();
+ bool process();
+};
+
+const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f;
+
+//
+// A background streaming thread (keeps active streams processed)
+//
+struct OpenAL_Output::StreamThread {
+ typedef std::vector<OpenAL_SoundStream*> StreamVec;
+ StreamVec mStreams;
+ boost::recursive_mutex mMutex;
+ boost::thread mThread;
+
+ StreamThread()
+ : mThread(boost::ref(*this))
+ {
+ }
+ ~StreamThread()
+ {
+ mThread.interrupt();
+ }
+
+ // boost::thread entry point
+ void operator()()
+ {
+ while(1)
+ {
+ mMutex.lock();
+ StreamVec::iterator iter = mStreams.begin();
+ while(iter != mStreams.end())
+ {
+ if((*iter)->process() == false)
+ iter = mStreams.erase(iter);
+ else
+ ++iter;
+ }
+ mMutex.unlock();
+ boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+ }
+ }
+
+ void add(OpenAL_SoundStream *stream)
+ {
+ mMutex.lock();
+ if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end())
+ mStreams.push_back(stream);
+ mMutex.unlock();
+ }
+
+ void remove(OpenAL_SoundStream *stream)
+ {
+ mMutex.lock();
+ StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream);
+ if(iter != mStreams.end())
+ mStreams.erase(iter);
+ mMutex.unlock();
+ }
+
+ void removeAll()
+ {
+ mMutex.lock();
+ mStreams.clear();
+ mMutex.unlock();
+ }
+
+private:
+ StreamThread(const StreamThread &rhs);
+ StreamThread& operator=(const StreamThread &rhs);
+};
+
+
+OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags)
+ : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags)
+ , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true)
+{
+ throwALerror();
+
+ alGenBuffers(sNumBuffers, mBuffers);
+ throwALerror();
+ try
+ {
+ int srate;
+ ChannelConfig chans;
+ SampleType type;
+
+ mDecoder->getInfo(&srate, &chans, &type);
+ mFormat = getALFormat(chans, type);
+ mSampleRate = srate;
+
+ mBufferSize = static_cast<ALuint>(sBufferLength*srate);
+ mBufferSize = framesToBytes(mBufferSize, chans, type);
+
+ mOutput.mActiveSounds.push_back(this);
+ }
+ catch(std::exception &e)
+ {
+ alDeleteBuffers(sNumBuffers, mBuffers);
+ alGetError();
+ throw;
+ }
+}
+OpenAL_SoundStream::~OpenAL_SoundStream()
+{
+ mOutput.mStreamThread->remove(this);
+
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+
+ mOutput.mFreeSources.push_back(mSource);
+ alDeleteBuffers(sNumBuffers, mBuffers);
+ alGetError();
+
+ mDecoder->close();
+
+ mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(),
+ mOutput.mActiveSounds.end(), this));
+}
+
+void OpenAL_SoundStream::play()
+{
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+ throwALerror();
+ mSamplesQueued = 0;
+
+ for(ALuint i = 0;i < sNumBuffers;i++)
+ alBufferData(mBuffers[i], mFormat, this, 0, mSampleRate);
+ throwALerror();
+
+ alSourceQueueBuffers(mSource, sNumBuffers, mBuffers);
+ alSourcePlay(mSource);
+ throwALerror();
+
+ mIsFinished = false;
+ mOutput.mStreamThread->add(this);
+}
+
+void OpenAL_SoundStream::stop()
+{
+ mOutput.mStreamThread->remove(this);
+ mIsFinished = true;
+
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+ throwALerror();
+ mSamplesQueued = 0;
+
+ mDecoder->rewind();
+}
+
+bool OpenAL_SoundStream::isPlaying()
+{
+ ALint state;
+
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ throwALerror();
+
+ if(state == AL_PLAYING || state == AL_PAUSED)
+ return true;
+ return !mIsFinished;
+}
+
+double OpenAL_SoundStream::getTimeOffset()
+{
+ ALint state = AL_STOPPED;
+ ALfloat offset = 0.0f;
+ double t;
+
+ mOutput.mStreamThread->mMutex.lock();
+ alGetSourcef(mSource, AL_SEC_OFFSET, &offset);
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ if(state == AL_PLAYING || state == AL_PAUSED)
+ t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset;
+ else
+ t = (double)mDecoder->getSampleOffset() / (double)mSampleRate;
+ mOutput.mStreamThread->mMutex.unlock();
+
+ throwALerror();
+ return t;
+}
+
+void OpenAL_SoundStream::updateAll(bool local)
+{
+ alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance);
+ alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance);
+ if(local)
+ {
+ alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f);
+ alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ }
+ else
+ {
+ alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f);
+ alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE);
+ }
+ alSourcei(mSource, AL_LOOPING, AL_FALSE);
+
+ update();
+}
+
+void OpenAL_SoundStream::update()
+{
+ ALfloat gain = mVolume*mBaseVolume;
+ ALfloat pitch = mPitch;
+ if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater)
+ {
+ gain *= 0.9f;
+ pitch *= 0.7f;
+ }
+
+ alSourcef(mSource, AL_GAIN, gain);
+ alSourcef(mSource, AL_PITCH, pitch);
+ alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]);
+ alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ throwALerror();
+}
+
+bool OpenAL_SoundStream::process()
+{
+ try {
+ bool finished = mIsFinished;
+ ALint processed, state;
+
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
+ throwALerror();
+
+ if(processed > 0)
+ {
+ std::vector<char> data(mBufferSize);
+ do {
+ ALuint bufid = 0;
+ size_t got;
+
+ alSourceUnqueueBuffers(mSource, 1, &bufid);
+ mSamplesQueued -= getBufferSampleCount(bufid);
+ processed--;
+
+ if(finished)
+ continue;
+
+ got = mDecoder->read(&data[0], data.size());
+ finished = (got < data.size());
+ if(got > 0)
+ {
+ alBufferData(bufid, mFormat, &data[0], got, mSampleRate);
+ alSourceQueueBuffers(mSource, 1, &bufid);
+ mSamplesQueued += getBufferSampleCount(bufid);
+ }
+ } while(processed > 0);
+ throwALerror();
+ }
+
+ if(state != AL_PLAYING && state != AL_PAUSED)
+ {
+ ALint queued = 0;
+
+ alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
+ if(queued > 0)
+ alSourcePlay(mSource);
+ throwALerror();
+ }
+
+ mIsFinished = finished;
+ }
+ catch(std::exception &e) {
+ std::cout<< "Error updating stream \""<<mDecoder->getName()<<"\"" <<std::endl;
+ mSamplesQueued = 0;
+ mIsFinished = true;
+ }
+ return !mIsFinished;
+}
+
+//
+// A regular 2D OpenAL sound
+//
+class OpenAL_Sound : public Sound
+{
+protected:
+ OpenAL_Output &mOutput;
+
+ ALuint mSource;
+ ALuint mBuffer;
+
+ friend class OpenAL_Output;
+
+ void updateAll(bool local);
+
+private:
+ OpenAL_Sound(const OpenAL_Sound &rhs);
+ OpenAL_Sound& operator=(const OpenAL_Sound &rhs);
+
+public:
+ OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags);
+ virtual ~OpenAL_Sound();
+
+ virtual void stop();
+ virtual bool isPlaying();
+ virtual double getTimeOffset();
+ virtual double getLength();
+ virtual void update();
+};
+
+//
+// A regular 3D OpenAL sound
+//
+class OpenAL_Sound3D : public OpenAL_Sound
+{
+ OpenAL_Sound3D(const OpenAL_Sound &rhs);
+ OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs);
+
+public:
+ OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf, const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags)
+ : OpenAL_Sound(output, src, buf, pos, vol, basevol, pitch, mindist, maxdist, flags)
+ { }
+
+ virtual void update();
+};
+
+OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags)
+ : Sound(pos, vol, basevol, pitch, mindist, maxdist, flags)
+ , mOutput(output), mSource(src), mBuffer(buf)
+{
+ mOutput.mActiveSounds.push_back(this);
+}
+OpenAL_Sound::~OpenAL_Sound()
+{
+ alSourceStop(mSource);
+ alSourcei(mSource, AL_BUFFER, 0);
+
+ mOutput.mFreeSources.push_back(mSource);
+ mOutput.bufferFinished(mBuffer);
+
+ mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(),
+ mOutput.mActiveSounds.end(), this));
+}
+
+void OpenAL_Sound::stop()
+{
+ alSourceStop(mSource);
+ throwALerror();
+}
+
+bool OpenAL_Sound::isPlaying()
+{
+ ALint state;
+
+ alGetSourcei(mSource, AL_SOURCE_STATE, &state);
+ throwALerror();
+
+ return state==AL_PLAYING || state==AL_PAUSED;
+}
+
+double OpenAL_Sound::getTimeOffset()
+{
+ ALfloat t;
+
+ alGetSourcef(mSource, AL_SEC_OFFSET, &t);
+ throwALerror();
+
+ return t;
+}
+
+double OpenAL_Sound::getLength()
+{
+ ALint bufferSize, frequency, channels, bitsPerSample;
+ alGetBufferi(mBuffer, AL_SIZE, &bufferSize);
+ alGetBufferi(mBuffer, AL_FREQUENCY, &frequency);
+ alGetBufferi(mBuffer, AL_CHANNELS, &channels);
+ alGetBufferi(mBuffer, AL_BITS, &bitsPerSample);
+
+ return (8.0*bufferSize)/(frequency*channels*bitsPerSample);
+}
+
+void OpenAL_Sound::updateAll(bool local)
+{
+ alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance);
+ alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance);
+ if(local)
+ {
+ alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f);
+ alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ }
+ else
+ {
+ alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f);
+ alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE);
+ }
+ alSourcei(mSource, AL_LOOPING, (mFlags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE);
+
+ update();
+}
+
+void OpenAL_Sound::update()
+{
+ ALfloat gain = mVolume*mBaseVolume;
+ ALfloat pitch = mPitch;
+
+ if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater)
+ {
+ gain *= 0.9f;
+ pitch *= 0.7f;
+ }
+
+ alSourcef(mSource, AL_GAIN, gain);
+ alSourcef(mSource, AL_PITCH, pitch);
+ alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]);
+ alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ throwALerror();
+}
+
+void OpenAL_Sound3D::update()
+{
+ ALfloat gain = mVolume*mBaseVolume;
+ ALfloat pitch = mPitch;
+ if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance)
+ gain = 0.0f;
+ else if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater)
+ {
+ gain *= 0.9f;
+ pitch *= 0.7f;
+ }
+
+ alSourcef(mSource, AL_GAIN, gain);
+ alSourcef(mSource, AL_PITCH, pitch);
+ alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]);
+ alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
+ alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+ throwALerror();
+}
+
+
+//
+// An OpenAL output device
+//
+std::vector<std::string> OpenAL_Output::enumerate()
+{
+ std::vector<std::string> devlist;
+ const ALCchar *devnames;
+
+ if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
+ devnames = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+ else
+ devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
+ while(devnames && *devnames)
+ {
+ devlist.push_back(devnames);
+ devnames += strlen(devnames)+1;
+ }
+ return devlist;
+}
+
+void OpenAL_Output::init(const std::string &devname)
+{
+ deinit();
+
+ mDevice = alcOpenDevice(devname.c_str());
+ if(!mDevice)
+ {
+ if(devname.empty())
+ fail("Failed to open default device");
+ else
+ fail("Failed to open \""+devname+"\"");
+ }
+ else
+ {
+ const ALCchar *name = NULL;
+ if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT"))
+ name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER);
+ if(alcGetError(mDevice) != AL_NO_ERROR || !name)
+ name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER);
+ std::cout << "Opened \""<<name<<"\"" << std::endl;
+ }
+
+ mContext = alcCreateContext(mDevice, NULL);
+ if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE)
+ {
+ if(mContext)
+ alcDestroyContext(mContext);
+ mContext = 0;
+ fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice)));
+ }
+
+ alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
+ throwALerror();
+
+ ALCint maxmono=0, maxstereo=0;
+ alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono);
+ alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo);
+ throwALCerror(mDevice);
+
+ try
+ {
+ ALCuint maxtotal = std::min<ALCuint>(maxmono+maxstereo, 256);
+ if (maxtotal == 0) // workaround for broken implementations
+ maxtotal = 256;
+ for(size_t i = 0;i < maxtotal;i++)
+ {
+ ALuint src = 0;
+ alGenSources(1, &src);
+ throwALerror();
+ mFreeSources.push_back(src);
+ }
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Error: "<<e.what()<<", trying to continue"<< std::endl;
+ }
+ if(mFreeSources.empty())
+ fail("Could not allocate any sources");
+
+ mInitialized = true;
+}
+
+void OpenAL_Output::deinit()
+{
+ mStreamThread->removeAll();
+
+ for(size_t i = 0;i < mFreeSources.size();i++)
+ alDeleteSources(1, &mFreeSources[i]);
+ mFreeSources.clear();
+
+ mBufferRefs.clear();
+ mUnusedBuffers.clear();
+ while(!mBufferCache.empty())
+ {
+ alDeleteBuffers(1, &mBufferCache.begin()->second);
+ mBufferCache.erase(mBufferCache.begin());
+ }
+
+ alcMakeContextCurrent(0);
+ if(mContext)
+ alcDestroyContext(mContext);
+ mContext = 0;
+ if(mDevice)
+ alcCloseDevice(mDevice);
+ mDevice = 0;
+
+ mInitialized = false;
+}
+
+
+ALuint OpenAL_Output::getBuffer(const std::string &fname)
+{
+ ALuint buf = 0;
+
+ NameMap::iterator iditer = mBufferCache.find(fname);
+ if(iditer != mBufferCache.end())
+ {
+ buf = iditer->second;
+ if(mBufferRefs[buf]++ == 0)
+ {
+ IDDq::iterator iter = std::find(mUnusedBuffers.begin(),
+ mUnusedBuffers.end(), buf);
+ if(iter != mUnusedBuffers.end())
+ mUnusedBuffers.erase(iter);
+ }
+
+ return buf;
+ }
+ throwALerror();
+
+ std::vector<char> data;
+ ChannelConfig chans;
+ SampleType type;
+ ALenum format;
+ int srate;
+
+ DecoderPtr decoder = mManager.getDecoder();
+ try
+ {
+ decoder->open(fname);
+ }
+ catch(Ogre::FileNotFoundException &e)
+ {
+ std::string::size_type pos = fname.rfind('.');
+ if(pos == std::string::npos)
+ throw;
+ decoder->open(fname.substr(0, pos)+".mp3");
+ }
+
+ decoder->getInfo(&srate, &chans, &type);
+ format = getALFormat(chans, type);
+
+ decoder->readAll(data);
+ decoder->close();
+
+ alGenBuffers(1, &buf);
+ throwALerror();
+
+ alBufferData(buf, format, &data[0], data.size(), srate);
+ mBufferCache[fname] = buf;
+ mBufferRefs[buf] = 1;
+
+ ALint bufsize = 0;
+ alGetBufferi(buf, AL_SIZE, &bufsize);
+ mBufferCacheMemSize += bufsize;
+
+ // NOTE: Max buffer cache: 15MB
+ while(mBufferCacheMemSize > 15*1024*1024)
+ {
+ if(mUnusedBuffers.empty())
+ {
+ std::cout <<"No more unused buffers to clear!"<< std::endl;
+ break;
+ }
+
+ ALuint oldbuf = mUnusedBuffers.front();
+ mUnusedBuffers.pop_front();
+
+ NameMap::iterator nameiter = mBufferCache.begin();
+ while(nameiter != mBufferCache.end())
+ {
+ if(nameiter->second == oldbuf)
+ mBufferCache.erase(nameiter++);
+ else
+ ++nameiter;
+ }
+
+ bufsize = 0;
+ alGetBufferi(oldbuf, AL_SIZE, &bufsize);
+ alDeleteBuffers(1, &oldbuf);
+ mBufferCacheMemSize -= bufsize;
+ }
+ return buf;
+}
+
+void OpenAL_Output::bufferFinished(ALuint buf)
+{
+ if(mBufferRefs.at(buf)-- == 1)
+ {
+ mBufferRefs.erase(mBufferRefs.find(buf));
+ mUnusedBuffers.push_back(buf);
+ }
+}
+
+MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags,float offset)
+{
+ boost::shared_ptr<OpenAL_Sound> sound;
+ ALuint src=0, buf=0;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ buf = getBuffer(fname);
+ sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ if(buf && alIsBuffer(buf))
+ bufferFinished(buf);
+ alGetError();
+ throw;
+ }
+
+ sound->updateAll(true);
+ if(offset<0)
+ offset=0;
+ if(offset>1)
+ offset=1;
+
+ alSourcei(src, AL_BUFFER, buf);
+ alSourcef(src, AL_SEC_OFFSET, sound->getLength()*offset/pitch);
+ alSourcePlay(src);
+ throwALerror();
+
+ return sound;
+}
+
+MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch,
+ float min, float max, int flags, float offset)
+{
+ boost::shared_ptr<OpenAL_Sound> sound;
+ ALuint src=0, buf=0;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ try
+ {
+ buf = getBuffer(fname);
+ sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ if(buf && alIsBuffer(buf))
+ bufferFinished(buf);
+ alGetError();
+ throw;
+ }
+
+ sound->updateAll(false);
+
+ if(offset<0)
+ offset=0;
+ if(offset>1)
+ offset=1;
+
+ alSourcei(src, AL_BUFFER, buf);
+ alSourcef(src, AL_SEC_OFFSET, sound->getLength()*offset/pitch);
+
+ alSourcePlay(src);
+ throwALerror();
+
+ return sound;
+}
+
+
+MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags)
+{
+ boost::shared_ptr<OpenAL_SoundStream> sound;
+ ALuint src;
+
+ if(mFreeSources.empty())
+ fail("No free sources");
+ src = mFreeSources.front();
+ mFreeSources.pop_front();
+
+ if((flags&MWBase::SoundManager::Play_Loop))
+ std::cout <<"Warning: cannot loop stream \""<<decoder->getName()<<"\""<< std::endl;
+ try
+ {
+ sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags));
+ }
+ catch(std::exception &e)
+ {
+ mFreeSources.push_back(src);
+ throw;
+ }
+
+ sound->updateAll(true);
+
+ sound->play();
+ return sound;
+}
+
+
+void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env)
+{
+ mPos = pos;
+ mLastEnvironment = env;
+
+ if(mContext)
+ {
+ ALfloat orient[6] = {
+ atdir.x, atdir.y, atdir.z,
+ updir.x, updir.y, updir.z
+ };
+ alListener3f(AL_POSITION, mPos.x, mPos.y, mPos.z);
+ alListenerfv(AL_ORIENTATION, orient);
+ throwALerror();
+ }
+}
+
+
+void OpenAL_Output::pauseSounds(int types)
+{
+ std::vector<ALuint> sources;
+ SoundVec::const_iterator iter = mActiveSounds.begin();
+ while(iter != mActiveSounds.end())
+ {
+ const OpenAL_SoundStream *stream = dynamic_cast<OpenAL_SoundStream*>(*iter);
+ if(stream)
+ {
+ if(stream->mSource && (stream->getPlayType()&types))
+ sources.push_back(stream->mSource);
+ }
+ else
+ {
+ const OpenAL_Sound *sound = dynamic_cast<OpenAL_Sound*>(*iter);
+ if(sound && sound->mSource && (sound->getPlayType()&types))
+ sources.push_back(sound->mSource);
+ }
+ ++iter;
+ }
+ if(!sources.empty())
+ {
+ alSourcePausev(sources.size(), &sources[0]);
+ throwALerror();
+ }
+}
+
+void OpenAL_Output::resumeSounds(int types)
+{
+ std::vector<ALuint> sources;
+ SoundVec::const_iterator iter = mActiveSounds.begin();
+ while(iter != mActiveSounds.end())
+ {
+ const OpenAL_SoundStream *stream = dynamic_cast<OpenAL_SoundStream*>(*iter);
+ if(stream)
+ {
+ if(stream->mSource && (stream->getPlayType()&types))
+ sources.push_back(stream->mSource);
+ }
+ else
+ {
+ const OpenAL_Sound *sound = dynamic_cast<OpenAL_Sound*>(*iter);
+ if(sound && sound->mSource && (sound->getPlayType()&types))
+ sources.push_back(sound->mSource);
+ }
+ ++iter;
+ }
+ if(!sources.empty())
+ {
+ alSourcePlayv(sources.size(), &sources[0]);
+ throwALerror();
+ }
+}
+
+
+OpenAL_Output::OpenAL_Output(SoundManager &mgr)
+ : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0),
+ mLastEnvironment(Env_Normal), mStreamThread(new StreamThread)
+{
+}
+
+OpenAL_Output::~OpenAL_Output()
+{
+ deinit();
+}
+
+}
diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp
new file mode 100644
index 0000000000..31edf73599
--- /dev/null
+++ b/apps/openmw/mwsound/openal_output.hpp
@@ -0,0 +1,79 @@
+#ifndef GAME_SOUND_OPENAL_OUTPUT_H
+#define GAME_SOUND_OPENAL_OUTPUT_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <deque>
+
+#include "alc.h"
+#include "al.h"
+
+#include "sound_output.hpp"
+
+namespace MWSound
+{
+ class SoundManager;
+ class Sound;
+
+ class OpenAL_Output : public Sound_Output
+ {
+ ALCdevice *mDevice;
+ ALCcontext *mContext;
+
+ typedef std::deque<ALuint> IDDq;
+ IDDq mFreeSources;
+ IDDq mUnusedBuffers;
+
+ typedef std::map<std::string,ALuint> NameMap;
+ NameMap mBufferCache;
+
+ typedef std::map<ALuint,ALuint> IDRefMap;
+ IDRefMap mBufferRefs;
+
+ uint64_t mBufferCacheMemSize;
+
+ typedef std::vector<Sound*> SoundVec;
+ SoundVec mActiveSounds;
+
+ ALuint getBuffer(const std::string &fname);
+ void bufferFinished(ALuint buffer);
+
+ Environment mLastEnvironment;
+
+ virtual std::vector<std::string> enumerate();
+ virtual void init(const std::string &devname="");
+ virtual void deinit();
+
+ /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+ virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset);
+ /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+ virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos,
+ float vol, float basevol, float pitch, float min, float max, int flags, float offset);
+ virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags);
+
+ virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env);
+
+ virtual void pauseSounds(int types);
+ virtual void resumeSounds(int types);
+
+ OpenAL_Output& operator=(const OpenAL_Output &rhs);
+ OpenAL_Output(const OpenAL_Output &rhs);
+
+ OpenAL_Output(SoundManager &mgr);
+ virtual ~OpenAL_Output();
+
+ class StreamThread;
+ std::auto_ptr<StreamThread> mStreamThread;
+
+ friend class OpenAL_Sound;
+ friend class OpenAL_Sound3D;
+ friend class OpenAL_SoundStream;
+ friend class SoundManager;
+ };
+#ifndef DEFAULT_OUTPUT
+#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x))
+#endif
+}
+
+#endif
diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp
new file mode 100644
index 0000000000..670002a30f
--- /dev/null
+++ b/apps/openmw/mwsound/sound.hpp
@@ -0,0 +1,55 @@
+#ifndef GAME_SOUND_SOUND_H
+#define GAME_SOUND_SOUND_H
+
+#include <OgreVector3.h>
+
+#include "soundmanagerimp.hpp"
+
+namespace MWSound
+{
+ class Sound
+ {
+ virtual void update() = 0;
+
+ Sound& operator=(const Sound &rhs);
+ Sound(const Sound &rhs);
+
+ protected:
+ Ogre::Vector3 mPos;
+ float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */
+ float mBaseVolume;
+ float mPitch;
+ float mMinDistance;
+ float mMaxDistance;
+ int mFlags;
+ float mFadeOutTime;
+
+ public:
+ virtual void stop() = 0;
+ virtual bool isPlaying() = 0;
+ virtual double getTimeOffset() = 0;
+ void setPosition(const Ogre::Vector3 &pos) { mPos = pos; }
+ void setVolume(float volume) { mVolume = volume; }
+ void setFadeout(float duration) { mFadeOutTime=duration; }
+ MWBase::SoundManager::PlayType getPlayType() const
+ { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); }
+
+
+ Sound(const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags)
+ : mPos(pos)
+ , mVolume(vol)
+ , mBaseVolume(basevol)
+ , mPitch(pitch)
+ , mMinDistance(mindist)
+ , mMaxDistance(maxdist)
+ , mFlags(flags)
+ , mFadeOutTime(0)
+ { }
+ virtual ~Sound() { }
+
+ friend class OpenAL_Output;
+ friend class SoundManager;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp
new file mode 100644
index 0000000000..151b580360
--- /dev/null
+++ b/apps/openmw/mwsound/sound_decoder.hpp
@@ -0,0 +1,54 @@
+#ifndef GAME_SOUND_SOUND_DECODER_H
+#define GAME_SOUND_SOUND_DECODER_H
+
+#include <string>
+
+#include <OgreResourceGroupManager.h>
+
+namespace MWSound
+{
+ enum SampleType {
+ SampleType_UInt8,
+ SampleType_Int16,
+ SampleType_Float32
+ };
+ const char *getSampleTypeName(SampleType type);
+
+ enum ChannelConfig {
+ ChannelConfig_Mono,
+ ChannelConfig_Stereo,
+ ChannelConfig_Quad,
+ ChannelConfig_5point1,
+ ChannelConfig_7point1
+ };
+ const char *getChannelConfigName(ChannelConfig config);
+
+ size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type);
+ size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type);
+
+ struct Sound_Decoder
+ {
+ Ogre::ResourceGroupManager &mResourceMgr;
+
+ virtual void open(const std::string &fname) = 0;
+ virtual void close() = 0;
+
+ virtual std::string getName() = 0;
+ virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0;
+
+ virtual size_t read(char *buffer, size_t bytes) = 0;
+ virtual void readAll(std::vector<char> &output);
+ virtual void rewind() = 0;
+ virtual size_t getSampleOffset() = 0;
+
+ Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton())
+ { }
+ virtual ~Sound_Decoder() { }
+
+ private:
+ Sound_Decoder(const Sound_Decoder &rhs);
+ Sound_Decoder& operator=(const Sound_Decoder &rhs);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp
new file mode 100644
index 0000000000..91e25db521
--- /dev/null
+++ b/apps/openmw/mwsound/sound_output.hpp
@@ -0,0 +1,61 @@
+#ifndef GAME_SOUND_SOUND_OUTPUT_H
+#define GAME_SOUND_SOUND_OUTPUT_H
+
+#include <string>
+#include <memory>
+
+#include <OgreVector3.h>
+
+#include "soundmanagerimp.hpp"
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWSound
+{
+ class SoundManager;
+ class Sound_Decoder;
+ class Sound;
+
+ class Sound_Output
+ {
+ SoundManager &mManager;
+
+ virtual std::vector<std::string> enumerate() = 0;
+ virtual void init(const std::string &devname="") = 0;
+ virtual void deinit() = 0;
+
+ /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+ virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset) = 0;
+ /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
+ virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos,
+ float vol, float basevol, float pitch, float min, float max, int flags, float offset) = 0;
+ virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0;
+
+ virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0;
+
+ virtual void pauseSounds(int types) = 0;
+ virtual void resumeSounds(int types) = 0;
+
+ Sound_Output& operator=(const Sound_Output &rhs);
+ Sound_Output(const Sound_Output &rhs);
+
+ protected:
+ bool mInitialized;
+ Ogre::Vector3 mPos;
+
+ Sound_Output(SoundManager &mgr)
+ : mManager(mgr)
+ , mInitialized(false)
+ , mPos(0.0f, 0.0f, 0.0f)
+ { }
+ public:
+ virtual ~Sound_Output() { }
+
+ bool isInitialized() const { return mInitialized; }
+
+ friend class OpenAL_Output;
+ friend class SoundManager;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp
new file mode 100644
index 0000000000..372be83938
--- /dev/null
+++ b/apps/openmw/mwsound/soundmanagerimp.cpp
@@ -0,0 +1,708 @@
+#include "soundmanagerimp.hpp"
+
+#include <iostream>
+#include <algorithm>
+#include <map>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwworld/esmstore.hpp"
+#include "../mwworld/player.hpp"
+
+#include "sound_output.hpp"
+#include "sound_decoder.hpp"
+#include "sound.hpp"
+
+#include "openal_output.hpp"
+#define SOUND_OUT "OpenAL"
+/* Set up the sound manager to use FFMPEG, MPG123+libsndfile, or Audiere for
+ * input. The OPENMW_USE_x macros are set in CMakeLists.txt.
+*/
+#ifdef OPENMW_USE_FFMPEG
+#include "ffmpeg_decoder.hpp"
+#ifndef SOUND_IN
+#define SOUND_IN "FFmpeg"
+#endif
+#endif
+
+#ifdef OPENMW_USE_AUDIERE
+#include "audiere_decoder.hpp"
+#ifndef SOUND_IN
+#define SOUND_IN "Audiere"
+#endif
+#endif
+
+#ifdef OPENMW_USE_MPG123
+#include "mpgsnd_decoder.hpp"
+#ifndef SOUND_IN
+#define SOUND_IN "mpg123,sndfile"
+#endif
+#endif
+
+
+namespace MWSound
+{
+ SoundManager::SoundManager(bool useSound)
+ : mResourceMgr(Ogre::ResourceGroupManager::getSingleton())
+ , mOutput(new DEFAULT_OUTPUT(*this))
+ , mMasterVolume(1.0f)
+ , mSFXVolume(1.0f)
+ , mMusicVolume(1.0f)
+ , mFootstepsVolume(1.0f)
+ , mVoiceVolume(1.0f)
+ , mPausedSoundTypes(0)
+ , mListenerPos(0,0,0)
+ , mListenerDir(1,0,0)
+ , mListenerUp(0,0,1)
+ {
+ if(!useSound)
+ return;
+
+ mMasterVolume = Settings::Manager::getFloat("master volume", "Sound");
+ mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f);
+ mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound");
+ mSFXVolume = std::min(std::max(mSFXVolume, 0.0f), 1.0f);
+ mMusicVolume = Settings::Manager::getFloat("music volume", "Sound");
+ mMusicVolume = std::min(std::max(mMusicVolume, 0.0f), 1.0f);
+ mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound");
+ mVoiceVolume = std::min(std::max(mVoiceVolume, 0.0f), 1.0f);
+ mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound");
+ mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f);
+
+ std::cout << "Sound output: " << SOUND_OUT << std::endl;
+ std::cout << "Sound decoder: " << SOUND_IN << std::endl;
+
+ try
+ {
+ std::vector<std::string> names = mOutput->enumerate();
+ std::cout <<"Enumerated output devices:"<< std::endl;
+ for(size_t i = 0;i < names.size();i++)
+ std::cout <<" "<<names[i]<< std::endl;
+
+ std::string devname = Settings::Manager::getString("device", "Sound");
+ try
+ {
+ mOutput->init(devname);
+ }
+ catch(std::exception &e)
+ {
+ if(devname.empty())
+ throw;
+ std::cerr <<"Failed to open device \""<<devname<<"\": " << e.what() << std::endl;
+ mOutput->init();
+ Settings::Manager::setString("device", "Sound", "");
+ }
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Sound init failed: "<<e.what()<< std::endl;
+ }
+ }
+
+ SoundManager::~SoundManager()
+ {
+ mUnderwaterSound.reset();
+ mActiveSounds.clear();
+ mMusic.reset();
+ mOutput.reset();
+ }
+
+ // Return a new decoder instance, used as needed by the output implementations
+ DecoderPtr SoundManager::getDecoder()
+ {
+ return DecoderPtr(new DEFAULT_DECODER);
+ }
+
+ // Convert a soundId to file name, and modify the volume
+ // according to the sounds local volume setting, minRange and
+ // maxRange.
+ std::string SoundManager::lookup(const std::string &soundId,
+ float &volume, float &min, float &max)
+ {
+ const ESM::Sound *snd =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>().find(soundId);
+
+ volume *= pow(10.0, (snd->mData.mVolume/255.0*3348.0 - 3348.0) / 2000.0);
+
+ if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0)
+ {
+ min = 100.0f;
+ max = 2000.0f;
+ }
+ else
+ {
+ min = snd->mData.mMinRange * 20.0f;
+ max = snd->mData.mMaxRange * 50.0f;
+ min = std::max(min, 1.0f);
+ max = std::max(min, max);
+ }
+
+ return "Sound/"+snd->mSound;
+ }
+
+ // Gets the combined volume settings for the given sound type
+ float SoundManager::volumeFromType(PlayType type) const
+ {
+ float volume = mMasterVolume;
+ switch(type)
+ {
+ case Play_TypeSfx:
+ volume *= mSFXVolume;
+ break;
+ case Play_TypeVoice:
+ volume *= mVoiceVolume;
+ break;
+ case Play_TypeFoot:
+ volume *= mFootstepsVolume;
+ break;
+ case Play_TypeMusic:
+ case Play_TypeMovie:
+ volume *= mMusicVolume;
+ break;
+ case Play_TypeMask:
+ break;
+ }
+ return volume;
+ }
+
+ bool SoundManager::isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const
+ {
+ SoundMap::const_iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == ptr && snditer->second.second == id)
+ return snditer->first->isPlaying();
+ ++snditer;
+ }
+ return false;
+ }
+
+
+ void SoundManager::stopMusic()
+ {
+ if(mMusic)
+ mMusic->stop();
+ mMusic.reset();
+ }
+
+ void SoundManager::streamMusicFull(const std::string& filename)
+ {
+ if(!mOutput->isInitialized())
+ return;
+ std::cout <<"Playing "<<filename<< std::endl;
+ try
+ {
+ stopMusic();
+
+ DecoderPtr decoder = getDecoder();
+ decoder->open(filename);
+
+ mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic),
+ 1.0f, Play_NoEnv|Play_TypeMusic);
+ }
+ catch(std::exception &e)
+ {
+ std::cout << "Music Error: " << e.what() << "\n";
+ }
+ }
+
+ void SoundManager::streamMusic(const std::string& filename)
+ {
+ streamMusicFull("Music/"+filename);
+ }
+
+ void SoundManager::startRandomTitle()
+ {
+ Ogre::StringVector filelist;
+
+ Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups ();
+ for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ Ogre::StringVectorPtr resourcesInThisGroup = mResourceMgr.findResourceNames(*it,
+ "Music/"+mCurrentPlaylist+"/*");
+ filelist.insert(filelist.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end());
+ }
+
+ if(!filelist.size())
+ return;
+
+ int i = rand()%filelist.size();
+ streamMusicFull(filelist[i]);
+ }
+
+ bool SoundManager::isMusicPlaying()
+ {
+ return mMusic && mMusic->isPlaying();
+ }
+
+ void SoundManager::playPlaylist(const std::string &playlist)
+ {
+ mCurrentPlaylist = playlist;
+ startRandomTitle();
+ }
+
+ void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename)
+ {
+ if(!mOutput->isInitialized())
+ return;
+ try
+ {
+ // The range values are not tested
+ 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]);
+
+ MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f,
+ 20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0);
+ mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound"));
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Sound Error: "<<e.what()<< std::endl;
+ }
+ }
+
+ void SoundManager::say(const std::string& filename)
+ {
+ if(!mOutput->isInitialized())
+ return;
+ try
+ {
+ float basevol = volumeFromType(Play_TypeVoice);
+ std::string filePath = "Sound/"+filename;
+
+ MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice, 0);
+ mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound"));
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Sound Error: "<<e.what()<< std::endl;
+ }
+ }
+
+ bool SoundManager::sayDone(const MWWorld::Ptr &ptr) const
+ {
+ return !isPlaying(ptr, "_say_sound");
+ }
+
+ void SoundManager::stopSay(const MWWorld::Ptr &ptr)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == ptr && snditer->second.second == "_say_sound")
+ {
+ snditer->first->stop();
+ mActiveSounds.erase(snditer++);
+ }
+ else
+ ++snditer;
+ }
+ }
+
+
+ MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type)
+ {
+ MWBase::SoundPtr track;
+ if(!mOutput->isInitialized())
+ return track;
+ try
+ {
+ track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type);
+ }
+ catch(std::exception &e)
+ {
+ std::cout <<"Sound Error: "<<e.what()<< std::endl;
+ }
+ return track;
+ }
+
+
+ MWBase::SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, PlayType type, PlayMode mode, float offset)
+ {
+ MWBase::SoundPtr sound;
+ if(!mOutput->isInitialized())
+ return sound;
+ try
+ {
+ float basevol = volumeFromType(type);
+ float min, max;
+ std::string file = lookup(soundId, volume, min, max);
+
+ sound = mOutput->playSound(file, volume, basevol, pitch, mode|type, offset);
+ mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId);
+ }
+ catch(std::exception &e)
+ {
+ //std::cout <<"Sound Error: "<<e.what()<< std::endl;
+ }
+ return sound;
+ }
+
+ MWBase::SoundPtr SoundManager::playSound3D(const MWWorld::Ptr &ptr, const std::string& soundId,
+ float volume, float pitch, PlayType type, PlayMode mode, float offset)
+ {
+ MWBase::SoundPtr sound;
+ if(!mOutput->isInitialized())
+ return sound;
+ try
+ {
+ // Look up the sound in the ESM data
+ float basevol = volumeFromType(type);
+ 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]);
+
+ sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset);
+ if((mode&Play_NoTrack))
+ mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId);
+ else
+ mActiveSounds[sound] = std::make_pair(ptr, soundId);
+ }
+ catch(std::exception &e)
+ {
+ //std::cout <<"Sound Error: "<<e.what()<< std::endl;
+ }
+ return sound;
+ }
+
+ void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == ptr && snditer->second.second == soundId)
+ {
+ snditer->first->stop();
+ mActiveSounds.erase(snditer++);
+ }
+ else
+ ++snditer;
+ }
+ }
+
+ void SoundManager::stopSound3D(const MWWorld::Ptr &ptr)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == ptr)
+ {
+ snditer->first->stop();
+ mActiveSounds.erase(snditer++);
+ }
+ else
+ ++snditer;
+ }
+ }
+
+ void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first != MWWorld::Ptr() &&
+ snditer->second.first.getCell() == cell)
+ {
+ snditer->first->stop();
+ mActiveSounds.erase(snditer++);
+ }
+ else
+ ++snditer;
+ }
+ }
+
+ void SoundManager::stopSound(const std::string& soundId)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == MWWorld::Ptr() &&
+ snditer->second.second == soundId)
+ {
+ snditer->first->stop();
+ mActiveSounds.erase(snditer++);
+ }
+ else
+ ++snditer;
+ }
+ }
+
+ void SoundManager::fadeOutSound3D(const MWWorld::Ptr &ptr,
+ const std::string& soundId, float duration)
+ {
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(snditer->second.first == ptr && snditer->second.second == soundId)
+ {
+ snditer->first->setFadeout(duration);
+ }
+ snditer++;
+ }
+ }
+
+ bool SoundManager::getSoundPlaying(const MWWorld::Ptr &ptr, const std::string& soundId) const
+ {
+ return isPlaying(ptr, soundId);
+ }
+
+
+ void SoundManager::pauseSounds(int types)
+ {
+ if(mOutput->isInitialized())
+ {
+ types &= Play_TypeMask;
+ mOutput->pauseSounds(types);
+ mPausedSoundTypes |= types;
+ }
+ }
+
+ void SoundManager::resumeSounds(int types)
+ {
+ if(mOutput->isInitialized())
+ {
+ types &= types&Play_TypeMask&mPausedSoundTypes;
+ mOutput->resumeSounds(types);
+ mPausedSoundTypes &= ~types;
+ }
+ }
+
+
+ void SoundManager::updateRegionSound(float duration)
+ {
+ static float sTimeToNextEnvSound = 0.0f;
+ static int total = 0;
+ static std::string regionName = "";
+ static float sTimePassed = 0.0;
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWWorld::Ptr player = world->getPlayer().getPlayer();
+ const ESM::Cell *cell = player.getCell()->mCell;
+
+ sTimePassed += duration;
+ if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound)
+ return;
+
+ float a = std::rand() / (double)RAND_MAX;
+ // NOTE: We should use the "Minimum Time Between Environmental Sounds" and
+ // "Maximum Time Between Environmental Sounds" fallback settings here.
+ sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a);
+ sTimePassed = 0;
+
+ if(regionName != cell->mRegion)
+ {
+ regionName = cell->mRegion;
+ total = 0;
+ }
+
+ const ESM::Region *regn = world->getStore().get<ESM::Region>().search(regionName);
+ if(regn == NULL)
+ return;
+
+ std::vector<ESM::Region::SoundRef>::const_iterator soundIter;
+ if(total == 0)
+ {
+ soundIter = regn->mSoundList.begin();
+ while(soundIter != regn->mSoundList.end())
+ {
+ total += (int)soundIter->mChance;
+ ++soundIter;
+ }
+ if(total == 0)
+ return;
+ }
+
+ int r = (int)(rand()/((double)RAND_MAX+1) * total);
+ int pos = 0;
+
+ soundIter = regn->mSoundList.begin();
+ while(soundIter != regn->mSoundList.end())
+ {
+ if(r - pos < soundIter->mChance)
+ {
+ playSound(soundIter->mSound.toString(), 1.0f, 1.0f);
+ break;
+ }
+ pos += soundIter->mChance;
+
+ ++soundIter;
+ }
+ }
+
+ void SoundManager::updateSounds(float duration)
+ {
+ static float timePassed = 0.0;
+
+ timePassed += duration;
+ if(timePassed < (1.0f/30.0f))
+ return;
+ duration = timePassed;
+ timePassed = 0.0f;
+
+ // Make sure music is still playing
+ if(!isMusicPlaying())
+ startRandomTitle();
+
+ MWWorld::Ptr player =
+ MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const ESM::Cell *cell = player.getCell()->mCell;
+
+ Environment env = Env_Normal;
+ if((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater)
+ {
+ env = Env_Underwater;
+ //play underwater sound
+ if(!(mUnderwaterSound && mUnderwaterSound->isPlaying()))
+ mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv);
+ }
+ else if(mUnderwaterSound)
+ {
+ mUnderwaterSound->stop();
+ mUnderwaterSound.reset();
+ }
+
+ mOutput->updateListener(
+ mListenerPos,
+ mListenerDir,
+ mListenerUp,
+ env
+ );
+
+ // Check if any sounds are finished playing, and trash them
+ // Lower volume on fading out sounds
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ if(!snditer->first->isPlaying())
+ mActiveSounds.erase(snditer++);
+ else
+ {
+ const MWWorld::Ptr &ptr = snditer->second.first;
+ if(!ptr.isEmpty())
+ {
+ const ESM::Position &pos = ptr.getRefData().getPosition();
+ const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]);
+ snditer->first->setPosition(objpos);
+ }
+ //update fade out
+ if(snditer->first->mFadeOutTime>0)
+ {
+ float soundDuration=duration;
+ if(soundDuration>snditer->first->mFadeOutTime)
+ soundDuration=snditer->first->mFadeOutTime;
+ snditer->first->setVolume(snditer->first->mVolume
+ - soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume);
+ snditer->first->mFadeOutTime -= soundDuration;
+ }
+ snditer->first->update();
+ ++snditer;
+ }
+ }
+ }
+
+ void SoundManager::update(float duration)
+ {
+ if(!mOutput->isInitialized())
+ return;
+ updateSounds(duration);
+ updateRegionSound(duration);
+ }
+
+
+ void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings)
+ {
+ mMasterVolume = Settings::Manager::getFloat("master volume", "Sound");
+ mMusicVolume = Settings::Manager::getFloat("music volume", "Sound");
+ mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound");
+ mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound");
+ mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound");
+
+ SoundMap::iterator snditer = mActiveSounds.begin();
+ while(snditer != mActiveSounds.end())
+ {
+ snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType());
+ snditer->first->update();
+ ++snditer;
+ }
+ if(mMusic)
+ {
+ mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType());
+ mMusic->update();
+ }
+ }
+
+ void SoundManager::setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up)
+ {
+ mListenerPos = pos;
+ mListenerDir = dir;
+ mListenerUp = up;
+ }
+
+ // Default readAll implementation, for decoders that can't do anything
+ // better
+ void Sound_Decoder::readAll(std::vector<char> &output)
+ {
+ size_t total = output.size();
+ size_t got;
+
+ output.resize(total+32768);
+ while((got=read(&output[total], output.size()-total)) > 0)
+ {
+ total += got;
+ output.resize(total*2);
+ }
+ output.resize(total);
+ }
+
+
+ const char *getSampleTypeName(SampleType type)
+ {
+ switch(type)
+ {
+ case SampleType_UInt8: return "U8";
+ case SampleType_Int16: return "S16";
+ case SampleType_Float32: return "Float32";
+ }
+ return "(unknown sample type)";
+ }
+
+ const char *getChannelConfigName(ChannelConfig config)
+ {
+ switch(config)
+ {
+ case ChannelConfig_Mono: return "Mono";
+ case ChannelConfig_Stereo: return "Stereo";
+ case ChannelConfig_Quad: return "Quad";
+ case ChannelConfig_5point1: return "5.1 Surround";
+ case ChannelConfig_7point1: return "7.1 Surround";
+ }
+ return "(unknown channel config)";
+ }
+
+ size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type)
+ {
+ switch(config)
+ {
+ case ChannelConfig_Mono: frames *= 1; break;
+ case ChannelConfig_Stereo: frames *= 2; break;
+ case ChannelConfig_Quad: frames *= 4; break;
+ case ChannelConfig_5point1: frames *= 6; break;
+ case ChannelConfig_7point1: frames *= 8; break;
+ }
+ switch(type)
+ {
+ case SampleType_UInt8: frames *= 1; break;
+ case SampleType_Int16: frames *= 2; break;
+ case SampleType_Float32: frames *= 4; break;
+ }
+ return frames;
+ }
+
+ size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type)
+ {
+ return bytes / framesToBytes(1, config, type);
+ }
+}
diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp
new file mode 100644
index 0000000000..f62e62d503
--- /dev/null
+++ b/apps/openmw/mwsound/soundmanagerimp.hpp
@@ -0,0 +1,154 @@
+#ifndef GAME_SOUND_SOUNDMANAGER_H
+#define GAME_SOUND_SOUNDMANAGER_H
+
+#include <string>
+#include <utility>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+#include <OgreVector3.h>
+#include <OgreResourceGroupManager.h>
+
+#include <components/settings/settings.hpp>
+
+#include "../mwbase/soundmanager.hpp"
+
+namespace MWSound
+{
+ class Sound_Output;
+ struct Sound_Decoder;
+ class Sound;
+
+ enum Environment {
+ Env_Normal,
+ Env_Underwater
+ };
+
+ class SoundManager : public MWBase::SoundManager
+ {
+ Ogre::ResourceGroupManager& mResourceMgr;
+
+ std::auto_ptr<Sound_Output> mOutput;
+
+ float mMasterVolume;
+ float mSFXVolume;
+ float mMusicVolume;
+ float mVoiceVolume;
+ float mFootstepsVolume;
+
+ boost::shared_ptr<Sound> mMusic;
+ std::string mCurrentPlaylist;
+
+ typedef std::pair<MWWorld::Ptr,std::string> PtrIDPair;
+ typedef std::map<MWBase::SoundPtr,PtrIDPair> SoundMap;
+ SoundMap mActiveSounds;
+
+ MWBase::SoundPtr mUnderwaterSound;
+
+ Ogre::Vector3 mListenerPos;
+ Ogre::Vector3 mListenerDir;
+ Ogre::Vector3 mListenerUp;
+
+ int mPausedSoundTypes;
+
+ std::string lookup(const std::string &soundId,
+ float &volume, float &min, float &max);
+ void streamMusicFull(const std::string& filename);
+ bool isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const;
+ void updateSounds(float duration);
+ void updateRegionSound(float duration);
+
+ float volumeFromType(PlayType type) const;
+
+ SoundManager(const SoundManager &rhs);
+ SoundManager& operator=(const SoundManager &rhs);
+
+ protected:
+ DecoderPtr getDecoder();
+ friend class OpenAL_Output;
+
+ public:
+ SoundManager(bool useSound);
+ virtual ~SoundManager();
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& settings);
+
+ virtual void stopMusic();
+ ///< Stops music if it's playing
+
+ virtual void streamMusic(const std::string& filename);
+ ///< Play a soundifle
+ /// \param filename name of a sound file in "Music/" in the data directory.
+
+ virtual void startRandomTitle();
+ ///< Starts a random track from the current playlist
+
+ virtual bool isMusicPlaying();
+ ///< Returns true if music is playing
+
+ virtual void playPlaylist(const std::string &playlist);
+ ///< Start playing music from the selected folder
+ /// \param name of the folder that contains the playlist
+
+ virtual void say(const MWWorld::Ptr &reference, const std::string& filename);
+ ///< Make an actor say some text.
+ /// \param filename name of a sound file in "Sound/" in the data directory.
+
+ virtual void say(const std::string& filename);
+ ///< Say some text, without an actor ref
+ /// \param filename name of a sound file in "Sound/" in the data directory.
+
+ virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const;
+ ///< Is actor not speaking?
+
+ virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr());
+ ///< Stop an actor speaking
+
+ virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type);
+ ///< Play a 2D audio track, using a custom decoder
+
+ virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0);
+ ///< Play a sound, independently of 3D-position
+ ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end.
+
+ virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
+ float volume, float pitch, PlayType type=Play_TypeSfx,
+ PlayMode mode=Play_Normal, float offset=0);
+ ///< Play a sound from an object
+ ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end.
+
+ virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId);
+ ///< Stop the given object from playing the given sound,
+
+ virtual void stopSound3D(const MWWorld::Ptr &reference);
+ ///< Stop the given object from playing all sounds.
+
+ virtual void stopSound(const MWWorld::CellStore *cell);
+ ///< Stop all sounds for the given cell.
+
+ virtual void stopSound(const std::string& soundId);
+ ///< Stop a non-3d looping sound
+
+ virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration);
+ ///< Fade out given sound (that is already playing) of given object
+ ///< @param reference Reference to object, whose sound is faded out
+ ///< @param soundId ID of the sound to fade out.
+ ///< @param duration Time until volume reaches 0.
+
+ virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const;
+ ///< Is the given sound currently playing on the given object?
+
+ virtual void pauseSounds(int types=Play_TypeMask);
+ ///< Pauses all currently playing sounds, including music.
+
+ virtual void resumeSounds(int types=Play_TypeMask);
+ ///< Resumes all previously paused sounds.
+
+ virtual void update(float duration);
+
+ virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp
new file mode 100644
index 0000000000..0fe061e5ce
--- /dev/null
+++ b/apps/openmw/mwworld/action.cpp
@@ -0,0 +1,48 @@
+
+#include "action.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwbase/soundmanager.hpp"
+
+const MWWorld::Ptr& MWWorld::Action::getTarget() const
+{
+ return mTarget;
+}
+
+MWWorld::Action::Action (bool keepSound, const Ptr& target) : mKeepSound (keepSound), mTarget (target), mSoundOffset(0)
+{}
+
+MWWorld::Action::~Action() {}
+
+void MWWorld::Action::execute (const Ptr& actor)
+{
+ if (!mSoundId.empty())
+ {
+ if (mKeepSound && actor.getRefData().getHandle()=="player")
+ MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0,
+ MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal,mSoundOffset);
+ else
+ {
+ bool local = mTarget.isEmpty() || !mTarget.isInCell(); // no usable target
+
+ MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget,
+ mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx,
+ mKeepSound ? MWBase::SoundManager::Play_NoTrack : MWBase::SoundManager::Play_Normal,
+ mSoundOffset);
+ }
+ }
+
+ executeImp (actor);
+}
+
+void MWWorld::Action::setSound (const std::string& id)
+{
+ mSoundId = id;
+}
+
+void MWWorld::Action::setSoundOffset(float offset)
+{
+ mSoundOffset=offset;
+}
diff --git a/apps/openmw/mwworld/action.hpp b/apps/openmw/mwworld/action.hpp
new file mode 100644
index 0000000000..3e0e8ad1bd
--- /dev/null
+++ b/apps/openmw/mwworld/action.hpp
@@ -0,0 +1,42 @@
+#ifndef GAME_MWWORLD_ACTION_H
+#define GAME_MWWORLD_ACTION_H
+
+#include <string>
+
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ /// \brief Abstract base for actions
+ class Action
+ {
+ std::string mSoundId;
+ bool mKeepSound;
+ float mSoundOffset;
+ Ptr mTarget;
+
+ // not implemented
+ Action (const Action& action);
+ Action& operator= (const Action& action);
+
+ virtual void executeImp (const Ptr& actor) = 0;
+
+ protected:
+
+ const Ptr& getTarget() const;
+
+ public:
+
+ Action (bool keepSound = false, const Ptr& target = Ptr());
+ ///< \param keepSound Keep playing the sound even if the object the sound is played on is removed.
+
+ virtual ~Action();
+
+ void execute (const Ptr& actor);
+
+ void setSound (const std::string& id);
+ void setSoundOffset(float offset);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp
new file mode 100644
index 0000000000..bba75bc499
--- /dev/null
+++ b/apps/openmw/mwworld/actionalchemy.cpp
@@ -0,0 +1,12 @@
+#include "actionalchemy.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWWorld
+{
+ void ActionAlchemy::executeImp (const Ptr& actor)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy);
+ }
+}
diff --git a/apps/openmw/mwworld/actionalchemy.hpp b/apps/openmw/mwworld/actionalchemy.hpp
new file mode 100644
index 0000000000..e6d1a7976c
--- /dev/null
+++ b/apps/openmw/mwworld/actionalchemy.hpp
@@ -0,0 +1,14 @@
+#ifndef GAME_MWWORLD_ACTIONALCHEMY_H
+#define GAME_MWWORLD_ACTIONALCHEMY_H
+
+#include "action.hpp"
+
+namespace MWWorld
+{
+ class ActionAlchemy : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp
new file mode 100644
index 0000000000..f78b8f7988
--- /dev/null
+++ b/apps/openmw/mwworld/actionapply.cpp
@@ -0,0 +1,28 @@
+
+#include "actionapply.hpp"
+
+#include "class.hpp"
+
+namespace MWWorld
+{
+ ActionApply::ActionApply (const Ptr& target, const std::string& id)
+ : Action (false, target), mId (id)
+ {}
+
+ void ActionApply::executeImp (const Ptr& actor)
+ {
+ MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor);
+ }
+
+
+ ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& target, const std::string& id,
+ int skillIndex, int usageType)
+ : Action (false, target), mId (id), mSkillIndex (skillIndex), mUsageType (usageType)
+ {}
+
+ void ActionApplyWithSkill::executeImp (const Ptr& actor)
+ {
+ if (MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor) && mUsageType!=-1)
+ MWWorld::Class::get (getTarget()).skillUsageSucceeded (actor, mSkillIndex, mUsageType);
+ }
+}
diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp
new file mode 100644
index 0000000000..3353ae0eed
--- /dev/null
+++ b/apps/openmw/mwworld/actionapply.hpp
@@ -0,0 +1,38 @@
+
+#ifndef GAME_MWWORLD_ACTIONAPPLY_H
+#define GAME_MWWORLD_ACTIONAPPLY_H
+
+#include <string>
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionApply : public Action
+ {
+ std::string mId;
+
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionApply (const Ptr& target, const std::string& id);
+ };
+
+ class ActionApplyWithSkill : public Action
+ {
+ std::string mId;
+ int mSkillIndex;
+ int mUsageType;
+
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionApplyWithSkill (const Ptr& target, const std::string& id,
+ int skillIndex, int usageType);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actiondoor.cpp b/apps/openmw/mwworld/actiondoor.cpp
new file mode 100644
index 0000000000..6e3441e220
--- /dev/null
+++ b/apps/openmw/mwworld/actiondoor.cpp
@@ -0,0 +1,16 @@
+#include "actiondoor.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWWorld
+{
+ ActionDoor::ActionDoor (const MWWorld::Ptr& object) : Action (false, object)
+ {
+ }
+
+ void ActionDoor::executeImp (const MWWorld::Ptr& actor)
+ {
+ MWBase::Environment::get().getWorld()->activateDoor(getTarget());
+ }
+}
diff --git a/apps/openmw/mwworld/actiondoor.hpp b/apps/openmw/mwworld/actiondoor.hpp
new file mode 100644
index 0000000000..2dc5ad8c13
--- /dev/null
+++ b/apps/openmw/mwworld/actiondoor.hpp
@@ -0,0 +1,18 @@
+#ifndef GAME_MWWORLD_ACTIONDOOR_H
+#define GAME_MWWORLD_ACTIONDOOR_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionDoor : public Action
+ {
+ virtual void executeImp (const MWWorld::Ptr& actor);
+
+ public:
+ ActionDoor (const Ptr& object);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp
new file mode 100644
index 0000000000..63efff738e
--- /dev/null
+++ b/apps/openmw/mwworld/actioneat.cpp
@@ -0,0 +1,48 @@
+
+#include "actioneat.hpp"
+
+#include <cstdlib>
+
+#include <components/esm/loadskil.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.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());
+
+ 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
+ Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1);
+ }
+ }
+
+ ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {}
+}
diff --git a/apps/openmw/mwworld/actioneat.hpp b/apps/openmw/mwworld/actioneat.hpp
new file mode 100644
index 0000000000..ce5330db77
--- /dev/null
+++ b/apps/openmw/mwworld/actioneat.hpp
@@ -0,0 +1,19 @@
+#ifndef GAME_MWWORLD_ACTIONEAT_H
+#define GAME_MWWORLD_ACTIONEAT_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionEat : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionEat (const MWWorld::Ptr& object);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp
new file mode 100644
index 0000000000..0f1a85ddaa
--- /dev/null
+++ b/apps/openmw/mwworld/actionequip.cpp
@@ -0,0 +1,87 @@
+#include "actionequip.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include <components/compiler/locals.hpp>
+
+#include "inventorystore.hpp"
+#include "player.hpp"
+#include "class.hpp"
+
+namespace MWWorld
+{
+ ActionEquip::ActionEquip (const MWWorld::Ptr& object) : Action (false, object)
+ {
+ }
+
+ void ActionEquip::executeImp (const Ptr& actor)
+ {
+ MWWorld::Ptr object = getTarget();
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
+
+ std::pair <int, std::string> result = MWWorld::Class::get (object).canBeEquipped (object, actor);
+
+ // display error message if the player tried to equip something
+ if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
+ MWBase::Environment::get().getWindowManager()->messageBox(result.second);
+
+ switch(result.first)
+ {
+ case 0:
+ return;
+ case 2:
+ invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end());
+ break;
+ case 3:
+ invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end());
+ break;
+ }
+
+ // slots that this item can be equipped in
+ std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget());
+
+ // retrieve ContainerStoreIterator to the item
+ MWWorld::ContainerStoreIterator it = invStore.begin();
+ for (; it != invStore.end(); ++it)
+ {
+ if (*it == object)
+ {
+ break;
+ }
+ }
+
+ assert(it != invStore.end());
+
+ 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)
+ {
+
+ // if all slots are occupied, replace the last slot
+ if (slot == --slots.first.end())
+ {
+ invStore.equip(*slot, it);
+ equipped = true;
+ break;
+ }
+
+ if (invStore.getSlot(*slot) == invStore.end())
+ {
+ // slot is not occupied
+ invStore.equip(*slot, it);
+ equipped = true;
+ break;
+ }
+ }
+
+ std::string script = MWWorld::Class::get(object).getScript(object);
+
+ /* Set OnPCEquip Variable on item's script, if the player is equipping it, and it has a script with that variable declared */
+ if(equipped && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && script != "")
+ object.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
+ }
+}
diff --git a/apps/openmw/mwworld/actionequip.hpp b/apps/openmw/mwworld/actionequip.hpp
new file mode 100644
index 0000000000..3b56c74027
--- /dev/null
+++ b/apps/openmw/mwworld/actionequip.hpp
@@ -0,0 +1,19 @@
+#ifndef GAME_MWWORLD_ACTIONEQUIP_H
+#define GAME_MWWORLD_ACTIONEQUIP_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionEquip : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+ /// @param item to equip
+ ActionEquip (const Ptr& object);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp
new file mode 100644
index 0000000000..728b6e32bf
--- /dev/null
+++ b/apps/openmw/mwworld/actionopen.cpp
@@ -0,0 +1,27 @@
+#include "actionopen.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwgui/container.hpp"
+
+#include "class.hpp"
+#include "containerstore.hpp"
+
+namespace MWWorld
+{
+ ActionOpen::ActionOpen (const MWWorld::Ptr& container, bool loot)
+ : Action (false, container)
+ , mLoot(loot)
+ {
+ }
+
+ void ActionOpen::executeImp (const MWWorld::Ptr& actor)
+ {
+ if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ return;
+
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container);
+ MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget(), mLoot);
+ }
+}
diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp
new file mode 100644
index 0000000000..8578995aee
--- /dev/null
+++ b/apps/openmw/mwworld/actionopen.hpp
@@ -0,0 +1,25 @@
+
+#ifndef GAME_MWWORLD_ACTIONOPEN_H
+#define GAME_MWWORLD_ACTIONOPEN_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+
+namespace MWWorld
+{
+ class ActionOpen : public Action
+ {
+ virtual void executeImp (const MWWorld::Ptr& actor);
+
+ public:
+ ActionOpen (const Ptr& container, bool loot=false);
+ ///< \param container The Container the Player has activated.
+ /// \param loot If true, display the "dispose of corpse" button
+
+ private:
+ bool mLoot;
+ };
+}
+
+#endif // ACTIONOPEN_H
diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp
new file mode 100644
index 0000000000..6d5d9d8fde
--- /dev/null
+++ b/apps/openmw/mwworld/actionread.cpp
@@ -0,0 +1,57 @@
+#include "actionread.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwgui/bookwindow.hpp"
+#include "../mwgui/scrollwindow.hpp"
+
+#include "player.hpp"
+#include "class.hpp"
+#include "esmstore.hpp"
+
+namespace MWWorld
+{
+ ActionRead::ActionRead (const MWWorld::Ptr& object) : Action (false, object)
+ {
+ }
+
+ void ActionRead::executeImp (const MWWorld::Ptr& actor)
+ {
+ LiveCellRef<ESM::Book> *ref = getTarget().get<ESM::Book>();
+
+ if (ref->mBase->mData.mIsScroll)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Scroll);
+ MWBase::Environment::get().getWindowManager()->getScrollWindow()->open(getTarget());
+ }
+ else
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Book);
+ MWBase::Environment::get().getWindowManager()->getBookWindow()->open(getTarget());
+ }
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
+
+ // Skill gain from books
+ if (ref->mBase->mData.mSkillID >= 0 && ref->mBase->mData.mSkillID < ESM::Skill::Length
+ && !npcStats.hasBeenUsed (ref->mBase->mId))
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *playerRef = player.get<ESM::NPC>();
+
+ const ESM::Class *class_ =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (
+ playerRef->mBase->mClass
+ );
+
+ npcStats.increaseSkill (ref->mBase->mData.mSkillID, *class_, true);
+
+ npcStats.flagAsUsed (ref->mBase->mId);
+ }
+
+ }
+}
diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp
new file mode 100644
index 0000000000..00a4756dda
--- /dev/null
+++ b/apps/openmw/mwworld/actionread.hpp
@@ -0,0 +1,19 @@
+#ifndef GAME_MWWORLD_ACTIONREAD_H
+#define GAME_MWWORLD_ACTIONREAD_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionRead : public Action
+ {
+ virtual void executeImp (const MWWorld::Ptr& actor);
+
+ public:
+ /// @param book or scroll to read
+ ActionRead (const Ptr& object);
+ };
+}
+
+#endif // ACTIONOPEN_H
diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp
new file mode 100644
index 0000000000..bd56421165
--- /dev/null
+++ b/apps/openmw/mwworld/actionrepair.cpp
@@ -0,0 +1,18 @@
+#include "actionrepair.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWWorld
+{
+ ActionRepair::ActionRepair(const Ptr &item)
+ : Action(false, item)
+ {
+ }
+
+ void ActionRepair::executeImp (const Ptr& actor)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Repair);
+ MWBase::Environment::get().getWindowManager()->startRepairItem(getTarget());
+ }
+}
diff --git a/apps/openmw/mwworld/actionrepair.hpp b/apps/openmw/mwworld/actionrepair.hpp
new file mode 100644
index 0000000000..1d3ef2bc11
--- /dev/null
+++ b/apps/openmw/mwworld/actionrepair.hpp
@@ -0,0 +1,17 @@
+#ifndef GAME_MWWORLD_ACTIONREPAIR_H
+#define GAME_MWWORLD_ACTIONREPAIR_H
+
+#include "action.hpp"
+
+namespace MWWorld
+{
+ class ActionRepair : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+ ActionRepair(const MWWorld::Ptr& item);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp
new file mode 100644
index 0000000000..6746f692f1
--- /dev/null
+++ b/apps/openmw/mwworld/actionsoulgem.cpp
@@ -0,0 +1,21 @@
+#include "actionsoulgem.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+
+namespace MWWorld
+{
+
+ActionSoulgem::ActionSoulgem(const Ptr &object)
+ : Action(false, object)
+{
+
+}
+
+void ActionSoulgem::executeImp(const Ptr &actor)
+{
+ MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget());
+}
+
+
+}
diff --git a/apps/openmw/mwworld/actionsoulgem.hpp b/apps/openmw/mwworld/actionsoulgem.hpp
new file mode 100644
index 0000000000..0dd5266570
--- /dev/null
+++ b/apps/openmw/mwworld/actionsoulgem.hpp
@@ -0,0 +1,19 @@
+#ifndef GAME_MWWORLD_ACTIONSOULGEM_H
+#define GAME_MWWORLD_ACTIONSOULGEM_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionSoulgem : public Action
+ {
+ virtual void executeImp (const MWWorld::Ptr& actor);
+
+ public:
+ /// @param soulgem to use
+ ActionSoulgem (const Ptr& object);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp
new file mode 100644
index 0000000000..d3c4aa2f63
--- /dev/null
+++ b/apps/openmw/mwworld/actiontake.cpp
@@ -0,0 +1,24 @@
+
+#include "actiontake.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "class.hpp"
+#include "containerstore.hpp"
+
+namespace MWWorld
+{
+ ActionTake::ActionTake (const MWWorld::Ptr& object) : Action (true, object) {}
+
+ void ActionTake::executeImp (const Ptr& actor)
+ {
+ // insert into player's inventory
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPtr ("player", true);
+
+ MWWorld::Class::get (player).getContainerStore (player).add (getTarget(), player);
+
+ MWBase::Environment::get().getWorld()->deleteObject (getTarget());
+ }
+}
diff --git a/apps/openmw/mwworld/actiontake.hpp b/apps/openmw/mwworld/actiontake.hpp
new file mode 100644
index 0000000000..b0a9b82478
--- /dev/null
+++ b/apps/openmw/mwworld/actiontake.hpp
@@ -0,0 +1,19 @@
+#ifndef GAME_MWWORLD_ACTIONTAKE_H
+#define GAME_MWWORLD_ACTIONTAKE_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class ActionTake : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionTake (const MWWorld::Ptr& object);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp
new file mode 100644
index 0000000000..905497f85b
--- /dev/null
+++ b/apps/openmw/mwworld/actiontalk.cpp
@@ -0,0 +1,15 @@
+
+#include "actiontalk.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+
+namespace MWWorld
+{
+ ActionTalk::ActionTalk (const Ptr& actor) : Action (false, actor) {}
+
+ void ActionTalk::executeImp (const Ptr& actor)
+ {
+ MWBase::Environment::get().getDialogueManager()->startDialogue (getTarget());
+ }
+}
diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp
new file mode 100644
index 0000000000..b88b168d8c
--- /dev/null
+++ b/apps/openmw/mwworld/actiontalk.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_MWWORLD_ACTIONTALK_H
+#define GAME_MWWORLD_ACTIONTALK_H
+
+#include "ptr.hpp"
+#include "action.hpp"
+
+namespace MWWorld
+{
+ class ActionTalk : public Action
+ {
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionTalk (const Ptr& actor);
+ ///< \param actor The actor the player is talking to
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp
new file mode 100644
index 0000000000..ae5ffc3b90
--- /dev/null
+++ b/apps/openmw/mwworld/actionteleport.cpp
@@ -0,0 +1,22 @@
+
+#include "actionteleport.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWWorld
+{
+ ActionTeleport::ActionTeleport (const std::string& cellName,
+ const ESM::Position& position)
+ : Action (true), mCellName (cellName), mPosition (position)
+ {
+ }
+
+ void ActionTeleport::executeImp (const Ptr& actor)
+ {
+ if (mCellName.empty())
+ MWBase::Environment::get().getWorld()->changeToExteriorCell (mPosition);
+ else
+ MWBase::Environment::get().getWorld()->changeToInteriorCell (mCellName, mPosition);
+ }
+}
diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp
new file mode 100644
index 0000000000..a13cb61b21
--- /dev/null
+++ b/apps/openmw/mwworld/actionteleport.hpp
@@ -0,0 +1,26 @@
+#ifndef GAME_MWWORLD_ACTIONTELEPORT_H
+#define GAME_MWWORLD_ACTIONTELEPORT_H
+
+#include <string>
+
+#include <components/esm/defs.hpp>
+
+#include "action.hpp"
+
+namespace MWWorld
+{
+ class ActionTeleport : public Action
+ {
+ std::string mCellName;
+ ESM::Position mPosition;
+
+ virtual void executeImp (const Ptr& actor);
+
+ public:
+
+ ActionTeleport (const std::string& cellName, const ESM::Position& position);
+ ///< If cellName is empty, an exterior cell is asumed.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellfunctors.hpp
new file mode 100644
index 0000000000..4b1f70096a
--- /dev/null
+++ b/apps/openmw/mwworld/cellfunctors.hpp
@@ -0,0 +1,33 @@
+#ifndef GAME_MWWORLD_CELLFUNCTORS_H
+#define GAME_MWWORLD_CELLFUNCTORS_H
+
+#include <vector>
+#include <string>
+
+#include "refdata.hpp"
+
+namespace ESM
+{
+ class CellRef;
+}
+
+namespace MWWorld
+{
+ /// List all (Ogre-)handles, then reset RefData::mBaseNode to 0.
+ struct ListAndResetHandles
+ {
+ std::vector<Ogre::SceneNode*> mHandles;
+
+ bool operator() (ESM::CellRef& ref, RefData& data)
+ {
+ Ogre::SceneNode* handle = data.getBaseNode();
+ if (handle)
+ mHandles.push_back (handle);
+
+ data.setBaseNode(0);
+ return true;
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp
new file mode 100644
index 0000000000..37c4b6a3f4
--- /dev/null
+++ b/apps/openmw/mwworld/cells.cpp
@@ -0,0 +1,263 @@
+#include "cells.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "class.hpp"
+#include "esmstore.hpp"
+#include "containerstore.hpp"
+
+MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell)
+{
+ if (cell->mData.mFlags & ESM::Cell::Interior)
+ {
+ std::map<std::string, Ptr::CellStore>::iterator result = mInteriors.find (Misc::StringUtils::lowerCase(cell->mName));
+
+ if (result==mInteriors.end())
+ {
+ result = mInteriors.insert (std::make_pair (Misc::StringUtils::lowerCase(cell->mName), Ptr::CellStore (cell))).first;
+ }
+
+ return &result->second;
+ }
+ else
+ {
+ std::map<std::pair<int, int>, Ptr::CellStore>::iterator result =
+ mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY()));
+
+ if (result==mExteriors.end())
+ {
+ result = mExteriors.insert (std::make_pair (
+ std::make_pair (cell->getGridX(), cell->getGridY()), Ptr::CellStore (cell))).first;
+
+ }
+
+ return &result->second;
+ }
+}
+
+void MWWorld::Cells::clear()
+{
+ mInteriors.clear();
+ mExteriors.clear();
+ std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::Ptr::CellStore*)0));
+ mIdCacheIndex = 0;
+}
+
+MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellStore& cellStore)
+{
+ Ptr ptr = getPtr (name, cellStore);
+
+ if (!ptr.isEmpty() && ptr.isInCell())
+ {
+ mIdCache[mIdCacheIndex].first = name;
+ mIdCache[mIdCacheIndex].second = &cellStore;
+ if (++mIdCacheIndex>=mIdCache.size())
+ mIdCacheIndex = 0;
+ }
+
+ return ptr;
+}
+
+MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
+: mStore (store), mReader (reader),
+ mIdCache (40, std::pair<std::string, Ptr::CellStore *> ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable
+ mIdCacheIndex (0)
+{}
+
+MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
+{
+ std::map<std::pair<int, int>, Ptr::CellStore>::iterator result =
+ mExteriors.find (std::make_pair (x, y));
+
+ if (result==mExteriors.end())
+ {
+ const ESM::Cell *cell = mStore.get<ESM::Cell>().search(x, y);
+
+ if (!cell)
+ {
+ // Cell isn't predefined. Make one on the fly.
+ ESM::Cell record;
+
+ record.mData.mFlags = 0;
+ record.mData.mX = x;
+ record.mData.mY = y;
+ record.mWater = 0;
+ record.mMapColor = 0;
+
+ cell = MWBase::Environment::get().getWorld()->createRecord (record);
+ }
+
+ result = mExteriors.insert (std::make_pair (
+ std::make_pair (x, y), CellStore (cell))).first;
+ }
+
+ if (result->second.mState!=Ptr::CellStore::State_Loaded)
+ {
+ // Multiple plugin support for landscape data is much easier than for references. The last plugin wins.
+ result->second.load (mStore, mReader);
+ }
+
+ return &result->second;
+}
+
+MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name)
+{
+ std::string lowerName = Misc::StringUtils::lowerCase(name);
+ std::map<std::string, Ptr::CellStore>::iterator result = mInteriors.find (lowerName);
+
+ if (result==mInteriors.end())
+ {
+ const ESM::Cell *cell = mStore.get<ESM::Cell>().find(lowerName);
+
+ result = mInteriors.insert (std::make_pair (lowerName, Ptr::CellStore (cell))).first;
+ }
+
+ if (result->second.mState!=Ptr::CellStore::State_Loaded)
+ {
+ result->second.load (mStore, mReader);
+ }
+
+ return &result->second;
+}
+
+MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& cell,
+ bool searchInContainers)
+{
+ if (cell.mState==Ptr::CellStore::State_Unloaded)
+ cell.preload (mStore, mReader);
+
+ 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))
+ {
+ cell.load (mStore, mReader);
+ }
+ else
+ return Ptr();
+ }
+
+ if (MWWorld::LiveCellRef<ESM::Activator> *ref = cell.mActivators.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Potion> *ref = cell.mPotions.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Apparatus> *ref = cell.mAppas.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Armor> *ref = cell.mArmors.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Book> *ref = cell.mBooks.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Clothing> *ref = cell.mClothes.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Container> *ref = cell.mContainers.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Creature> *ref = cell.mCreatures.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Door> *ref = cell.mDoors.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Ingredient> *ref = cell.mIngreds.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::CreatureLevList> *ref = cell.mCreatureLists.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::ItemLevList> *ref = cell.mItemLists.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Light> *ref = cell.mLights.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Lockpick> *ref = cell.mLockpicks.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Miscellaneous> *ref = cell.mMiscItems.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::NPC> *ref = cell.mNpcs.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Probe> *ref = cell.mProbes.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Repair> *ref = cell.mRepairs.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Static> *ref = cell.mStatics.find (name))
+ return Ptr (ref, &cell);
+
+ if (MWWorld::LiveCellRef<ESM::Weapon> *ref = cell.mWeapons.find (name))
+ return Ptr (ref, &cell);
+
+ if (searchInContainers)
+ return cell.searchInContainer (name);
+
+ return Ptr();
+}
+
+MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
+{
+ // First check the cache
+ for (std::vector<std::pair<std::string, Ptr::CellStore *> >::iterator iter (mIdCache.begin());
+ iter!=mIdCache.end(); ++iter)
+ if (iter->first==name && iter->second)
+ {
+ Ptr ptr = getPtr (name, *iter->second);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ // Then check cells that are already listed
+ 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())
+ return ptr;
+ }
+
+ for (std::map<std::string, Ptr::CellStore>::iterator iter = mInteriors.begin();
+ iter!=mInteriors.end(); ++iter)
+ {
+ Ptr ptr = getPtrAndCache (name, iter->second);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ // Now try the other cells
+ const MWWorld::Store<ESM::Cell> &cells = mStore.get<ESM::Cell>();
+ MWWorld::Store<ESM::Cell>::iterator iter;
+
+ for (iter = cells.extBegin(); iter != cells.extEnd(); ++iter)
+ {
+ Ptr::CellStore *cellStore = getCellStore (&(*iter));
+
+ Ptr ptr = getPtrAndCache (name, *cellStore);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ for (iter = cells.intBegin(); iter != cells.intEnd(); ++iter)
+ {
+ Ptr::CellStore *cellStore = getCellStore (&(*iter));
+
+ Ptr ptr = getPtrAndCache (name, *cellStore);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ // giving up
+ return Ptr();
+}
diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp
new file mode 100644
index 0000000000..0c51cf4520
--- /dev/null
+++ b/apps/openmw/mwworld/cells.hpp
@@ -0,0 +1,55 @@
+#ifndef GAME_MWWORLD_CELLS_H
+#define GAME_MWWORLD_CELLS_H
+
+#include <map>
+#include <list>
+#include <string>
+
+#include "ptr.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+}
+
+namespace MWWorld
+{
+ class ESMStore;
+
+ /// \brief Cell container
+ class Cells
+ {
+ const MWWorld::ESMStore& mStore;
+ std::vector<ESM::ESMReader>& mReader;
+ std::map<std::string, CellStore> mInteriors;
+ std::map<std::pair<int, int>, CellStore> mExteriors;
+ std::vector<std::pair<std::string, CellStore *> > mIdCache;
+ std::size_t mIdCacheIndex;
+
+ Cells (const Cells&);
+ Cells& operator= (const Cells&);
+
+ CellStore *getCellStore (const ESM::Cell *cell);
+
+ Ptr getPtrAndCache (const std::string& name, CellStore& cellStore);
+
+ public:
+
+ void clear();
+
+ Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader);
+ ///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole
+ /// world
+
+ CellStore *getExterior (int x, int y);
+
+ CellStore *getInterior (const std::string& name);
+
+ Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false);
+ ///< \param searchInContainers Only affect loaded cells.
+
+ Ptr getPtr (const std::string& name);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp
new file mode 100644
index 0000000000..0c145ab600
--- /dev/null
+++ b/apps/openmw/mwworld/cellstore.cpp
@@ -0,0 +1,277 @@
+#include "cellstore.hpp"
+
+#include <iostream>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "ptr.hpp"
+#include "esmstore.hpp"
+#include "class.hpp"
+#include "containerstore.hpp"
+
+namespace
+{
+ template<typename T>
+ MWWorld::Ptr searchInContainerList (MWWorld::CellRefList<T>& containerList, const std::string& id)
+ {
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (containerList.mList.begin());
+ iter!=containerList.mList.end(); ++iter)
+ {
+ MWWorld::Ptr container (&*iter, 0);
+
+ MWWorld::Ptr ptr =
+ MWWorld::Class::get (container).getContainerStore (container).search (id);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ return MWWorld::Ptr();
+ }
+}
+
+namespace MWWorld
+{
+
+ template <typename X>
+ void CellRefList<X>::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
+ {
+ // Get existing reference, in case we need to overwrite it.
+ typename std::list<LiveRef>::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefnum);
+
+ // Skip this when reference was deleted.
+ // TODO: Support respawning references, in this case, we need to track it somehow.
+ if (ref.mDeleted) {
+ if (iter != mList.end())
+ mList.erase(iter);
+ return;
+ }
+
+ // for throwing exception on unhandled record type
+ const MWWorld::Store<X> &store = esmStore.get<X>();
+ const X *ptr = store.search(ref.mRefID);
+
+ /// \note no longer redundant - changed to Store<X>::search(), don't throw
+ /// an exception on miss, try to continue (that's how MW does it, anyway)
+ if (ptr == NULL) {
+ std::cout << "Warning: could not resolve cell reference " << ref.mRefID << ", trying to continue anyway" << std::endl;
+ } else {
+ if (iter != mList.end())
+ *iter = LiveRef(ref, ptr);
+ else
+ mList.push_back(LiveRef(ref, ptr));
+ }
+ }
+
+ template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum)
+ {
+ return (ref.mRef.mRefnum == pRefnum);
+ }
+
+ CellStore::CellStore (const ESM::Cell *cell)
+ : mCell (cell), mState (State_Unloaded)
+ {
+ mWaterLevel = cell->mWater;
+ }
+
+ void CellStore::load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
+ {
+ if (mState!=State_Loaded)
+ {
+ if (mState==State_Preloaded)
+ mIds.clear();
+
+ std::cout << "loading cell " << mCell->getDescription() << std::endl;
+
+ loadRefs (store, esm);
+
+ mState = State_Loaded;
+ }
+ }
+
+ void CellStore::preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
+ {
+ if (mState==State_Unloaded)
+ {
+ listRefs (store, esm);
+
+ mState = State_Preloaded;
+ }
+ }
+
+ void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
+ {
+ assert (mCell);
+
+ if (mCell->mContextList.empty())
+ return; // this is a dynamically generated cell -> skipping.
+
+ // Load references from all plugins that do something with this cell.
+ for (size_t i = 0; i < mCell->mContextList.size(); i++)
+ {
+ // Reopen the ESM reader and seek to the right position.
+ int index = mCell->mContextList.at(i).index;
+ mCell->restore (esm[index], i);
+
+ ESM::CellRef ref;
+
+ // Get each reference in turn
+ while (mCell->getNextRef (esm[index], ref))
+ {
+ std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID);
+ if (ref.mDeleted) {
+ // Right now, don't do anything. Where is "listRefs" actually used, anyway?
+ // Skipping for now...
+ continue;
+ }
+
+ mIds.push_back (lowerCase);
+ }
+ }
+
+ std::sort (mIds.begin(), mIds.end());
+ }
+
+ void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
+ {
+ assert (mCell);
+
+ if (mCell->mContextList.empty())
+ return; // this is a dynamically generated cell -> skipping.
+
+ // Load references from all plugins that do something with this cell.
+ for (size_t i = 0; i < mCell->mContextList.size(); i++)
+ {
+ // Reopen the ESM reader and seek to the right position.
+ int index = mCell->mContextList.at(i).index;
+ mCell->restore (esm[index], i);
+
+ ESM::CellRef ref;
+
+ // Get each reference in turn
+ while(mCell->getNextRef(esm[index], ref))
+ {
+ // Don't load reference if it was moved to a different cell.
+ std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID);
+ ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefnum);
+ if (iter != mCell->mMovedRefs.end()) {
+ continue;
+ }
+ int rec = store.find(ref.mRefID);
+
+ ref.mRefID = lowerCase;
+
+ /* We can optimize this further by storing the pointer to the
+ record itself in store.all, so that we don't need to look it
+ up again here. However, never optimize. There are infinite
+ opportunities to do that later.
+ */
+ switch(rec)
+ {
+ case ESM::REC_ACTI: mActivators.load(ref, store); break;
+ case ESM::REC_ALCH: mPotions.load(ref, store); break;
+ case ESM::REC_APPA: mAppas.load(ref, store); break;
+ case ESM::REC_ARMO: mArmors.load(ref, store); break;
+ case ESM::REC_BOOK: mBooks.load(ref, store); break;
+ case ESM::REC_CLOT: mClothes.load(ref, store); break;
+ case ESM::REC_CONT: mContainers.load(ref, store); break;
+ case ESM::REC_CREA: mCreatures.load(ref, store); break;
+ case ESM::REC_DOOR: mDoors.load(ref, store); break;
+ case ESM::REC_INGR: mIngreds.load(ref, store); break;
+ case ESM::REC_LEVC: mCreatureLists.load(ref, store); break;
+ case ESM::REC_LEVI: mItemLists.load(ref, store); break;
+ case ESM::REC_LIGH: mLights.load(ref, store); break;
+ case ESM::REC_LOCK: mLockpicks.load(ref, store); break;
+ case ESM::REC_MISC: mMiscItems.load(ref, store); break;
+ case ESM::REC_NPC_: mNpcs.load(ref, store); break;
+ case ESM::REC_PROB: mProbes.load(ref, store); break;
+ case ESM::REC_REPA: mRepairs.load(ref, store); break;
+ case ESM::REC_STAT: mStatics.load(ref, store); break;
+ case ESM::REC_WEAP: mWeapons.load(ref, store); break;
+
+ case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
+ default:
+ std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
+ }
+ }
+ }
+
+ // Load moved references, from separately tracked list.
+ for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it)
+ {
+ // Doesn't seem to work in one line... huh? Too sleepy to check...
+ ESM::CellRef &ref = const_cast<ESM::CellRef&>(*it);
+ //ESM::CellRef &ref = const_cast<ESM::CellRef&>(it->second);
+
+ std::string lowerCase;
+
+ std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
+ (int(*)(int)) std::tolower);
+
+ int rec = store.find(ref.mRefID);
+
+ ref.mRefID = lowerCase;
+
+ /* We can optimize this further by storing the pointer to the
+ record itself in store.all, so that we don't need to look it
+ up again here. However, never optimize. There are infinite
+ opportunities to do that later.
+ */
+ switch(rec)
+ {
+ case ESM::REC_ACTI: mActivators.load(ref, store); break;
+ case ESM::REC_ALCH: mPotions.load(ref, store); break;
+ case ESM::REC_APPA: mAppas.load(ref, store); break;
+ case ESM::REC_ARMO: mArmors.load(ref, store); break;
+ case ESM::REC_BOOK: mBooks.load(ref, store); break;
+ case ESM::REC_CLOT: mClothes.load(ref, store); break;
+ case ESM::REC_CONT: mContainers.load(ref, store); break;
+ case ESM::REC_CREA: mCreatures.load(ref, store); break;
+ case ESM::REC_DOOR: mDoors.load(ref, store); break;
+ case ESM::REC_INGR: mIngreds.load(ref, store); break;
+ case ESM::REC_LEVC: mCreatureLists.load(ref, store); break;
+ case ESM::REC_LEVI: mItemLists.load(ref, store); break;
+ case ESM::REC_LIGH: mLights.load(ref, store); break;
+ case ESM::REC_LOCK: mLockpicks.load(ref, store); break;
+ case ESM::REC_MISC: mMiscItems.load(ref, store); break;
+ case ESM::REC_NPC_: mNpcs.load(ref, store); break;
+ case ESM::REC_PROB: mProbes.load(ref, store); break;
+ case ESM::REC_REPA: mRepairs.load(ref, store); break;
+ case ESM::REC_STAT: mStatics.load(ref, store); break;
+ case ESM::REC_WEAP: mWeapons.load(ref, store); break;
+
+ case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
+ default:
+ std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
+ }
+
+ }
+ }
+
+ Ptr CellStore::searchInContainer (const std::string& id)
+ {
+ {
+ Ptr ptr = searchInContainerList (mContainers, id);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchInContainerList (mCreatures, id);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchInContainerList (mNpcs, id);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ return Ptr();
+ }
+}
diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp
new file mode 100644
index 0000000000..bcbc5e415a
--- /dev/null
+++ b/apps/openmw/mwworld/cellstore.hpp
@@ -0,0 +1,157 @@
+#ifndef GAME_MWWORLD_CELLSTORE_H
+#define GAME_MWWORLD_CELLSTORE_H
+
+#include <deque>
+#include <algorithm>
+
+#include "livecellref.hpp"
+#include "esmstore.hpp"
+
+namespace MWWorld
+{
+
+ /// A list of cell references
+ template <typename X>
+ struct CellRefList
+ {
+ typedef LiveCellRef<X> LiveRef;
+ typedef std::list<LiveRef> List;
+ List mList;
+
+ // Search for the given reference in the given reclist from
+ // ESMStore. Insert the reference into the list if a match is
+ // found. If not, throw an exception.
+ // Moved to cpp file, as we require a custom compare operator for it,
+ // and the build will fail with an ugly three-way cyclic header dependence
+ // so we need to pass the instantiation of the method to the lnker, when
+ // all methods are known.
+ void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore);
+
+ LiveRef *find (const std::string& name)
+ {
+ for (typename std::list<LiveRef>::iterator iter (mList.begin()); iter!=mList.end(); ++iter)
+ {
+ if (iter->mData.getCount() > 0 && iter->mRef.mRefID == name)
+ return &*iter;
+ }
+
+ return 0;
+ }
+
+ LiveRef &insert(const LiveRef &item) {
+ mList.push_back(item);
+ return mList.back();
+ }
+ };
+
+ /// A storage struct for one single cell reference.
+ class CellStore
+ {
+ public:
+
+ enum State
+ {
+ State_Unloaded, State_Preloaded, State_Loaded
+ };
+
+ CellStore (const ESM::Cell *cell_);
+
+ const ESM::Cell *mCell;
+ State mState;
+ std::vector<std::string> mIds;
+
+ float mWaterLevel;
+
+ // Lists for each individual object type
+ CellRefList<ESM::Activator> mActivators;
+ CellRefList<ESM::Potion> mPotions;
+ CellRefList<ESM::Apparatus> mAppas;
+ CellRefList<ESM::Armor> mArmors;
+ CellRefList<ESM::Book> mBooks;
+ CellRefList<ESM::Clothing> mClothes;
+ CellRefList<ESM::Container> mContainers;
+ CellRefList<ESM::Creature> mCreatures;
+ CellRefList<ESM::Door> mDoors;
+ CellRefList<ESM::Ingredient> mIngreds;
+ CellRefList<ESM::CreatureLevList> mCreatureLists;
+ CellRefList<ESM::ItemLevList> mItemLists;
+ CellRefList<ESM::Light> mLights;
+ CellRefList<ESM::Lockpick> mLockpicks;
+ CellRefList<ESM::Miscellaneous> mMiscItems;
+ CellRefList<ESM::NPC> mNpcs;
+ CellRefList<ESM::Probe> mProbes;
+ CellRefList<ESM::Repair> mRepairs;
+ CellRefList<ESM::Static> mStatics;
+ CellRefList<ESM::Weapon> mWeapons;
+
+ void load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
+
+ void preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
+
+ /// Call functor (ref) for each reference. functor must return a bool. Returning
+ /// false will abort the iteration.
+ /// \return Iteration completed?
+ template<class Functor>
+ bool forEach (Functor& functor)
+ {
+ return
+ forEachImp (functor, mActivators) &&
+ forEachImp (functor, mPotions) &&
+ forEachImp (functor, mAppas) &&
+ forEachImp (functor, mArmors) &&
+ forEachImp (functor, mBooks) &&
+ forEachImp (functor, mClothes) &&
+ forEachImp (functor, mContainers) &&
+ forEachImp (functor, mCreatures) &&
+ forEachImp (functor, mDoors) &&
+ forEachImp (functor, mIngreds) &&
+ forEachImp (functor, mCreatureLists) &&
+ forEachImp (functor, mItemLists) &&
+ forEachImp (functor, mLights) &&
+ forEachImp (functor, mLockpicks) &&
+ forEachImp (functor, mMiscItems) &&
+ forEachImp (functor, mNpcs) &&
+ forEachImp (functor, mProbes) &&
+ forEachImp (functor, mRepairs) &&
+ forEachImp (functor, mStatics) &&
+ forEachImp (functor, mWeapons);
+ }
+
+ bool operator==(const CellStore &cell) {
+ return mCell->mName == cell.mCell->mName &&
+ mCell->mData.mX == cell.mCell->mData.mX &&
+ mCell->mData.mY == cell.mCell->mData.mY;
+ }
+
+ bool operator!=(const CellStore &cell) {
+ return !(*this == cell);
+ }
+
+ bool isExterior() const {
+ return mCell->isExterior();
+ }
+
+ Ptr searchInContainer (const std::string& id);
+
+ private:
+
+ template<class Functor, class List>
+ bool forEachImp (Functor& functor, List& list)
+ {
+ for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end();
+ ++iter)
+ if (!functor (iter->mRef, iter->mData))
+ return false;
+
+ return true;
+ }
+
+ /// Run through references and store IDs
+ void listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
+
+ void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
+
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp
new file mode 100644
index 0000000000..c739ea831c
--- /dev/null
+++ b/apps/openmw/mwworld/class.cpp
@@ -0,0 +1,360 @@
+
+#include "class.hpp"
+
+#include <stdexcept>
+
+#include <OgreVector3.h>
+
+#include <components/esm/defs.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/world.hpp"
+
+#include "ptr.hpp"
+#include "refdata.hpp"
+#include "nullaction.hpp"
+#include "failedaction.hpp"
+#include "actiontake.hpp"
+#include "containerstore.hpp"
+
+#include "../mwgui/tooltips.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/magiceffects.hpp"
+
+namespace MWWorld
+{
+ std::map<std::string, boost::shared_ptr<Class> > Class::sClasses;
+
+ Class::Class() {}
+
+ Class::~Class() {}
+
+ std::string Class::getId (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not support ID retrieval");
+ }
+
+ void Class::insertObjectRendering (const Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
+ {
+
+ }
+
+ void Class::insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics) const
+ {
+
+ }
+
+ bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const
+ {
+ return false;
+ }
+
+ void Class::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
+ {
+ throw std::runtime_error ("class does not represent an actor");
+ }
+
+ bool Class::canSell (const MWWorld::Ptr& item, int npcServices) const
+ {
+ return false;
+ }
+
+ int Class::getServices(const Ptr &actor) const
+ {
+ throw std::runtime_error ("class does not have services");
+ }
+
+ MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have creature stats");
+ }
+
+ MWMechanics::NpcStats& Class::getNpcStats (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have NPC stats");
+ }
+
+ bool Class::hasItemHealth (const Ptr& ptr) const
+ {
+ return false;
+ }
+
+ int Class::getItemMaxHealth (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have item health");
+ }
+
+ void Class::hit(const Ptr& ptr, int type) const
+ {
+ throw std::runtime_error("class cannot hit");
+ }
+
+ void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const
+ {
+ throw std::runtime_error("class cannot be hit");
+ }
+
+ void Class::setActorHealth(const Ptr& ptr, float health, const Ptr& attacker) const
+ {
+ throw std::runtime_error("class does not have actor health");
+ }
+
+ boost::shared_ptr<Action> Class::activate (const Ptr& ptr, const Ptr& actor) const
+ {
+ return boost::shared_ptr<Action> (new NullAction);
+ }
+
+ boost::shared_ptr<Action> Class::use (const Ptr& ptr) const
+ {
+ return boost::shared_ptr<Action> (new NullAction);
+ }
+
+ ContainerStore& Class::getContainerStore (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have a container store");
+ }
+
+ InventoryStore& Class::getInventoryStore (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have an inventory store");
+ }
+
+ void Class::lock (const Ptr& ptr, int lockLevel) const
+ {
+ throw std::runtime_error ("class does not support locking");
+ }
+
+ void Class::unlock (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not support unlocking");
+ }
+
+ std::string Class::getScript (const Ptr& ptr) const
+ {
+ return "";
+ }
+
+ void Class::setForceStance (const Ptr& ptr, Stance stance, bool force) const
+ {
+ throw std::runtime_error ("stance not supported by class");
+ }
+
+ void Class::setStance (const Ptr& ptr, Stance stance, bool set) const
+ {
+ throw std::runtime_error ("stance not supported by class");
+ }
+
+ bool Class::getStance (const Ptr& ptr, Stance stance, bool ignoreForce) const
+ {
+ return false;
+ }
+
+ float Class::getSpeed (const Ptr& ptr) const
+ {
+ return 0;
+ }
+
+ float Class::getJump (const Ptr& ptr) const
+ {
+ return 0;
+ }
+
+ float Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not support enchanting");
+ }
+
+ MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("movement settings not supported by class");
+ }
+
+ Ogre::Vector3 Class::getMovementVector (const Ptr& ptr) const
+ {
+ return Ogre::Vector3 (0, 0, 0);
+ }
+
+ Ogre::Vector3 Class::getRotationVector (const Ptr& ptr) const
+ {
+ return Ogre::Vector3 (0, 0, 0);
+ }
+
+ std::pair<std::vector<int>, bool> Class::getEquipmentSlots (const Ptr& ptr) const
+ {
+ return std::make_pair (std::vector<int>(), false);
+ }
+
+ int Class::getEquipmentSkill (const Ptr& ptr) const
+ {
+ return -1;
+ }
+
+ int Class::getValue (const Ptr& ptr) const
+ {
+ throw std::logic_error ("value not supported by this class");
+ }
+
+ float Class::getCapacity (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error ("capacity not supported by this class");
+ }
+
+ float Class::getWeight(const Ptr &ptr) const
+ {
+ throw std::runtime_error ("weight not supported by this class");
+ }
+
+ float Class::getEncumbrance (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error ("encumbrance not supported by class");
+ }
+
+ bool Class::isEssential (const MWWorld::Ptr& ptr) const
+ {
+ return false;
+ }
+
+ bool Class::hasDetected (const MWWorld::Ptr& ptr, const MWWorld::Ptr& ptr2) const
+ {
+ return true;
+ }
+
+ float Class::getArmorRating (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error("Class does not support armor rating");
+ }
+
+ const Class& Class::get (const std::string& key)
+ {
+ if (key.empty())
+ throw std::logic_error ("Class::get(): attempting to get an empty key");
+
+ std::map<std::string, boost::shared_ptr<Class> >::const_iterator iter = sClasses.find (key);
+
+ if (iter==sClasses.end())
+ throw std::logic_error ("Class::get(): unknown class key: " + key);
+
+ return *iter->second;
+ }
+
+ bool Class::isPersistent(const Ptr &ptr) const
+ {
+ throw std::runtime_error ("class does not support persistence");
+ }
+
+ void Class::registerClass(const std::string& key, boost::shared_ptr<Class> instance)
+ {
+ instance->mTypeName = key;
+ sClasses.insert(std::make_pair(key, instance));
+ }
+
+ std::string Class::getUpSoundId (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have an up sound");
+ }
+
+ std::string Class::getDownSoundId (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have an down sound");
+ }
+
+ std::string Class::getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const
+ {
+ throw std::runtime_error("class does not support soundgen look up");
+ }
+
+ std::string Class::getInventoryIcon (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have any inventory icon");
+ }
+
+ MWGui::ToolTipInfo Class::getToolTipInfo (const Ptr& ptr) const
+ {
+ throw std::runtime_error ("class does not have a tool tip");
+ }
+
+ bool Class::hasToolTip (const Ptr& ptr) const
+ {
+ return false;
+ }
+
+ std::string Class::getEnchantment (const Ptr& ptr) const
+ {
+ return "";
+ }
+
+ void Class::adjustScale(const MWWorld::Ptr& ptr,float& scale) const
+ {
+ }
+
+ void Class::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const
+ {
+ }
+
+ std::string Class::getModel(const MWWorld::Ptr &ptr) const
+ {
+ return "";
+ }
+
+ void Class::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
+ {
+ throw std::runtime_error ("class can't be enchanted");
+ }
+
+ std::pair<int, std::string> Class::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
+ {
+ return std::make_pair (1, "");
+ }
+
+ void Class::adjustPosition(const MWWorld::Ptr& ptr) const
+ {
+ }
+
+ boost::shared_ptr<Action> Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const
+ {
+ if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ return boost::shared_ptr<Action>(new NullAction());
+
+ if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfItem");
+
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
+ if(sound) action->setSound(sound->mId);
+
+ return action;
+ }
+
+ boost::shared_ptr<MWWorld::Action> action(new ActionTake(ptr));
+ action->setSound(getUpSoundId(ptr));
+
+ return action;
+ }
+
+ MWWorld::Ptr
+ Class::copyToCellImpl(const Ptr &ptr, CellStore &cell) const
+ {
+ throw std::runtime_error("unable to move class to cell");
+ }
+
+ MWWorld::Ptr
+ Class::copyToCell(const Ptr &ptr, CellStore &cell) const
+ {
+ Ptr newPtr = copyToCellImpl(ptr, cell);
+
+ return newPtr;
+ }
+
+ MWWorld::Ptr
+ Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const
+ {
+ Ptr newPtr = copyToCell(ptr, cell);
+ newPtr.getRefData().getPosition() = pos;
+
+ return newPtr;
+ }
+}
diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp
new file mode 100644
index 0000000000..28e37cbf3c
--- /dev/null
+++ b/apps/openmw/mwworld/class.hpp
@@ -0,0 +1,309 @@
+#ifndef GAME_MWWORLD_CLASS_H
+#define GAME_MWWORLD_CLASS_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include "ptr.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace MWRender
+{
+ class RenderingInterface;
+}
+
+namespace MWMechanics
+{
+ class CreatureStats;
+ class NpcStats;
+ struct Movement;
+}
+
+namespace MWGui
+{
+ struct ToolTipInfo;
+}
+
+namespace ESM
+{
+ struct Position;
+}
+
+namespace MWWorld
+{
+ class Ptr;
+ class ContainerStore;
+ class InventoryStore;
+ class PhysicsSystem;
+ class CellStore;
+ class Action;
+
+ /// \brief Base class for referenceable esm records
+ class Class
+ {
+ static std::map<std::string, boost::shared_ptr<Class> > sClasses;
+
+ std::string mTypeName;
+
+ // not implemented
+ Class (const Class&);
+ Class& operator= (const Class&);
+
+ protected:
+
+ Class();
+
+ boost::shared_ptr<Action> defaultItemActivate(const Ptr &ptr, const Ptr &actor) const;
+ ///< Generate default action for activating inventory items
+
+ virtual Ptr copyToCellImpl(const Ptr &ptr, CellStore &cell) const;
+
+ public:
+
+ /// NPC-stances.
+ enum Stance
+ {
+ Run, Sneak, Combat
+ };
+
+ virtual ~Class();
+
+ const std::string& getTypeName() const {
+ return mTypeName;
+ }
+
+ virtual std::string getId (const Ptr& ptr) const;
+ ///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval
+ /// (default implementation: throw an exception)
+
+ virtual void insertObjectRendering (const Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ virtual void insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics) const;
+ ///< Add reference into a cell for rendering (default implementation: don't render anything).
+
+ virtual std::string getName (const Ptr& ptr) const = 0;
+ ///< \return name (the one that is to be presented to the user; not the internal one);
+ /// can return an empty string.
+
+ virtual void adjustPosition(const MWWorld::Ptr& ptr) const;
+ ///< Adjust position to stand on ground. Must be called post model load
+
+ virtual MWMechanics::CreatureStats& getCreatureStats (const Ptr& ptr) const;
+ ///< Return creature stats or throw an exception, if class does not have creature stats
+ /// (default implementation: throw an exceoption)
+
+ virtual bool hasToolTip (const Ptr& ptr) const;
+ ///< @return true if this object has a tooltip when focused (default implementation: false)
+
+ virtual MWGui::ToolTipInfo getToolTipInfo (const Ptr& ptr) const;
+ ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.
+
+ virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const;
+ ///< Return NPC stats or throw an exception, if class does not have NPC stats
+ /// (default implementation: throw an exceoption)
+
+ virtual bool hasItemHealth (const Ptr& ptr) const;
+ ///< \return Item health data available? (default implementation: false)
+
+ virtual int getItemMaxHealth (const Ptr& ptr) const;
+ ///< Return item max health or throw an exception, if class does not have item health
+ /// (default implementation: throw an exceoption)
+
+ virtual void hit(const Ptr& ptr, int type=-1) const;
+ ///< Execute a melee hit, using the current weapon. This will check the relevant skills
+ /// of the given attacker, and whoever is hit.
+ /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType
+ /// enums. ignored for creature attacks.
+ /// (default implementation: throw an exceoption)
+
+ virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
+ ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is
+ /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the
+ /// actor responsible for the attack, and \a successful specifies if the hit is
+ /// successful or not.
+
+ virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const;
+ ///< Sets a new current health value for the actor, optionally specifying the object causing
+ /// the change. Use this instead of using CreatureStats directly as this will make sure the
+ /// correct dialog and actor states are properly handled when being hurt or healed.
+ /// (default implementation: throw an exceoption)
+
+ virtual boost::shared_ptr<Action> activate (const Ptr& ptr, const Ptr& actor) const;
+ ///< Generate action for activation (default implementation: return a null action).
+
+ virtual boost::shared_ptr<Action> use (const Ptr& ptr)
+ const;
+ ///< Generate action for using via inventory menu (default implementation: return a
+ /// null action).
+
+ virtual ContainerStore& getContainerStore (const Ptr& ptr) const;
+ ///< Return container store or throw an exception, if class does not have a
+ /// container store (default implementation: throw an exceoption)
+
+ virtual InventoryStore& getInventoryStore (const Ptr& ptr) const;
+ ///< Return inventory store or throw an exception, if class does not have a
+ /// inventory store (default implementation: throw an exceoption)
+
+ virtual void lock (const Ptr& ptr, int lockLevel) const;
+ ///< Lock object (default implementation: throw an exception)
+
+ virtual void unlock (const Ptr& ptr) const;
+ ///< Unlock object (default implementation: throw an exception)
+
+ virtual std::string getScript (const Ptr& ptr) const;
+ ///< Return name of the script attached to ptr (default implementation: return an empty
+ /// string).
+
+ virtual void setForceStance (const Ptr& ptr, Stance stance, bool force) const;
+ ///< Force or unforce a stance.
+
+ virtual void setStance (const Ptr& ptr, Stance stance, bool set) const;
+ ///< Set or unset a stance.
+
+ virtual bool getStance (const Ptr& ptr, Stance stance, bool ignoreForce = false) const;
+ ///< Check if a stance is active or not.
+
+ virtual float getSpeed (const Ptr& ptr) const;
+ ///< Return movement speed.
+
+ virtual float getJump(const MWWorld::Ptr &ptr) const;
+ ///< Return jump velocity (not accounting for movement)
+
+ virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const;
+ ///< Return desired movement.
+
+ virtual Ogre::Vector3 getMovementVector (const Ptr& ptr) const;
+ ///< Return desired movement vector (determined based on movement settings,
+ /// stance and stats).
+
+ virtual Ogre::Vector3 getRotationVector (const Ptr& ptr) const;
+ ///< Return desired rotations, as euler angles.
+
+ virtual std::pair<std::vector<int>, bool> getEquipmentSlots (const Ptr& ptr) const;
+ ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
+ /// stay stacked when equipped?
+ ///
+ /// Default implementation: return (empty vector, false).
+
+ virtual int getEquipmentSkill (const Ptr& ptr)
+ const;
+ /// Return the index of the skill this item corresponds to when equiopped or -1, if there is
+ /// no such skill.
+ /// (default implementation: return -1)
+
+ virtual int getValue (const Ptr& ptr) const;
+ ///< Return trade value of the object. Throws an exception, if the object can't be traded.
+ /// (default implementation: throws an exception)
+
+ virtual float getCapacity (const MWWorld::Ptr& ptr) const;
+ ///< Return total weight that fits into the object. Throws an exception, if the object can't
+ /// hold other objects.
+ /// (default implementation: throws an exception)
+
+ virtual float getEncumbrance (const MWWorld::Ptr& ptr) const;
+ ///< Returns total weight of objects inside this object (including modifications from magic
+ /// effects). Throws an exception, if the object can't hold other objects.
+ /// (default implementation: throws an exception)
+
+ virtual bool apply (const MWWorld::Ptr& ptr, const std::string& id,
+ const MWWorld::Ptr& actor) const;
+ ///< Apply \a id on \a ptr.
+ /// \param actor Actor that is resposible for the ID being applied to \a ptr.
+ /// \return Any effect?
+ ///
+ /// (default implementation: ignore and return false)
+
+ virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const;
+ ///< Inform actor \a ptr that a skill use has succeeded.
+ ///
+ /// (default implementations: throws an exception)
+
+ virtual bool isEssential (const MWWorld::Ptr& ptr) const;
+ ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable)
+ ///
+ /// (default implementation: return false)
+
+ virtual bool hasDetected (const MWWorld::Ptr& ptr, const MWWorld::Ptr& ptr2) const;
+ ///< Has \æ ptr detected \a ptr2?
+ ///
+ /// (default implementation: return false)
+
+ virtual std::string getUpSoundId (const Ptr& ptr) const;
+ ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval
+ /// (default implementation: throw an exception)
+
+ virtual std::string getDownSoundId (const Ptr& ptr) const;
+ ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval
+ /// (default implementation: throw an exception)
+
+ virtual std::string getSoundIdFromSndGen(const Ptr &ptr, const std::string &type) const;
+ ///< Returns the sound ID for \a ptr of the given soundgen \a type.
+
+ virtual float getArmorRating (const MWWorld::Ptr& ptr) const;
+ ///< @return combined armor rating of this actor
+
+ virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const;
+ ///< Return name of inventory icon.
+
+ virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const;
+ ///< @return the enchantment ID if the object is enchanted, otherwise an empty string
+ /// (default implementation: return empty string)
+
+ virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ ///< @return the number of enchantment points available for possible enchanting
+
+ virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const;
+
+ virtual void adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const;
+
+ virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
+ ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices
+
+ virtual int getServices (const MWWorld::Ptr& actor) const;
+
+ virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+
+ virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const;
+
+ virtual std::pair<int, std::string> canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const;
+ ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that.
+ /// Second item in the pair specifies the error message
+
+ virtual float getWeight (const MWWorld::Ptr& ptr) const;
+
+ virtual bool isPersistent (const MWWorld::Ptr& ptr) const;
+
+ virtual Ptr
+ copyToCell(const Ptr &ptr, CellStore &cell) const;
+
+ virtual Ptr
+ copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const;
+
+ virtual bool isActor() const {
+ return false;
+ }
+
+ virtual bool isNpc() const {
+ return false;
+ }
+
+ static const Class& get (const std::string& key);
+ ///< If there is no class for this \a key, an exception is thrown.
+
+ static const Class& get (const Ptr& ptr)
+ {
+ return ptr.getClass();
+ }
+ ///< If there is no class for this pointer, an exception is thrown.
+
+ static void registerClass (const std::string& key, boost::shared_ptr<Class> instance);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp
new file mode 100644
index 0000000000..9d111a5257
--- /dev/null
+++ b/apps/openmw/mwworld/containerstore.cpp
@@ -0,0 +1,749 @@
+
+#include "containerstore.hpp"
+
+#include <cassert>
+#include <typeinfo>
+#include <stdexcept>
+
+#include <boost/algorithm/string.hpp>
+
+#include <components/esm/loadcont.hpp>
+#include <components/compiler/locals.hpp>
+#include <components/misc/stringops.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+
+#include "manualref.hpp"
+#include "refdata.hpp"
+#include "class.hpp"
+#include "localscripts.hpp"
+#include "player.hpp"
+
+namespace
+{
+ template<typename T>
+ float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList)
+ {
+ float sum = 0;
+
+ for (typename MWWorld::CellRefList<T>::List::const_iterator iter (
+ cellRefList.mList.begin());
+ iter!=cellRefList.mList.end();
+ ++iter)
+ {
+ if (iter->mData.getCount()>0)
+ sum += iter->mData.getCount()*iter->mBase->mData.mWeight;
+ }
+
+ return sum;
+ }
+
+ template<typename T>
+ MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id,
+ MWWorld::ContainerStore *store)
+ {
+ std::string id2 = Misc::StringUtils::lowerCase (id);
+
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
+ iter!=list.mList.end(); ++iter)
+ {
+ if (Misc::StringUtils::lowerCase (iter->mBase->mId)==id2)
+ {
+ MWWorld::Ptr ptr (&*iter, 0);
+ ptr.setContainerStore (store);
+ return ptr;
+ }
+ }
+
+ return MWWorld::Ptr();
+ }
+}
+
+MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {}
+
+MWWorld::ContainerStore::~ContainerStore() {}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin (int mask)
+{
+ return ContainerStoreIterator (mask, this);
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
+{
+ return ContainerStoreIterator (this);
+}
+
+bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
+{
+ /// \todo add current enchantment charge here when it is implemented
+ if ( 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)
+ && 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;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr)
+{
+ MWWorld::ContainerStoreIterator it = addImp(itemPtr);
+ MWWorld::Ptr item = *it;
+
+ std::string script = MWWorld::Class::get(item).getScript(item);
+ if(script != "")
+ {
+ CellStore *cell;
+
+ Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+
+ if(&(MWWorld::Class::get (player).getContainerStore (player)) == this)
+ {
+ cell = 0; // Items in player's inventory have cell set to 0, so their scripts will never be removed
+
+ // Set OnPCAdd special variable, if it is declared
+ item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1);
+ }
+ else
+ cell = player.getCell();
+
+ item.mCell = cell;
+ item.mContainerStore = 0;
+ MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item);
+ }
+
+ return it;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
+{
+ int type = getType(ptr);
+
+ const MWWorld::ESMStore &esmStore =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001
+ // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold)
+ if (Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_001")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_005")
+ || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_010")
+ || 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);
+ flagAsModified();
+ return iter;
+ }
+ }
+
+ return addImpl(ref.getPtr());
+ }
+
+ // determine whether to stack or not
+ for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter)
+ {
+ if (stacks(*iter, ptr))
+ {
+ // stack
+ iter->getRefData().setCount( iter->getRefData().getCount() + ptr.getRefData().getCount() );
+
+ flagAsModified();
+ return iter;
+ }
+ }
+ // if we got here, this means no stacking
+ return addImpl(ptr);
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr)
+{
+ ContainerStoreIterator it = begin();
+
+ switch (getType(ptr))
+ {
+ case Type_Potion: potions.mList.push_back (*ptr.get<ESM::Potion>()); it = ContainerStoreIterator(this, --potions.mList.end()); break;
+ case Type_Apparatus: appas.mList.push_back (*ptr.get<ESM::Apparatus>()); it = ContainerStoreIterator(this, --appas.mList.end()); break;
+ case Type_Armor: armors.mList.push_back (*ptr.get<ESM::Armor>()); it = ContainerStoreIterator(this, --armors.mList.end()); break;
+ case Type_Book: books.mList.push_back (*ptr.get<ESM::Book>()); it = ContainerStoreIterator(this, --books.mList.end()); break;
+ case Type_Clothing: clothes.mList.push_back (*ptr.get<ESM::Clothing>()); it = ContainerStoreIterator(this, --clothes.mList.end()); break;
+ case Type_Ingredient: ingreds.mList.push_back (*ptr.get<ESM::Ingredient>()); it = ContainerStoreIterator(this, --ingreds.mList.end()); break;
+ case Type_Light: lights.mList.push_back (*ptr.get<ESM::Light>()); it = ContainerStoreIterator(this, --lights.mList.end()); break;
+ case Type_Lockpick: lockpicks.mList.push_back (*ptr.get<ESM::Lockpick>()); it = ContainerStoreIterator(this, --lockpicks.mList.end()); break;
+ case Type_Miscellaneous: miscItems.mList.push_back (*ptr.get<ESM::Miscellaneous>()); it = ContainerStoreIterator(this, --miscItems.mList.end()); break;
+ case Type_Probe: probes.mList.push_back (*ptr.get<ESM::Probe>()); it = ContainerStoreIterator(this, --probes.mList.end()); break;
+ case Type_Repair: repairs.mList.push_back (*ptr.get<ESM::Repair>()); it = ContainerStoreIterator(this, --repairs.mList.end()); break;
+ case Type_Weapon: weapons.mList.push_back (*ptr.get<ESM::Weapon>()); it = ContainerStoreIterator(this, --weapons.mList.end()); break;
+ }
+
+ flagAsModified();
+ return it;
+}
+
+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();
+ ++iter)
+ {
+ std::string id = iter->mItem.toString();
+ addInitialItem(id, owner, iter->mCount);
+ }
+
+ flagAsModified();
+}
+
+void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance, bool topLevel)
+{
+ count = std::abs(count); /// \todo implement item restocking (indicated by negative count)
+
+ try
+ {
+ ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id);
+
+ if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name())
+ {
+ const ESM::ItemLevList* levItem = ref.getPtr().get<ESM::ItemLevList>()->mBase;
+ const std::vector<ESM::LeveledListBase::LevelItem>& items = levItem->mList;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ int playerLevel = MWWorld::Class::get(player).getCreatureStats(player).getLevel();
+
+ failChance += levItem->mChanceNone;
+
+ if (topLevel && count > 1 && levItem->mFlags & ESM::ItemLevList::Each)
+ {
+ for (int i=0; i<count; ++i)
+ addInitialItem(id, owner, 1, failChance, false);
+ return;
+ }
+
+ float random = static_cast<float> (std::rand()) / RAND_MAX;
+ if (random >= failChance/100.f)
+ {
+ std::vector<std::string> candidates;
+ int highestLevel = 0;
+ for (std::vector<ESM::LeveledListBase::LevelItem>::const_iterator it = items.begin(); it != items.end(); ++it)
+ {
+ if (it->mLevel > highestLevel)
+ highestLevel = it->mLevel;
+ }
+
+ std::pair<int, std::string> highest = std::make_pair(-1, "");
+ for (std::vector<ESM::LeveledListBase::LevelItem>::const_iterator it = items.begin(); it != items.end(); ++it)
+ {
+ if (playerLevel >= it->mLevel
+ && (levItem->mFlags & ESM::ItemLevList::AllLevels || it->mLevel == highestLevel))
+ {
+ candidates.push_back(it->mId);
+ if (it->mLevel >= highest.first)
+ highest = std::make_pair(it->mLevel, it->mId);
+ }
+
+ }
+ if (candidates.empty())
+ return;
+ std::string item = candidates[std::rand()%candidates.size()];
+ addInitialItem(item, owner, count, failChance, false);
+ }
+ }
+ else
+ {
+ ref.getPtr().getRefData().setCount (count);
+ ref.getPtr().getCellRef().mOwner = owner;
+ addImp (ref.getPtr());
+ }
+ }
+ catch (std::logic_error& e)
+ {
+ // Vanilla doesn't fail on nonexistent items in levelled lists
+ std::cerr << "Warning: ignoring nonexistent item '" << id << "'" << std::endl;
+ return;
+ }
+}
+
+void MWWorld::ContainerStore::clear()
+{
+ for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
+ iter->getRefData().setCount (0);
+
+ flagAsModified();
+}
+
+void MWWorld::ContainerStore::flagAsModified()
+{
+ ++mStateId;
+ mWeightUpToDate = false;
+}
+
+int MWWorld::ContainerStore::getStateId() const
+{
+ return mStateId;
+}
+
+float MWWorld::ContainerStore::getWeight() const
+{
+ if (!mWeightUpToDate)
+ {
+ mCachedWeight = 0;
+
+ mCachedWeight += getTotalWeight (potions);
+ mCachedWeight += getTotalWeight (appas);
+ mCachedWeight += getTotalWeight (armors);
+ mCachedWeight += getTotalWeight (books);
+ mCachedWeight += getTotalWeight (clothes);
+ mCachedWeight += getTotalWeight (ingreds);
+ mCachedWeight += getTotalWeight (lights);
+ mCachedWeight += getTotalWeight (lockpicks);
+ mCachedWeight += getTotalWeight (miscItems);
+ mCachedWeight += getTotalWeight (probes);
+ mCachedWeight += getTotalWeight (repairs);
+ mCachedWeight += getTotalWeight (weapons);
+
+ mWeightUpToDate = true;
+ }
+
+ return mCachedWeight;
+}
+
+int MWWorld::ContainerStore::getType (const Ptr& ptr)
+{
+ if (ptr.isEmpty())
+ throw std::runtime_error ("can't put a non-existent object into a container");
+
+ if (ptr.getTypeName()==typeid (ESM::Potion).name())
+ return Type_Potion;
+
+ if (ptr.getTypeName()==typeid (ESM::Apparatus).name())
+ return Type_Apparatus;
+
+ if (ptr.getTypeName()==typeid (ESM::Armor).name())
+ return Type_Armor;
+
+ if (ptr.getTypeName()==typeid (ESM::Book).name())
+ return Type_Book;
+
+ if (ptr.getTypeName()==typeid (ESM::Clothing).name())
+ return Type_Clothing;
+
+ if (ptr.getTypeName()==typeid (ESM::Ingredient).name())
+ return Type_Ingredient;
+
+ if (ptr.getTypeName()==typeid (ESM::Light).name())
+ return Type_Light;
+
+ if (ptr.getTypeName()==typeid (ESM::Lockpick).name())
+ return Type_Lockpick;
+
+ if (ptr.getTypeName()==typeid (ESM::Miscellaneous).name())
+ return Type_Miscellaneous;
+
+ if (ptr.getTypeName()==typeid (ESM::Probe).name())
+ return Type_Probe;
+
+ if (ptr.getTypeName()==typeid (ESM::Repair).name())
+ return Type_Repair;
+
+ if (ptr.getTypeName()==typeid (ESM::Weapon).name())
+ return Type_Weapon;
+
+ throw std::runtime_error (
+ "Object of type " + ptr.getTypeName() + " can not be placed into a container");
+}
+
+MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
+{
+ {
+ Ptr ptr = searchId (potions, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (appas, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (armors, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (books, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (clothes, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (ingreds, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (lights, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (lockpicks, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (miscItems, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (probes, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (repairs, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ {
+ Ptr ptr = searchId (weapons, id, this);
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ return Ptr();
+}
+
+
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container)
+: mType (-1), mMask (0), mContainer (container)
+{}
+
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (int mask, ContainerStore *container)
+: mType (0), mMask (mask), mContainer (container)
+{
+ nextType();
+
+ if (mType==-1 || (**this).getRefData().getCount())
+ return;
+
+ ++*this;
+}
+
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Potion>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Potion), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mPotion(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Apparatus>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Apparatus), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mApparatus(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Armor>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Armor), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mArmor(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Book>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Book), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mBook(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Clothing>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Clothing), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mClothing(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Ingredient>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Ingredient), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mIngredient(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Light>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Light), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLight(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Lockpick>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Lockpick), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLockpick(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Miscellaneous>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Miscellaneous), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mMiscellaneous(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Probe>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Probe), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mProbe(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Repair>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Repair), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mRepair(iterator){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Weapon>::List::iterator iterator)
+ : mType(MWWorld::ContainerStore::Type_Weapon), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mWeapon(iterator){}
+
+void MWWorld::ContainerStoreIterator::incType()
+{
+ if (mType==0)
+ mType = 1;
+ else if (mType!=-1)
+ {
+ mType <<= 1;
+
+ if (mType>ContainerStore::Type_Last)
+ mType = -1;
+ }
+}
+
+void MWWorld::ContainerStoreIterator::nextType()
+{
+ while (mType!=-1)
+ {
+ incType();
+
+ if ((mType & mMask) && mType>0)
+ if (resetIterator())
+ break;
+ }
+}
+
+bool MWWorld::ContainerStoreIterator::resetIterator()
+{
+ switch (mType)
+ {
+ case ContainerStore::Type_Potion:
+
+ mPotion = mContainer->potions.mList.begin();
+ return mPotion!=mContainer->potions.mList.end();
+
+ case ContainerStore::Type_Apparatus:
+
+ mApparatus = mContainer->appas.mList.begin();
+ return mApparatus!=mContainer->appas.mList.end();
+
+ case ContainerStore::Type_Armor:
+
+ mArmor = mContainer->armors.mList.begin();
+ return mArmor!=mContainer->armors.mList.end();
+
+ case ContainerStore::Type_Book:
+
+ mBook = mContainer->books.mList.begin();
+ return mBook!=mContainer->books.mList.end();
+
+ case ContainerStore::Type_Clothing:
+
+ mClothing = mContainer->clothes.mList.begin();
+ return mClothing!=mContainer->clothes.mList.end();
+
+ case ContainerStore::Type_Ingredient:
+
+ mIngredient = mContainer->ingreds.mList.begin();
+ return mIngredient!=mContainer->ingreds.mList.end();
+
+ case ContainerStore::Type_Light:
+
+ mLight = mContainer->lights.mList.begin();
+ return mLight!=mContainer->lights.mList.end();
+
+ case ContainerStore::Type_Lockpick:
+
+ mLockpick = mContainer->lockpicks.mList.begin();
+ return mLockpick!=mContainer->lockpicks.mList.end();
+
+ case ContainerStore::Type_Miscellaneous:
+
+ mMiscellaneous = mContainer->miscItems.mList.begin();
+ return mMiscellaneous!=mContainer->miscItems.mList.end();
+
+ case ContainerStore::Type_Probe:
+
+ mProbe = mContainer->probes.mList.begin();
+ return mProbe!=mContainer->probes.mList.end();
+
+ case ContainerStore::Type_Repair:
+
+ mRepair = mContainer->repairs.mList.begin();
+ return mRepair!=mContainer->repairs.mList.end();
+
+ case ContainerStore::Type_Weapon:
+
+ mWeapon = mContainer->weapons.mList.begin();
+ return mWeapon!=mContainer->weapons.mList.end();
+ }
+
+ return false;
+}
+
+bool MWWorld::ContainerStoreIterator::incIterator()
+{
+ switch (mType)
+ {
+ case ContainerStore::Type_Potion:
+
+ ++mPotion;
+ return mPotion==mContainer->potions.mList.end();
+
+ case ContainerStore::Type_Apparatus:
+
+ ++mApparatus;
+ return mApparatus==mContainer->appas.mList.end();
+
+ case ContainerStore::Type_Armor:
+
+ ++mArmor;
+ return mArmor==mContainer->armors.mList.end();
+
+ case ContainerStore::Type_Book:
+
+ ++mBook;
+ return mBook==mContainer->books.mList.end();
+
+ case ContainerStore::Type_Clothing:
+
+ ++mClothing;
+ return mClothing==mContainer->clothes.mList.end();
+
+ case ContainerStore::Type_Ingredient:
+
+ ++mIngredient;
+ return mIngredient==mContainer->ingreds.mList.end();
+
+ case ContainerStore::Type_Light:
+
+ ++mLight;
+ return mLight==mContainer->lights.mList.end();
+
+ case ContainerStore::Type_Lockpick:
+
+ ++mLockpick;
+ return mLockpick==mContainer->lockpicks.mList.end();
+
+ case ContainerStore::Type_Miscellaneous:
+
+ ++mMiscellaneous;
+ return mMiscellaneous==mContainer->miscItems.mList.end();
+
+ case ContainerStore::Type_Probe:
+
+ ++mProbe;
+ return mProbe==mContainer->probes.mList.end();
+
+ case ContainerStore::Type_Repair:
+
+ ++mRepair;
+ return mRepair==mContainer->repairs.mList.end();
+
+ case ContainerStore::Type_Weapon:
+
+ ++mWeapon;
+ return mWeapon==mContainer->weapons.mList.end();
+ }
+
+ return true;
+}
+
+MWWorld::Ptr *MWWorld::ContainerStoreIterator::operator->() const
+{
+ mPtr = **this;
+ return &mPtr;
+}
+
+MWWorld::Ptr MWWorld::ContainerStoreIterator::operator*() const
+{
+ Ptr ptr;
+
+ switch (mType)
+ {
+ case ContainerStore::Type_Potion: ptr = MWWorld::Ptr (&*mPotion, 0); break;
+ case ContainerStore::Type_Apparatus: ptr = MWWorld::Ptr (&*mApparatus, 0); break;
+ case ContainerStore::Type_Armor: ptr = MWWorld::Ptr (&*mArmor, 0); break;
+ case ContainerStore::Type_Book: ptr = MWWorld::Ptr (&*mBook, 0); break;
+ case ContainerStore::Type_Clothing: ptr = MWWorld::Ptr (&*mClothing, 0); break;
+ case ContainerStore::Type_Ingredient: ptr = MWWorld::Ptr (&*mIngredient, 0); break;
+ case ContainerStore::Type_Light: ptr = MWWorld::Ptr (&*mLight, 0); break;
+ case ContainerStore::Type_Lockpick: ptr = MWWorld::Ptr (&*mLockpick, 0); break;
+ case ContainerStore::Type_Miscellaneous: ptr = MWWorld::Ptr (&*mMiscellaneous, 0); break;
+ case ContainerStore::Type_Probe: ptr = MWWorld::Ptr (&*mProbe, 0); break;
+ case ContainerStore::Type_Repair: ptr = MWWorld::Ptr (&*mRepair, 0); break;
+ case ContainerStore::Type_Weapon: ptr = MWWorld::Ptr (&*mWeapon, 0); break;
+ }
+
+ if (ptr.isEmpty())
+ throw std::runtime_error ("invalid iterator");
+
+ ptr.setContainerStore (mContainer);
+
+ return ptr;
+}
+
+MWWorld::ContainerStoreIterator& MWWorld::ContainerStoreIterator::operator++()
+{
+ do
+ {
+ if (incIterator())
+ nextType();
+ }
+ while (mType!=-1 && !(**this).getRefData().getCount());
+
+ return *this;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::ContainerStoreIterator::operator++ (int)
+{
+ ContainerStoreIterator iter (*this);
+ ++*this;
+ return iter;
+}
+
+bool MWWorld::ContainerStoreIterator::isEqual (const ContainerStoreIterator& iter) const
+{
+ if (mContainer!=iter.mContainer)
+ return false;
+
+ if (mType!=iter.mType)
+ return false;
+
+ switch (mType)
+ {
+ case ContainerStore::Type_Potion: return mPotion==iter.mPotion;
+ case ContainerStore::Type_Apparatus: return mApparatus==iter.mApparatus;
+ case ContainerStore::Type_Armor: return mArmor==iter.mArmor;
+ case ContainerStore::Type_Book: return mBook==iter.mBook;
+ case ContainerStore::Type_Clothing: return mClothing==iter.mClothing;
+ case ContainerStore::Type_Ingredient: return mIngredient==iter.mIngredient;
+ case ContainerStore::Type_Light: return mLight==iter.mLight;
+ case ContainerStore::Type_Lockpick: return mLockpick==iter.mLockpick;
+ case ContainerStore::Type_Miscellaneous: return mMiscellaneous==iter.mMiscellaneous;
+ case ContainerStore::Type_Probe: return mProbe==iter.mProbe;
+ case ContainerStore::Type_Repair: return mRepair==iter.mRepair;
+ case ContainerStore::Type_Weapon: return mWeapon==iter.mWeapon;
+ case -1: return true;
+ }
+
+ return false;
+}
+
+int MWWorld::ContainerStoreIterator::getType() const
+{
+ return mType;
+}
+
+const MWWorld::ContainerStore *MWWorld::ContainerStoreIterator::getContainerStore() const
+{
+ return mContainer;
+}
+
+bool MWWorld::operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right)
+{
+ return left.isEqual (right);
+}
+
+bool MWWorld::operator!= (const ContainerStoreIterator& left, const ContainerStoreIterator& right)
+{
+ return !(left==right);
+}
diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp
new file mode 100644
index 0000000000..9a11f1603b
--- /dev/null
+++ b/apps/openmw/mwworld/containerstore.hpp
@@ -0,0 +1,197 @@
+#ifndef GAME_MWWORLD_CONTAINERSTORE_H
+#define GAME_MWWORLD_CONTAINERSTORE_H
+
+#include <iterator>
+
+#include "ptr.hpp"
+
+namespace ESM
+{
+ struct InventoryList;
+}
+
+namespace MWWorld
+{
+ class ContainerStoreIterator;
+
+ class ContainerStore
+ {
+ public:
+
+ static const int Type_Potion = 0x0001;
+ static const int Type_Apparatus = 0x0002;
+ static const int Type_Armor = 0x0004;
+ static const int Type_Book = 0x0008;
+ static const int Type_Clothing = 0x0010;
+ static const int Type_Ingredient = 0x0020;
+ static const int Type_Light = 0x0040;
+ static const int Type_Lockpick = 0x0080;
+ static const int Type_Miscellaneous = 0x0100;
+ static const int Type_Probe = 0x0200;
+ static const int Type_Repair = 0x0400;
+ static const int Type_Weapon = 0x0800;
+
+ static const int Type_Last = Type_Weapon;
+
+ static const int Type_All = 0xffff;
+
+ private:
+
+ MWWorld::CellRefList<ESM::Potion> potions;
+ MWWorld::CellRefList<ESM::Apparatus> appas;
+ MWWorld::CellRefList<ESM::Armor> armors;
+ MWWorld::CellRefList<ESM::Book> books;
+ MWWorld::CellRefList<ESM::Clothing> clothes;
+ MWWorld::CellRefList<ESM::Ingredient> ingreds;
+ MWWorld::CellRefList<ESM::Light> lights;
+ MWWorld::CellRefList<ESM::Lockpick> lockpicks;
+ MWWorld::CellRefList<ESM::Miscellaneous> miscItems;
+ 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);
+ void addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance=0, bool topLevel=true);
+
+ public:
+
+ ContainerStore();
+
+ virtual ~ContainerStore();
+
+ ContainerStoreIterator begin (int mask = Type_All);
+
+ ContainerStoreIterator end();
+
+ virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr);
+ ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
+ ///
+ /// \note The item pointed to is not required to exist beyond this function call.
+ ///
+ /// \attention Do not add items to an existing stack by increasing the count instead of
+ /// calling this function!
+ ///
+ /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item.
+
+ protected:
+ ContainerStoreIterator addImpl (const Ptr& ptr);
+ ///< Add the item to this container (no stacking)
+
+ public:
+
+ virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2);
+ ///< @return true if the two specified objects can stack with each other
+
+ void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store);
+ ///< Insert items into *this.
+
+ 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.
+
+ static int getType (const Ptr& ptr);
+ ///< This function throws an exception, if ptr does not point to an object, that can be
+ /// put into a container.
+
+ Ptr search (const std::string& id);
+
+ friend class ContainerStoreIterator;
+ };
+
+ /// \brief Iteration over a subset of objects in a ContainerStore
+ ///
+ /// \note The iterator will automatically skip over deleted objects.
+ class ContainerStoreIterator
+ : public std::iterator<std::forward_iterator_tag, Ptr, std::ptrdiff_t, Ptr *, Ptr&>
+ {
+ int mType;
+ int mMask;
+ ContainerStore *mContainer;
+ mutable Ptr mPtr;
+
+ MWWorld::CellRefList<ESM::Potion>::List::iterator mPotion;
+ MWWorld::CellRefList<ESM::Apparatus>::List::iterator mApparatus;
+ MWWorld::CellRefList<ESM::Armor>::List::iterator mArmor;
+ MWWorld::CellRefList<ESM::Book>::List::iterator mBook;
+ MWWorld::CellRefList<ESM::Clothing>::List::iterator mClothing;
+ MWWorld::CellRefList<ESM::Ingredient>::List::iterator mIngredient;
+ MWWorld::CellRefList<ESM::Light>::List::iterator mLight;
+ MWWorld::CellRefList<ESM::Lockpick>::List::iterator mLockpick;
+ MWWorld::CellRefList<ESM::Miscellaneous>::List::iterator mMiscellaneous;
+ MWWorld::CellRefList<ESM::Probe>::List::iterator mProbe;
+ MWWorld::CellRefList<ESM::Repair>::List::iterator mRepair;
+ MWWorld::CellRefList<ESM::Weapon>::List::iterator mWeapon;
+
+ private:
+
+ ContainerStoreIterator (ContainerStore *container);
+ ///< End-iterator
+
+ ContainerStoreIterator (int mask, ContainerStore *container);
+ ///< Begin-iterator
+
+ // construct iterator using a CellRefList iterator
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Potion>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Apparatus>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Armor>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Book>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Clothing>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Ingredient>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Light>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Lockpick>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Miscellaneous>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Probe>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Repair>::List::iterator);
+ ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Weapon>::List::iterator);
+
+ void incType();
+
+ void nextType();
+
+ bool resetIterator();
+ ///< Reset iterator for selected type.
+ ///
+ /// \return Type not empty?
+
+ bool incIterator();
+ ///< Increment iterator for selected type.
+ ///
+ /// \return reached the end?
+
+ public:
+
+ Ptr *operator->() const;
+
+ Ptr operator*() const;
+
+ ContainerStoreIterator& operator++();
+
+ ContainerStoreIterator operator++ (int);
+
+ bool isEqual (const ContainerStoreIterator& iter) const;
+
+ int getType() const;
+
+ const ContainerStore *getContainerStore() const;
+
+ friend class ContainerStore;
+ };
+
+ bool operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right);
+ bool operator!= (const ContainerStoreIterator& left, const ContainerStoreIterator& right);
+}
+
+#endif
diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp
new file mode 100644
index 0000000000..588991fe40
--- /dev/null
+++ b/apps/openmw/mwworld/customdata.hpp
@@ -0,0 +1,17 @@
+#ifndef GAME_MWWORLD_CUSTOMDATA_H
+#define GAME_MWWORLD_CUSTOMDATA_H
+
+namespace MWWorld
+{
+ /// \brief Base class for the MW-class-specific part of RefData
+ class CustomData
+ {
+ public:
+
+ virtual ~CustomData() {}
+
+ virtual CustomData *clone() const = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp
new file mode 100644
index 0000000000..7703f2d233
--- /dev/null
+++ b/apps/openmw/mwworld/esmstore.cpp
@@ -0,0 +1,144 @@
+#include "esmstore.hpp"
+
+#include <set>
+#include <iostream>
+
+#include <boost/filesystem/operations.hpp>
+
+#include <components/loadinglistener/loadinglistener.hpp>
+
+namespace MWWorld
+{
+
+static bool isCacheableRecord(int id)
+{
+ if (id == ESM::REC_ACTI || id == ESM::REC_ALCH || id == ESM::REC_APPA || id == ESM::REC_ARMO ||
+ id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA ||
+ id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI ||
+ id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ ||
+ id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP)
+ {
+ return true;
+ }
+ return false;
+}
+
+void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
+{
+ listener->setProgressRange(1000);
+
+ std::set<std::string> missing;
+
+ ESM::Dialogue *dialogue = 0;
+
+ /// \todo Move this to somewhere else. ESMReader?
+ // Cache parent esX files by tracking their indices in the global list of
+ // all files/readers used by the engine. This will greaty accelerate
+ // refnumber mangling, as required for handling moved references.
+ int index = ~0;
+ const std::vector<ESM::Header::MasterData> &masters = esm.getMasters();
+ std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
+ for (size_t j = 0; j < masters.size(); j++) {
+ ESM::Header::MasterData &mast = const_cast<ESM::Header::MasterData&>(masters[j]);
+ std::string fname = mast.name;
+ for (int i = 0; i < esm.getIndex(); i++) {
+ const std::string &candidate = allPlugins->at(i).getContext().filename;
+ std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
+ if (fname == fnamecandidate) {
+ index = i;
+ break;
+ }
+ }
+ if (index == (int)~0) {
+ // Tried to load a parent file that has not been loaded yet. This is bad,
+ // the launcher should have taken care of this.
+ std::string fstring = "File " + fname + " asks for parent file " + masters[j].name
+ + ", but it has not been loaded yet. Please check your load order.";
+ esm.fail(fstring);
+ }
+ mast.index = index;
+ }
+
+ // Loop through all records
+ while(esm.hasMoreRecs())
+ {
+ ESM::NAME n = esm.getRecName();
+ esm.getRecHeader();
+
+ // Look up the record type.
+ std::map<int, StoreBase *>::iterator it = mStores.find(n.val);
+
+ if (it == mStores.end()) {
+ if (n.val == ESM::REC_INFO) {
+ if (dialogue) {
+ dialogue->mInfo.push_back(ESM::DialInfo());
+ dialogue->mInfo.back().load(esm);
+ } else {
+ std::cerr << "error: info record without dialog" << std::endl;
+ esm.skipRecord();
+ }
+ } else if (n.val == ESM::REC_MGEF) {
+ mMagicEffects.load (esm);
+ } else if (n.val == ESM::REC_SKIL) {
+ mSkills.load (esm);
+ } else {
+ // Not found (this would be an error later)
+ esm.skipRecord();
+ missing.insert(n.toString());
+ }
+ } else {
+ // Load it
+ std::string id = esm.getHNOString("NAME");
+ // ... unless it got deleted! This means that the following record
+ // has been deleted, and trying to load it using standard assumptions
+ // on the structure will (probably) fail.
+ if (esm.isNextSub("DELE")) {
+ esm.skipRecord();
+ it->second->eraseStatic(id);
+ continue;
+ }
+ it->second->load(esm, id);
+
+ if (n.val==ESM::REC_DIAL) {
+ // dirty hack, but it is better than non-const search()
+ // or friends
+ //dialogue = &mDialogs.mStatic.back();
+ dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id));
+ assert (dialogue->mId == id);
+ } else {
+ dialogue = 0;
+ }
+ // Insert the reference into the global lookup
+ if (!id.empty() && isCacheableRecord(n.val)) {
+ mIds[id] = n.val;
+ }
+ }
+ listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000);
+ }
+
+ /* This information isn't needed on screen. But keep the code around
+ for debugging purposes later.
+
+ cout << "\n" << mStores.size() << " record types:\n";
+ for(RecListList::iterator it = mStores.begin(); it != mStores.end(); it++)
+ cout << " " << toStr(it->first) << ": " << it->second->getSize() << endl;
+ cout << "\nNot implemented yet: ";
+ for(set<string>::iterator it = missing.begin();
+ it != missing.end(); it++ )
+ cout << *it << " ";
+ cout << endl;
+ */
+}
+
+void ESMStore::setUp()
+{
+ std::map<int, StoreBase *>::iterator it = mStores.begin();
+ for (; it != mStores.end(); ++it) {
+ it->second->setUp();
+ }
+ mSkills.setUp();
+ mMagicEffects.setUp();
+ mAttributes.setUp();
+}
+
+} // end namespace
diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp
new file mode 100644
index 0000000000..ebb086cee5
--- /dev/null
+++ b/apps/openmw/mwworld/esmstore.hpp
@@ -0,0 +1,471 @@
+#ifndef OPENMW_MWWORLD_ESMSTORE_H
+#define OPENMW_MWWORLD_ESMSTORE_H
+
+#include <stdexcept>
+
+#include <components/esm/records.hpp>
+#include "store.hpp"
+
+namespace Loading
+{
+ class Listener;
+}
+
+namespace MWWorld
+{
+ class ESMStore
+ {
+ Store<ESM::Activator> mActivators;
+ Store<ESM::Potion> mPotions;
+ Store<ESM::Apparatus> mAppas;
+ Store<ESM::Armor> mArmors;
+ Store<ESM::BodyPart> mBodyParts;
+ Store<ESM::Book> mBooks;
+ Store<ESM::BirthSign> mBirthSigns;
+ Store<ESM::Class> mClasses;
+ Store<ESM::Clothing> mClothes;
+ Store<ESM::LoadCNTC> mContChange;
+ Store<ESM::Container> mContainers;
+ Store<ESM::Creature> mCreatures;
+ Store<ESM::LoadCREC> mCreaChange;
+ Store<ESM::Dialogue> mDialogs;
+ Store<ESM::Door> mDoors;
+ Store<ESM::Enchantment> mEnchants;
+ Store<ESM::Faction> mFactions;
+ Store<ESM::Global> mGlobals;
+ Store<ESM::Ingredient> mIngreds;
+ Store<ESM::CreatureLevList> mCreatureLists;
+ Store<ESM::ItemLevList> mItemLists;
+ Store<ESM::Light> mLights;
+ Store<ESM::Lockpick> mLockpicks;
+ Store<ESM::Miscellaneous> mMiscItems;
+ Store<ESM::NPC> mNpcs;
+ Store<ESM::LoadNPCC> mNpcChange;
+ Store<ESM::Probe> mProbes;
+ Store<ESM::Race> mRaces;
+ Store<ESM::Region> mRegions;
+ Store<ESM::Repair> mRepairs;
+ Store<ESM::SoundGenerator> mSoundGens;
+ Store<ESM::Sound> mSounds;
+ Store<ESM::Spell> mSpells;
+ Store<ESM::StartScript> mStartScripts;
+ Store<ESM::Static> mStatics;
+ Store<ESM::Weapon> mWeapons;
+
+ Store<ESM::GameSetting> mGameSettings;
+ Store<ESM::Script> mScripts;
+
+ // Lists that need special rules
+ Store<ESM::Cell> mCells;
+ Store<ESM::Land> mLands;
+ Store<ESM::LandTexture> mLandTextures;
+ Store<ESM::Pathgrid> mPathgrids;
+
+ Store<ESM::MagicEffect> mMagicEffects;
+ Store<ESM::Skill> mSkills;
+
+ // Special entry which is hardcoded and not loaded from an ESM
+ Store<ESM::Attribute> mAttributes;
+
+ // Lookup of all IDs. Makes looking up references faster. Just
+ // maps the id name to the record type.
+ std::map<std::string, int> mIds;
+ std::map<int, StoreBase *> mStores;
+
+ ESM::NPC mPlayerTemplate;
+
+ unsigned int mDynamicCount;
+
+ public:
+ /// \todo replace with SharedIterator<StoreBase>
+ typedef std::map<int, StoreBase *>::const_iterator iterator;
+
+ iterator begin() const {
+ return mStores.begin();
+ }
+
+ iterator end() const {
+ return mStores.end();
+ }
+
+ // Look up the given ID in 'all'. Returns 0 if not found.
+ int find(const std::string &id) const
+ {
+ std::map<std::string, int>::const_iterator it = mIds.find(id);
+ if (it == mIds.end()) {
+ return 0;
+ }
+ return it->second;
+ }
+
+ ESMStore()
+ : mDynamicCount(0)
+ {
+ // Cell store needs access to this for tracking moved references
+ mCells.mEsmStore = this;
+
+ mStores[ESM::REC_ACTI] = &mActivators;
+ mStores[ESM::REC_ALCH] = &mPotions;
+ mStores[ESM::REC_APPA] = &mAppas;
+ mStores[ESM::REC_ARMO] = &mArmors;
+ mStores[ESM::REC_BODY] = &mBodyParts;
+ mStores[ESM::REC_BOOK] = &mBooks;
+ mStores[ESM::REC_BSGN] = &mBirthSigns;
+ mStores[ESM::REC_CELL] = &mCells;
+ mStores[ESM::REC_CLAS] = &mClasses;
+ mStores[ESM::REC_CLOT] = &mClothes;
+ mStores[ESM::REC_CNTC] = &mContChange;
+ mStores[ESM::REC_CONT] = &mContainers;
+ mStores[ESM::REC_CREA] = &mCreatures;
+ mStores[ESM::REC_CREC] = &mCreaChange;
+ mStores[ESM::REC_DIAL] = &mDialogs;
+ mStores[ESM::REC_DOOR] = &mDoors;
+ mStores[ESM::REC_ENCH] = &mEnchants;
+ mStores[ESM::REC_FACT] = &mFactions;
+ mStores[ESM::REC_GLOB] = &mGlobals;
+ mStores[ESM::REC_GMST] = &mGameSettings;
+ mStores[ESM::REC_INGR] = &mIngreds;
+ mStores[ESM::REC_LAND] = &mLands;
+ mStores[ESM::REC_LEVC] = &mCreatureLists;
+ mStores[ESM::REC_LEVI] = &mItemLists;
+ mStores[ESM::REC_LIGH] = &mLights;
+ mStores[ESM::REC_LOCK] = &mLockpicks;
+ mStores[ESM::REC_LTEX] = &mLandTextures;
+ mStores[ESM::REC_MISC] = &mMiscItems;
+ mStores[ESM::REC_NPC_] = &mNpcs;
+ mStores[ESM::REC_NPCC] = &mNpcChange;
+ mStores[ESM::REC_PGRD] = &mPathgrids;
+ mStores[ESM::REC_PROB] = &mProbes;
+ mStores[ESM::REC_RACE] = &mRaces;
+ mStores[ESM::REC_REGN] = &mRegions;
+ mStores[ESM::REC_REPA] = &mRepairs;
+ mStores[ESM::REC_SCPT] = &mScripts;
+ mStores[ESM::REC_SNDG] = &mSoundGens;
+ mStores[ESM::REC_SOUN] = &mSounds;
+ mStores[ESM::REC_SPEL] = &mSpells;
+ mStores[ESM::REC_SSCR] = &mStartScripts;
+ mStores[ESM::REC_STAT] = &mStatics;
+ mStores[ESM::REC_WEAP] = &mWeapons;
+ }
+
+ void clearDynamic ()
+ {
+ for (std::map<int, StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
+ it->second->clearDynamic();
+
+ mNpcs.insert(mPlayerTemplate);
+ }
+
+ void movePlayerRecord ()
+ {
+ mPlayerTemplate = *mNpcs.find("player");
+ mNpcs.eraseStatic(mPlayerTemplate.mId);
+ mNpcs.insert(mPlayerTemplate);
+ }
+
+ void load(ESM::ESMReader &esm, Loading::Listener* listener);
+
+ template <class T>
+ const Store<T> &get() const {
+ throw std::runtime_error("Storage for this type not exist");
+ }
+
+ template <class T>
+ const T *insert(const T &x) {
+ Store<T> &store = const_cast<Store<T> &>(get<T>());
+ if (store.search(x.mId) != 0) {
+ std::ostringstream msg;
+ msg << "Try to override existing record '" << x.mId << "'";
+ throw std::runtime_error(msg.str());
+ }
+ T record = x;
+
+ std::ostringstream id;
+ id << "$dynamic" << mDynamicCount++;
+ record.mId = id.str();
+
+ T *ptr = store.insert(record);
+ for (iterator it = mStores.begin(); it != mStores.end(); ++it) {
+ if (it->second == &store) {
+ mIds[ptr->mId] = it->first;
+ }
+ }
+ return ptr;
+ }
+
+ template <class T>
+ const T *insertStatic(const T &x) {
+ Store<T> &store = const_cast<Store<T> &>(get<T>());
+ if (store.search(x.mId) != 0) {
+ std::ostringstream msg;
+ msg << "Try to override existing record '" << x.mId << "'";
+ throw std::runtime_error(msg.str());
+ }
+ T record = x;
+
+ T *ptr = store.insertStatic(record);
+ for (iterator it = mStores.begin(); it != mStores.end(); ++it) {
+ if (it->second == &store) {
+ mIds[ptr->mId] = it->first;
+ }
+ }
+ return ptr;
+ }
+
+ // This method must be called once, after loading all master/plugin files. This can only be done
+ // from the outside, so it must be public.
+ void setUp();
+ };
+
+ template <>
+ inline const ESM::Cell *ESMStore::insert<ESM::Cell>(const ESM::Cell &cell) {
+ return mCells.insert(cell);
+ }
+
+ template <>
+ inline const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc) {
+ if (Misc::StringUtils::ciEqual(npc.mId, "player")) {
+ return mNpcs.insert(npc);
+ } else if (mNpcs.search(npc.mId) != 0) {
+ std::ostringstream msg;
+ msg << "Try to override existing record '" << npc.mId << "'";
+ throw std::runtime_error(msg.str());
+ }
+ ESM::NPC record = npc;
+
+ std::ostringstream id;
+ id << "$dynamic" << mDynamicCount++;
+ record.mId = id.str();
+
+ ESM::NPC *ptr = mNpcs.insert(record);
+ mIds[ptr->mId] = ESM::REC_NPC_;
+ return ptr;
+ }
+
+ template <>
+ inline const Store<ESM::Activator> &ESMStore::get<ESM::Activator>() const {
+ return mActivators;
+ }
+
+ template <>
+ inline const Store<ESM::Potion> &ESMStore::get<ESM::Potion>() const {
+ return mPotions;
+ }
+
+ template <>
+ inline const Store<ESM::Apparatus> &ESMStore::get<ESM::Apparatus>() const {
+ return mAppas;
+ }
+
+ template <>
+ inline const Store<ESM::Armor> &ESMStore::get<ESM::Armor>() const {
+ return mArmors;
+ }
+
+ template <>
+ inline const Store<ESM::BodyPart> &ESMStore::get<ESM::BodyPart>() const {
+ return mBodyParts;
+ }
+
+ template <>
+ inline const Store<ESM::Book> &ESMStore::get<ESM::Book>() const {
+ return mBooks;
+ }
+
+ template <>
+ inline const Store<ESM::BirthSign> &ESMStore::get<ESM::BirthSign>() const {
+ return mBirthSigns;
+ }
+
+ template <>
+ inline const Store<ESM::Class> &ESMStore::get<ESM::Class>() const {
+ return mClasses;
+ }
+
+ template <>
+ inline const Store<ESM::Clothing> &ESMStore::get<ESM::Clothing>() const {
+ return mClothes;
+ }
+
+ template <>
+ inline const Store<ESM::LoadCNTC> &ESMStore::get<ESM::LoadCNTC>() const {
+ return mContChange;
+ }
+
+ template <>
+ inline const Store<ESM::Container> &ESMStore::get<ESM::Container>() const {
+ return mContainers;
+ }
+
+ template <>
+ inline const Store<ESM::Creature> &ESMStore::get<ESM::Creature>() const {
+ return mCreatures;
+ }
+
+ template <>
+ inline const Store<ESM::LoadCREC> &ESMStore::get<ESM::LoadCREC>() const {
+ return mCreaChange;
+ }
+
+ template <>
+ inline const Store<ESM::Dialogue> &ESMStore::get<ESM::Dialogue>() const {
+ return mDialogs;
+ }
+
+ template <>
+ inline const Store<ESM::Door> &ESMStore::get<ESM::Door>() const {
+ return mDoors;
+ }
+
+ template <>
+ inline const Store<ESM::Enchantment> &ESMStore::get<ESM::Enchantment>() const {
+ return mEnchants;
+ }
+
+ template <>
+ inline const Store<ESM::Faction> &ESMStore::get<ESM::Faction>() const {
+ return mFactions;
+ }
+
+ template <>
+ inline const Store<ESM::Global> &ESMStore::get<ESM::Global>() const {
+ return mGlobals;
+ }
+
+ template <>
+ inline const Store<ESM::Ingredient> &ESMStore::get<ESM::Ingredient>() const {
+ return mIngreds;
+ }
+
+ template <>
+ inline const Store<ESM::CreatureLevList> &ESMStore::get<ESM::CreatureLevList>() const {
+ return mCreatureLists;
+ }
+
+ template <>
+ inline const Store<ESM::ItemLevList> &ESMStore::get<ESM::ItemLevList>() const {
+ return mItemLists;
+ }
+
+ template <>
+ inline const Store<ESM::Light> &ESMStore::get<ESM::Light>() const {
+ return mLights;
+ }
+
+ template <>
+ inline const Store<ESM::Lockpick> &ESMStore::get<ESM::Lockpick>() const {
+ return mLockpicks;
+ }
+
+ template <>
+ inline const Store<ESM::Miscellaneous> &ESMStore::get<ESM::Miscellaneous>() const {
+ return mMiscItems;
+ }
+
+ template <>
+ inline const Store<ESM::NPC> &ESMStore::get<ESM::NPC>() const {
+ return mNpcs;
+ }
+
+ template <>
+ inline const Store<ESM::LoadNPCC> &ESMStore::get<ESM::LoadNPCC>() const {
+ return mNpcChange;
+ }
+
+ template <>
+ inline const Store<ESM::Probe> &ESMStore::get<ESM::Probe>() const {
+ return mProbes;
+ }
+
+ template <>
+ inline const Store<ESM::Race> &ESMStore::get<ESM::Race>() const {
+ return mRaces;
+ }
+
+ template <>
+ inline const Store<ESM::Region> &ESMStore::get<ESM::Region>() const {
+ return mRegions;
+ }
+
+ template <>
+ inline const Store<ESM::Repair> &ESMStore::get<ESM::Repair>() const {
+ return mRepairs;
+ }
+
+ template <>
+ inline const Store<ESM::SoundGenerator> &ESMStore::get<ESM::SoundGenerator>() const {
+ return mSoundGens;
+ }
+
+ template <>
+ inline const Store<ESM::Sound> &ESMStore::get<ESM::Sound>() const {
+ return mSounds;
+ }
+
+ template <>
+ inline const Store<ESM::Spell> &ESMStore::get<ESM::Spell>() const {
+ return mSpells;
+ }
+
+ template <>
+ inline const Store<ESM::StartScript> &ESMStore::get<ESM::StartScript>() const {
+ return mStartScripts;
+ }
+
+ template <>
+ inline const Store<ESM::Static> &ESMStore::get<ESM::Static>() const {
+ return mStatics;
+ }
+
+ template <>
+ inline const Store<ESM::Weapon> &ESMStore::get<ESM::Weapon>() const {
+ return mWeapons;
+ }
+
+ template <>
+ inline const Store<ESM::GameSetting> &ESMStore::get<ESM::GameSetting>() const {
+ return mGameSettings;
+ }
+
+ template <>
+ inline const Store<ESM::Script> &ESMStore::get<ESM::Script>() const {
+ return mScripts;
+ }
+
+ template <>
+ inline const Store<ESM::Cell> &ESMStore::get<ESM::Cell>() const {
+ return mCells;
+ }
+
+ template <>
+ inline const Store<ESM::Land> &ESMStore::get<ESM::Land>() const {
+ return mLands;
+ }
+
+ template <>
+ inline const Store<ESM::LandTexture> &ESMStore::get<ESM::LandTexture>() const {
+ return mLandTextures;
+ }
+
+ template <>
+ inline const Store<ESM::Pathgrid> &ESMStore::get<ESM::Pathgrid>() const {
+ return mPathgrids;
+ }
+
+ template <>
+ inline const Store<ESM::MagicEffect> &ESMStore::get<ESM::MagicEffect>() const {
+ return mMagicEffects;
+ }
+
+ template <>
+ inline const Store<ESM::Skill> &ESMStore::get<ESM::Skill>() const {
+ return mSkills;
+ }
+
+ template <>
+ inline const Store<ESM::Attribute> &ESMStore::get<ESM::Attribute>() const {
+ return mAttributes;
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp
new file mode 100644
index 0000000000..7c8ef8c7bf
--- /dev/null
+++ b/apps/openmw/mwworld/failedaction.cpp
@@ -0,0 +1,19 @@
+#include "failedaction.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+
+namespace MWWorld
+{
+ FailedAction::FailedAction(const std::string &msg)
+ : Action(false), mMessage(msg)
+ { }
+
+ void FailedAction::executeImp(const Ptr &actor)
+ {
+ if(actor.getRefData().getHandle() == "player" && !mMessage.empty())
+ MWBase::Environment::get().getWindowManager()->messageBox(mMessage);
+ }
+}
diff --git a/apps/openmw/mwworld/failedaction.hpp b/apps/openmw/mwworld/failedaction.hpp
new file mode 100644
index 0000000000..c69d620230
--- /dev/null
+++ b/apps/openmw/mwworld/failedaction.hpp
@@ -0,0 +1,20 @@
+#ifndef GAME_MWWORLD_FAILEDACTION_H
+#define GAME_MWWORLD_FAILEDACTION_H
+
+#include "action.hpp"
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ class FailedAction : public Action
+ {
+ std::string mMessage;
+
+ virtual void executeImp(const Ptr &actor);
+
+ public:
+ FailedAction(const std::string &message = std::string());
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/apps/openmw/mwworld/fallback.cpp b/apps/openmw/mwworld/fallback.cpp
new file mode 100644
index 0000000000..569a6b50c2
--- /dev/null
+++ b/apps/openmw/mwworld/fallback.cpp
@@ -0,0 +1,49 @@
+#include "fallback.hpp"
+#include "boost/lexical_cast.hpp"
+namespace MWWorld
+{
+ Fallback::Fallback(const std::map<std::string,std::string>& fallback):mFallbackMap(fallback)
+ {}
+
+ std::string Fallback::getFallbackString(const std::string& fall) const
+ {
+ std::map<std::string,std::string>::const_iterator it;
+ if((it = mFallbackMap.find(fall)) == mFallbackMap.end())
+ {
+ return "";
+ }
+ return it->second;
+ }
+ float Fallback::getFallbackFloat(const std::string& fall) const
+ {
+ std::string fallback=getFallbackString(fall);
+ if(fallback.empty())
+ return 0;
+ else
+ return boost::lexical_cast<float>(fallback);
+ }
+ bool Fallback::getFallbackBool(const std::string& fall) const
+ {
+ std::string fallback=getFallbackString(fall);
+ if(fallback.empty())
+ return false;
+ else
+ return boost::lexical_cast<bool>(fallback);
+ }
+ Ogre::ColourValue Fallback::getFallbackColour(const std::string& fall) const
+ {
+ std::string sum=getFallbackString(fall);
+ if(sum.empty())
+ return Ogre::ColourValue(0,0,0);
+ else
+ {
+ std::string ret[3];
+ unsigned int j=0;
+ for(unsigned int i=0;i<sum.length();++i){
+ if(sum[i]==',') j++;
+ else ret[j]+=sum[i];
+ }
+ return Ogre::ColourValue(boost::lexical_cast<int>(ret[0])/255.f,boost::lexical_cast<int>(ret[1])/255.f,boost::lexical_cast<int>(ret[2])/255.f);
+ }
+ }
+}
diff --git a/apps/openmw/mwworld/fallback.hpp b/apps/openmw/mwworld/fallback.hpp
new file mode 100644
index 0000000000..6c5802e071
--- /dev/null
+++ b/apps/openmw/mwworld/fallback.hpp
@@ -0,0 +1,22 @@
+#ifndef GAME_MWWORLD_FALLBACK_H
+#define GAME_MWWORLD_FALLBACK_H
+
+#include <map>
+#include <string>
+
+#include <OgreColourValue.h>
+
+namespace MWWorld
+{
+ class Fallback
+ {
+ const std::map<std::string,std::string> mFallbackMap;
+ public:
+ Fallback(const std::map<std::string,std::string>& fallback);
+ std::string getFallbackString(const std::string& fall) const;
+ float getFallbackFloat(const std::string& fall) const;
+ bool getFallbackBool(const std::string& fall) const;
+ Ogre::ColourValue getFallbackColour(const std::string& fall) const;
+ };
+}
+#endif
diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp
new file mode 100644
index 0000000000..a905f8aaed
--- /dev/null
+++ b/apps/openmw/mwworld/globals.cpp
@@ -0,0 +1,159 @@
+
+#include "globals.hpp"
+
+#include <stdexcept>
+
+#include "esmstore.hpp"
+
+namespace MWWorld
+{
+ std::vector<std::string> Globals::getGlobals () const
+ {
+ std::vector<std::string> retval;
+ Collection::const_iterator it;
+ for(it = mVariables.begin(); it != mVariables.end(); ++it){
+ retval.push_back(it->first);
+ }
+
+ return retval;
+ }
+
+ Globals::Collection::const_iterator Globals::find (const std::string& name) const
+ {
+ Collection::const_iterator iter = mVariables.find (name);
+
+ if (iter==mVariables.end())
+ throw std::runtime_error ("unknown global variable: " + name);
+
+ return iter;
+ }
+
+ Globals::Collection::iterator Globals::find (const std::string& name)
+ {
+ Collection::iterator iter = mVariables.find (name);
+
+ if (iter==mVariables.end())
+ throw std::runtime_error ("unknown global variable: " + name);
+
+ return iter;
+ }
+
+ Globals::Globals (const MWWorld::ESMStore& store)
+ {
+ const MWWorld::Store<ESM::Global> &globals = store.get<ESM::Global>();
+ MWWorld::Store<ESM::Global>::iterator iter = globals.begin();
+ for (; iter != globals.end(); ++iter)
+ {
+ char type = ' ';
+ Data value;
+
+ switch (iter->mValue.getType())
+ {
+ case ESM::VT_Short:
+
+ type = 's';
+ value.mShort = iter->mValue.getInteger();
+ break;
+
+ case ESM::VT_Long:
+
+ type = 'l';
+ value.mLong = iter->mValue.getInteger();
+ break;
+
+ case ESM::VT_Float:
+
+ type = 'f';
+ value.mFloat = iter->mValue.getFloat();
+ break;
+
+ default:
+
+ throw std::runtime_error ("unsupported global variable type");
+ }
+
+ mVariables.insert (std::make_pair (iter->mId, std::make_pair (type, value)));
+ }
+ }
+
+ const Globals::Data& Globals::operator[] (const std::string& name) const
+ {
+ Collection::const_iterator iter = find (name);
+
+ return iter->second.second;
+ }
+
+ Globals::Data& Globals::operator[] (const std::string& name)
+ {
+ Collection::iterator iter = find (name);
+
+ return iter->second.second;
+ }
+
+ void Globals::setInt (const std::string& name, int value)
+ {
+ Collection::iterator iter = find (name);
+
+ switch (iter->second.first)
+ {
+ case 's': iter->second.second.mShort = value; break;
+ case 'l': iter->second.second.mLong = value; break;
+ case 'f': iter->second.second.mFloat = value; break;
+
+ default: throw std::runtime_error ("unsupported global variable type");
+ }
+ }
+
+ void Globals::setFloat (const std::string& name, float value)
+ {
+ Collection::iterator iter = find (name);
+
+ switch (iter->second.first)
+ {
+ case 's': iter->second.second.mShort = value; break;
+ case 'l': iter->second.second.mLong = value; break;
+ case 'f': iter->second.second.mFloat = value; break;
+
+ default: throw std::runtime_error ("unsupported global variable type");
+ }
+ }
+
+ int Globals::getInt (const std::string& name) const
+ {
+ Collection::const_iterator iter = find (name);
+
+ switch (iter->second.first)
+ {
+ case 's': return iter->second.second.mShort;
+ case 'l': return iter->second.second.mLong;
+ case 'f': return iter->second.second.mFloat;
+
+ default: throw std::runtime_error ("unsupported global variable type");
+ }
+ }
+
+ float Globals::getFloat (const std::string& name) const
+ {
+ Collection::const_iterator iter = find (name);
+
+ switch (iter->second.first)
+ {
+ case 's': return iter->second.second.mShort;
+ case 'l': return iter->second.second.mLong;
+ case 'f': return iter->second.second.mFloat;
+
+ default: throw std::runtime_error ("unsupported global variable type");
+ }
+ }
+
+ char Globals::getType (const std::string& name) const
+ {
+ Collection::const_iterator iter = mVariables.find (name);
+
+ if (iter==mVariables.end())
+ return ' ';
+
+ return iter->second.first;
+ }
+}
+
diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp
new file mode 100644
index 0000000000..681bd560e5
--- /dev/null
+++ b/apps/openmw/mwworld/globals.hpp
@@ -0,0 +1,62 @@
+#ifndef GAME_MWWORLD_GLOBALS_H
+#define GAME_MWWORLD_GLOBALS_H
+
+#include <vector>
+#include <string>
+#include <map>
+
+#include <components/interpreter/types.hpp>
+
+namespace MWWorld
+{
+ class ESMStore;
+
+ class Globals
+ {
+ public:
+
+ union Data
+ {
+ Interpreter::Type_Float mFloat;
+ Interpreter::Type_Float mLong; // Why Morrowind, why? :(
+ Interpreter::Type_Float mShort;
+ };
+
+ typedef std::map<std::string, std::pair<char, Data> > Collection;
+
+ private:
+
+ Collection mVariables; // type, value
+
+ Collection::const_iterator find (const std::string& name) const;
+
+ Collection::iterator find (const std::string& name);
+
+ public:
+
+ Globals (const MWWorld::ESMStore& store);
+
+ const Data& operator[] (const std::string& name) const;
+
+ Data& operator[] (const std::string& name);
+
+ void setInt (const std::string& name, int value);
+ ///< Set value independently from real type.
+
+ void setFloat (const std::string& name, float value);
+ ///< Set value independently from real type.
+
+ int getInt (const std::string& name) const;
+ ///< Get value independently from real type.
+
+ float getFloat (const std::string& name) const;
+ ///< Get value independently from real type.
+
+ char getType (const std::string& name) const;
+ ///< If there is no global variable with this name, ' ' is returned.
+
+ std::vector<std::string> getGlobals () const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp
new file mode 100644
index 0000000000..98cb6d347b
--- /dev/null
+++ b/apps/openmw/mwworld/inventorystore.cpp
@@ -0,0 +1,327 @@
+
+#include "inventorystore.hpp"
+
+#include <iterator>
+#include <algorithm>
+
+#include <components/esm/loadench.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "esmstore.hpp"
+#include "class.hpp"
+
+void MWWorld::InventoryStore::copySlots (const InventoryStore& store)
+{
+ // some const-trickery, required because of a flaw in the handling of MW-references and the
+ // resulting workarounds
+ for (std::vector<ContainerStoreIterator>::const_iterator iter (
+ const_cast<InventoryStore&> (store).mSlots.begin());
+ iter!=const_cast<InventoryStore&> (store).mSlots.end(); ++iter)
+ {
+ std::size_t distance = std::distance (const_cast<InventoryStore&> (store).begin(), *iter);
+
+ ContainerStoreIterator slot = begin();
+
+ std::advance (slot, distance);
+
+ mSlots.push_back (slot);
+ }
+}
+
+void MWWorld::InventoryStore::initSlots (TSlots& slots)
+{
+ for (int i=0; i<Slots; ++i)
+ slots.push_back (end());
+}
+
+MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false)
+ , mSelectedEnchantItem(end())
+{
+ initSlots (mSlots);
+}
+
+MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
+: ContainerStore (store)
+ , mSelectedEnchantItem(end())
+{
+ mMagicEffects = store.mMagicEffects;
+ mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
+ mSelectedEnchantItem = store.mSelectedEnchantItem;
+ copySlots (store);
+}
+
+MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
+{
+ mMagicEffects = store.mMagicEffects;
+ mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
+ ContainerStore::operator= (store);
+ mSlots.clear();
+ copySlots (store);
+ return *this;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const Ptr& actorPtr)
+{
+ const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, actorPtr);
+
+ // 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()))
+ {
+ std::string type = itemPtr.getTypeName();
+ if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
+ autoEquip(actorPtr);
+ }
+
+ return retVal;
+}
+
+void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator)
+{
+ 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);
+
+ 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;
+ }
+ }
+ }
+
+ // 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
+ {
+ // 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);
+ iterator->getRefData().setCount(1);
+ }
+
+ mSlots[slot] = iterator;
+
+ flagAsModified();
+}
+
+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);
+ }
+ }
+}
+
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
+{
+ if (slot<0 || slot>=static_cast<int> (mSlots.size()))
+ throw std::runtime_error ("slot number out of range");
+
+ if (mSlots[slot]==end())
+ return end();
+
+ if (mSlots[slot]->getRefData().getCount()<1)
+ {
+ // object has been deleted
+ mSlots[slot] = end();
+ return end();
+ }
+
+ return mSlots[slot];
+}
+
+void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
+{
+ const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc);
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
+
+ TSlots slots;
+ initSlots (slots);
+
+ for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
+ {
+ Ptr test = *iter;
+ int testSkill = MWWorld::Class::get (test).getEquipmentSkill (test);
+
+ std::pair<std::vector<int>, bool> itemsSlots =
+ MWWorld::Class::get (*iter).getEquipmentSlots (*iter);
+
+ for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
+ iter2!=itemsSlots.first.end(); ++iter2)
+ {
+ bool use = false;
+
+ if (slots.at (*iter2)==end())
+ use = true; // slot was empty before -> skip all further checks
+ else
+ {
+ Ptr old = *slots.at (*iter2);
+
+ if (!use)
+ {
+ // check skill
+ int oldSkill =
+ MWWorld::Class::get (old).getEquipmentSkill (old);
+
+ if (testSkill!=-1 && oldSkill==-1)
+ use = true;
+ else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill)
+ {
+ if (stats.getSkill (oldSkill).getModified()>stats.getSkill (testSkill).getModified())
+ continue; // rejected, because old item better matched the NPC's skills.
+
+ if (stats.getSkill (oldSkill).getModified()<stats.getSkill (testSkill).getModified())
+ use = true;
+ }
+ }
+
+ if (!use)
+ {
+ // check value
+ if (MWWorld::Class::get (old).getValue (old)>=
+ MWWorld::Class::get (test).getValue (test))
+ {
+ continue;
+ }
+
+ use = true;
+ }
+ }
+
+ switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first)
+ {
+ case 0:
+ continue;
+ case 2:
+ invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end());
+ break;
+ case 3:
+ invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end());
+ break;
+ }
+
+ if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped
+ {
+ // unstack item pointed to by iterator if required
+ if (iter->getRefData().getCount() > 1)
+ {
+ // 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);
+ iter->getRefData().setCount(1);
+ }
+ }
+
+ slots[*iter2] = iter;
+ break;
+ }
+ }
+
+ bool changed = false;
+
+ for (std::size_t i=0; i<slots.size(); ++i)
+ if (slots[i]!=mSlots[i])
+ {
+ changed = true;
+ }
+
+ if (changed)
+ {
+ mSlots.swap (slots);
+ flagAsModified();
+ }
+}
+
+const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects()
+{
+ if (!mMagicEffectsUpToDate)
+ {
+ mMagicEffects = MWMechanics::MagicEffects();
+
+ for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
+ if (*iter!=end())
+ {
+ std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter);
+
+ if (!enchantmentId.empty())
+ {
+ const ESM::Enchantment& enchantment =
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
+
+ if (enchantment.mData.mType==ESM::Enchantment::ConstantEffect)
+ mMagicEffects.add (enchantment.mEffects);
+ }
+ }
+
+ mMagicEffectsUpToDate = true;
+ }
+
+ return mMagicEffects;
+}
+
+void MWWorld::InventoryStore::flagAsModified()
+{
+ ContainerStore::flagAsModified();
+ mMagicEffectsUpToDate = false;
+}
+
+bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
+{
+ bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);
+ if (!canStack)
+ return false;
+
+ // don't stack if the item being checked against 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;
+ }
+
+ return true;
+}
+
+void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator)
+{
+ mSelectedEnchantItem = iterator;
+}
+
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem()
+{
+ return mSelectedEnchantItem;
+}
diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp
new file mode 100644
index 0000000000..f0cba0f9fe
--- /dev/null
+++ b/apps/openmw/mwworld/inventorystore.hpp
@@ -0,0 +1,114 @@
+#ifndef GAME_MWWORLD_INVENTORYSTORE_H
+#define GAME_MWWORLD_INVENTORYSTORE_H
+
+#include "containerstore.hpp"
+
+#include "../mwmechanics/magiceffects.hpp"
+
+namespace MWMechanics
+{
+ class NpcStats;
+}
+
+namespace MWWorld
+{
+ ///< \brief Variant of the ContainerStore for NPCs
+ class InventoryStore : public ContainerStore
+ {
+ public:
+
+ static const int Slot_Helmet = 0;
+ static const int Slot_Cuirass = 1;
+ static const int Slot_Greaves = 2;
+ static const int Slot_LeftPauldron = 3;
+ static const int Slot_RightPauldron = 4;
+ static const int Slot_LeftGauntlet = 5;
+ static const int Slot_RightGauntlet = 6;
+ static const int Slot_Boots = 7;
+ static const int Slot_Shirt = 8;
+ static const int Slot_Pants = 9;
+ static const int Slot_Skirt = 10;
+ static const int Slot_Robe = 11;
+ static const int Slot_LeftRing = 12;
+ static const int Slot_RightRing = 13;
+ static const int Slot_Amulet = 14;
+ static const int Slot_Belt = 15;
+ static const int Slot_CarriedRight = 16;
+ static const int Slot_CarriedLeft = 17;
+ static const int Slot_Ammunition = 18;
+
+ static const int Slots = 19;
+
+ static const int Slot_NoSlot = -1;
+
+ private:
+
+ mutable MWMechanics::MagicEffects mMagicEffects;
+ mutable bool mMagicEffectsUpToDate;
+
+ typedef std::vector<ContainerStoreIterator> TSlots;
+
+ mutable TSlots mSlots;
+
+ // selected magic item (for using enchantments of type "Cast once" or "Cast when used")
+ ContainerStoreIterator mSelectedEnchantItem;
+
+ void copySlots (const InventoryStore& store);
+
+ void initSlots (TSlots& slots);
+
+ public:
+
+ InventoryStore();
+
+ InventoryStore (const InventoryStore& store);
+
+ InventoryStore& operator= (const InventoryStore& store);
+
+ virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr);
+ ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
+ /// Auto-equip items if specific conditions are fulfilled (see the implementation).
+ ///
+ /// \note The item pointed to is not required to exist beyond this function call.
+ ///
+ /// \attention Do not add items to an existing stack by increasing the count instead of
+ /// calling this function!
+ ///
+ /// @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);
+ ///< \note \a iterator can be an end-iterator
+
+ void setSelectedEnchantItem(const ContainerStoreIterator& iterator);
+ ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used")
+ /// \note to unset the selected item, call this method with end() iterator
+
+ ContainerStoreIterator getSelectedEnchantItem();
+ ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used")
+ /// \note if no item selected, return end() iterator
+
+ ContainerStoreIterator getSlot (int slot);
+
+ void unequipAll(const MWWorld::Ptr& actor);
+ ///< Unequip all currently equipped items.
+
+ void autoEquip (const MWWorld::Ptr& npc);
+ ///< Auto equip items according to stats and item value.
+
+ const MWMechanics::MagicEffects& getMagicEffects();
+ ///< 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
+ /// outside.
+
+ 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
+
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp
new file mode 100644
index 0000000000..415351e783
--- /dev/null
+++ b/apps/openmw/mwworld/livecellref.hpp
@@ -0,0 +1,58 @@
+#ifndef GAME_MWWORLD_LIVECELLREF_H
+#define GAME_MWWORLD_LIVECELLREF_H
+
+#include <typeinfo>
+
+#include <components/esm/cellref.hpp>
+
+#include "refdata.hpp"
+
+namespace MWWorld
+{
+ class Ptr;
+ class ESMStore;
+ class Class;
+
+ /// Used to create pointers to hold any type of LiveCellRef<> object.
+ struct LiveCellRefBase
+ {
+ const Class *mClass;
+
+ /** Information about this instance, such as 3D location and rotation
+ * and individual type-dependent data.
+ */
+ ESM::CellRef mRef;
+
+ /** runtime-data */
+ RefData mData;
+
+ LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef());
+ /* Need this for the class to be recognized as polymorphic */
+ virtual ~LiveCellRefBase() { }
+ };
+
+ /// A reference to one object (of any type) in a cell.
+ ///
+ /// Constructing this with a CellRef instance in the constructor means that
+ /// in practice (where D is RefData) the possibly mutable data is copied
+ /// across to mData. If later adding data (such as position) to CellRef
+ /// this would have to be manually copied across.
+ template <typename X>
+ struct LiveCellRef : public LiveCellRefBase
+ {
+ LiveCellRef(const ESM::CellRef& cref, const X* b = NULL)
+ : LiveCellRefBase(typeid(X).name(), cref), mBase(b)
+ {}
+
+ LiveCellRef(const X* b = NULL)
+ : LiveCellRefBase(typeid(X).name()), mBase(b)
+ {}
+
+ // The object that this instance is based on.
+ const X* mBase;
+ };
+
+// template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum);
+}
+
+#endif
diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp
new file mode 100644
index 0000000000..5ec5ca9b56
--- /dev/null
+++ b/apps/openmw/mwworld/localscripts.cpp
@@ -0,0 +1,175 @@
+#include "localscripts.hpp"
+
+#include "esmstore.hpp"
+#include "cellstore.hpp"
+
+#include "class.hpp"
+#include "containerstore.hpp"
+
+
+namespace
+{
+ template<typename T>
+ void listCellScripts (MWWorld::LocalScripts& localScripts,
+ MWWorld::CellRefList<T>& cellRefList, MWWorld::Ptr::CellStore *cell)
+ {
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (
+ cellRefList.mList.begin());
+ iter!=cellRefList.mList.end(); ++iter)
+ {
+ if (!iter->mBase->mScript.empty() && iter->mData.getCount())
+ {
+ localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell));
+ }
+ }
+ }
+
+ // Adds scripts for items in containers (containers/npcs/creatures)
+ template<typename T>
+ void listCellScriptsCont (MWWorld::LocalScripts& localScripts,
+ MWWorld::CellRefList<T>& cellRefList, MWWorld::Ptr::CellStore *cell)
+ {
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (
+ cellRefList.mList.begin());
+ iter!=cellRefList.mList.end(); ++iter)
+ {
+
+ MWWorld::Ptr containerPtr (&*iter, cell);
+
+ MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr);
+ for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3)
+ {
+ std::string script = MWWorld::Class::get(*it3).getScript(*it3);
+ if(script != "")
+ {
+ MWWorld::Ptr item = *it3;
+ item.mCell = cell;
+ localScripts.add (script, item);
+ }
+ }
+ }
+ }
+}
+
+MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {}
+
+void MWWorld::LocalScripts::setIgnore (const Ptr& ptr)
+{
+ mIgnore = ptr;
+}
+
+void MWWorld::LocalScripts::startIteration()
+{
+ mIter = mScripts.begin();
+}
+
+bool MWWorld::LocalScripts::isFinished() const
+{
+ if (mIter==mScripts.end())
+ return true;
+
+ if (!mIgnore.isEmpty() && mIter->second==mIgnore)
+ {
+ std::list<std::pair<std::string, Ptr> >::iterator iter = mIter;
+ return ++iter==mScripts.end();
+ }
+
+ return false;
+}
+
+std::pair<std::string, MWWorld::Ptr> MWWorld::LocalScripts::getNext()
+{
+ assert (!isFinished());
+
+ std::list<std::pair<std::string, Ptr> >::iterator iter = mIter++;
+
+ if (mIgnore.isEmpty() || iter->second!=mIgnore)
+ return *iter;
+
+ return getNext();
+}
+
+void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr)
+{
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (scriptName))
+ {
+ ptr.getRefData().setLocals (*script);
+
+ mScripts.push_back (std::make_pair (scriptName, ptr));
+ }
+}
+
+void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell)
+{
+ listCellScripts (*this, cell->mActivators, cell);
+ listCellScripts (*this, cell->mPotions, cell);
+ listCellScripts (*this, cell->mAppas, cell);
+ listCellScripts (*this, cell->mArmors, cell);
+ listCellScripts (*this, cell->mBooks, cell);
+ listCellScripts (*this, cell->mClothes, cell);
+ listCellScripts (*this, cell->mContainers, cell);
+ listCellScriptsCont (*this, cell->mContainers, cell);
+ listCellScripts (*this, cell->mCreatures, cell);
+ listCellScriptsCont (*this, cell->mCreatures, cell);
+ listCellScripts (*this, cell->mDoors, cell);
+ listCellScripts (*this, cell->mIngreds, cell);
+ listCellScripts (*this, cell->mLights, cell);
+ listCellScripts (*this, cell->mLockpicks, cell);
+ listCellScripts (*this, cell->mMiscItems, cell);
+ listCellScripts (*this, cell->mNpcs, cell);
+ listCellScriptsCont (*this, cell->mNpcs, cell);
+ listCellScripts (*this, cell->mProbes, cell);
+ listCellScripts (*this, cell->mRepairs, cell);
+ listCellScripts (*this, cell->mWeapons, cell);
+}
+
+void MWWorld::LocalScripts::clear()
+{
+ mScripts.clear();
+}
+
+void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell)
+{
+ std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
+
+ while (iter!=mScripts.end())
+ {
+ if (iter->second.mCell==cell)
+ {
+ if (iter==mIter)
+ ++mIter;
+
+ mScripts.erase (iter++);
+ }
+ else
+ ++iter;
+ }
+}
+
+void MWWorld::LocalScripts::remove (RefData *ref)
+{
+ for (std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
+ iter!=mScripts.end(); ++iter)
+ if (&(iter->second.getRefData()) == ref)
+ {
+ if (iter==mIter)
+ ++mIter;
+
+ mScripts.erase (iter);
+ break;
+ }
+}
+
+void MWWorld::LocalScripts::remove (const Ptr& ptr)
+{
+ for (std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
+ iter!=mScripts.end(); ++iter)
+ if (iter->second==ptr)
+ {
+ if (iter==mIter)
+ ++mIter;
+
+ mScripts.erase (iter);
+ break;
+ }
+}
diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp
new file mode 100644
index 0000000000..840243fffc
--- /dev/null
+++ b/apps/openmw/mwworld/localscripts.hpp
@@ -0,0 +1,59 @@
+#ifndef GAME_MWWORLD_LOCALSCRIPTS_H
+#define GAME_MWWORLD_LOCALSCRIPTS_H
+
+#include <list>
+#include <string>
+
+#include "ptr.hpp"
+
+namespace MWWorld
+{
+ struct ESMStore;
+ class CellStore;
+ class RefData;
+
+ /// \brief List of active local scripts
+ class LocalScripts
+ {
+ std::list<std::pair<std::string, Ptr> > mScripts;
+ std::list<std::pair<std::string, Ptr> >::iterator mIter;
+ MWWorld::Ptr mIgnore;
+ const MWWorld::ESMStore& mStore;
+
+ public:
+
+ LocalScripts (const MWWorld::ESMStore& store);
+
+ void setIgnore (const Ptr& ptr);
+ ///< Mark a single reference for ignoring during iteration over local scripts (will revoke
+ /// previous ignores).
+
+ void startIteration();
+ ///< Set the iterator to the begin of the script list.
+
+ bool isFinished() const;
+ ///< Is iteration finished?
+
+ std::pair<std::string, Ptr> getNext();
+ ///< Get next local script (must not be called if isFinished())
+
+ void add (const std::string& scriptName, const Ptr& ptr);
+ ///< Add script to collection of active local scripts.
+
+ void addCell (CellStore *cell);
+ ///< Add all local scripts in a cell.
+
+ void clear();
+ ///< Clear active local scripts collection.
+
+ void clearCell (CellStore *cell);
+ ///< Remove all scripts belonging to \a cell.
+
+ void remove (RefData *ref);
+
+ void remove (const Ptr& ptr);
+ ///< Remove script for given reference (ignored if reference does not have a scirpt listed).
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp
new file mode 100644
index 0000000000..6616165fae
--- /dev/null
+++ b/apps/openmw/mwworld/manualref.hpp
@@ -0,0 +1,86 @@
+#ifndef GAME_MWWORLD_MANUALREF_H
+#define GAME_MWWORLD_MANUALREF_H
+
+#include <boost/any.hpp>
+
+#include "esmstore.hpp"
+#include "ptr.hpp"
+#include "cellstore.hpp"
+
+namespace MWWorld
+{
+ /// \brief Manually constructed live cell ref
+ class ManualRef
+ {
+ boost::any mRef;
+ Ptr mPtr;
+
+ ManualRef (const ManualRef&);
+ ManualRef& operator= (const ManualRef&);
+
+ template<typename T>
+ bool create (const MWWorld::Store<T>& list, const std::string& name)
+ {
+ if (const T *instance = list.search (name))
+ {
+ LiveCellRef<T> ref;
+ ref.mBase = instance;
+
+ mRef = ref;
+ mPtr = Ptr (&boost::any_cast<LiveCellRef<T>&> (mRef), 0);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public:
+
+ ManualRef (const MWWorld::ESMStore& store, const std::string& name)
+ {
+ // create
+ if (!create (store.get<ESM::Activator>(), name) &&
+ !create (store.get<ESM::Potion>(), name) &&
+ !create (store.get<ESM::Apparatus>(), name) &&
+ !create (store.get<ESM::Armor>(), name) &&
+ !create (store.get<ESM::Book>(), name) &&
+ !create (store.get<ESM::Clothing>(), name) &&
+ !create (store.get<ESM::Container>(), name) &&
+ !create (store.get<ESM::Creature>(), name) &&
+ !create (store.get<ESM::Door>(), name) &&
+ !create (store.get<ESM::Ingredient>(), name) &&
+ !create (store.get<ESM::CreatureLevList>(), name) &&
+ !create (store.get<ESM::ItemLevList>(), name) &&
+ !create (store.get<ESM::Light>(), name) &&
+ !create (store.get<ESM::Lockpick>(), name) &&
+ !create (store.get<ESM::Miscellaneous>(), name) &&
+ !create (store.get<ESM::NPC>(), name) &&
+ !create (store.get<ESM::Probe>(), name) &&
+ !create (store.get<ESM::Repair>(), name) &&
+ !create (store.get<ESM::Static>(), name) &&
+ !create (store.get<ESM::Weapon>(), name))
+ throw std::logic_error ("failed to create manual cell ref for " + name);
+
+ // initialise
+ ESM::CellRef& cellRef = mPtr.getCellRef();
+ cellRef.mRefID = name;
+ cellRef.mRefnum = -1;
+ cellRef.mScale = 1;
+ cellRef.mFactIndex = 0;
+ cellRef.mCharge = -1;
+ cellRef.mGoldValue = 1;
+ cellRef.mEnchantmentCharge = -1;
+ cellRef.mTeleport = false;
+ cellRef.mLockLevel = 0;
+ cellRef.mReferenceBlocked = 0;
+ }
+
+ const Ptr& getPtr() const
+ {
+ return mPtr;
+ }
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/nullaction.hpp b/apps/openmw/mwworld/nullaction.hpp
new file mode 100644
index 0000000000..7ef8b4a065
--- /dev/null
+++ b/apps/openmw/mwworld/nullaction.hpp
@@ -0,0 +1,15 @@
+#ifndef GAME_MWWORLD_NULLACTION_H
+#define GAME_MWWORLD_NULLACTION_H
+
+#include "action.hpp"
+
+namespace MWWorld
+{
+ /// \brief Action: do nothing
+ class NullAction : public Action
+ {
+ virtual void executeImp (const Ptr& actor) {}
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp
new file mode 100644
index 0000000000..433fe08925
--- /dev/null
+++ b/apps/openmw/mwworld/physicssystem.cpp
@@ -0,0 +1,588 @@
+#include "physicssystem.hpp"
+
+#include <stdexcept>
+
+#include <OgreRoot.h>
+#include <OgreRenderWindow.h>
+#include <OgreSceneManager.h>
+#include <OgreViewport.h>
+#include <OgreCamera.h>
+#include <OgreTextureManager.h>
+
+#include <openengine/bullet/trace.h>
+#include <openengine/bullet/physic.hpp>
+#include <openengine/ogre/renderer.hpp>
+
+#include <components/nifbullet/bulletnifloader.hpp>
+
+#include "../mwbase/world.hpp" // FIXME
+#include "../mwbase/environment.hpp"
+
+#include <components/esm/loadgmst.hpp>
+#include "../mwworld/esmstore.hpp"
+
+#include "ptr.hpp"
+#include "class.hpp"
+
+using namespace Ogre;
+namespace MWWorld
+{
+
+ static const float sMaxSlope = 60.0f;
+ static const float sStepSize = 32.0f;
+ // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
+ static const int sMaxIterations = 8;
+
+ class MovementSolver
+ {
+ private:
+ static float getSlope(const Ogre::Vector3 &normal)
+ {
+ return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
+ }
+
+ static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position,
+ const Ogre::Vector3 &velocity, float &remainingTime,
+ OEngine::Physic::PhysicEngine *engine)
+ {
+ OEngine::Physic::ActorTracer tracer, stepper;
+
+ stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine);
+ if(stepper.mFraction < std::numeric_limits<float>::epsilon())
+ return false;
+
+ tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + velocity*remainingTime, engine);
+ if(tracer.mFraction < std::numeric_limits<float>::epsilon())
+ return false;
+
+ stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine);
+ if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope)
+ {
+ // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
+ position = stepper.mEndPos;
+ remainingTime *= (1.0f-tracer.mFraction);
+ return true;
+ }
+
+ return false;
+ }
+
+
+ ///Project a vector u on another vector v
+ static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v)
+ {
+ return v * u.dotProduct(v);
+ }
+
+ ///Helper for computing the character sliding
+ static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal)
+ {
+ return direction - project(direction, planeNormal);
+ }
+
+
+ public:
+ static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
+ {
+ const ESM::Position &refpos = ptr.getRefData().getPosition();
+ Ogre::Vector3 position(refpos.pos);
+
+ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
+ if (!physicActor)
+ return position;
+
+ const int maxHeight = 200.f;
+ OEngine::Physic::ActorTracer tracer;
+ tracer.findGround(physicActor->getCollisionBody(), position, position-Ogre::Vector3(0,0,maxHeight), engine);
+ if(tracer.mFraction >= 1.0f)
+ {
+ physicActor->setOnGround(false);
+ return position;
+ }
+
+ physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope);
+
+ return tracer.mEndPos;
+ }
+
+ static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
+ bool isFlying, float waterlevel, OEngine::Physic::PhysicEngine *engine)
+ {
+ const ESM::Position &refpos = ptr.getRefData().getPosition();
+ Ogre::Vector3 position(refpos.pos);
+
+ /* Anything to collide with? */
+ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
+ if(!physicActor || !physicActor->getCollisionMode())
+ {
+ // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why?
+ return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*
+ Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
+ Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) *
+ movement * time;
+ }
+
+ btCollisionObject *colobj = physicActor->getCollisionBody();
+ Ogre::Vector3 halfExtents = physicActor->getHalfExtents();
+ position.z += halfExtents.z;
+
+ waterlevel -= halfExtents.z * 0.5;
+
+ OEngine::Physic::ActorTracer tracer;
+ bool wasOnGround = false;
+ bool isOnGround = false;
+ Ogre::Vector3 inertia(0.0f);
+ Ogre::Vector3 velocity;
+ if(position.z < waterlevel || isFlying)
+ {
+ velocity = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*
+ Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
+ Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) *
+ movement;
+ }
+ else
+ {
+ velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z) * movement;
+ if(!physicActor->getOnGround())
+ {
+ // If falling, add part of the incoming velocity with the current inertia
+ velocity = velocity*time + physicActor->getInertialForce();
+ }
+ inertia = velocity;
+
+ if(!(movement.z > 0.0f))
+ {
+ wasOnGround = physicActor->getOnGround();
+ tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,2), engine);
+ if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope)
+ isOnGround = true;
+ }
+ }
+
+ if(isOnGround)
+ {
+ // if we're on the ground, don't try to fall
+ velocity.z = std::max(0.0f, velocity.z);
+ }
+
+ Ogre::Vector3 newPosition = position;
+ float remainingTime = time;
+ for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations)
+ {
+ Ogre::Vector3 nextpos = newPosition + velocity*remainingTime;
+
+ if(newPosition.z < waterlevel && !isFlying &&
+ nextpos.z > waterlevel && newPosition.z <= waterlevel)
+ {
+ const Ogre::Vector3 down(0,0,-1);
+ Ogre::Real movelen = velocity.normalise();
+ Ogre::Vector3 reflectdir = velocity.reflect(down);
+ reflectdir.normalise();
+ velocity = slide(reflectdir, down)*movelen;
+ continue;
+ }
+
+ // trace to where character would go if there were no obstructions
+ tracer.doTrace(colobj, newPosition, nextpos, engine);
+
+ // check for obstructions
+ if(tracer.mFraction >= 1.0f)
+ {
+ newPosition = tracer.mEndPos;
+ remainingTime *= (1.0f-tracer.mFraction);
+ break;
+ }
+
+ // We hit something. Try to step up onto it.
+ if(stepMove(colobj, newPosition, velocity, remainingTime, engine))
+ isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity
+ else
+ {
+ // Can't move this way, try to find another spot along the plane
+ Ogre::Real movelen = velocity.normalise();
+ Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal);
+ reflectdir.normalise();
+ velocity = slide(reflectdir, tracer.mPlaneNormal)*movelen;
+
+ // Do not allow sliding upward if there is gravity. Stepping will have taken
+ // care of that.
+ if(!(newPosition.z < waterlevel || isFlying))
+ velocity.z = std::min(velocity.z, 0.0f);
+ }
+ }
+
+ if(isOnGround || wasOnGround)
+ {
+ tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+2.0f), engine);
+ if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope)
+ {
+ newPosition.z = tracer.mEndPos.z + 1.0f;
+ isOnGround = true;
+ }
+ else
+ isOnGround = false;
+ }
+
+ if(isOnGround || newPosition.z < waterlevel || isFlying)
+ physicActor->setInertialForce(Ogre::Vector3(0.0f));
+ else
+ {
+ inertia.z += time*-627.2f;
+ physicActor->setInertialForce(inertia);
+ }
+ physicActor->setOnGround(isOnGround);
+
+ newPosition.z -= halfExtents.z;
+ return newPosition;
+ }
+ };
+
+
+ PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) :
+ mRender(_rend), mEngine(0), mTimeAccum(0.0f)
+ {
+ // Create physics. shapeLoader is deleted by the physic engine
+ NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader();
+ mEngine = new OEngine::Physic::PhysicEngine(shapeLoader);
+ }
+
+ PhysicsSystem::~PhysicsSystem()
+ {
+ delete mEngine;
+ }
+
+ OEngine::Physic::PhysicEngine* PhysicsSystem::getEngine()
+ {
+ return mEngine;
+ }
+
+ std::pair<float, std::string> PhysicsSystem::getFacedHandle(float queryDistance)
+ {
+ Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5);
+
+ Ogre::Vector3 origin_ = ray.getOrigin();
+ btVector3 origin(origin_.x, origin_.y, origin_.z);
+ Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy();
+ btVector3 dir(dir_.x, dir_.y, dir_.z);
+
+ btVector3 dest = origin + dir * queryDistance;
+ std::pair <std::string, float> result = mEngine->rayTest(origin, dest);
+ result.second *= queryDistance;
+
+ return std::make_pair (result.second, result.first);
+ }
+
+ std::vector < std::pair <float, std::string> > PhysicsSystem::getFacedHandles (float queryDistance)
+ {
+ Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5);
+
+ Ogre::Vector3 origin_ = ray.getOrigin();
+ btVector3 origin(origin_.x, origin_.y, origin_.z);
+ Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy();
+ btVector3 dir(dir_.x, dir_.y, dir_.z);
+
+ btVector3 dest = origin + dir * queryDistance;
+ std::vector < std::pair <float, std::string> > results;
+ /* auto */ results = mEngine->rayTest2(origin, dest);
+ std::vector < std::pair <float, std::string> >::iterator i;
+ for (/* auto */ i = results.begin (); i != results.end (); ++i)
+ i->first *= queryDistance;
+ return results;
+ }
+
+ std::vector < std::pair <float, std::string> > PhysicsSystem::getFacedHandles (float mouseX, float mouseY, float queryDistance)
+ {
+ Ray ray = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY);
+ Ogre::Vector3 from = ray.getOrigin();
+ Ogre::Vector3 to = ray.getPoint(queryDistance);
+
+ btVector3 _from, _to;
+ _from = btVector3(from.x, from.y, from.z);
+ _to = btVector3(to.x, to.y, to.z);
+
+ std::vector < std::pair <float, std::string> > results;
+ /* auto */ results = mEngine->rayTest2(_from,_to);
+ std::vector < std::pair <float, std::string> >::iterator i;
+ for (/* auto */ i = results.begin (); i != results.end (); ++i)
+ i->first *= queryDistance;
+ return results;
+ }
+
+ std::pair<std::string,Ogre::Vector3> PhysicsSystem::getHitContact(const std::string &name,
+ const Ogre::Vector3 &origin,
+ const Ogre::Quaternion &orient,
+ float queryDistance)
+ {
+ const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ btConeShape shape(Ogre::Degree(store.find("fCombatAngleXY")->getFloat()/2.0f).valueRadians(),
+ queryDistance);
+ shape.setLocalScaling(btVector3(1, 1, Ogre::Degree(store.find("fCombatAngleZ")->getFloat()/2.0f).valueRadians() /
+ shape.getRadius()));
+
+ // The shape origin is its center, so we have to move it forward by half the length. The
+ // real origin will be provided to getFilteredContact to find the closest.
+ Ogre::Vector3 center = origin + (orient * Ogre::Vector3(0.0f, queryDistance*0.5f, 0.0f));
+
+ btCollisionObject object;
+ object.setCollisionShape(&shape);
+ object.setWorldTransform(btTransform(btQuaternion(orient.x, orient.y, orient.z, orient.w),
+ btVector3(center.x, center.y, center.z)));
+
+ std::pair<const OEngine::Physic::RigidBody*,btVector3> result = mEngine->getFilteredContact(
+ name, btVector3(origin.x, origin.y, origin.z), &object);
+ if(!result.first)
+ return std::make_pair(std::string(), Ogre::Vector3(&result.second[0]));
+ return std::make_pair(result.first->mName, Ogre::Vector3(&result.second[0]));
+ }
+
+
+ bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to, bool raycastingObjectOnly,bool ignoreHeightMap)
+ {
+ btVector3 _from, _to;
+ _from = btVector3(from.x, from.y, from.z);
+ _to = btVector3(to.x, to.y, to.z);
+
+ std::pair<std::string, float> result = mEngine->rayTest(_from, _to, raycastingObjectOnly,ignoreHeightMap);
+ return !(result.first == "");
+ }
+
+ std::pair<bool, Ogre::Vector3>
+ PhysicsSystem::castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len)
+ {
+ Ogre::Ray ray = Ogre::Ray(orig, dir);
+ Ogre::Vector3 to = ray.getPoint(len);
+
+ btVector3 btFrom = btVector3(orig.x, orig.y, orig.z);
+ btVector3 btTo = btVector3(to.x, to.y, to.z);
+
+ std::pair<std::string, float> test = mEngine->rayTest(btFrom, btTo);
+ if (test.second == -1) {
+ return std::make_pair(false, Ogre::Vector3());
+ }
+ return std::make_pair(true, ray.getPoint(len * test.second));
+ }
+
+ std::pair<bool, Ogre::Vector3> PhysicsSystem::castRay(float mouseX, float mouseY)
+ {
+ Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay(
+ mouseX,
+ mouseY);
+ Ogre::Vector3 from = ray.getOrigin();
+ Ogre::Vector3 to = ray.getPoint(200); /// \todo make this distance (ray length) configurable
+
+ btVector3 _from, _to;
+ _from = btVector3(from.x, from.y, from.z);
+ _to = btVector3(to.x, to.y, to.z);
+
+ std::pair<std::string, float> result = mEngine->rayTest(_from, _to);
+
+ if (result.first == "")
+ return std::make_pair(false, Ogre::Vector3());
+ else
+ {
+ return std::make_pair(true, ray.getPoint(200*result.second)); /// \todo make this distance (ray length) configurable
+ }
+ }
+
+ std::vector<std::string> PhysicsSystem::getCollisions(const Ptr &ptr)
+ {
+ return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName());
+ }
+
+ Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr)
+ {
+ return MovementSolver::traceDown(ptr, mEngine);
+ }
+
+ void PhysicsSystem::addHeightField (float* heights,
+ int x, int y, float yoffset,
+ float triSize, float sqrtVerts)
+ {
+ mEngine->addHeightField(heights, x, y, yoffset, triSize, sqrtVerts);
+ }
+
+ void PhysicsSystem::removeHeightField (int x, int y)
+ {
+ mEngine->removeHeightField(x, y);
+ }
+
+ void PhysicsSystem::addObject (const Ptr& ptr, bool placeable)
+ {
+ std::string mesh = MWWorld::Class::get(ptr).getModel(ptr);
+ Ogre::SceneNode* node = ptr.getRefData().getBaseNode();
+ handleToMesh[node->getName()] = mesh;
+ OEngine::Physic::RigidBody* body = mEngine->createAndAdjustRigidBody(
+ mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, false, placeable);
+ OEngine::Physic::RigidBody* raycastingBody = mEngine->createAndAdjustRigidBody(
+ mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, true, placeable);
+ mEngine->addRigidBody(body, true, raycastingBody);
+ }
+
+ void PhysicsSystem::addActor (const Ptr& ptr)
+ {
+ std::string mesh = MWWorld::Class::get(ptr).getModel(ptr);
+ Ogre::SceneNode* node = ptr.getRefData().getBaseNode();
+ //TODO:optimize this. Searching the std::map isn't very efficient i think.
+ mEngine->addCharacter(node->getName(), mesh, node->getPosition(), node->getScale().x, node->getOrientation());
+ }
+
+ void PhysicsSystem::removeObject (const std::string& handle)
+ {
+ mEngine->removeCharacter(handle);
+ mEngine->removeRigidBody(handle);
+ mEngine->deleteRigidBody(handle);
+ }
+
+ void PhysicsSystem::moveObject (const Ptr& ptr)
+ {
+ Ogre::SceneNode *node = ptr.getRefData().getBaseNode();
+ const std::string &handle = node->getName();
+ const Ogre::Vector3 &position = node->getPosition();
+
+ if(OEngine::Physic::RigidBody *body = mEngine->getRigidBody(handle))
+ body->getWorldTransform().setOrigin(btVector3(position.x,position.y,position.z));
+
+ if(OEngine::Physic::RigidBody *body = mEngine->getRigidBody(handle, true))
+ body->getWorldTransform().setOrigin(btVector3(position.x,position.y,position.z));
+
+ if(OEngine::Physic::PhysicActor *physact = mEngine->getCharacter(handle))
+ physact->setPosition(position);
+ }
+
+ void PhysicsSystem::rotateObject (const Ptr& ptr)
+ {
+ Ogre::SceneNode* node = ptr.getRefData().getBaseNode();
+ const std::string &handle = node->getName();
+ const Ogre::Quaternion &rotation = node->getOrientation();
+ if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle))
+ {
+ //Needs to be changed
+ act->setRotation(rotation);
+ }
+ if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle))
+ {
+ if(dynamic_cast<btBoxShape*>(body->getCollisionShape()) == NULL)
+ body->getWorldTransform().setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w));
+ else
+ mEngine->boxAdjustExternal(handleToMesh[handle], body, node->getScale().x, node->getPosition(), rotation);
+ }
+ if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle, true))
+ {
+ if(dynamic_cast<btBoxShape*>(body->getCollisionShape()) == NULL)
+ body->getWorldTransform().setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w));
+ else
+ mEngine->boxAdjustExternal(handleToMesh[handle], body, node->getScale().x, node->getPosition(), rotation);
+ }
+ }
+
+ void PhysicsSystem::scaleObject (const Ptr& ptr)
+ {
+ Ogre::SceneNode* node = ptr.getRefData().getBaseNode();
+ const std::string &handle = node->getName();
+ if(handleToMesh.find(handle) != handleToMesh.end())
+ {
+ bool placeable = false;
+ if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,true))
+ placeable = body->mPlaceable;
+ else if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,false))
+ placeable = body->mPlaceable;
+ removeObject(handle);
+ addObject(ptr, placeable);
+ }
+
+ if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle))
+ act->setScale(node->getScale().x);
+ }
+
+ bool PhysicsSystem::toggleCollisionMode()
+ {
+ for(std::map<std::string,OEngine::Physic::PhysicActor*>::iterator it = mEngine->mActorMap.begin(); it != mEngine->mActorMap.end();++it)
+ {
+ if (it->first=="player")
+ {
+ OEngine::Physic::PhysicActor* act = it->second;
+
+ bool cmode = act->getCollisionMode();
+ if(cmode)
+ {
+ act->enableCollisions(false);
+ return false;
+ }
+ else
+ {
+ act->enableCollisions(true);
+ return true;
+ }
+ }
+ }
+
+ throw std::logic_error ("can't find player");
+ }
+
+ bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max)
+ {
+ std::string model = MWWorld::Class::get(ptr).getModel(ptr);
+ if (model.empty()) {
+ return false;
+ }
+ btVector3 btMin, btMax;
+ float scale = ptr.getCellRef().mScale;
+ mEngine->getObjectAABB(model, scale, btMin, btMax);
+
+ min.x = btMin.x();
+ min.y = btMin.y();
+ min.z = btMin.z();
+
+ max.x = btMax.x();
+ max.y = btMax.y();
+ max.z = btMax.z();
+
+ return true;
+ }
+
+
+ void PhysicsSystem::queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &movement)
+ {
+ PtrVelocityList::iterator iter = mMovementQueue.begin();
+ for(;iter != mMovementQueue.end();iter++)
+ {
+ if(iter->first == ptr)
+ {
+ iter->second = movement;
+ return;
+ }
+ }
+
+ mMovementQueue.push_back(std::make_pair(ptr, movement));
+ }
+
+ const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt)
+ {
+ mMovementResults.clear();
+
+ mTimeAccum += dt;
+ if(mTimeAccum >= 1.0f/60.0f)
+ {
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ PtrVelocityList::iterator iter = mMovementQueue.begin();
+ for(;iter != mMovementQueue.end();iter++)
+ {
+ float waterlevel = -std::numeric_limits<float>::max();
+ const ESM::Cell *cell = iter->first.getCell()->mCell;
+ if(cell->hasWater())
+ waterlevel = cell->mWater;
+
+ Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum,
+ world->isFlying(iter->first),
+ waterlevel, mEngine);
+ mMovementResults.push_back(std::make_pair(iter->first, newpos));
+ }
+
+ mTimeAccum = 0.0f;
+ }
+ mMovementQueue.clear();
+
+ return mMovementResults;
+ }
+}
diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp
new file mode 100644
index 0000000000..3dcd088f54
--- /dev/null
+++ b/apps/openmw/mwworld/physicssystem.hpp
@@ -0,0 +1,102 @@
+#ifndef GAME_MWWORLD_PHYSICSSYSTEM_H
+#define GAME_MWWORLD_PHYSICSSYSTEM_H
+
+#include <OgreVector3.h>
+
+#include <btBulletCollisionCommon.h>
+
+#include "ptr.hpp"
+
+
+namespace OEngine
+{
+ namespace Render
+ {
+ class OgreRenderer;
+ }
+ namespace Physic
+ {
+ class PhysicEngine;
+ }
+}
+
+namespace MWWorld
+{
+ class World;
+
+ typedef std::vector<std::pair<Ptr,Ogre::Vector3> > PtrVelocityList;
+
+ class PhysicsSystem
+ {
+ public:
+ PhysicsSystem (OEngine::Render::OgreRenderer &_rend);
+ ~PhysicsSystem ();
+
+ void addObject (const MWWorld::Ptr& ptr, bool placeable=false);
+
+ void addActor (const MWWorld::Ptr& ptr);
+
+ void addHeightField (float* heights,
+ int x, int y, float yoffset,
+ float triSize, float sqrtVerts);
+
+ void removeHeightField (int x, int y);
+
+ // have to keep this as handle for now as unloadcell only knows scenenode names
+ void removeObject (const std::string& handle);
+
+ void moveObject (const MWWorld::Ptr& ptr);
+
+ void rotateObject (const MWWorld::Ptr& ptr);
+
+ void scaleObject (const MWWorld::Ptr& ptr);
+
+ bool toggleCollisionMode();
+
+ std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with
+ Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr);
+
+ std::pair<float, std::string> getFacedHandle(float queryDistance);
+ std::pair<std::string,Ogre::Vector3> getHitContact(const std::string &name,
+ const Ogre::Vector3 &origin,
+ const Ogre::Quaternion &orientation,
+ float queryDistance);
+ std::vector < std::pair <float, std::string> > getFacedHandles (float queryDistance);
+ std::vector < std::pair <float, std::string> > getFacedHandles (float mouseX, float mouseY, float queryDistance);
+
+ // cast ray, return true if it hit something. if raycasringObjectOnlt is set to false, it ignores NPCs and objects with no collisions.
+ bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to, bool raycastingObjectOnly = true,bool ignoreHeightMap = false);
+
+ std::pair<bool, Ogre::Vector3>
+ castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len);
+
+ std::pair<bool, Ogre::Vector3> castRay(float mouseX, float mouseY);
+ ///< cast ray from the mouse, return true if it hit something and the first result (in OGRE coordinates)
+
+ OEngine::Physic::PhysicEngine* getEngine();
+
+ bool getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max);
+
+ /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will
+ /// be overwritten. Valid until the next call to applyQueuedMovement.
+ void queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &velocity);
+
+ const PtrVelocityList& applyQueuedMovement(float dt);
+
+ private:
+
+ OEngine::Render::OgreRenderer &mRender;
+ OEngine::Physic::PhysicEngine* mEngine;
+ std::map<std::string, std::string> handleToMesh;
+
+ PtrVelocityList mMovementQueue;
+ PtrVelocityList mMovementResults;
+
+ float mTimeAccum;
+
+ PhysicsSystem (const PhysicsSystem&);
+ PhysicsSystem& operator= (const PhysicsSystem&);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp
new file mode 100644
index 0000000000..e26c2e2a52
--- /dev/null
+++ b/apps/openmw/mwworld/player.cpp
@@ -0,0 +1,148 @@
+
+#include "player.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwmechanics/movement.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "class.hpp"
+
+namespace MWWorld
+{
+ Player::Player (const ESM::NPC *player, const MWBase::World& world)
+ : mCellStore(0),
+ mAutoMove(false),
+ mForwardBackward (0)
+ {
+ mPlayer.mBase = player;
+ mPlayer.mRef.mRefID = "player";
+
+ float* playerPos = mPlayer.mData.getPosition().pos;
+ playerPos[0] = playerPos[1] = playerPos[2] = 0;
+ }
+
+ void Player::set(const ESM::NPC *player)
+ {
+ mPlayer.mBase = player;
+
+ float* playerPos = mPlayer.mData.getPosition().pos;
+ playerPos[0] = playerPos[1] = playerPos[2] = 0;
+ }
+
+ void Player::setCell (MWWorld::CellStore *cellStore)
+ {
+ mCellStore = cellStore;
+ }
+
+ MWWorld::Ptr Player::getPlayer()
+ {
+ MWWorld::Ptr ptr (&mPlayer, mCellStore);
+ return ptr;
+ }
+
+ void Player::setBirthSign (const std::string &sign)
+ {
+ mSign = sign;
+ }
+
+ const std::string& Player::getBirthSign() const
+ {
+ return mSign;
+ }
+
+ void Player::setDrawState (MWMechanics::DrawState_ state)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ MWWorld::Class::get(ptr).getNpcStats(ptr).setDrawState (state);
+ }
+
+ bool Player::getAutoMove() const
+ {
+ return mAutoMove;
+ }
+
+ void Player::setAutoMove (bool enable)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+
+ mAutoMove = enable;
+
+ int value = mForwardBackward;
+
+ if (mAutoMove)
+ value = 1;
+
+ MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[1] = value;
+ }
+
+ void Player::setLeftRight (int value)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+
+ MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[0] = value;
+ }
+
+ void Player::setForwardBackward (int value)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+
+ mForwardBackward = value;
+
+ if (mAutoMove)
+ value = 1;
+
+ MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[1] = value;
+ }
+
+ void Player::setUpDown(int value)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+
+ MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[2] = value;
+ }
+
+ void Player::setRunState(bool run)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ MWWorld::Class::get(ptr).setStance(ptr, MWWorld::Class::Run, run);
+ }
+
+ void Player::setSneak(bool sneak)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+
+ MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Sneak, sneak);
+
+ // TODO show sneak indicator only when the player is not detected by any actor
+ MWBase::Environment::get().getWindowManager()->setSneakVisibility(sneak);
+ }
+
+ void Player::yaw(float yaw)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[2] += yaw;
+ }
+ void Player::pitch(float pitch)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[0] += pitch;
+ }
+ void Player::roll(float roll)
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[1] += roll;
+ }
+
+ MWMechanics::DrawState_ Player::getDrawState()
+ {
+ MWWorld::Ptr ptr = getPlayer();
+ return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState();
+ }
+}
diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp
new file mode 100644
index 0000000000..d78b1901c4
--- /dev/null
+++ b/apps/openmw/mwworld/player.hpp
@@ -0,0 +1,69 @@
+#ifndef GAME_MWWORLD_PLAYER_H
+#define GAME_MWWORLD_PLAYER_H
+
+#include "../mwworld/refdata.hpp"
+#include "../mwworld/livecellref.hpp"
+
+#include "../mwmechanics/drawstate.hpp"
+
+namespace ESM
+{
+ struct NPC;
+}
+
+namespace MWBase
+{
+ class World;
+ class Ptr;
+}
+
+namespace MWWorld
+{
+ class CellStore;
+
+ /// \brief NPC object representing the player and additional player data
+ class Player
+ {
+ LiveCellRef<ESM::NPC> mPlayer;
+ MWWorld::CellStore *mCellStore;
+ std::string mSign;
+
+ bool mAutoMove;
+ int mForwardBackward;
+
+ public:
+
+ Player(const ESM::NPC *player, const MWBase::World& world);
+
+ void set (const ESM::NPC *player);
+
+ void setCell (MWWorld::CellStore *cellStore);
+
+ MWWorld::Ptr getPlayer();
+
+ void setBirthSign(const std::string &sign);
+
+ const std::string &getBirthSign() const;
+
+ void setDrawState (MWMechanics::DrawState_ state);
+
+ bool getAutoMove() const;
+
+ MWMechanics::DrawState_ getDrawState(); /// \todo constness
+
+ void setAutoMove (bool enable);
+
+ void setLeftRight (int value);
+
+ void setForwardBackward (int value);
+ void setUpDown(int value);
+
+ void setRunState(bool run);
+ void setSneak(bool sneak);
+
+ void yaw(float yaw);
+ void pitch(float pitch);
+ void roll(float roll);
+ };
+}
+#endif
diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp
new file mode 100644
index 0000000000..127ab1364c
--- /dev/null
+++ b/apps/openmw/mwworld/ptr.cpp
@@ -0,0 +1,55 @@
+
+#include "ptr.hpp"
+
+#include <cassert>
+
+#include "containerstore.hpp"
+#include "class.hpp"
+
+
+/* This shouldn't really be here. */
+MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref)
+ : mClass(&Class::get(type)), mRef(cref), mData(mRef)
+{
+}
+
+
+const std::string& MWWorld::Ptr::getTypeName() const
+{
+ if(mRef != 0)
+ return mRef->mClass->getTypeName();
+ throw std::runtime_error("Can't get type name from an empty object.");
+}
+
+ESM::CellRef& MWWorld::Ptr::getCellRef() const
+{
+ assert(mRef);
+
+ if (mContainerStore)
+ mContainerStore->flagAsModified();
+
+ return mRef->mRef;
+}
+
+MWWorld::RefData& MWWorld::Ptr::getRefData() const
+{
+ assert(mRef);
+
+ if (mContainerStore)
+ mContainerStore->flagAsModified();
+
+ return mRef->mData;
+}
+
+void MWWorld::Ptr::setContainerStore (ContainerStore *store)
+{
+ assert (store);
+ assert (!mCell);
+
+ mContainerStore = store;
+}
+
+MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const
+{
+ return mContainerStore;
+}
diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp
new file mode 100644
index 0000000000..e5352da280
--- /dev/null
+++ b/apps/openmw/mwworld/ptr.hpp
@@ -0,0 +1,111 @@
+#ifndef GAME_MWWORLD_PTR_H
+#define GAME_MWWORLD_PTR_H
+
+#include "cellstore.hpp"
+#include "livecellref.hpp"
+
+namespace MWWorld
+{
+ class ContainerStore;
+
+ /// \brief Pointer to a LiveCellRef
+
+ class Ptr
+ {
+ public:
+
+ typedef MWWorld::CellStore CellStore;
+ ///< \deprecated
+
+ MWWorld::LiveCellRefBase *mRef;
+ CellStore *mCell;
+ ContainerStore *mContainerStore;
+
+ public:
+ Ptr(MWWorld::LiveCellRefBase *liveCellRef=0, CellStore *cell=0)
+ : mRef(liveCellRef), mCell(cell), mContainerStore(0)
+ {
+ }
+
+ bool isEmpty() const
+ {
+ return mRef == 0;
+ }
+
+ const std::string& getTypeName() const;
+
+ const Class& getClass() const
+ {
+ if(mRef != 0)
+ return *(mRef->mClass);
+ throw std::runtime_error("Cannot get class of an empty object");
+ }
+
+ template<typename T>
+ MWWorld::LiveCellRef<T> *get() const
+ {
+ MWWorld::LiveCellRef<T> *ref = dynamic_cast<MWWorld::LiveCellRef<T>*>(mRef);
+ if(ref) return ref;
+
+ std::stringstream str;
+ str<< "Bad LiveCellRef cast to "<<typeid(T).name()<<" from ";
+ if(mRef != 0) str<< getTypeName();
+ else str<< "an empty object";
+
+ throw std::runtime_error(str.str());
+ }
+
+ ESM::CellRef& getCellRef() const;
+
+ RefData& getRefData() const;
+
+ Ptr::CellStore *getCell() const
+ {
+ assert(mCell);
+ return mCell;
+ }
+
+ bool isInCell() const
+ {
+ return (mContainerStore == 0) && (mCell != 0);
+ }
+
+ void setContainerStore (ContainerStore *store);
+ ///< Must not be called on references that are in a cell.
+
+ ContainerStore *getContainerStore() const;
+ ///< May return a 0-pointer, if reference is not in a container.
+ };
+
+ inline bool operator== (const Ptr& left, const Ptr& right)
+ {
+ return left.mRef==right.mRef;
+ }
+
+ inline bool operator!= (const Ptr& left, const Ptr& right)
+ {
+ return !(left==right);
+ }
+
+ inline bool operator< (const Ptr& left, const Ptr& right)
+ {
+ return left.mRef<right.mRef;
+ }
+
+ inline bool operator>= (const Ptr& left, const Ptr& right)
+ {
+ return !(left<right);
+ }
+
+ inline bool operator> (const Ptr& left, const Ptr& right)
+ {
+ return right<left;
+ }
+
+ inline bool operator<= (const Ptr& left, const Ptr& right)
+ {
+ return !(left>right);
+ }
+}
+
+#endif
diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp
new file mode 100644
index 0000000000..7de4f5565a
--- /dev/null
+++ b/apps/openmw/mwworld/recordcmp.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_MWWORLD_RECORDCMP_H
+#define OPENMW_MWWORLD_RECORDCMP_H
+
+#include <cctype>
+
+#include <components/esm/records.hpp>
+
+namespace MWWorld
+{
+ struct RecordCmp
+ {
+ template <class T>
+ bool operator()(const T &x, const T& y) const {
+ return x.mId < y.mId;
+ }
+ };
+
+ template <>
+ inline bool RecordCmp::operator()<ESM::Dialogue>(const ESM::Dialogue &x, const ESM::Dialogue &y) const {
+ return Misc::StringUtils::ciLess(x.mId, y.mId);
+ }
+
+ template <>
+ inline bool RecordCmp::operator()<ESM::Cell>(const ESM::Cell &x, const ESM::Cell &y) const {
+ return Misc::StringUtils::ciLess(x.mName, y.mName);
+ }
+
+ template <>
+ inline bool RecordCmp::operator()<ESM::Pathgrid>(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const {
+ return Misc::StringUtils::ciLess(x.mCell, y.mCell);
+ }
+
+} // end namespace
+#endif
diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp
new file mode 100644
index 0000000000..c1a3ae7859
--- /dev/null
+++ b/apps/openmw/mwworld/refdata.cpp
@@ -0,0 +1,167 @@
+
+#include "refdata.hpp"
+
+#include <OgreSceneNode.h>
+
+#include "customdata.hpp"
+#include "cellstore.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+namespace MWWorld
+{
+ void RefData::copy (const RefData& refData)
+ {
+ mBaseNode = refData.mBaseNode;
+ mLocals = refData.mLocals;
+ mHasLocals = refData.mHasLocals;
+ mEnabled = refData.mEnabled;
+ mCount = refData.mCount;
+ mPosition = refData.mPosition;
+ mLocalRotation = refData.mLocalRotation;
+
+ mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0;
+ }
+
+ void RefData::cleanup()
+ {
+ mBaseNode = 0;
+
+ delete mCustomData;
+ mCustomData = 0;
+ }
+
+ RefData::RefData (const ESM::CellRef& cellRef)
+ : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos),
+ mCustomData (0)
+ {
+ mLocalRotation.rot[0]=0;
+ mLocalRotation.rot[1]=0;
+ mLocalRotation.rot[2]=0;
+ }
+
+ RefData::RefData (const RefData& refData)
+ : mBaseNode(0), mCustomData (0)
+ {
+ try
+ {
+ copy (refData);
+ }
+ catch (...)
+ {
+ cleanup();
+ throw;
+ }
+ }
+
+ RefData& RefData::operator= (const RefData& refData)
+ {
+ try
+ {
+ cleanup();
+ copy (refData);
+ }
+ catch (...)
+ {
+ cleanup();
+ throw;
+ }
+
+ return *this;
+ }
+
+ RefData::~RefData()
+ {
+ try
+ {
+ cleanup();
+ }
+ catch (...)
+ {}
+ }
+
+ const std::string &RefData::getHandle()
+ {
+ if(!mBaseNode)
+ {
+ static const std::string empty;
+ return empty;
+ }
+
+ return mBaseNode->getName();
+ }
+
+ Ogre::SceneNode* RefData::getBaseNode()
+ {
+ return mBaseNode;
+ }
+
+ void RefData::setBaseNode(Ogre::SceneNode* base)
+ {
+ mBaseNode = base;
+ }
+
+ int RefData::getCount() const
+ {
+ return mCount;
+ }
+
+ void RefData::setLocals (const ESM::Script& script)
+ {
+ if (!mHasLocals)
+ {
+ mLocals.configure (script);
+ mHasLocals = true;
+ }
+ }
+
+ void RefData::setCount (int count)
+ {
+ if(count == 0)
+ MWBase::Environment::get().getWorld()->removeRefScript(this);
+
+ mCount = count;
+ }
+
+ MWScript::Locals& RefData::getLocals()
+ {
+ return mLocals;
+ }
+
+ bool RefData::isEnabled() const
+ {
+ return mEnabled;
+ }
+
+ void RefData::enable()
+ {
+ mEnabled = true;
+ }
+
+ void RefData::disable()
+ {
+ mEnabled = false;
+ }
+
+ ESM::Position& RefData::getPosition()
+ {
+ return mPosition;
+ }
+
+ LocalRotation& RefData::getLocalRotation()
+ {
+ return mLocalRotation;
+ }
+
+ void RefData::setCustomData (CustomData *data)
+ {
+ delete mCustomData;
+ mCustomData = data;
+ }
+
+ CustomData *RefData::getCustomData()
+ {
+ return mCustomData;
+ }
+}
diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp
new file mode 100644
index 0000000000..642f5412c8
--- /dev/null
+++ b/apps/openmw/mwworld/refdata.hpp
@@ -0,0 +1,98 @@
+#ifndef GAME_MWWORLD_REFDATA_H
+#define GAME_MWWORLD_REFDATA_H
+
+#include <components/esm/defs.hpp>
+
+#include "../mwscript/locals.hpp"
+
+namespace Ogre
+{
+ class SceneNode;
+}
+
+namespace ESM
+{
+ class Script;
+ class CellRef;
+}
+
+namespace MWWorld
+{
+ struct LocalRotation{
+ float rot[3];
+ };
+
+ class CustomData;
+
+ class RefData
+ {
+ Ogre::SceneNode* mBaseNode;
+
+
+ MWScript::Locals mLocals; // if we find the overhead of heaving a locals
+ // object in the refdata of refs without a script,
+ // we can make this a pointer later.
+ bool mHasLocals;
+ bool mEnabled;
+ int mCount; // 0: deleted
+
+ ESM::Position mPosition;
+
+ LocalRotation mLocalRotation;
+
+ CustomData *mCustomData;
+
+ void copy (const RefData& refData);
+
+ void cleanup();
+
+ public:
+
+ /// @param cellRef Used to copy constant data such as position into this class where it can
+ /// be altered without effecting the original data. This makes it possible
+ /// to reset the position as the orignal data is still held in the CellRef
+ RefData (const ESM::CellRef& cellRef);
+
+ RefData (const RefData& refData);
+
+ ~RefData();
+
+ RefData& operator= (const RefData& refData);
+
+ /// Return OGRE handle (may be empty).
+ const std::string &getHandle();
+
+ /// Return OGRE base node (can be a null pointer).
+ Ogre::SceneNode* getBaseNode();
+
+ /// Set OGRE base node (can be a null pointer).
+ void setBaseNode (Ogre::SceneNode* base);
+
+ int getCount() const;
+
+ void setLocals (const ESM::Script& script);
+
+ void setCount (int count);
+
+ MWScript::Locals& getLocals();
+
+ bool isEnabled() const;
+
+ void enable();
+
+ void disable();
+
+ ESM::Position& getPosition();
+
+ LocalRotation& getLocalRotation();
+
+ void setCustomData (CustomData *data);
+ ///< Set custom data (potentially replacing old custom data). The ownership of \æ data is
+ /// transferred to this.
+
+ CustomData *getCustomData();
+ ///< May return a 0-pointer. The ownership of the return data object is not transferred.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp
new file mode 100644
index 0000000000..0c98ca504f
--- /dev/null
+++ b/apps/openmw/mwworld/scene.cpp
@@ -0,0 +1,529 @@
+#include "scene.hpp"
+
+#include <OgreSceneNode.h>
+
+#include <components/nif/niffile.hpp>
+
+#include <libs/openengine/ogre/fader.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp" /// FIXME
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "physicssystem.hpp"
+#include "player.hpp"
+#include "localscripts.hpp"
+#include "esmstore.hpp"
+#include "class.hpp"
+
+#include "cellfunctors.hpp"
+
+namespace
+{
+
+ template<typename T>
+ void insertCellRefList(MWRender::RenderingManager& rendering,
+ T& cellRefList, MWWorld::CellStore &cell, MWWorld::PhysicsSystem& physics, bool rescale, Loading::Listener* loadingListener)
+ {
+ if (!cellRefList.mList.empty())
+ {
+ const MWWorld::Class& class_ =
+ MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.mList.begin(), &cell));
+ for (typename T::List::iterator it = cellRefList.mList.begin();
+ it != cellRefList.mList.end(); it++)
+ {
+ if (rescale)
+ {
+ if (it->mRef.mScale<0.5)
+ it->mRef.mScale = 0.5;
+ else if (it->mRef.mScale>2)
+ it->mRef.mScale = 2;
+ }
+
+ if (it->mData.getCount() && it->mData.isEnabled())
+ {
+ MWWorld::Ptr ptr (&*it, &cell);
+
+ try
+ {
+ rendering.addObject(ptr);
+ class_.insertObject(ptr, physics);
+
+ float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees();
+ float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees();
+ float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees();
+ MWBase::Environment::get().getWorld()->localRotateObject(ptr, ax, ay, az);
+
+ MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().mScale);
+ class_.adjustPosition(ptr);
+ }
+ catch (const std::exception& e)
+ {
+ std::string error ("error during rendering: ");
+ std::cerr << error + e.what() << std::endl;
+ }
+ }
+
+ loadingListener->increaseProgress(1);
+ }
+ }
+ }
+
+}
+
+
+namespace MWWorld
+{
+
+ void Scene::update (float duration, bool paused){
+ mRendering.update (duration, paused);
+ }
+
+ void Scene::unloadCell (CellStoreCollection::iterator iter)
+ {
+ std::cout << "Unloading cell\n";
+ ListAndResetHandles functor;
+
+ (*iter)->forEach<ListAndResetHandles>(functor);
+ {
+ // silence annoying g++ warning
+ for (std::vector<Ogre::SceneNode*>::const_iterator iter2 (functor.mHandles.begin());
+ iter2!=functor.mHandles.end(); ++iter2)
+ {
+ Ogre::SceneNode* node = *iter2;
+ mPhysics->removeObject (node->getName());
+ }
+ }
+
+ if ((*iter)->mCell->isExterior())
+ {
+ ESM::Land* land =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(
+ (*iter)->mCell->getGridX(),
+ (*iter)->mCell->getGridY()
+ );
+ if (land)
+ mPhysics->removeHeightField( (*iter)->mCell->getGridX(), (*iter)->mCell->getGridY() );
+ }
+
+ 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)
+ {
+ float verts = ESM::Land::LAND_SIZE;
+ float worldsize = ESM::Land::REAL_SIZE;
+
+ // Load terrain physics first...
+ if (cell->mCell->isExterior())
+ {
+ ESM::Land* land =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(
+ cell->mCell->getGridX(),
+ cell->mCell->getGridY()
+ );
+ if (land) {
+ mPhysics->addHeightField (
+ land->mLandData->mHeights,
+ cell->mCell->getGridX(),
+ cell->mCell->getGridY(),
+ 0,
+ worldsize / (verts-1),
+ verts)
+ ;
+ }
+ }
+
+ // ... then references. This is important for adjustPosition to work correctly.
+ /// \todo rescale depending on the state of a new GMST
+ insertCell (*cell, true, loadingListener);
+
+ mRendering.cellAdded (cell);
+
+ mRendering.configureAmbient(*cell);
+ mRendering.requestMap(cell);
+ mRendering.configureAmbient(*cell);
+ }
+ }
+
+ void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ world->getPlayer().setCell(cell);
+
+ MWWorld::Ptr player = world->getPlayer().getPlayer();
+ mRendering.updatePlayerPtr(player);
+
+ if (adjustPlayerPos) {
+ world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]);
+
+ float x = Ogre::Radian(pos.rot[0]).valueDegrees();
+ float y = Ogre::Radian(pos.rot[1]).valueDegrees();
+ float z = Ogre::Radian(pos.rot[2]).valueDegrees();
+ world->rotateObject(player, x, y, z);
+
+ MWWorld::Class::get(player).adjustPosition(player);
+ }
+
+ MWBase::MechanicsManager *mechMgr =
+ MWBase::Environment::get().getMechanicsManager();
+
+ mechMgr->add(player);
+ mechMgr->watchActor(player);
+
+ MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
+ }
+
+ void Scene::changeToVoid()
+ {
+ CellStoreCollection::iterator active = mActiveCells.begin();
+ while (active!=mActiveCells.end())
+ unloadCell (active++);
+ assert(mActiveCells.empty());
+ mCurrentCell = NULL;
+ }
+
+ void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos)
+ {
+ 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);
+
+ CellStoreCollection::iterator active = mActiveCells.begin();
+
+ // get the number of cells to unload
+ int numUnload = 0;
+ while (active!=mActiveCells.end())
+ {
+ if ((*active)->mCell->isExterior())
+ {
+ if (std::abs (X-(*active)->mCell->getGridX())<=1 &&
+ std::abs (Y-(*active)->mCell->getGridY())<=1)
+ {
+ // keep cells within the new 3x3 grid
+ ++active;
+ continue;
+ }
+ }
+ ++active;
+ ++numUnload;
+ }
+
+ active = mActiveCells.begin();
+ while (active!=mActiveCells.end())
+ {
+ if ((*active)->mCell->isExterior())
+ {
+ if (std::abs (X-(*active)->mCell->getGridX())<=1 &&
+ std::abs (Y-(*active)->mCell->getGridY())<=1)
+ {
+ // keep cells within the new 3x3 grid
+ ++active;
+ continue;
+ }
+ }
+ unloadCell (active++);
+ }
+
+ int refsToLoad = 0;
+ // get the number of refs to load
+ for (int x=X-1; x<=X+1; ++x)
+ for (int y=Y-1; y<=Y+1; ++y)
+ {
+ CellStoreCollection::iterator iter = mActiveCells.begin();
+
+ while (iter!=mActiveCells.end())
+ {
+ assert ((*iter)->mCell->isExterior());
+
+ if (x==(*iter)->mCell->getGridX() &&
+ y==(*iter)->mCell->getGridY())
+ break;
+
+ ++iter;
+ }
+
+ if (iter==mActiveCells.end())
+ refsToLoad += countRefs(*MWBase::Environment::get().getWorld()->getExterior(x, y));
+ }
+
+ loadingListener->setProgressRange(refsToLoad);
+
+ // Load cells
+ for (int x=X-1; x<=X+1; ++x)
+ for (int y=Y-1; y<=Y+1; ++y)
+ {
+ CellStoreCollection::iterator iter = mActiveCells.begin();
+
+ while (iter!=mActiveCells.end())
+ {
+ assert ((*iter)->mCell->isExterior());
+
+ if (x==(*iter)->mCell->getGridX() &&
+ y==(*iter)->mCell->getGridY())
+ break;
+
+ ++iter;
+ }
+
+ if (iter==mActiveCells.end())
+ {
+ CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
+
+ loadCell (cell, loadingListener);
+ }
+ }
+
+ // find current cell
+ CellStoreCollection::iterator iter = mActiveCells.begin();
+
+ while (iter!=mActiveCells.end())
+ {
+ assert ((*iter)->mCell->isExterior());
+
+ if (X==(*iter)->mCell->getGridX() &&
+ Y==(*iter)->mCell->getGridY())
+ break;
+
+ ++iter;
+ }
+
+ assert (iter!=mActiveCells.end());
+
+ mCurrentCell = *iter;
+
+ // adjust player
+ playerCellChange (mCurrentCell, position, adjustPlayerPos);
+
+ // Sky system
+ MWBase::Environment::get().getWorld()->adjustSky();
+
+ mRendering.switchToExterior();
+
+ mCellChanged = true;
+
+ loadingListener->removeWallpaper();
+ }
+
+ //We need the ogre renderer and a scene node.
+ Scene::Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics)
+ : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering)
+ {
+ }
+
+ Scene::~Scene()
+ {
+ }
+
+ bool Scene::hasCellChanged() const
+ {
+ return mCellChanged;
+ }
+
+ const Scene::CellStoreCollection& Scene::getActiveCells() const
+ {
+ return mActiveCells;
+ }
+
+ void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
+ {
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5);
+
+ mRendering.enableTerrain(false);
+
+ Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
+ Loading::ScopedLoad load(loadingListener);
+
+ std::string loadingInteriorText = "#{sLoadingMessage2}";
+ loadingListener->setLabel(loadingInteriorText);
+
+ CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
+ bool loadcell = (mCurrentCell == NULL);
+ if(!loadcell)
+ loadcell = *mCurrentCell != *cell;
+
+ if(!loadcell)
+ {
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ world->moveObject(world->getPlayer().getPlayer(), position.pos[0], position.pos[1], position.pos[2]);
+
+ float x = Ogre::Radian(position.rot[0]).valueDegrees();
+ float y = Ogre::Radian(position.rot[1]).valueDegrees();
+ float z = Ogre::Radian(position.rot[2]).valueDegrees();
+ world->rotateObject(world->getPlayer().getPlayer(), x, y, z);
+
+ MWWorld::Class::get(world->getPlayer().getPlayer()).adjustPosition(world->getPlayer().getPlayer());
+ world->getFader()->fadeIn(0.5f);
+ return;
+ }
+
+ std::cout << "Changing to interior\n";
+
+ // remove active
+ CellStoreCollection::iterator active = mActiveCells.begin();
+
+ // count number of cells to unload
+ int numUnload = 0;
+ while (active!=mActiveCells.end())
+ {
+ ++active;
+ ++numUnload;
+ }
+
+ // unload
+ int current = 0;
+ active = mActiveCells.begin();
+ while (active!=mActiveCells.end())
+ {
+ unloadCell (active++);
+ ++current;
+ }
+
+ int refsToLoad = countRefs(*cell);
+ loadingListener->setProgressRange(refsToLoad);
+
+ // Load cell.
+ std::cout << "cellName: " << cell->mCell->mName << std::endl;
+
+ //Loading Interior loading text
+
+ loadCell (cell, loadingListener);
+
+ mCurrentCell = cell;
+
+ // adjust fog
+ mRendering.switchToInterior();
+ mRendering.configureFog(*mCurrentCell);
+
+ // adjust player
+ playerCellChange (mCurrentCell, position);
+
+ // Sky system
+ MWBase::Environment::get().getWorld()->adjustSky();
+
+ mCellChanged = true;
+ MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.5);
+
+ loadingListener->removeWallpaper();
+ }
+
+ void Scene::changeToExteriorCell (const ESM::Position& position)
+ {
+ int x = 0;
+ int y = 0;
+
+ MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y);
+
+ mRendering.enableTerrain(true);
+
+ changeCell (x, y, position, true);
+ }
+
+ Ptr::CellStore* Scene::getCurrentCell ()
+ {
+ return mCurrentCell;
+ }
+
+ void Scene::markCellAsUnchanged()
+ {
+ mCellChanged = false;
+ }
+
+ int Scene::countRefs (const Ptr::CellStore& cell)
+ {
+ return cell.mActivators.mList.size()
+ + cell.mPotions.mList.size()
+ + cell.mAppas.mList.size()
+ + cell.mArmors.mList.size()
+ + cell.mBooks.mList.size()
+ + cell.mClothes.mList.size()
+ + cell.mContainers.mList.size()
+ + cell.mDoors.mList.size()
+ + cell.mIngreds.mList.size()
+ + cell.mCreatureLists.mList.size()
+ + cell.mItemLists.mList.size()
+ + cell.mLights.mList.size()
+ + cell.mLockpicks.mList.size()
+ + cell.mMiscItems.mList.size()
+ + cell.mProbes.mList.size()
+ + cell.mRepairs.mList.size()
+ + cell.mStatics.mList.size()
+ + cell.mWeapons.mList.size()
+ + cell.mCreatures.mList.size()
+ + cell.mNpcs.mList.size();
+ }
+
+ void Scene::insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener)
+ {
+ // Loop through all references in the cell
+ insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mPotions, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mAppas, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mArmors, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mBooks, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mClothes, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mMiscItems, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mProbes, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mRepairs, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mStatics, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mWeapons, cell, *mPhysics, rescale, loadingListener);
+ // Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly)
+ insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener);
+ insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale, loadingListener);
+ }
+
+ void Scene::addObjectToScene (const Ptr& ptr)
+ {
+ mRendering.addObject(ptr);
+ MWWorld::Class::get(ptr).insertObject(ptr, *mPhysics);
+ MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true);
+ MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().mScale);
+ }
+
+ void Scene::removeObjectFromScene (const Ptr& ptr)
+ {
+ MWBase::Environment::get().getMechanicsManager()->remove (ptr);
+ MWBase::Environment::get().getSoundManager()->stopSound3D (ptr);
+ mPhysics->removeObject (ptr.getRefData().getHandle());
+ mRendering.removeObject (ptr);
+ }
+
+ bool Scene::isCellActive(const CellStore &cell)
+ {
+ CellStoreCollection::iterator active = mActiveCells.begin();
+ while (active != mActiveCells.end()) {
+ if (**active == cell) {
+ return true;
+ }
+ ++active;
+ }
+ return false;
+ }
+}
diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp
new file mode 100644
index 0000000000..e3edad352a
--- /dev/null
+++ b/apps/openmw/mwworld/scene.hpp
@@ -0,0 +1,107 @@
+#ifndef GAME_MWWORLD_SCENE_H
+#define GAME_MWWORLD_SCENE_H
+
+#include "../mwrender/renderingmanager.hpp"
+
+#include "ptr.hpp"
+#include "globals.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace ESM
+{
+ struct Position;
+}
+
+namespace Files
+{
+ class Collections;
+}
+
+namespace Render
+{
+ class OgreRenderer;
+}
+
+namespace MWRender
+{
+ class SkyManager;
+ class CellRender;
+}
+
+namespace MWWorld
+{
+ class PhysicsSystem;
+ class Player;
+ class CellStore;
+
+ class Scene
+ {
+ public:
+
+ typedef std::set<CellStore *> CellStoreCollection;
+
+ private:
+
+ //OEngine::Render::OgreRenderer& mRenderer;
+ CellStore* mCurrentCell; // the cell the player is in
+ CellStoreCollection mActiveCells;
+ bool mCellChanged;
+ PhysicsSystem *mPhysics;
+ MWRender::RenderingManager& mRendering;
+
+ void playerCellChange (CellStore *cell, const ESM::Position& position,
+ bool adjustPlayerPos = true);
+
+ void insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener);
+
+ int countRefs (const Ptr::CellStore& cell);
+
+ public:
+
+ Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics);
+
+ ~Scene();
+
+ void unloadCell (CellStoreCollection::iterator iter);
+
+ 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 ();
+
+ const CellStoreCollection& getActiveCells () const;
+
+ bool hasCellChanged() const;
+ ///< Has the player moved to a different cell, since the last frame?
+
+ void changeToInteriorCell (const std::string& cellName, const ESM::Position& position);
+ ///< Move to interior cell.
+
+ void changeToExteriorCell (const ESM::Position& position);
+ ///< Move to exterior cell.
+
+ void changeToVoid();
+ ///< Change into a void
+
+ void markCellAsUnchanged();
+
+ void update (float duration, bool paused);
+
+ void addObjectToScene (const Ptr& ptr);
+ ///< Add an object that already exists in the world model to the scene.
+
+ void removeObjectFromScene (const Ptr& ptr);
+ ///< Remove an object from the scene, but not from the world model.
+
+ bool isCellActive(const CellStore &cell);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp
new file mode 100644
index 0000000000..512883f1a3
--- /dev/null
+++ b/apps/openmw/mwworld/store.cpp
@@ -0,0 +1,97 @@
+#include "store.hpp"
+#include "esmstore.hpp"
+
+namespace MWWorld {
+
+
+void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
+{
+ // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell,
+ // and we merge all this data into one Cell object. However, we can't simply search for the cell id,
+ // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they
+ // are not available until both cells have been loaded! So first, proceed as usual.
+
+ // All cells have a name record, even nameless exterior cells.
+ std::string idLower = Misc::StringUtils::lowerCase(id);
+ ESM::Cell *cell = new ESM::Cell;
+ cell->mName = id;
+
+ //First part of cell loading
+ cell->preLoad(esm);
+
+ //Handling MovedCellRefs, there is no way to do it inside loadcell
+ while (esm.isNextSub("MVRF")) {
+ ESM::CellRef ref;
+ ESM::MovedCellRef cMRef;
+ cell->getNextMVRF(esm, cMRef);
+
+ MWWorld::Store<ESM::Cell> &cStore = const_cast<MWWorld::Store<ESM::Cell>&>(mEsmStore->get<ESM::Cell>());
+ ESM::Cell *cellAlt = const_cast<ESM::Cell*>(cStore.searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1]));
+
+ // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following
+ // implementation when the oher implementation works as well.
+ cell->getNextRef(esm, ref);
+ std::string lowerCase;
+
+ std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
+ (int(*)(int)) std::tolower);
+
+ // Add data required to make reference appear in the correct cell.
+ // We should not need to test for duplicates, as this part of the code is pre-cell merge.
+ cell->mMovedRefs.push_back(cMRef);
+ // But there may be duplicates here!
+ ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum);
+ if (iter == cellAlt->mLeasedRefs.end())
+ cellAlt->mLeasedRefs.push_back(ref);
+ else
+ *iter = ref;
+ }
+
+ //Second part of cell loading
+ cell->postLoad(esm);
+
+ if(cell->mData.mFlags & ESM::Cell::Interior)
+ {
+ // Store interior cell by name, try to merge with existing parent data.
+ ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(idLower));
+ if (oldcell) {
+ // push the new references on the list of references to manage
+ oldcell->mContextList.push_back(cell->mContextList.at(0));
+ // copy list into new cell
+ cell->mContextList = oldcell->mContextList;
+ // have new cell replace old cell
+ *oldcell = *cell;
+ } else
+ mInt[idLower] = *cell;
+ }
+ else
+ {
+ // Store exterior cells by grid position, try to merge with existing parent data.
+ ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(cell->getGridX(), cell->getGridY()));
+ if (oldcell) {
+ // push the new references on the list of references to manage
+ oldcell->mContextList.push_back(cell->mContextList.at(0));
+ // copy list into new cell
+ cell->mContextList = oldcell->mContextList;
+ // merge lists of leased references, use newer data in case of conflict
+ for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); ++it) {
+ // remove reference from current leased ref tracker and add it to new cell
+ ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefnum);
+ if (itold != oldcell->mMovedRefs.end()) {
+ ESM::MovedCellRef target0 = *itold;
+ ESM::Cell *wipecell = const_cast<ESM::Cell*>(search(target0.mTarget[0], target0.mTarget[1]));
+ ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefnum);
+ wipecell->mLeasedRefs.erase(it_lease);
+ *itold = *it;
+ }
+ }
+ cell->mMovedRefs = oldcell->mMovedRefs;
+ // have new cell replace old cell
+ *oldcell = *cell;
+ } else
+ mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
+ }
+ delete cell;
+}
+
+}
diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp
new file mode 100644
index 0000000000..2ee23dbd61
--- /dev/null
+++ b/apps/openmw/mwworld/store.hpp
@@ -0,0 +1,1080 @@
+#ifndef OPENMW_MWWORLD_STORE_H
+#define OPENMW_MWWORLD_STORE_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <stdexcept>
+
+#include "recordcmp.hpp"
+
+namespace MWWorld
+{
+ struct StoreBase
+ {
+ virtual ~StoreBase() {}
+
+ virtual void setUp() {}
+ virtual void listIdentifier(std::vector<std::string> &list) const {}
+
+ virtual size_t getSize() const = 0;
+ virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
+
+ virtual bool eraseStatic(const std::string &id) {return false;}
+ virtual void clearDynamic() {}
+ };
+
+ template <class T>
+ class SharedIterator
+ {
+ typedef typename std::vector<T *>::const_iterator Iter;
+
+ Iter mIter;
+
+ public:
+ SharedIterator() {}
+
+ SharedIterator(const SharedIterator &orig)
+ : mIter(orig.mIter)
+ {}
+
+ SharedIterator(const Iter &iter)
+ : mIter(iter)
+ {}
+
+ SharedIterator &operator++() {
+ ++mIter;
+ return *this;
+ }
+
+ SharedIterator operator++(int) {
+ SharedIterator iter = *this;
+ ++mIter;
+
+ return iter;
+ }
+
+ SharedIterator &operator--() {
+ --mIter;
+ return *this;
+ }
+
+ SharedIterator operator--(int) {
+ SharedIterator iter = *this;
+ --mIter;
+
+ return iter;
+ }
+
+ bool operator==(const SharedIterator &x) const {
+ return mIter == x.mIter;
+ }
+
+ bool operator!=(const SharedIterator &x) const {
+ return !(*this == x);
+ }
+
+ const T &operator*() const {
+ return **mIter;
+ }
+
+ const T *operator->() const {
+ return &(**mIter);
+ }
+ };
+
+ class ESMStore;
+
+ template <class T>
+ class Store : public StoreBase
+ {
+ std::map<std::string, T> mStatic;
+ std::vector<T *> mShared;
+ std::map<std::string, T> mDynamic;
+
+ typedef std::map<std::string, T> Dynamic;
+ typedef std::map<std::string, T> Static;
+
+ class GetRecords {
+ const std::string mFind;
+ std::vector<const T*> *mRecords;
+
+ public:
+ GetRecords(const std::string &str, std::vector<const T*> *records)
+ : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records)
+ { }
+
+ void operator()(const T *item)
+ {
+ if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0)
+ mRecords->push_back(item);
+ }
+ };
+
+
+ friend class ESMStore;
+
+ public:
+ Store()
+ {}
+
+ Store(const Store<T> &orig)
+ : mStatic(orig.mData)
+ {}
+
+ typedef SharedIterator<T> iterator;
+
+ // setUp needs to be called again after
+ virtual void clearDynamic()
+ {
+ mDynamic.clear();
+ mShared.clear();
+ }
+
+ const T *search(const std::string &id) const {
+ T item;
+ item.mId = Misc::StringUtils::lowerCase(id);
+
+ typename std::map<std::string, T>::const_iterator it = mStatic.find(item.mId);
+
+ if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
+ return &(it->second);
+ }
+
+ typename Dynamic::const_iterator dit = mDynamic.find(item.mId);
+ if (dit != mDynamic.end()) {
+ return &dit->second;
+ }
+
+ return 0;
+ }
+
+ /** Returns a random record that starts with the named ID, or NULL if not found. */
+ const T *searchRandom(const std::string &id) const
+ {
+ std::vector<const T*> results;
+ std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results));
+ if(!results.empty())
+ return results[int(std::rand()/((double)RAND_MAX+1)*results.size())];
+ return NULL;
+ }
+
+ const T *find(const std::string &id) const {
+ const T *ptr = search(id);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Object '" << id << "' not found (const)";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ /** Returns a random record that starts with the named ID. An exception is thrown if none
+ * are found. */
+ const T *findRandom(const std::string &id) const
+ {
+ const T *ptr = searchRandom(id);
+ if(ptr == 0)
+ {
+ std::ostringstream msg;
+ msg << "Object starting with '"<<id<<"' not found (const)";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ void load(ESM::ESMReader &esm, const std::string &id) {
+ std::string idLower = Misc::StringUtils::lowerCase(id);
+ mStatic[idLower] = T();
+ mStatic[idLower].mId = idLower;
+ mStatic[idLower].load(esm);
+ }
+
+ void setUp() {
+ //std::sort(mStatic.begin(), mStatic.end(), RecordCmp());
+
+ mShared.reserve(mStatic.size());
+ typename std::map<std::string, T>::iterator it = mStatic.begin();
+ for (; it != mStatic.end(); ++it) {
+ mShared.push_back(&(it->second));
+ }
+ }
+
+ iterator begin() const {
+ return mShared.begin();
+ }
+
+ iterator end() const {
+ return mShared.end();
+ }
+
+ size_t getSize() const {
+ return mShared.size();
+ }
+
+ void listIdentifier(std::vector<std::string> &list) const {
+ list.reserve(list.size() + getSize());
+ typename std::vector<T *>::const_iterator it = mShared.begin();
+ for (; it != mShared.end(); ++it) {
+ list.push_back((*it)->mId);
+ }
+ }
+
+ T *insert(const T &item) {
+ std::string id = Misc::StringUtils::lowerCase(item.mId);
+ std::pair<typename Dynamic::iterator, bool> result =
+ mDynamic.insert(std::pair<std::string, T>(id, item));
+ T *ptr = &result.first->second;
+ if (result.second) {
+ mShared.push_back(ptr);
+ } else {
+ *ptr = item;
+ }
+ return ptr;
+ }
+
+ T *insertStatic(const T &item) {
+ std::string id = Misc::StringUtils::lowerCase(item.mId);
+ std::pair<typename Static::iterator, bool> result =
+ mStatic.insert(std::pair<std::string, T>(id, item));
+ T *ptr = &result.first->second;
+ if (result.second) {
+ mShared.push_back(ptr);
+ } else {
+ *ptr = item;
+ }
+ return ptr;
+ }
+
+
+ bool eraseStatic(const std::string &id) {
+ T item;
+ item.mId = Misc::StringUtils::lowerCase(id);
+
+ typename std::map<std::string, T>::iterator it = mStatic.find(item.mId);
+
+ if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
+ // delete from the static part of mShared
+ typename std::vector<T *>::iterator sharedIter = mShared.begin();
+ typename std::vector<T *>::iterator end = sharedIter + mStatic.size();
+
+ while (sharedIter != mShared.end() && sharedIter != end) {
+ if((*sharedIter)->mId == item.mId) {
+ mShared.erase(sharedIter);
+ break;
+ }
+ ++sharedIter;
+ }
+ mStatic.erase(it);
+ }
+
+ return true;
+ }
+
+ bool erase(const std::string &id) {
+ std::string key = Misc::StringUtils::lowerCase(id);
+ typename Dynamic::iterator it = mDynamic.find(key);
+ if (it == mDynamic.end()) {
+ return false;
+ }
+ mDynamic.erase(it);
+
+ // have to reinit the whole shared part
+ mShared.erase(mShared.begin() + mStatic.size(), mShared.end());
+ for (it = mDynamic.begin(); it != mDynamic.end(); ++it) {
+ mShared.push_back(&it->second);
+ }
+ return true;
+ }
+
+ bool erase(const T &item) {
+ return erase(item.mId);
+ }
+ };
+
+ template <>
+ inline void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) {
+ std::string idLower = Misc::StringUtils::lowerCase(id);
+
+ std::map<std::string, ESM::Dialogue>::iterator it = mStatic.find(idLower);
+ if (it == mStatic.end()) {
+ it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first;
+ it->second.mId = id; // don't smash case here, as this line is printed... I think
+ }
+
+ //I am not sure is it need to load the dialog from a plugin if it was already loaded from prevois plugins
+ it->second.load(esm);
+ }
+
+ template <>
+ inline void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) {
+ ESM::Script scpt;
+ scpt.load(esm);
+ Misc::StringUtils::toLower(scpt.mId);
+ mStatic[scpt.mId] = scpt;
+ }
+
+ template <>
+ inline void Store<ESM::StartScript>::load(ESM::ESMReader &esm, const std::string &id) {
+ ESM::StartScript s;
+ s.load(esm);
+ s.mId = Misc::StringUtils::toLower(s.mScript);
+ mStatic[s.mId] = s;
+ }
+
+ template <>
+ class Store<ESM::LandTexture> : public StoreBase
+ {
+ // For multiple ESM/ESP files we need one list per file.
+ typedef std::vector<ESM::LandTexture> LandTextureList;
+ std::vector<LandTextureList> mStatic;
+
+ public:
+ Store<ESM::LandTexture>() {
+ mStatic.push_back(LandTextureList());
+ LandTextureList &ltexl = mStatic[0];
+ // More than enough to hold Morrowind.esm. Extra lists for plugins will we
+ // added on-the-fly in a different method.
+ ltexl.reserve(128);
+ }
+
+ typedef std::vector<ESM::LandTexture>::const_iterator iterator;
+
+ const ESM::LandTexture *search(size_t index, size_t plugin) const {
+ assert(plugin < mStatic.size());
+ const LandTextureList &ltexl = mStatic[plugin];
+
+ assert(index < ltexl.size());
+ return &ltexl.at(index);
+ }
+
+ const ESM::LandTexture *find(size_t index, size_t plugin) const {
+ const ESM::LandTexture *ptr = search(index, plugin);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Land texture with index " << index << " not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ size_t getSize() const {
+ return mStatic.size();
+ }
+
+ size_t getSize(size_t plugin) const {
+ assert(plugin < mStatic.size());
+ return mStatic[plugin].size();
+ }
+
+ void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) {
+ ESM::LandTexture lt;
+ lt.load(esm);
+ lt.mId = id;
+
+ // Make sure we have room for the structure
+ if (plugin >= mStatic.size()) {
+ mStatic.resize(plugin+1);
+ }
+ LandTextureList &ltexl = mStatic[plugin];
+ if(lt.mIndex + 1 > (int)ltexl.size())
+ ltexl.resize(lt.mIndex+1);
+
+ // Store it
+ ltexl[lt.mIndex] = lt;
+ }
+
+ void load(ESM::ESMReader &esm, const std::string &id) {
+ load(esm, id, esm.getIndex());
+ }
+
+ iterator begin(size_t plugin) const {
+ assert(plugin < mStatic.size());
+ return mStatic[plugin].begin();
+ }
+
+ iterator end(size_t plugin) const {
+ assert(plugin < mStatic.size());
+ return mStatic[plugin].end();
+ }
+ };
+
+ template <>
+ class Store<ESM::Land> : public StoreBase
+ {
+ std::vector<ESM::Land *> mStatic;
+
+ struct Compare
+ {
+ bool operator()(const ESM::Land *x, const ESM::Land *y) {
+ if (x->mX == y->mX) {
+ return x->mY < y->mY;
+ }
+ return x->mX < y->mX;
+ }
+ };
+
+ public:
+ typedef SharedIterator<ESM::Land> iterator;
+
+ virtual ~Store<ESM::Land>()
+ {
+ for (std::vector<ESM::Land *>::const_iterator it =
+ mStatic.begin(); it != mStatic.end(); ++it)
+ {
+ delete *it;
+ }
+
+ }
+
+ size_t getSize() const {
+ return mStatic.size();
+ }
+
+ iterator begin() const {
+ return iterator(mStatic.begin());
+ }
+
+ iterator end() const {
+ return iterator(mStatic.end());
+ }
+
+ ESM::Land *search(int x, int y) const {
+ ESM::Land land;
+ land.mX = x, land.mY = y;
+
+ std::vector<ESM::Land *>::const_iterator it =
+ std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare());
+
+ if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) {
+ return const_cast<ESM::Land *>(*it);
+ }
+ return 0;
+ }
+
+ ESM::Land *find(int x, int y) const{
+ ESM::Land *ptr = search(x, y);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Land at (" << x << ", " << y << ") not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ void load(ESM::ESMReader &esm, const std::string &id) {
+ ESM::Land *ptr = new ESM::Land();
+ ptr->load(esm);
+
+ // Same area defined in multiple plugins? -> last plugin wins
+ // Can't use search() because we aren't sorted yet - is there any other way to speed this up?
+ for (std::vector<ESM::Land*>::iterator it = mStatic.begin(); it != mStatic.end(); ++it)
+ {
+ if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY)
+ {
+ delete *it;
+ mStatic.erase(it);
+ break;
+ }
+ }
+
+ mStatic.push_back(ptr);
+ }
+
+ void setUp() {
+ std::sort(mStatic.begin(), mStatic.end(), Compare());
+ }
+ };
+
+ template <>
+ class Store<ESM::Cell> : public StoreBase
+ {
+ struct ExtCmp
+ {
+ bool operator()(const ESM::Cell &x, const ESM::Cell &y) {
+ if (x.mData.mX == y.mData.mX) {
+ return x.mData.mY < y.mData.mY;
+ }
+ return x.mData.mX < y.mData.mX;
+ }
+ };
+
+ struct DynamicExtCmp
+ {
+ bool operator()(const std::pair<int, int> &left, const std::pair<int, int> &right) const {
+ if (left.first == right.first) {
+ return left.second < right.second;
+ }
+ return left.first < right.first;
+ }
+ };
+
+ typedef std::map<std::string, ESM::Cell> DynamicInt;
+ typedef std::map<std::pair<int, int>, ESM::Cell, DynamicExtCmp> DynamicExt;
+
+ DynamicInt mInt;
+ DynamicExt mExt;
+
+ std::vector<ESM::Cell *> mSharedInt;
+ std::vector<ESM::Cell *> mSharedExt;
+
+ DynamicInt mDynamicInt;
+ DynamicExt mDynamicExt;
+
+ const ESM::Cell *search(const ESM::Cell &cell) const {
+ if (cell.isExterior()) {
+ return search(cell.getGridX(), cell.getGridY());
+ }
+ return search(cell.mName);
+ }
+
+ public:
+ ESMStore *mEsmStore;
+
+ typedef SharedIterator<ESM::Cell> iterator;
+
+ Store<ESM::Cell>()
+ {}
+
+ const ESM::Cell *search(const std::string &id) const {
+ ESM::Cell cell;
+ cell.mName = Misc::StringUtils::lowerCase(id);
+
+ std::map<std::string, ESM::Cell>::const_iterator it = mInt.find(cell.mName);
+
+ if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) {
+ return &(it->second);
+ }
+
+ DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName);
+ if (dit != mDynamicInt.end()) {
+ return &dit->second;
+ }
+
+ return 0;
+ }
+
+ const ESM::Cell *search(int x, int y) const {
+ ESM::Cell cell;
+ cell.mData.mX = x, cell.mData.mY = y;
+
+ std::pair<int, int> key(x, y);
+ DynamicExt::const_iterator it = mExt.find(key);
+ if (it != mExt.end()) {
+ return &(it->second);
+ }
+
+ DynamicExt::const_iterator dit = mDynamicExt.find(key);
+ if (dit != mDynamicExt.end()) {
+ return &dit->second;
+ }
+
+ return 0;
+ }
+
+ const ESM::Cell *searchOrCreate(int x, int y) {
+ ESM::Cell cell;
+ cell.mData.mX = x, cell.mData.mY = y;
+
+ std::pair<int, int> key(x, y);
+ DynamicExt::const_iterator it = mExt.find(key);
+ if (it != mExt.end()) {
+ return &(it->second);
+ }
+
+ DynamicExt::const_iterator dit = mDynamicExt.find(key);
+ if (dit != mDynamicExt.end()) {
+ return &dit->second;
+ }
+
+ ESM::Cell *newCell = new ESM::Cell;
+ newCell->mData.mX = x;
+ newCell->mData.mY = y;
+ mExt[std::make_pair(x, y)] = *newCell;
+ delete newCell;
+
+ return &mExt[std::make_pair(x, y)];
+ }
+
+ const ESM::Cell *find(const std::string &id) const {
+ const ESM::Cell *ptr = search(id);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Interior cell '" << id << "' not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ const ESM::Cell *find(int x, int y) const {
+ const ESM::Cell *ptr = search(x, y);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Exterior at (" << x << ", " << y << ") not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ void setUp() {
+ //typedef std::vector<ESM::Cell>::iterator Iterator;
+ typedef DynamicExt::iterator ExtIterator;
+ typedef std::map<std::string, ESM::Cell>::iterator IntIterator;
+
+ //std::sort(mInt.begin(), mInt.end(), RecordCmp());
+ mSharedInt.reserve(mInt.size());
+ for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) {
+ mSharedInt.push_back(&(it->second));
+ }
+
+ //std::sort(mExt.begin(), mExt.end(), ExtCmp());
+ mSharedExt.reserve(mExt.size());
+ for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) {
+ mSharedExt.push_back(&(it->second));
+ }
+ }
+
+ // HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get
+ // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find.
+ // There some nasty three-way cyclic header dependency involved, which I could only fix by moving
+ // this method.
+ void load(ESM::ESMReader &esm, const std::string &id);
+
+ iterator intBegin() const {
+ return iterator(mSharedInt.begin());
+ }
+
+ iterator intEnd() const {
+ return iterator(mSharedInt.end());
+ }
+
+ iterator extBegin() const {
+ return iterator(mSharedExt.begin());
+ }
+
+ iterator extEnd() const {
+ return iterator(mSharedExt.end());
+ }
+
+ /// \todo implement appropriate index
+ const ESM::Cell *searchExtByName(const std::string &id) const {
+ std::vector<ESM::Cell *>::const_iterator it = mSharedExt.begin();
+ for (; it != mSharedExt.end(); ++it) {
+ if (Misc::StringUtils::ciEqual((*it)->mName, id)) {
+ return *it;
+ }
+ }
+ return 0;
+ }
+
+ /// \todo implement appropriate index
+ const ESM::Cell *searchExtByRegion(const std::string &id) const {
+ std::vector<ESM::Cell *>::const_iterator it = mSharedExt.begin();
+ for (; it != mSharedExt.end(); ++it) {
+ if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) {
+ return *it;
+ }
+ }
+ return 0;
+ }
+
+ size_t getSize() const {
+ return mSharedInt.size() + mSharedExt.size();
+ }
+
+ void listIdentifier(std::vector<std::string> &list) const {
+ list.reserve(list.size() + mSharedInt.size());
+
+ std::vector<ESM::Cell *>::const_iterator it = mSharedInt.begin();
+ for (; it != mSharedInt.end(); ++it) {
+ list.push_back((*it)->mName);
+ }
+ }
+
+ ESM::Cell *insert(const ESM::Cell &cell) {
+ if (search(cell) != 0) {
+ std::ostringstream msg;
+ msg << "Failed to create ";
+ msg << ((cell.isExterior()) ? "exterior" : "interior");
+ msg << " cell";
+
+ throw std::runtime_error(msg.str());
+ }
+ ESM::Cell *ptr;
+ if (cell.isExterior()) {
+ std::pair<int, int> key(cell.getGridX(), cell.getGridY());
+
+ // duplicate insertions are avoided by search(ESM::Cell &)
+ std::pair<DynamicExt::iterator, bool> result =
+ mDynamicExt.insert(std::make_pair(key, cell));
+
+ ptr = &result.first->second;
+ mSharedExt.push_back(ptr);
+ } else {
+ std::string key = Misc::StringUtils::lowerCase(cell.mName);
+
+ // duplicate insertions are avoided by search(ESM::Cell &)
+ std::pair<DynamicInt::iterator, bool> result =
+ mDynamicInt.insert(std::make_pair(key, cell));
+
+ ptr = &result.first->second;
+ mSharedInt.push_back(ptr);
+ }
+ return ptr;
+ }
+
+ bool erase(const ESM::Cell &cell) {
+ if (cell.isExterior()) {
+ return erase(cell.getGridX(), cell.getGridY());
+ }
+ return erase(cell.mName);
+ }
+
+ bool erase(const std::string &id) {
+ std::string key = Misc::StringUtils::lowerCase(id);
+ DynamicInt::iterator it = mDynamicInt.find(key);
+
+ if (it == mDynamicInt.end()) {
+ return false;
+ }
+ mDynamicInt.erase(it);
+ mSharedInt.erase(
+ mSharedInt.begin() + mSharedInt.size(),
+ mSharedInt.end()
+ );
+
+ for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) {
+ mSharedInt.push_back(&it->second);
+ }
+
+ return true;
+ }
+
+ bool erase(int x, int y) {
+ std::pair<int, int> key(x, y);
+ DynamicExt::iterator it = mDynamicExt.find(key);
+
+ if (it == mDynamicExt.end()) {
+ return false;
+ }
+ mDynamicExt.erase(it);
+ mSharedExt.erase(
+ mSharedExt.begin() + mSharedExt.size(),
+ mSharedExt.end()
+ );
+
+ for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) {
+ mSharedExt.push_back(&it->second);
+ }
+
+ return true;
+ }
+ };
+
+ template <>
+ class Store<ESM::Pathgrid> : public StoreBase
+ {
+ public:
+ typedef std::vector<ESM::Pathgrid>::const_iterator iterator;
+
+ private:
+ std::vector<ESM::Pathgrid> mStatic;
+
+ std::vector<ESM::Pathgrid>::iterator mIntBegin, mIntEnd, mExtBegin, mExtEnd;
+
+ struct IntExtOrdering
+ {
+ bool operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const {
+ // interior pathgrids precedes exterior ones (x < y)
+ if ((x.mData.mX == 0 && x.mData.mY == 0) &&
+ (y.mData.mX != 0 || y.mData.mY != 0))
+ {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ struct ExtCompare
+ {
+ bool operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const {
+ if (x.mData.mX == y.mData.mX) {
+ return x.mData.mY < y.mData.mY;
+ }
+ return x.mData.mX < y.mData.mX;
+ }
+ };
+
+ public:
+
+ void load(ESM::ESMReader &esm, const std::string &id) {
+ mStatic.push_back(ESM::Pathgrid());
+ mStatic.back().load(esm);
+ }
+
+ size_t getSize() const {
+ return mStatic.size();
+ }
+
+ void setUp() {
+ IntExtOrdering cmp;
+ std::sort(mStatic.begin(), mStatic.end(), cmp);
+
+ ESM::Pathgrid pg;
+ pg.mData.mX = pg.mData.mY = 1;
+ mExtBegin =
+ std::lower_bound(mStatic.begin(), mStatic.end(), pg, cmp);
+ mExtEnd = mStatic.end();
+
+ mIntBegin = mStatic.begin();
+ mIntEnd = mExtBegin;
+
+ std::sort(mIntBegin, mIntEnd, RecordCmp());
+ std::sort(mExtBegin, mExtEnd, ExtCompare());
+ }
+
+ const ESM::Pathgrid *search(int x, int y) const {
+ ESM::Pathgrid pg;
+ pg.mData.mX = x;
+ pg.mData.mY = y;
+
+ iterator it =
+ std::lower_bound(mExtBegin, mExtEnd, pg, ExtCompare());
+ if (it != mExtEnd && it->mData.mX == x && it->mData.mY == y) {
+ return &(*it);
+ }
+ return 0;
+ }
+
+ const ESM::Pathgrid *find(int x, int y) const {
+ const ESM::Pathgrid *ptr = search(x, y);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Pathgrid at (" << x << ", " << y << ") not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ const ESM::Pathgrid *search(const std::string &name) const {
+ ESM::Pathgrid pg;
+ pg.mCell = name;
+
+ iterator it = std::lower_bound(mIntBegin, mIntEnd, pg, RecordCmp());
+ if (it != mIntEnd && Misc::StringUtils::ciEqual(it->mCell, name)) {
+ return &(*it);
+ }
+ return 0;
+ }
+
+ const ESM::Pathgrid *find(const std::string &name) const {
+ const ESM::Pathgrid *ptr = search(name);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Pathgrid in cell '" << name << "' not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ const ESM::Pathgrid *search(const ESM::Cell &cell) const {
+ if (cell.mData.mFlags & ESM::Cell::Interior) {
+ return search(cell.mName);
+ }
+ return search(cell.mData.mX, cell.mData.mY);
+ }
+
+ const ESM::Pathgrid *find(const ESM::Cell &cell) const {
+ if (cell.mData.mFlags & ESM::Cell::Interior) {
+ return find(cell.mName);
+ }
+ return find(cell.mData.mX, cell.mData.mY);
+ }
+
+ iterator begin() const {
+ return mStatic.begin();
+ }
+
+ iterator end() const {
+ return mStatic.end();
+ }
+
+ iterator interiorPathsBegin() const {
+ return mIntBegin;
+ }
+
+ iterator interiorPathsEnd() const {
+ return mIntEnd;
+ }
+
+ iterator exteriorPathsBegin() const {
+ return mExtBegin;
+ }
+
+ iterator exteriorPathsEnd() const {
+ return mExtEnd;
+ }
+ };
+
+ template <class T>
+ class IndexedStore
+ {
+ struct Compare
+ {
+ bool operator()(const T &x, const T &y) const {
+ return x.mIndex < y.mIndex;
+ }
+ };
+ protected:
+ std::vector<T> mStatic;
+
+ public:
+ typedef typename std::vector<T>::const_iterator iterator;
+
+ IndexedStore() {}
+
+ IndexedStore(unsigned int size) {
+ mStatic.reserve(size);
+ }
+
+ iterator begin() const {
+ return mStatic.begin();
+ }
+
+ iterator end() const {
+ return mStatic.end();
+ }
+
+ /// \todo refine loading order
+ void load(ESM::ESMReader &esm) {
+ mStatic.push_back(T());
+ mStatic.back().load(esm);
+ }
+
+ int getSize() const {
+ return mStatic.size();
+ }
+
+ void setUp() {
+ /// \note This method sorts indexed values for further
+ /// searches. Every loaded item is present in storage, but
+ /// latest loaded shadows any previous while searching.
+ /// If memory cost will be too high, it is possible to remove
+ /// unused values.
+
+ Compare cmp;
+
+ std::stable_sort(mStatic.begin(), mStatic.end(), cmp);
+
+ typename std::vector<T>::iterator first, next;
+ next = first = mStatic.begin();
+
+ while (first != mStatic.end() && ++next != mStatic.end()) {
+ while (next != mStatic.end() && !cmp(*first, *next)) {
+ ++next;
+ }
+ if (first != --next) {
+ std::swap(*first, *next);
+ }
+ first = ++next;
+ }
+ }
+
+ const T *search(int index) const {
+ T item;
+ item.mIndex = index;
+
+ iterator it =
+ std::lower_bound(mStatic.begin(), mStatic.end(), item, Compare());
+ if (it != mStatic.end() && it->mIndex == index) {
+ return &(*it);
+ }
+ return 0;
+ }
+
+ const T *find(int index) const {
+ const T *ptr = search(index);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Object with index " << index << " not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+ };
+
+ template <>
+ struct Store<ESM::Skill> : public IndexedStore<ESM::Skill>
+ {
+ Store() {}
+ Store(unsigned int size)
+ : IndexedStore<ESM::Skill>(size)
+ {}
+ };
+
+ template <>
+ struct Store<ESM::MagicEffect> : public IndexedStore<ESM::MagicEffect>
+ {
+ Store() {}
+ Store(unsigned int size)
+ : IndexedStore<ESM::MagicEffect>(size)
+ {}
+ };
+
+ template <>
+ class Store<ESM::Attribute> : public IndexedStore<ESM::Attribute>
+ {
+ std::vector<ESM::Attribute> mStatic;
+
+ public:
+ typedef std::vector<ESM::Attribute>::const_iterator iterator;
+
+ Store() {
+ mStatic.reserve(ESM::Attribute::Length);
+ }
+
+ const ESM::Attribute *search(size_t index) const {
+ if (index >= mStatic.size()) {
+ return 0;
+ }
+ return &mStatic.at(index);
+ }
+
+ const ESM::Attribute *find(size_t index) const {
+ const ESM::Attribute *ptr = search(index);
+ if (ptr == 0) {
+ std::ostringstream msg;
+ msg << "Attribute with index " << index << " not found";
+ throw std::runtime_error(msg.str());
+ }
+ return ptr;
+ }
+
+ void setUp() {
+ for (int i = 0; i < ESM::Attribute::Length; ++i) {
+ mStatic.push_back(
+ ESM::Attribute(
+ ESM::Attribute::sAttributeIds[i],
+ ESM::Attribute::sGmstAttributeIds[i],
+ ESM::Attribute::sGmstAttributeDescIds[i]
+ )
+ );
+ }
+ }
+
+ size_t getSize() const {
+ return mStatic.size();
+ }
+
+ iterator begin() const {
+ return mStatic.begin();
+ }
+
+ iterator end() const {
+ return mStatic.end();
+ }
+ };
+
+} //end namespace
+
+#endif
diff --git a/apps/openmw/mwworld/timestamp.cpp b/apps/openmw/mwworld/timestamp.cpp
new file mode 100644
index 0000000000..126d5490c5
--- /dev/null
+++ b/apps/openmw/mwworld/timestamp.cpp
@@ -0,0 +1,108 @@
+
+#include "timestamp.hpp"
+
+#include <cmath>
+
+#include <stdexcept>
+
+namespace MWWorld
+{
+ TimeStamp::TimeStamp (float hour, int day)
+ : mHour (hour), mDay (day)
+ {
+ if (hour<0 || hour>=24 || day<0)
+ throw std::runtime_error ("invalid time stamp");
+ }
+
+ float TimeStamp::getHour() const
+ {
+ return mHour;
+ }
+
+ int TimeStamp::getDay() const
+ {
+ return mDay;
+ }
+
+ TimeStamp& TimeStamp::operator+= (double hours)
+ {
+ if (hours<0)
+ throw std::runtime_error ("can't move time stamp backwards in time");
+
+ hours += mHour;
+
+ mHour = static_cast<float> (std::fmod (hours, 24));
+
+ mDay += hours / 24;
+
+ return *this;
+ }
+
+ bool operator== (const TimeStamp& left, const TimeStamp& right)
+ {
+ return left.getHour()==right.getHour() && left.getDay()==right.getDay();
+ }
+
+ bool operator!= (const TimeStamp& left, const TimeStamp& right)
+ {
+ return !(left==right);
+ }
+
+ bool operator< (const TimeStamp& left, const TimeStamp& right)
+ {
+ if (left.getDay()<right.getDay())
+ return true;
+
+ if (left.getDay()>right.getDay())
+ return false;
+
+ return left.getHour()<right.getHour();
+ }
+
+ bool operator<= (const TimeStamp& left, const TimeStamp& right)
+ {
+ return left<right || left==right;
+ }
+
+ bool operator> (const TimeStamp& left, const TimeStamp& right)
+ {
+ return !(left<=right);
+ }
+
+ bool operator>= (const TimeStamp& left, const TimeStamp& right)
+ {
+ return !(left<right);
+ }
+
+ TimeStamp operator+ (const TimeStamp& stamp, double hours)
+ {
+ return TimeStamp (stamp) += hours;
+ }
+
+ TimeStamp operator+ (double hours, const TimeStamp& stamp)
+ {
+ return TimeStamp (stamp) += hours;
+ }
+
+ double operator- (const TimeStamp& left, const TimeStamp& right)
+ {
+ if (left<right)
+ return -(right-left);
+
+ int days = left.getDay() - right.getDay();
+
+ double hours = 0;
+
+ if (left.getHour()<right.getHour())
+ {
+ hours = 24-right.getHour()+left.getHour();
+ ++days;
+ }
+ else
+ {
+ hours = left.getHour()-right.getHour();
+ }
+
+ return hours + 24*days;
+ }
+}
diff --git a/apps/openmw/mwworld/timestamp.hpp b/apps/openmw/mwworld/timestamp.hpp
new file mode 100644
index 0000000000..e2d8b242ae
--- /dev/null
+++ b/apps/openmw/mwworld/timestamp.hpp
@@ -0,0 +1,44 @@
+#ifndef GAME_MWWORLD_TIMESTAMP_H
+#define GAME_MWWORLD_TIMESTAMP_H
+
+namespace MWWorld
+{
+ /// \brief In-game time stamp
+ ///
+ /// This class is based on the global variables GameHour and DaysPassed.
+ class TimeStamp
+ {
+ float mHour;
+ int mDay;
+
+ public:
+
+ explicit TimeStamp (float hour = 0, int day = 0);
+ ///< \oaram hour [0, 23)
+ /// \param day >=0
+
+ float getHour() const;
+
+ int getDay() const;
+
+ TimeStamp& operator+= (double hours);
+ ///< \param hours >=0
+ };
+
+ bool operator== (const TimeStamp& left, const TimeStamp& right);
+ bool operator!= (const TimeStamp& left, const TimeStamp& right);
+
+ bool operator< (const TimeStamp& left, const TimeStamp& right);
+ bool operator<= (const TimeStamp& left, const TimeStamp& right);
+
+ bool operator> (const TimeStamp& left, const TimeStamp& right);
+ bool operator>= (const TimeStamp& left, const TimeStamp& right);
+
+ TimeStamp operator+ (const TimeStamp& stamp, double hours);
+ TimeStamp operator+ (double hours, const TimeStamp& stamp);
+
+ double operator- (const TimeStamp& left, const TimeStamp& right);
+ ///< Returns the difference between \a left and \a right in in-game hours.
+}
+
+#endif
diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp
new file mode 100644
index 0000000000..6f5dbe23fe
--- /dev/null
+++ b/apps/openmw/mwworld/weather.cpp
@@ -0,0 +1,709 @@
+#include "weather.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwrender/renderingmanager.hpp"
+
+#include "player.hpp"
+#include "esmstore.hpp"
+#include "fallback.hpp"
+
+using namespace Ogre;
+using namespace MWWorld;
+using namespace MWSound;
+
+namespace
+{
+ float lerp (float x, float y, float factor)
+ {
+ return x * (1-factor) + y * factor;
+ }
+
+ Ogre::ColourValue lerp (const Ogre::ColourValue& x, const Ogre::ColourValue& y, float factor)
+ {
+ return x * (1-factor) + y * factor;
+ }
+}
+
+void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name)
+{
+ std::string upper=name;
+ upper[0]=toupper(name[0]);
+ weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent");
+ weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta");
+ weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color");
+ weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color");
+ weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color");
+ weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color");
+ weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color");
+ weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color");
+ weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color");
+ weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color");
+ weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color");
+ weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color");
+ weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color");
+ weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color");
+ weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color");
+ weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color");
+ weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color");
+ weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color");
+ weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color");
+ weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth");
+ weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth");
+ weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed");
+ weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed");
+ weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View");
+ mWeatherSettings[name] = weather;
+}
+
+
+float WeatherManager::calculateHourFade (const std::string& moonName) const
+{
+ float fadeOutStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Start");
+ float fadeOutFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Finish");
+ float fadeInStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Start");
+ float fadeInFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Finish");
+
+ if (mHour >= fadeOutStart && mHour <= fadeOutFinish)
+ return (1 - ((mHour - fadeOutStart) / (fadeOutFinish - fadeOutStart)));
+ if (mHour >= fadeInStart && mHour <= fadeInFinish)
+ return (1 - ((mHour - fadeInStart) / (fadeInFinish - fadeInStart)));
+ else
+ return 1;
+}
+
+float WeatherManager::calculateAngleFade (const std::string& moonName, float angle) const
+{
+ float endAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_End_Angle");
+ float startAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Start_Angle");
+ if (angle <= startAngle && angle >= endAngle)
+ return (1 - ((angle - endAngle)/(startAngle-endAngle)));
+ else if (angle > startAngle)
+ return 0.f;
+ else
+ return 1.f;
+}
+
+WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fallback* fallback) :
+ mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true),
+ mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0),
+ mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0),
+ mMonth(0), mDay(0), mTimePassed(0), mFallback(fallback), mWindSpeed(0.f),
+ mRendering(rendering)
+{
+ //Globals
+ mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0");
+ mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1");
+ mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2");
+ mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3");
+ mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time");
+ mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time");
+ mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration");
+ mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration");
+ mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes");
+ mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
+ mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency");
+ mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold");
+ mThunderSoundDelay = 0.25;
+
+ //Some useful values
+ /* TODO: Use pre-sunrise_time, pre-sunset_time,
+ * post-sunrise_time, and post-sunset_time to better
+ * describe sunrise/sunset time.
+ * These values are fallbacks attached to weather.
+ */
+ mNightStart = mSunsetTime + mSunsetDuration;
+ mNightEnd = mSunriseTime - 0.5;
+ mDayStart = mSunriseTime + mSunriseDuration;
+ mDayEnd = mSunsetTime;
+
+ //Weather
+ Weather clear;
+ clear.mCloudTexture = "tx_sky_clear.dds";
+ setFallbackWeather(clear,"clear");
+
+ Weather cloudy;
+ cloudy.mCloudTexture = "tx_sky_cloudy.dds";
+ setFallbackWeather(cloudy,"cloudy");
+
+ Weather foggy;
+ foggy.mCloudTexture = "tx_sky_foggy.dds";
+ setFallbackWeather(foggy,"foggy");
+
+ Weather thunderstorm;
+ thunderstorm.mCloudTexture = "tx_sky_thunder.dds";
+ thunderstorm.mRainLoopSoundID = "rain heavy";
+ setFallbackWeather(thunderstorm,"thunderstorm");
+
+ Weather rain;
+ rain.mCloudTexture = "tx_sky_rainy.dds";
+ rain.mRainLoopSoundID = "rain";
+ setFallbackWeather(rain,"rain");
+
+ Weather overcast;
+ overcast.mCloudTexture = "tx_sky_overcast.dds";
+ setFallbackWeather(overcast,"overcast");
+
+ Weather ashstorm;
+ ashstorm.mCloudTexture = "tx_sky_ashstorm.dds";
+ ashstorm.mAmbientLoopSoundID = "ashstorm";
+ setFallbackWeather(ashstorm,"ashstorm");
+
+ Weather blight;
+ blight.mCloudTexture = "tx_sky_blight.dds";
+ blight.mAmbientLoopSoundID = "blight";
+ setFallbackWeather(blight,"blight");
+
+ Weather snow;
+ snow.mCloudTexture = "tx_bm_sky_snow.dds";
+ setFallbackWeather(snow, "snow");
+
+ Weather blizzard;
+ blizzard.mCloudTexture = "tx_bm_sky_blizzard.dds";
+ blizzard.mAmbientLoopSoundID = "BM Blizzard";
+ setFallbackWeather(blizzard,"blizzard");
+}
+
+WeatherManager::~WeatherManager()
+{
+ stopSounds(true);
+}
+
+void WeatherManager::setWeather(const String& weather, bool instant)
+{
+ if (weather == mCurrentWeather && mNextWeather == "")
+ {
+ mFirstUpdate = false;
+ return;
+ }
+
+ if (instant || mFirstUpdate)
+ {
+ mNextWeather = "";
+ mCurrentWeather = weather;
+ }
+ else
+ {
+ if (mNextWeather != "")
+ {
+ // transition more than 50% finished?
+ if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5)
+ mCurrentWeather = mNextWeather;
+ }
+
+ mNextWeather = weather;
+ mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600;
+ }
+ mFirstUpdate = false;
+}
+
+void WeatherManager::setResult(const String& weatherType)
+{
+ const Weather& current = mWeatherSettings[weatherType];
+
+ mResult.mCloudTexture = current.mCloudTexture;
+ mResult.mCloudBlendFactor = 0;
+ mResult.mCloudOpacity = current.mCloudsMaximumPercent;
+ mResult.mWindSpeed = current.mWindSpeed;
+ mResult.mCloudSpeed = current.mCloudSpeed;
+ mResult.mGlareView = current.mGlareView;
+ mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
+ mResult.mSunColor = current.mSunDiscSunsetColor;
+
+ mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1);
+
+ mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth;
+
+ // night
+ if (mHour <= mNightEnd || mHour >= mNightStart + 1)
+ {
+ mResult.mFogColor = current.mFogNightColor;
+ mResult.mAmbientColor = current.mAmbientNightColor;
+ mResult.mSunColor = current.mSunNightColor;
+ mResult.mSkyColor = current.mSkyNightColor;
+ mResult.mNightFade = 1.f;
+ }
+
+ // sunrise
+ else if (mHour >= mNightEnd && mHour <= mDayStart + 1)
+ {
+ if (mHour <= mSunriseTime)
+ {
+ // fade in
+ float advance = mSunriseTime - mHour;
+ float factor = advance / 0.5f;
+ mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor);
+ mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor);
+ mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor);
+ mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor);
+ mResult.mNightFade = factor;
+ }
+ else //if (mHour >= 6)
+ {
+ // fade out
+ float advance = mHour - mSunriseTime;
+ float factor = advance / 3.f;
+ mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor);
+ mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor);
+ mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor);
+ mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor);
+ }
+ }
+
+ // day
+ else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1)
+ {
+ mResult.mFogColor = current.mFogDayColor;
+ mResult.mAmbientColor = current.mAmbientDayColor;
+ mResult.mSunColor = current.mSunDayColor;
+ mResult.mSkyColor = current.mSkyDayColor;
+ }
+
+ // sunset
+ else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1)
+ {
+ if (mHour <= mDayEnd + 1)
+ {
+ // fade in
+ float advance = (mDayEnd + 1) - mHour;
+ float factor = (advance / 2);
+ mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor);
+ mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor);
+ mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor);
+ mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor);
+ }
+ else //if (mHour >= 19)
+ {
+ // fade out
+ float advance = mHour - (mDayEnd + 1);
+ float factor = advance / 2.f;
+ mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor);
+ mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor);
+ mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor);
+ mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor);
+ mResult.mNightFade = factor;
+ }
+ }
+}
+
+void WeatherManager::transition(float factor)
+{
+ setResult(mCurrentWeather);
+ const WeatherResult current = mResult;
+ setResult(mNextWeather);
+ const WeatherResult other = mResult;
+
+ mResult.mCloudTexture = current.mCloudTexture;
+ mResult.mNextCloudTexture = other.mCloudTexture;
+ mResult.mCloudBlendFactor = factor;
+
+ mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor);
+ mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor);
+ mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor);
+ mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor);
+
+ mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor);
+ mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor);
+ mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor);
+ mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor);
+ mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor);
+ mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor);
+ mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor);
+ mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor);
+
+ mResult.mNight = current.mNight;
+}
+
+void WeatherManager::update(float duration)
+{
+ float timePassed = mTimePassed;
+ mTimePassed = 0;
+
+ mWeatherUpdateTime -= timePassed;
+
+ const bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior());
+ if (!exterior)
+ {
+ mRendering->sunDisable(false);
+ mRendering->skyDisable();
+ mRendering->getSkyManager()->setLightningStrength(0.f);
+ stopSounds(true);
+ 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);
+ }
+
+ if (mNextWeather != "")
+ {
+ mRemainingTransitionTime -= timePassed;
+ if (mRemainingTransitionTime < 0)
+ {
+ mCurrentWeather = mNextWeather;
+ mNextWeather = "";
+ }
+ }
+
+ if (mNextWeather != "")
+ transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600)));
+ else
+ setResult(mCurrentWeather);
+
+ mWindSpeed = mResult.mWindSpeed;
+
+ mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor);
+
+ // disable sun during night
+ if (mHour >= mNightStart || mHour <= mSunriseTime)
+ mRendering->getSkyManager()->sunDisable();
+ else
+ mRendering->getSkyManager()->sunEnable();
+
+ // sun angle
+ float height;
+
+ //Day duration
+ float dayDuration = (mNightStart - 1) - mSunriseTime;
+
+ // rise at 6, set at 20
+ if (mHour >= mSunriseTime && mHour <= mNightStart)
+ height = 1 - std::abs(((mHour - dayDuration) / 7.f));
+ else if (mHour > mNightStart)
+ height = (mHour - mNightStart) / 4.f;
+ else //if (mHour > 0 && mHour < 6)
+ height = 1 - (mHour / mSunriseTime);
+
+ int facing = (mHour > 13.f) ? 1 : -1;
+
+ Vector3 final(
+ (height - 1) * facing,
+ (height - 1) * facing,
+ height);
+ mRendering->setSunDirection(final);
+
+ /*
+ * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish
+ * for masser and secunda
+ */
+
+ float fadeOutFinish=mFallback->getFallbackFloat("Moons_Masser_Fade_Out_Finish");
+ float fadeInStart=mFallback->getFallbackFloat("Moons_Masser_Fade_In_Start");
+
+ //moon calculations
+ float moonHeight;
+ if (mHour >= fadeInStart)
+ moonHeight = mHour - fadeInStart;
+ else if (mHour <= fadeOutFinish)
+ moonHeight = mHour + fadeOutFinish;
+ else
+ moonHeight = 0;
+
+ moonHeight /= (24.f - (fadeInStart - fadeOutFinish));
+
+ if (moonHeight != 0)
+ {
+ int facing = (moonHeight <= 1) ? 1 : -1;
+ Vector3 masser(
+ (moonHeight - 1) * facing,
+ (1 - moonHeight) * facing,
+ moonHeight);
+
+ Vector3 secunda(
+ (moonHeight - 1) * facing * 1.25,
+ (1 - moonHeight) * facing * 0.8,
+ moonHeight);
+
+ mRendering->getSkyManager()->setMasserDirection(masser);
+ mRendering->getSkyManager()->setSecundaDirection(secunda);
+ mRendering->getSkyManager()->masserEnable();
+ mRendering->getSkyManager()->secundaEnable();
+
+ float angle = (1-moonHeight) * 90.f * facing;
+ float masserHourFade = calculateHourFade("Masser");
+ float secundaHourFade = calculateHourFade("Secunda");
+ float masserAngleFade = calculateAngleFade("Masser", angle);
+ float secundaAngleFade = calculateAngleFade("Secunda", angle);
+
+ masserAngleFade *= masserHourFade;
+ secundaAngleFade *= secundaHourFade;
+
+ mRendering->getSkyManager()->setMasserFade(masserAngleFade);
+ mRendering->getSkyManager()->setSecundaFade(secundaAngleFade);
+ }
+ else
+ {
+ mRendering->getSkyManager()->masserDisable();
+ mRendering->getSkyManager()->secundaDisable();
+ }
+
+ if (mCurrentWeather == "thunderstorm" && mNextWeather == "")
+ {
+ if (mThunderFlash > 0)
+ {
+ // play the sound after a delay
+ mThunderSoundDelay -= duration;
+ if (mThunderSoundDelay <= 0)
+ {
+ // pick a random sound
+ int sound = rand() % 4;
+ std::string* soundName;
+ if (sound == 0) soundName = &mThunderSoundID0;
+ else if (sound == 1) soundName = &mThunderSoundID1;
+ else if (sound == 2) soundName = &mThunderSoundID2;
+ else if (sound == 3) soundName = &mThunderSoundID3;
+ MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0);
+ mThunderSoundDelay = 1000;
+ }
+
+ mThunderFlash -= duration;
+ if (mThunderFlash > 0)
+ mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );
+ else
+ {
+ mThunderChanceNeeded = rand() % 100;
+ mThunderChance = 0;
+ mRendering->getSkyManager()->setLightningStrength( 0.f );
+ }
+ }
+ else
+ {
+ // no thunder active
+ mThunderChance += duration*4; // chance increases by 4 percent every second
+ if (mThunderChance >= mThunderChanceNeeded)
+ {
+ mThunderFlash = mThunderThreshold;
+
+ mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );
+
+ mThunderSoundDelay = 0.25;
+ }
+ }
+ }
+ else
+ mRendering->getSkyManager()->setLightningStrength(0.f);
+
+ mRendering->setAmbientColour(mResult.mAmbientColor);
+ mRendering->sunEnable(false);
+ mRendering->setSunColour(mResult.mSunColor);
+
+ mRendering->getSkyManager()->setWeather(mResult);
+
+
+ // Play sounds
+ if (mNextWeather == "")
+ {
+ std::string ambientSnd = mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID;
+ if (!ambientSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end())
+ {
+ mSoundsPlaying.push_back(ambientSnd);
+ MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
+ }
+
+ std::string rainSnd = mWeatherSettings[mCurrentWeather].mRainLoopSoundID;
+ if (!rainSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end())
+ {
+ mSoundsPlaying.push_back(rainSnd);
+ MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
+ }
+ }
+
+ stopSounds(false);
+}
+
+void WeatherManager::stopSounds(bool stopAll)
+{
+ std::vector<std::string>::iterator it = mSoundsPlaying.begin();
+ while (it!=mSoundsPlaying.end())
+ {
+ if (stopAll ||
+ !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) ||
+ (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID)))
+ {
+ MWBase::Environment::get().getSoundManager()->stopSound(*it);
+ it = mSoundsPlaying.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+Ogre::String WeatherManager::nextWeather(const ESM::Region* region) const
+{
+ std::vector<char> probability;
+
+ RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId));
+ if(iter != mRegionMods.end())
+ probability = iter->second;
+ else
+ {
+ probability.reserve(10);
+ probability.push_back(region->mData.mClear);
+ probability.push_back(region->mData.mCloudy);
+ probability.push_back(region->mData.mFoggy);
+ probability.push_back(region->mData.mOvercast);
+ probability.push_back(region->mData.mRain);
+ probability.push_back(region->mData.mThunder);
+ probability.push_back(region->mData.mAsh);
+ probability.push_back(region->mData.mBlight);
+ probability.push_back(region->mData.mA);
+ probability.push_back(region->mData.mB);
+ }
+
+ /*
+ * All probabilities must add to 100 (responsibility of the user).
+ * If chances A and B has values 30 and 70 then by generating
+ * 100 numbers 1..100, 30% will be lesser or equal 30 and
+ * 70% will be greater than 30 (in theory).
+ */
+
+ int chance = (rand() % 100) + 1; // 1..100
+ int sum = 0;
+ unsigned int i = 0;
+ for (; i < probability.size(); ++i)
+ {
+ sum += probability[i];
+ if (chance < sum)
+ break;
+ }
+
+ switch (i)
+ {
+ case 1:
+ return "cloudy";
+ case 2:
+ return "foggy";
+ case 3:
+ return "overcast";
+ case 4:
+ return "rain";
+ case 5:
+ return "thunderstorm";
+ case 6:
+ return "ashstorm";
+ case 7:
+ return "blight";
+ case 8:
+ return "snow";
+ case 9:
+ return "blizzard";
+ default: // case 0
+ return "clear";
+ }
+}
+
+void WeatherManager::setHour(const float hour)
+{
+ mHour = hour;
+}
+
+void WeatherManager::setDate(const int day, const int month)
+{
+ mDay = day;
+ mMonth = month;
+}
+
+unsigned int WeatherManager::getWeatherID() const
+{
+ // Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather
+
+ if (mCurrentWeather == "clear")
+ return 0;
+ else if (mCurrentWeather == "cloudy")
+ return 1;
+ else if (mCurrentWeather == "foggy")
+ return 2;
+ else if (mCurrentWeather == "overcast")
+ return 3;
+ else if (mCurrentWeather == "rain")
+ return 4;
+ else if (mCurrentWeather == "thunderstorm")
+ return 5;
+ else if (mCurrentWeather == "ashstorm")
+ return 6;
+ else if (mCurrentWeather == "blight")
+ return 7;
+ else if (mCurrentWeather == "snow")
+ return 8;
+ else if (mCurrentWeather == "blizzard")
+ return 9;
+
+ else
+ return 0;
+}
+
+void WeatherManager::changeWeather(const std::string& region, const unsigned int id)
+{
+ // make sure this region exists
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().find(region);
+
+ std::string weather;
+ if (id==0)
+ weather = "clear";
+ else if (id==1)
+ weather = "cloudy";
+ else if (id==2)
+ weather = "foggy";
+ else if (id==3)
+ weather = "overcast";
+ else if (id==4)
+ weather = "rain";
+ else if (id==5)
+ weather = "thunderstorm";
+ else if (id==6)
+ weather = "ashstorm";
+ else if (id==7)
+ weather = "blight";
+ else if (id==8)
+ weather = "snow";
+ else if (id==9)
+ weather = "blizzard";
+ else
+ weather = "clear";
+
+ mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather;
+
+ std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion;
+ if (Misc::StringUtils::ciEqual(region, playerRegion))
+ setWeather(weather);
+}
+
+void WeatherManager::modRegion(const std::string &regionid, const std::vector<char> &chances)
+{
+ mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances;
+ // Start transitioning right away if the region no longer supports the current weather type
+ unsigned int current = getWeatherID();
+ if(current >= chances.size() || chances[current] == 0)
+ mWeatherUpdateTime = 0.0f;
+}
+
+float WeatherManager::getWindSpeed() const
+{
+ return mWindSpeed;
+}
diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp
new file mode 100644
index 0000000000..80cbe0418e
--- /dev/null
+++ b/apps/openmw/mwworld/weather.hpp
@@ -0,0 +1,217 @@
+#ifndef GAME_MWWORLD_WEATHER_H
+#define GAME_MWWORLD_WEATHER_H
+
+#include <OgreString.h>
+#include <OgreColourValue.h>
+
+namespace ESM
+{
+ struct Region;
+}
+
+namespace MWRender
+{
+ class RenderingManager;
+}
+
+namespace MWWorld
+{
+ class Fallback;
+
+ /// Defines the actual weather that results from weather setting (see below), time of day and weather transition
+ struct WeatherResult
+ {
+ Ogre::String mCloudTexture;
+ Ogre::String mNextCloudTexture;
+ float mCloudBlendFactor;
+
+ Ogre::ColourValue mFogColor;
+
+ Ogre::ColourValue mAmbientColor;
+
+ Ogre::ColourValue mSkyColor;
+
+ Ogre::ColourValue mSunColor;
+
+ Ogre::ColourValue mSunDiscColor;
+
+ float mFogDepth;
+
+ float mWindSpeed;
+
+ float mCloudSpeed;
+
+ float mCloudOpacity;
+
+ float mGlareView;
+
+ bool mNight; // use night skybox
+ float mNightFade; // fading factor for night skybox
+
+ Ogre::String mAmbientLoopSoundID;
+ };
+
+
+ /// Defines a single weather setting (according to INI)
+ struct Weather
+ {
+ Ogre::String mCloudTexture;
+
+ // Sky (atmosphere) colors
+ Ogre::ColourValue mSkySunriseColor,
+ mSkyDayColor,
+ mSkySunsetColor,
+ mSkyNightColor;
+
+ // Fog colors
+ Ogre::ColourValue mFogSunriseColor,
+ mFogDayColor,
+ mFogSunsetColor,
+ mFogNightColor;
+
+ // Ambient lighting colors
+ Ogre::ColourValue mAmbientSunriseColor,
+ mAmbientDayColor,
+ mAmbientSunsetColor,
+ mAmbientNightColor;
+
+ // Sun (directional) lighting colors
+ Ogre::ColourValue mSunSunriseColor,
+ mSunDayColor,
+ mSunSunsetColor,
+ mSunNightColor;
+
+ // Fog depth/density
+ float mLandFogDayDepth,
+ mLandFogNightDepth;
+
+ // Color modulation for the sun itself during sunset (not completely sure)
+ Ogre::ColourValue mSunDiscSunsetColor;
+
+ // Duration of weather transition (in days)
+ float mTransitionDelta;
+
+ // No idea what this one is used for?
+ float mWindSpeed;
+
+ // Cloud animation speed multiplier
+ float mCloudSpeed;
+
+ // Multiplier for clouds transparency
+ float mCloudsMaximumPercent;
+
+ // Value between 0 and 1, defines the strength of the sun glare effect
+ float mGlareView;
+
+ // Sound effect
+ // This is used for Blight, Ashstorm and Blizzard (Bloodmoon)
+ Ogre::String mAmbientLoopSoundID;
+
+ // Rain sound effect
+ Ogre::String mRainLoopSoundID;
+
+ /// \todo disease chance
+ };
+
+ ///
+ /// Interface for weather settings
+ ///
+ class WeatherManager
+ {
+ public:
+ WeatherManager(MWRender::RenderingManager*,MWWorld::Fallback* fallback);
+ ~WeatherManager();
+
+ /**
+ * Change the weather in the specified region
+ * @param region that should be changed
+ * @param ID of the weather setting to shift to
+ */
+ void changeWeather(const std::string& region, const unsigned int id);
+
+ /**
+ * Per-frame update
+ * @param duration
+ */
+ void update(float duration);
+
+ void stopSounds(bool stopAll);
+
+ void setHour(const float hour);
+
+ float getWindSpeed() const;
+
+ void setDate(const int day, const int month);
+
+ void advanceTime(double hours)
+ {
+ mTimePassed += hours*3600;
+ }
+
+ unsigned int getWeatherID() const;
+
+ void modRegion(const std::string &regionid, const std::vector<char> &chances);
+
+ private:
+ float mHour;
+ int mDay, mMonth;
+ float mWindSpeed;
+ MWWorld::Fallback* mFallback;
+ void setFallbackWeather(Weather& weather,const std::string& name);
+ MWRender::RenderingManager* mRendering;
+
+ std::map<Ogre::String, Weather> mWeatherSettings;
+
+ std::map<std::string, std::string> mRegionOverrides;
+
+ std::vector<std::string> mSoundsPlaying;
+
+ Ogre::String mCurrentWeather;
+ Ogre::String mNextWeather;
+
+ std::string mCurrentRegion;
+
+ bool mFirstUpdate;
+
+ float mRemainingTransitionTime;
+
+ float mThunderFlash;
+ float mThunderChance;
+ float mThunderChanceNeeded;
+
+ double mTimePassed; // time passed since last update
+
+ void transition(const float factor);
+ void setResult(const Ogre::String& weatherType);
+
+ float calculateHourFade (const std::string& moonName) const;
+ float calculateAngleFade (const std::string& moonName, float angle) const;
+
+ void setWeather(const Ogre::String& weatherType, bool instant=false);
+ Ogre::String nextWeather(const ESM::Region* region) const;
+ WeatherResult mResult;
+
+ typedef std::map<std::string,std::vector<char> > RegionModMap;
+ RegionModMap mRegionMods;
+
+ float mSunriseTime;
+ float mSunsetTime;
+ float mSunriseDuration;
+ float mSunsetDuration;
+ float mWeatherUpdateTime;
+ float mHoursBetweenWeatherChanges;
+ float mThunderFrequency;
+ float mThunderThreshold;
+ float mThunderSoundDelay;
+ float mNightStart;
+ float mNightEnd;
+ float mDayStart;
+ float mDayEnd;
+ std::string mThunderSoundID0;
+ std::string mThunderSoundID1;
+ std::string mThunderSoundID2;
+ std::string mThunderSoundID3;
+ };
+}
+
+#endif // GAME_MWWORLD_WEATHER_H
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
new file mode 100644
index 0000000000..eac0a39776
--- /dev/null
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -0,0 +1,1951 @@
+#include "worldimp.hpp"
+
+#include <OgreSceneNode.h>
+
+#include <libs/openengine/bullet/physic.hpp>
+
+#include <components/bsa/bsa_archive.hpp>
+#include <components/files/collections.hpp>
+#include <components/compiler/locals.hpp>
+
+#include <boost/math/special_functions/sign.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/soundmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/scriptmanager.hpp"
+
+#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/movement.hpp"
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwrender/sky.hpp"
+#include "../mwrender/animation.hpp"
+
+#include "../mwclass/door.hpp"
+
+#include "player.hpp"
+#include "manualref.hpp"
+#include "cellfunctors.hpp"
+#include "containerstore.hpp"
+#include "inventorystore.hpp"
+
+using namespace Ogre;
+
+namespace
+{
+/* // NOTE this code is never instantiated (proper copy in localscripts.cpp),
+ // so this commented out to not produce syntactic errors
+
+ template<typename T>
+ void listCellScripts (const MWWorld::ESMStore& store,
+ MWWorld::CellRefList<T>& cellRefList, MWWorld::LocalScripts& localScripts,
+ MWWorld::Ptr::CellStore *cell)
+ {
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (
+ cellRefList.mList.begin());
+ iter!=cellRefList.mList.end(); ++iter)
+ {
+ if (!iter->mBase->mScript.empty() && iter->mData.getCount())
+ {
+ if (const ESM::Script *script = store.get<ESM::Script>().find (iter->mBase->mScript))
+ {
+ iter->mData.setLocals (*script);
+
+ localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell));
+ }
+ }
+ }
+ }
+*/
+ template<typename T>
+ MWWorld::LiveCellRef<T> *searchViaHandle (const std::string& handle,
+ MWWorld::CellRefList<T>& refList)
+ {
+ typedef typename MWWorld::CellRefList<T>::List::iterator iterator;
+
+ for (iterator iter (refList.mList.begin()); iter!=refList.mList.end(); ++iter)
+ {
+ if (iter->mData.getCount() > 0 && iter->mData.getBaseNode()){
+ if (iter->mData.getHandle()==handle)
+ {
+ return &*iter;
+ }
+ }
+ }
+ return 0;
+ }
+}
+
+namespace MWWorld
+{
+ Ptr World::getPtrViaHandle (const std::string& handle, Ptr::CellStore& cell)
+ {
+ if (MWWorld::LiveCellRef<ESM::Activator> *ref =
+ searchViaHandle (handle, cell.mActivators))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Potion> *ref = searchViaHandle (handle, cell.mPotions))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Apparatus> *ref = searchViaHandle (handle, cell.mAppas))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Armor> *ref = searchViaHandle (handle, cell.mArmors))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Book> *ref = searchViaHandle (handle, cell.mBooks))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Clothing> *ref = searchViaHandle (handle, cell.mClothes))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Container> *ref =
+ searchViaHandle (handle, cell.mContainers))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Creature> *ref =
+ searchViaHandle (handle, cell.mCreatures))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Door> *ref = searchViaHandle (handle, cell.mDoors))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Ingredient> *ref =
+ searchViaHandle (handle, cell.mIngreds))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Light> *ref = searchViaHandle (handle, cell.mLights))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Lockpick> *ref = searchViaHandle (handle, cell.mLockpicks))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Miscellaneous> *ref = searchViaHandle (handle, cell.mMiscItems))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::NPC> *ref = searchViaHandle (handle, cell.mNpcs))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Probe> *ref = searchViaHandle (handle, cell.mProbes))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Repair> *ref = searchViaHandle (handle, cell.mRepairs))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Static> *ref = searchViaHandle (handle, cell.mStatics))
+ return Ptr (ref, &cell);
+ if (MWWorld::LiveCellRef<ESM::Weapon> *ref = searchViaHandle (handle, cell.mWeapons))
+ return Ptr (ref, &cell);
+ return Ptr();
+ }
+
+
+ int World::getDaysPerMonth (int month) const
+ {
+ switch (month)
+ {
+ case 0: return 31;
+ case 1: return 28;
+ case 2: return 31;
+ case 3: return 30;
+ case 4: return 31;
+ case 5: return 30;
+ case 6: return 31;
+ case 7: return 31;
+ case 8: return 30;
+ case 9: return 31;
+ case 10: return 30;
+ case 11: return 31;
+ }
+
+ throw std::runtime_error ("month out of range");
+ }
+
+ void World::adjustSky()
+ {
+ if (mSky && (isCellExterior() || isCellQuasiExterior()))
+ {
+ mRendering->skySetHour (mGlobalVariables->getFloat ("gamehour"));
+ mRendering->skySetDate (mGlobalVariables->getInt ("day"),
+ mGlobalVariables->getInt ("month"));
+
+ mRendering->skyEnable();
+ }
+ else
+ mRendering->skyDisable();
+ }
+
+ World::World (OEngine::Render::OgreRenderer& renderer,
+ const Files::Collections& fileCollections,
+ const std::vector<std::string>& master, const std::vector<std::string>& plugins,
+ const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir,
+ ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, int mActivationDistanceOverride)
+ : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
+ mSky (true), mCells (mStore, mEsm),
+ mActivationDistanceOverride (mActivationDistanceOverride),
+ mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true),
+ mFacedDistance(FLT_MAX)
+ {
+ mPhysics = new PhysicsSystem(renderer);
+ mPhysEngine = mPhysics->getEngine();
+
+ mRendering = new MWRender::RenderingManager(renderer, resDir, cacheDir, mPhysEngine,&mFallback);
+
+ mPhysEngine->setSceneManager(renderer.getScene());
+
+ mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback);
+
+ int idx = 0;
+ // NOTE: We might need to reserve one more for the running game / save.
+ mEsm.resize(master.size() + plugins.size());
+ Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
+ listener->loadingOn();
+ for (std::vector<std::string>::size_type i = 0; i < master.size(); i++, idx++)
+ {
+ boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i]));
+
+ std::cout << "Loading ESM " << masterPath.string() << "\n";
+ listener->setLabel(masterPath.filename().string());
+
+ // This parses the ESM file
+ ESM::ESMReader lEsm;
+ lEsm.setEncoder(encoder);
+ lEsm.setIndex(idx);
+ lEsm.setGlobalReaderList(&mEsm);
+ lEsm.open (masterPath.string());
+ mEsm[idx] = lEsm;
+ mStore.load (mEsm[idx], listener);
+ }
+
+ for (std::vector<std::string>::size_type i = 0; i < plugins.size(); i++, idx++)
+ {
+ boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i]));
+
+ std::cout << "Loading ESP " << pluginPath.string() << "\n";
+ listener->setLabel(pluginPath.filename().string());
+
+ // This parses the ESP file
+ ESM::ESMReader lEsm;
+ lEsm.setEncoder(encoder);
+ lEsm.setIndex(idx);
+ lEsm.setGlobalReaderList(&mEsm);
+ lEsm.open (pluginPath.string());
+ mEsm[idx] = lEsm;
+ mStore.load (mEsm[idx], listener);
+ }
+ listener->loadingOff();
+
+ // insert records that may not be present in all versions of MW
+ if (mEsm[0].getFormat() == 0)
+ ensureNeededRecords();
+
+ mStore.movePlayerRecord();
+ mStore.setUp();
+
+ mGlobalVariables = new Globals (mStore);
+
+ mWorldScene = new Scene(*mRendering, mPhysics);
+ }
+
+ void World::startNewGame()
+ {
+ mWorldScene->changeToVoid();
+
+ mStore.clearDynamic();
+ mStore.setUp();
+
+ mCells.clear();
+
+ // Rebuild player
+ setupPlayer();
+ MWWorld::Ptr player = mPlayer->getPlayer();
+
+ // removes NpcStats, ContainerStore etc
+ player.getRefData().setCustomData(NULL);
+
+ renderPlayer();
+ mRendering->resetCamera();
+
+ // make sure to do this so that local scripts from items that were in the players inventory are removed
+ mLocalScripts.clear();
+
+ MWBase::Environment::get().getWindowManager()->updatePlayer();
+
+ ESM::Position pos;
+ const int cellSize = 8192;
+ pos.pos[0] = cellSize/2;
+ pos.pos[1] = cellSize/2;
+ pos.pos[2] = 0;
+ pos.rot[0] = 0;
+ pos.rot[1] = 0;
+ pos.rot[2] = 0;
+ mWorldScene->changeToExteriorCell(pos);
+
+
+ // enable collision
+ if(!mPhysics->toggleCollisionMode())
+ mPhysics->toggleCollisionMode();
+
+ // FIXME: should be set to 1, but the sound manager won't pause newly started sounds
+ mPlayIntro = 2;
+
+ // global variables
+ delete mGlobalVariables;
+ mGlobalVariables = new Globals (mStore);
+
+ // set new game mark
+ mGlobalVariables->setInt ("chargenstate", 1);
+ mGlobalVariables->setInt ("pcrace", 3);
+
+ // we don't want old weather to persist on a new game
+ delete mWeatherManager;
+ mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback);
+
+ MWBase::Environment::get().getScriptManager()->resetGlobalScripts();
+ }
+
+
+ void World::ensureNeededRecords()
+ {
+ if (!mStore.get<ESM::GameSetting>().search("sCompanionShare"))
+ {
+ ESM::GameSetting sCompanionShare;
+ sCompanionShare.mId = "sCompanionShare";
+ ESM::Variant value;
+ value.setType(ESM::VT_String);
+ value.setString("Companion Share");
+ sCompanionShare.mValue = value;
+ mStore.insertStatic(sCompanionShare);
+ }
+ if (!mStore.get<ESM::Global>().search("dayspassed"))
+ {
+ // vanilla Morrowind does not define dayspassed.
+ ESM::Global dayspassed;
+ dayspassed.mId = "dayspassed";
+ ESM::Variant value;
+ value.setType(ESM::VT_Long);
+ value.setInteger(1); // but the addons start counting at 1 :(
+ dayspassed.mValue = value;
+ mStore.insertStatic(dayspassed);
+ }
+ if (!mStore.get<ESM::GameSetting>().search("fWereWolfRunMult"))
+ {
+ ESM::GameSetting fWereWolfRunMult;
+ fWereWolfRunMult.mId = "fWereWolfRunMult";
+ ESM::Variant value;
+ value.setType(ESM::VT_Float);
+ value.setFloat(1.f);
+ fWereWolfRunMult.mValue = value;
+ mStore.insertStatic(fWereWolfRunMult);
+ }
+ }
+
+ World::~World()
+ {
+ delete mWeatherManager;
+ delete mWorldScene;
+ delete mGlobalVariables;
+ delete mRendering;
+ delete mPhysics;
+
+ delete mPlayer;
+ }
+
+ const ESM::Cell *World::getExterior (const std::string& cellName) const
+ {
+ // first try named cells
+ const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
+ if (cell != 0) {
+ return cell;
+ }
+
+ // didn't work -> now check for regions
+ const MWWorld::Store<ESM::Region> &regions = mStore.get<ESM::Region>();
+ MWWorld::Store<ESM::Region>::iterator it = regions.begin();
+ for (; it != regions.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(cellName, it->mName))
+ {
+ return mStore.get<ESM::Cell>().searchExtByRegion(it->mId);
+ }
+ }
+
+ return 0;
+ }
+
+ const MWWorld::Fallback *World::getFallback() const
+ {
+ return &mFallback;
+ }
+
+ Ptr::CellStore *World::getExterior (int x, int y)
+ {
+ return mCells.getExterior (x, y);
+ }
+
+ Ptr::CellStore *World::getInterior (const std::string& name)
+ {
+ return mCells.getInterior (name);
+ }
+
+ MWWorld::Player& World::getPlayer()
+ {
+ return *mPlayer;
+ }
+
+ const MWWorld::ESMStore& World::getStore() const
+ {
+ return mStore;
+ }
+
+ std::vector<ESM::ESMReader>& World::getEsmReader()
+ {
+ return mEsm;
+ }
+
+ LocalScripts& World::getLocalScripts()
+ {
+ return mLocalScripts;
+ }
+
+ bool World::hasCellChanged() const
+ {
+ return mWorldScene->hasCellChanged();
+ }
+
+ Globals::Data& World::getGlobalVariable (const std::string& name)
+ {
+ return (*mGlobalVariables)[name];
+ }
+
+ Globals::Data World::getGlobalVariable (const std::string& name) const
+ {
+ return (*mGlobalVariables)[name];
+ }
+
+ char World::getGlobalVariableType (const std::string& name) const
+ {
+ return mGlobalVariables->getType (name);
+ }
+
+ std::vector<std::string> World::getGlobals () const
+ {
+ return mGlobalVariables->getGlobals();
+ }
+
+ std::string World::getCurrentCellName () const
+ {
+ std::string name;
+
+ Ptr::CellStore *cell = mWorldScene->getCurrentCell();
+ if (cell->mCell->isExterior())
+ {
+ if (cell->mCell->mName != "")
+ {
+ name = cell->mCell->mName;
+ }
+ else
+ {
+ const ESM::Region* region =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().search(cell->mCell->mRegion);
+ if (region)
+ name = region->mName;
+ else
+ {
+ const ESM::GameSetting *setting =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().search("sDefaultCellname");
+
+ if (setting && setting->mValue.getType()==ESM::VT_String)
+ name = setting->mValue.getString();
+ }
+
+ }
+ }
+ else
+ {
+ name = cell->mCell->mName;
+ }
+
+ return name;
+ }
+
+ void World::removeRefScript (MWWorld::RefData *ref)
+ {
+ mLocalScripts.remove (ref);
+ }
+
+ Ptr World::getPtr (const std::string& name, bool activeOnly)
+ {
+ // the player is always in an active cell.
+ if (name=="player")
+ {
+ return mPlayer->getPlayer();
+ }
+
+ Ptr ptr = Class::get (mPlayer->getPlayer()).
+ getContainerStore (mPlayer->getPlayer()).search (name);
+
+ if (!ptr.isEmpty())
+ return ptr;
+
+ // 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);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ if (!activeOnly)
+ {
+ Ptr ptr = mCells.getPtr (name);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ throw std::runtime_error ("unknown ID: " + name);
+ }
+
+ Ptr World::getPtrViaHandle (const std::string& handle)
+ {
+ Ptr res = searchPtrViaHandle (handle);
+ if (res.isEmpty ())
+ throw std::runtime_error ("unknown Ogre handle: " + handle);
+ return res;
+ }
+
+ Ptr World::searchPtrViaHandle (const std::string& handle)
+ {
+ if (mPlayer->getPlayer().getRefData().getHandle()==handle)
+ return mPlayer->getPlayer();
+ for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
+ iter!=mWorldScene->getActiveCells().end(); ++iter)
+ {
+ Ptr::CellStore* cellstore = *iter;
+ Ptr ptr = getPtrViaHandle (handle, *cellstore);
+
+ if (!ptr.isEmpty())
+ return ptr;
+ }
+
+ return MWWorld::Ptr();
+ }
+
+ void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell)
+ {
+ if( reference.getTypeName()==typeid (ESM::Container).name() ||
+ reference.getTypeName()==typeid (ESM::NPC).name() ||
+ reference.getTypeName()==typeid (ESM::Creature).name())
+ {
+ MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference);
+ for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
+ {
+ std::string script = MWWorld::Class::get(*it).getScript(*it);
+ if(script != "")
+ {
+ MWWorld::Ptr item = *it;
+ item.mCell = cell;
+ mLocalScripts.add (script, item);
+ }
+ }
+ }
+ }
+
+ void World::enable (const Ptr& reference)
+ {
+ if (!reference.getRefData().isEnabled())
+ {
+ reference.getRefData().enable();
+
+ if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
+ mWorldScene->addObjectToScene (reference);
+ }
+ }
+
+ void World::removeContainerScripts(const Ptr& reference)
+ {
+ if( reference.getTypeName()==typeid (ESM::Container).name() ||
+ reference.getTypeName()==typeid (ESM::NPC).name() ||
+ reference.getTypeName()==typeid (ESM::Creature).name())
+ {
+ MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference);
+ for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
+ {
+ std::string script = MWWorld::Class::get(*it).getScript(*it);
+ if(script != "")
+ {
+ MWWorld::Ptr item = *it;
+ mLocalScripts.remove (item);
+ }
+ }
+ }
+ }
+
+ void World::disable (const Ptr& reference)
+ {
+ if (reference.getRefData().isEnabled())
+ {
+ reference.getRefData().disable();
+
+ if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
+ mWorldScene->removeObjectFromScene (reference);
+ }
+ }
+
+ void World::advanceTime (double hours)
+ {
+ mWeatherManager->advanceTime (hours);
+
+ hours += mGlobalVariables->getFloat ("gamehour");
+
+ setHour (hours);
+
+ int days = hours / 24;
+
+ if (days>0)
+ mGlobalVariables->setInt ("dayspassed", days + mGlobalVariables->getInt ("dayspassed"));
+ }
+
+ void World::setHour (double hour)
+ {
+ if (hour<0)
+ hour = 0;
+
+ int days = hour / 24;
+
+ hour = std::fmod (hour, 24);
+
+ mGlobalVariables->setFloat ("gamehour", hour);
+
+ mRendering->skySetHour (hour);
+
+ mWeatherManager->setHour (hour);
+
+ if (days>0)
+ setDay (days + mGlobalVariables->getInt ("day"));
+ }
+
+ void World::setDay (int day)
+ {
+ if (day<1)
+ day = 1;
+
+ int month = mGlobalVariables->getInt ("month");
+
+ while (true)
+ {
+ int days = getDaysPerMonth (month);
+ if (day<=days)
+ break;
+
+ if (month<11)
+ {
+ ++month;
+ }
+ else
+ {
+ month = 0;
+ mGlobalVariables->setInt ("year", mGlobalVariables->getInt ("year")+1);
+ }
+
+ day -= days;
+ }
+
+ mGlobalVariables->setInt ("day", day);
+ mGlobalVariables->setInt ("month", month);
+
+ mRendering->skySetDate (day, month);
+
+ mWeatherManager->setDate (day, month);
+ }
+
+ void World::setMonth (int month)
+ {
+ if (month<0)
+ month = 0;
+
+ int years = month / 12;
+ month = month % 12;
+
+ int days = getDaysPerMonth (month);
+
+ if (mGlobalVariables->getInt ("day")>days)
+ mGlobalVariables->setInt ("day", days);
+
+ mGlobalVariables->setInt ("month", month);
+
+ if (years>0)
+ mGlobalVariables->setInt ("year", years+mGlobalVariables->getInt ("year"));
+
+ mRendering->skySetDate (mGlobalVariables->getInt ("day"), month);
+ }
+
+ int World::getDay()
+ {
+ return mGlobalVariables->getInt("day");
+ }
+
+ int World::getMonth()
+ {
+ return mGlobalVariables->getInt("month");
+ }
+
+ TimeStamp World::getTimeStamp() const
+ {
+ return TimeStamp (mGlobalVariables->getFloat ("gamehour"),
+ mGlobalVariables->getInt ("dayspassed"));
+ }
+
+ bool World::toggleSky()
+ {
+ if (mSky)
+ {
+ mSky = false;
+ mRendering->skyDisable();
+ return false;
+ }
+ else
+ {
+ mSky = true;
+ mRendering->skyEnable();
+ return true;
+ }
+ }
+
+ int World::getMasserPhase() const
+ {
+ return mRendering->skyGetMasserPhase();
+ }
+
+ int World::getSecundaPhase() const
+ {
+ return mRendering->skyGetSecundaPhase();
+ }
+
+ void World::setMoonColour (bool red)
+ {
+ mRendering->skySetMoonColour (red);
+ }
+
+ float World::getTimeScaleFactor() const
+ {
+ return mGlobalVariables->getFloat ("timescale");
+ }
+
+ void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
+ {
+ return mWorldScene->changeToInteriorCell(cellName, position);
+ }
+
+ void World::changeToExteriorCell (const ESM::Position& position)
+ {
+ return mWorldScene->changeToExteriorCell(position);
+ }
+
+ void World::markCellAsUnchanged()
+ {
+ return mWorldScene->markCellAsUnchanged();
+ }
+
+ float World::getMaxActivationDistance ()
+ {
+ if (mActivationDistanceOverride >= 0)
+ return mActivationDistanceOverride;
+
+ return (std::max) (getNpcActivationDistance (), getObjectActivationDistance ());
+ }
+
+ float World::getNpcActivationDistance ()
+ {
+ if (mActivationDistanceOverride >= 0)
+ return mActivationDistanceOverride;
+
+ return getStore().get<ESM::GameSetting>().find ("iMaxActivateDist")->getInt()*5/4;
+ }
+
+ float World::getObjectActivationDistance ()
+ {
+ if (mActivationDistanceOverride >= 0)
+ return mActivationDistanceOverride;
+
+ return getStore().get<ESM::GameSetting>().find ("iMaxActivateDist")->getInt();
+ }
+
+ 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;
+ }
+
+ std::pair<MWWorld::Ptr,Ogre::Vector3> World::getHitContact(const MWWorld::Ptr &ptr, float distance)
+ {
+ const ESM::Position &posdata = ptr.getRefData().getPosition();
+ Ogre::Vector3 pos(posdata.pos);
+ Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
+ Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::UNIT_X);
+
+ MWRender::Animation *anim = mRendering->getAnimation(ptr);
+ if(anim != NULL)
+ {
+ Ogre::Node *node = anim->getNode("Head");
+ if(node != NULL)
+ pos += node->_getDerivedPosition();
+ }
+
+ std::pair<std::string,Ogre::Vector3> result = mPhysics->getHitContact(ptr.getRefData().getHandle(),
+ pos, rot, distance);
+ if(result.first.empty())
+ return std::make_pair(MWWorld::Ptr(), Ogre::Vector3(0.0f));
+
+ return std::make_pair(searchPtrViaHandle(result.first), result.second);
+ }
+
+ void World::deleteObject (const Ptr& ptr)
+ {
+ if (ptr.getRefData().getCount()>0)
+ {
+ ptr.getRefData().setCount (0);
+
+ if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end() &&
+ ptr.getRefData().isEnabled())
+ {
+ mWorldScene->removeObjectFromScene (ptr);
+ mLocalScripts.remove (ptr);
+ removeContainerScripts (ptr);
+ }
+ }
+ }
+
+ void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z)
+ {
+ ESM::Position &pos = ptr.getRefData().getPosition();
+
+ pos.pos[0] = x;
+ pos.pos[1] = y;
+ pos.pos[2] = z;
+
+ Ogre::Vector3 vec(x, y, z);
+
+ CellStore *currCell = ptr.getCell();
+ bool isPlayer = ptr == mPlayer->getPlayer();
+ bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell);
+
+ if (*currCell != newCell)
+ {
+ removeContainerScripts(ptr);
+
+ if (isPlayer)
+ {
+ if (!newCell.isExterior())
+ changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.mCell->mName), pos);
+ else
+ {
+ int cellX = newCell.mCell->getGridX();
+ int cellY = newCell.mCell->getGridY();
+ mWorldScene->changeCell(cellX, cellY, pos, false);
+ }
+ }
+ else
+ {
+ if (!mWorldScene->isCellActive(*currCell))
+ 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;
+ }
+ else
+ {
+ MWWorld::Ptr copy =
+ MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
+
+ mRendering->updateObjectCell(ptr, copy);
+
+ MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
+ mechMgr->updateCell(ptr, copy);
+
+ std::string script =
+ MWWorld::Class::get(ptr).getScript(ptr);
+ if (!script.empty())
+ {
+ mLocalScripts.remove(ptr);
+ removeContainerScripts (ptr);
+ mLocalScripts.add(script, copy);
+ addContainerScripts (copy, &newCell);
+ }
+ }
+ ptr.getRefData().setCount(0);
+ }
+ }
+ if (haveToMove)
+ {
+ mRendering->moveObject(ptr, vec);
+ mPhysics->moveObject (ptr);
+ }
+ }
+
+ bool World::moveObjectImp(const Ptr& ptr, float x, float y, float z)
+ {
+ CellStore *cell = ptr.getCell();
+
+ if (cell->isExterior()) {
+ int cellX, cellY;
+ positionToIndex(x, y, cellX, cellY);
+
+ cell = getExterior(cellX, cellY);
+ }
+
+ moveObject(ptr, *cell, x, y, z);
+
+ return cell != ptr.getCell();
+ }
+
+ void World::moveObject (const Ptr& ptr, float x, float y, float z)
+ {
+ moveObjectImp(ptr, x, y, z);
+ }
+
+ void World::scaleObject (const Ptr& ptr, float scale)
+ {
+ ptr.getCellRef().mScale = scale;
+ MWWorld::Class::get(ptr).adjustScale(ptr,scale);
+
+ if(ptr.getRefData().getBaseNode() == 0)
+ return;
+ mRendering->scaleObject(ptr, Vector3(scale,scale,scale));
+ mPhysics->scaleObject(ptr);
+ }
+
+ void World::rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust)
+ {
+ const float two_pi = Ogre::Math::TWO_PI;
+ const float pi = Ogre::Math::PI;
+
+ float *objRot = ptr.getRefData().getPosition().rot;
+ if(adjust)
+ {
+ objRot[0] += rot.x;
+ objRot[1] += rot.y;
+ objRot[2] += rot.z;
+ }
+ else
+ {
+ objRot[0] = rot.x;
+ objRot[1] = rot.y;
+ objRot[2] = rot.z;
+ }
+
+ if(Class::get(ptr).isActor())
+ {
+ /* HACK? Actors shouldn't really be rotating around X (or Y), but
+ * currently it's done so for rotating the camera, which needs
+ * clamping.
+ */
+ const float half_pi = Ogre::Math::HALF_PI;
+
+ if(objRot[0] < -half_pi) objRot[0] = -half_pi;
+ else if(objRot[0] > half_pi) objRot[0] = half_pi;
+ }
+ else
+ {
+ while(objRot[0] < -pi) objRot[0] += two_pi;
+ while(objRot[0] > pi) objRot[0] -= two_pi;
+ }
+
+ while(objRot[1] < -pi) objRot[1] += two_pi;
+ while(objRot[1] > pi) objRot[1] -= two_pi;
+
+ while(objRot[2] < -pi) objRot[2] += two_pi;
+ while(objRot[2] > pi) objRot[2] -= two_pi;
+
+ if(ptr.getRefData().getBaseNode() != 0)
+ {
+ mRendering->rotateObject(ptr);
+ mPhysics->rotateObject(ptr);
+ }
+ }
+
+ void World::localRotateObject (const Ptr& ptr, float x, float y, float z)
+ {
+ if (ptr.getRefData().getBaseNode() != 0) {
+
+ ptr.getRefData().getLocalRotation().rot[0]=Ogre::Degree(x).valueRadians();
+ ptr.getRefData().getLocalRotation().rot[1]=Ogre::Degree(y).valueRadians();
+ ptr.getRefData().getLocalRotation().rot[2]=Ogre::Degree(z).valueRadians();
+
+ float fullRotateRad=Ogre::Degree(360).valueRadians();
+
+ while(ptr.getRefData().getLocalRotation().rot[0]>=fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[0]-=fullRotateRad;
+ while(ptr.getRefData().getLocalRotation().rot[1]>=fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[1]-=fullRotateRad;
+ while(ptr.getRefData().getLocalRotation().rot[2]>=fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[2]-=fullRotateRad;
+
+ while(ptr.getRefData().getLocalRotation().rot[0]<=-fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[0]+=fullRotateRad;
+ while(ptr.getRefData().getLocalRotation().rot[1]<=-fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[1]+=fullRotateRad;
+ while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad)
+ ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad;
+
+ float *worldRot = ptr.getRefData().getPosition().rot;
+
+ Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(-worldRot[0]), Ogre::Vector3::UNIT_X)*
+ Ogre::Quaternion(Ogre::Radian(-worldRot[1]), Ogre::Vector3::UNIT_Y)*
+ Ogre::Quaternion(Ogre::Radian(-worldRot[2]), Ogre::Vector3::UNIT_Z));
+
+ Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-x).valueRadians()), Ogre::Vector3::UNIT_X)*
+ Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-y).valueRadians()), Ogre::Vector3::UNIT_Y)*
+ Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-z).valueRadians()), Ogre::Vector3::UNIT_Z));
+
+ ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot);
+ mPhysics->rotateObject(ptr);
+ }
+ }
+
+ void World::adjustPosition(const Ptr &ptr)
+ {
+ Ogre::Vector3 pos (ptr.getRefData().getPosition().pos[0], ptr.getRefData().getPosition().pos[1], ptr.getRefData().getPosition().pos[2]);
+
+ if(!ptr.getRefData().getBaseNode())
+ {
+ // will be adjusted when Ptr's cell becomes active
+ return;
+ }
+
+ float terrainHeight = mRendering->getTerrainHeightAt(pos);
+
+ if (pos.z < terrainHeight)
+ pos.z = terrainHeight;
+
+ ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below
+
+ if (!isFlying(ptr))
+ {
+ Ogre::Vector3 traced = mPhysics->traceDown(ptr);
+ if (traced.z < pos.z)
+ pos.z = traced.z;
+ }
+
+ moveObject(ptr, *ptr.getCell(), pos.x, pos.y, pos.z);
+ }
+
+ void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust)
+ {
+ rotateObjectImp(ptr, Ogre::Vector3(Ogre::Degree(x).valueRadians(),
+ Ogre::Degree(y).valueRadians(),
+ Ogre::Degree(z).valueRadians()),
+ adjust);
+ }
+
+ void World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos)
+ {
+ copyObjectToCell(ptr,Cell,pos);
+ }
+
+ void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const
+ {
+ const int cellSize = 8192;
+
+ x = cellSize * cellX;
+ y = cellSize * cellY;
+
+ if (centre)
+ {
+ x += cellSize/2;
+ y += cellSize/2;
+ }
+ }
+
+ void World::positionToIndex (float x, float y, int &cellX, int &cellY) const
+ {
+ const int cellSize = 8192;
+
+ cellX = std::floor(x/cellSize);
+ cellY = std::floor(y/cellSize);
+ }
+
+ void World::queueMovement(const Ptr &ptr, const Vector3 &velocity)
+ {
+ mPhysics->queueObjectMovement(ptr, velocity);
+ }
+
+ void World::doPhysics(float duration)
+ {
+ /* No duration? Shouldn't be any movement, then. */
+ if(duration <= 0.0f)
+ return;
+
+ processDoors(duration);
+
+ const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
+ PtrVelocityList::const_iterator player(results.end());
+ for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++)
+ {
+ if(iter->first.getRefData().getHandle() == "player")
+ {
+ /* Handle player last, in case a cell transition occurs */
+ player = iter;
+ continue;
+ }
+ moveObjectImp(iter->first, iter->second.x, iter->second.y, iter->second.z);
+ }
+ if(player != results.end())
+ moveObjectImp(player->first, player->second.x, player->second.y, player->second.z);
+
+ mPhysEngine->stepSimulation(duration);
+ }
+
+ bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
+ {
+ Ogre::Vector3 a(x1,y1,z1);
+ Ogre::Vector3 b(x2,y2,z2);
+ return mPhysics->castRay(a,b,false,true);
+ }
+
+ void World::processDoors(float duration)
+ {
+ std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
+ while (it != mDoorStates.end())
+ {
+ if (!mWorldScene->isCellActive(*it->first.getCell()))
+ mDoorStates.erase(it++);
+ else
+ {
+ float oldRot = Ogre::Radian(it->first.getRefData().getLocalRotation().rot[2]).valueDegrees();
+ float diff = duration * 90;
+ float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second ? 1 : -1)), 90.f);
+ localRotateObject(it->first, 0, 0, targetRot);
+
+ /// \todo should use convexSweepTest here
+ std::vector<std::string> collisions = mPhysics->getCollisions(it->first);
+ for (std::vector<std::string>::iterator cit = collisions.begin(); cit != collisions.end(); ++cit)
+ {
+ MWWorld::Ptr ptr = getPtrViaHandle(*cit);
+ if (MWWorld::Class::get(ptr).isActor())
+ {
+ // we collided with an actor, we need to undo the rotation
+ localRotateObject(it->first, 0, 0, oldRot);
+ break;
+ }
+ }
+
+ if ((targetRot == 90.f && it->second) || targetRot == 0.f)
+ mDoorStates.erase(it++);
+ else
+ ++it;
+ }
+ }
+ }
+
+ bool World::toggleCollisionMode()
+ {
+ return mPhysics->toggleCollisionMode();;
+ }
+
+ bool World::toggleRenderMode (RenderMode mode)
+ {
+ return mRendering->toggleRenderMode (mode);
+ }
+
+ const ESM::Potion *World::createRecord (const ESM::Potion& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Class *World::createRecord (const ESM::Class& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Spell *World::createRecord (const ESM::Spell& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Cell *World::createRecord (const ESM::Cell& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::NPC *World::createRecord(const ESM::NPC &record)
+ {
+ bool update = false;
+
+ if (Misc::StringUtils::ciEqual(record.mId, "player"))
+ {
+ std::vector<std::string> ids;
+ getStore().get<ESM::Race>().listIdentifier(ids);
+
+ unsigned int i=0;
+
+ for (; i<ids.size(); ++i)
+ if (Misc::StringUtils::ciEqual (ids[i], record.mRace))
+ break;
+
+ mGlobalVariables->setInt ("pcrace", (i == ids.size()) ? 0 : i+1);
+
+ const ESM::NPC *player =
+ mPlayer->getPlayer().get<ESM::NPC>()->mBase;
+
+ update = record.isMale() != player->isMale() ||
+ !Misc::StringUtils::ciEqual(record.mRace, player->mRace) ||
+ !Misc::StringUtils::ciEqual(record.mHead, player->mHead) ||
+ !Misc::StringUtils::ciEqual(record.mHair, player->mHair);
+ }
+ const ESM::NPC *ret = mStore.insert(record);
+ if (update) {
+ mRendering->renderPlayer(mPlayer->getPlayer());
+ }
+ return ret;
+ }
+
+ const ESM::Armor *World::createRecord (const ESM::Armor& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Weapon *World::createRecord (const ESM::Weapon& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Clothing *World::createRecord (const ESM::Clothing& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Enchantment *World::createRecord (const ESM::Enchantment& record)
+ {
+ return mStore.insert(record);
+ }
+
+ const ESM::Book *World::createRecord (const ESM::Book& record)
+ {
+ return mStore.insert(record);
+ }
+
+ void World::update (float duration, bool paused)
+ {
+ if (mPlayIntro)
+ {
+ --mPlayIntro;
+ if (mPlayIntro == 0)
+ mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true);
+ }
+
+ mWeatherManager->update (duration);
+
+ mWorldScene->update (duration, paused);
+
+ doPhysics (duration);
+
+ performUpdateSceneQueries ();
+
+ updateWindowManager ();
+ }
+
+ void World::updateWindowManager ()
+ {
+ // inform the GUI about focused object
+ MWWorld::Ptr object = getFacedObject ();
+
+ MWBase::Environment::get().getWindowManager()->setFocusObject(object);
+
+ // retrieve object dimensions so we know where to place the floating label
+ if (!object.isEmpty ())
+ {
+ Ogre::SceneNode* node = object.getRefData().getBaseNode();
+ Ogre::AxisAlignedBox bounds = node->_getWorldAABB();
+ if (bounds.isFinite())
+ {
+ Vector4 screenCoords = mRendering->boundingBoxToScreen(bounds);
+ MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords(
+ screenCoords[0], screenCoords[1], screenCoords[2], screenCoords[3]);
+ }
+ }
+ }
+
+ void World::performUpdateSceneQueries ()
+ {
+ if (!mRendering->occlusionQuerySupported())
+ {
+ // cast a ray from player to sun to detect if the sun is visible
+ // this is temporary until we find a better place to put this code
+ // currently its here because we need to access the physics system
+ float* p = mPlayer->getPlayer().getRefData().getPosition().pos;
+ Vector3 sun = mRendering->getSkyManager()->getRealSunPos();
+ mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun));
+ }
+
+ updateFacedHandle ();
+ }
+
+ void World::updateFacedHandle ()
+ {
+ // send new query
+ // figure out which object we want to test against
+ std::vector < std::pair < float, std::string > > results;
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode())
+ {
+ float x, y;
+ MWBase::Environment::get().getWindowManager()->getMousePosition(x, y);
+ results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ());
+ if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
+ results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50);
+ }
+ else
+ {
+ results = mPhysics->getFacedHandles(getMaxActivationDistance ());
+ }
+
+ // ignore the player and other things we're not interested in
+ std::vector < std::pair < float, std::string > >::iterator it = results.begin();
+ while (it != results.end())
+ {
+ if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain
+ || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself)
+ {
+ it = results.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ if (results.empty())
+ {
+ mFacedHandle = "";
+ mFacedDistance = FLT_MAX;
+ }
+ else
+ {
+ mFacedHandle = results.front().second;
+ mFacedDistance = results.front().first;
+ }
+ }
+
+ bool World::isCellExterior() const
+ {
+ Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+ if (currentCell)
+ {
+ return currentCell->mCell->isExterior();
+ }
+ return false;
+ }
+
+ bool World::isCellQuasiExterior() const
+ {
+ Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+ if (currentCell)
+ {
+ if (!(currentCell->mCell->mData.mFlags & ESM::Cell::QuasiEx))
+ return false;
+ else
+ return true;
+ }
+ return false;
+ }
+
+ int World::getCurrentWeather() const
+ {
+ return mWeatherManager->getWeatherID();
+ }
+
+ void World::changeWeather(const std::string& region, const unsigned int id)
+ {
+ mWeatherManager->changeWeather(region, id);
+ }
+
+ void World::modRegion(const std::string &regionid, const std::vector<char> &chances)
+ {
+ mWeatherManager->modRegion(regionid, chances);
+ }
+
+ OEngine::Render::Fader* World::getFader()
+ {
+ return mRendering->getFader();
+ }
+
+ Ogre::Vector2 World::getNorthVector (CellStore* cell)
+ {
+ MWWorld::CellRefList<ESM::Static>& statics = cell->mStatics;
+ MWWorld::LiveCellRef<ESM::Static>* ref = statics.find("northmarker");
+ if (!ref)
+ return Vector2(0, 1);
+ Ogre::SceneNode* node = ref->mData.getBaseNode();
+ Vector3 dir = node->_getDerivedOrientation() * Ogre::Vector3(0,1,0);
+ Vector2 d = Vector2(dir.x, dir.y);
+ return d;
+ }
+
+ std::vector<World::DoorMarker> World::getDoorMarkers (CellStore* cell)
+ {
+ 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)
+ {
+ MWWorld::LiveCellRef<ESM::Door>& ref = *it;
+
+ if (ref.mRef.mTeleport)
+ {
+ World::DoorMarker newMarker;
+ newMarker.name = MWClass::Door::getDestination(ref);
+
+ ESM::Position pos = ref.mData.getPosition ();
+
+ newMarker.x = pos.pos[0];
+ newMarker.y = pos.pos[1];
+ result.push_back(newMarker);
+ }
+ }
+
+ return result;
+ }
+
+ void World::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y)
+ {
+ mRendering->getInteriorMapPosition(position, nX, nY, x, y);
+ }
+
+ bool World::isPositionExplored (float nX, float nY, int x, int y, bool interior)
+ {
+ return mRendering->isPositionExplored(nX, nY, x, y, interior);
+ }
+
+ void World::setWaterHeight(const float height)
+ {
+ mRendering->setWaterHeight(height);
+ }
+
+ void World::toggleWater()
+ {
+ mRendering->toggleWater();
+ }
+
+ void World::PCDropped (const Ptr& item)
+ {
+ std::string script = MWWorld::Class::get(item).getScript(item);
+
+ // Set OnPCDrop Variable on item's script, if it has a script with that variable declared
+ if(script != "")
+ item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1);
+ }
+
+ bool World::placeObject (const Ptr& object, float cursorX, float cursorY)
+ {
+ std::pair<bool, Ogre::Vector3> result = mPhysics->castRay(cursorX, cursorY);
+
+ if (!result.first)
+ return false;
+
+ CellStore* cell;
+ if (isCellExterior())
+ {
+ int cellX, cellY;
+ positionToIndex(result.second[0], result.second[1], cellX, cellY);
+ cell = mCells.getExterior(cellX, cellY);
+ }
+ else
+ cell = getPlayer().getPlayer().getCell();
+
+ ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition();
+ pos.pos[0] = result.second[0];
+ pos.pos[1] = result.second[1];
+ pos.pos[2] = result.second[2];
+ // We want only the Z part of the player's rotation
+ pos.rot[0] = 0;
+ pos.rot[1] = 0;
+
+ Ptr dropped = copyObjectToCell(object, *cell, pos);
+ PCDropped(dropped);
+ object.getRefData().setCount(0);
+
+ return true;
+ }
+
+ bool World::canPlaceObject(float cursorX, float cursorY)
+ {
+ std::pair<bool, Ogre::Vector3> result = mPhysics->castRay(cursorX, cursorY);
+
+ /// \todo also check if the wanted position is on a flat surface, and not e.g. against a vertical wall!
+
+ if (!result.first)
+ return false;
+ return true;
+ }
+
+
+ Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos)
+ {
+ /// \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 (mWorldScene->isCellActive(cell)) {
+ if (dropped.getRefData().isEnabled()) {
+ mWorldScene->addObjectToScene(dropped);
+ }
+ std::string script = MWWorld::Class::get(dropped).getScript(dropped);
+ if (!script.empty()) {
+ mLocalScripts.add(script, dropped);
+ }
+ addContainerScripts(dropped, &cell);
+ }
+
+ return dropped;
+ }
+
+ void World::dropObjectOnGround (const Ptr& actor, const Ptr& object)
+ {
+ MWWorld::Ptr::CellStore* cell = actor.getCell();
+
+ ESM::Position pos =
+ actor.getRefData().getPosition();
+ // We want only the Z part of the actor's rotation
+ pos.rot[0] = 0;
+ pos.rot[1] = 0;
+
+ Ogre::Vector3 orig =
+ Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]);
+ Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1);
+
+ float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2];
+ len += 100.0;
+
+ std::pair<bool, Ogre::Vector3> hit =
+ mPhysics->castRay(orig, dir, len);
+ pos.pos[2] = hit.second.z;
+
+ Ptr dropped = copyObjectToCell(object, *cell, pos);
+ if(actor == mPlayer->getPlayer()) // Only call if dropped by player
+ PCDropped(dropped);
+ object.getRefData().setCount(0);
+ }
+
+ void World::processChangedSettings(const Settings::CategorySettingVector& settings)
+ {
+ mRendering->processChangedSettings(settings);
+ }
+
+ void World::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches)
+ {
+ mRendering->getTriangleBatchCount(triangles, batches);
+ }
+
+ bool
+ World::isFlying(const MWWorld::Ptr &ptr) const
+ {
+ if(!ptr.getClass().isActor())
+ return false;
+
+ const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
+ if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0)
+ return true;
+
+ // TODO: Check if flying creature
+
+ const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle());
+ if(!actor || !actor->getCollisionMode())
+ return true;
+
+ return false;
+ }
+
+ bool World::isSubmerged(const MWWorld::Ptr &object) const
+ {
+ float *fpos = object.getRefData().getPosition().pos;
+ Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]);
+
+ const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle());
+ if(actor) pos.z += 1.85*actor->getHalfExtents().z;
+
+ return isUnderwater(object.getCell(), pos);
+ }
+
+ bool
+ World::isSwimming(const MWWorld::Ptr &object) const
+ {
+ /// \todo add check ifActor() - only actors can swim
+ float *fpos = object.getRefData().getPosition().pos;
+ Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]);
+
+ /// \fixme 3/4ths submerged?
+ const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle());
+ if(actor) pos.z += actor->getHalfExtents().z * 1.5;
+
+ return isUnderwater(object.getCell(), pos);
+ }
+
+ bool
+ World::isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const
+ {
+ if (!(cell->mCell->mData.mFlags & ESM::Cell::HasWater)) {
+ return false;
+ }
+ return pos.z < cell->mWaterLevel;
+ }
+
+ bool World::isOnGround(const MWWorld::Ptr &ptr) const
+ {
+ RefData &refdata = ptr.getRefData();
+ const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle());
+ return physactor && physactor->getOnGround();
+ }
+
+ bool World::vanityRotateCamera(float * rot)
+ {
+ return mRendering->vanityRotateCamera(rot);
+ }
+
+ void World::setCameraDistance(float dist, bool adjust, bool override)
+ {
+ return mRendering->setCameraDistance(dist, adjust, override);;
+ }
+
+ void World::setupPlayer()
+ {
+ const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
+ if (!mPlayer)
+ mPlayer = new MWWorld::Player(player, *this);
+ else
+ mPlayer->set(player);
+
+ Ptr ptr = mPlayer->getPlayer();
+ mRendering->setupPlayer(ptr);
+ }
+
+ void World::renderPlayer()
+ {
+ mRendering->renderPlayer(mPlayer->getPlayer());
+ mPhysics->addActor(mPlayer->getPlayer());
+ }
+
+ void World::setupExternalRendering (MWRender::ExternalRendering& rendering)
+ {
+ mRendering->setupExternalRendering (rendering);
+ }
+
+ int World::canRest ()
+ {
+ Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+
+ Ptr player = mPlayer->getPlayer();
+ RefData &refdata = player.getRefData();
+ Ogre::Vector3 playerPos(refdata.getPosition().pos);
+
+ const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle());
+ if((!physactor->getOnGround()&&physactor->getCollisionMode()) || isUnderwater(currentCell, playerPos))
+ return 2;
+ if((currentCell->mCell->mData.mFlags&ESM::Cell::NoSleep) ||
+ Class::get(player).getNpcStats(player).isWerewolf())
+ return 1;
+
+ return 0;
+ }
+
+ MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr)
+ {
+ return mRendering->getAnimation(ptr);
+ }
+
+ void World::playVideo (const std::string &name, bool allowSkipping)
+ {
+ mRendering->playVideo(name, allowSkipping);
+ }
+
+ void World::stopVideo ()
+ {
+ mRendering->stopVideo();
+ }
+
+ void World::frameStarted (float dt, bool paused)
+ {
+ mRendering->frameStarted(dt, paused);
+ }
+
+ void World::activateDoor(const MWWorld::Ptr& door)
+ {
+ if (mDoorStates.find(door) != mDoorStates.end())
+ {
+ // if currently opening, then close, if closing, then open
+ mDoorStates[door] = !mDoorStates[door];
+ }
+ else
+ {
+ if (door.getRefData().getLocalRotation().rot[2] == 0)
+ mDoorStates[door] = 1; // open
+ else
+ mDoorStates[door] = 0; // close
+ }
+ }
+
+ bool World::getOpenOrCloseDoor(const Ptr &door)
+ {
+ if (mDoorStates.find(door) != mDoorStates.end())
+ return !mDoorStates[door]; // if currently opening or closing, then do the opposite
+ return door.getRefData().getLocalRotation().rot[2] == 0;
+ }
+
+ bool World::getPlayerStandingOn (const MWWorld::Ptr& object)
+ {
+ MWWorld::Ptr player = mPlayer->getPlayer();
+ if (!mPhysEngine->getCharacter("player")->getOnGround())
+ return false;
+ btVector3 from (player.getRefData().getPosition().pos[0], player.getRefData().getPosition().pos[1], player.getRefData().getPosition().pos[2]);
+ btVector3 to = from - btVector3(0,0,5);
+ std::pair<std::string, float> result = mPhysEngine->rayTest(from, to);
+ return result.first == object.getRefData().getBaseNode()->getName();
+ }
+
+ bool World::getActorStandingOn (const MWWorld::Ptr& object)
+ {
+ return mPhysEngine->isAnyActorStandingOn(object.getRefData().getBaseNode()->getName());
+ }
+
+ float World::getWindSpeed()
+ {
+ if (isCellExterior() || isCellQuasiExterior())
+ return mWeatherManager->getWindSpeed();
+ else
+ return 0.f;
+ }
+
+ void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out)
+ {
+ const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells();
+ for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
+ {
+ MWWorld::CellRefList<ESM::Container>& containers = (*cellIt)->mContainers;
+ CellRefList<ESM::Container>::List& refList = containers.mList;
+ for (CellRefList<ESM::Container>::List::iterator container = refList.begin(); container != refList.end(); ++container)
+ {
+ MWWorld::Ptr ptr (&*container, *cellIt);
+ if (Misc::StringUtils::ciEqual(ptr.getCellRef().mOwner, npc.getCellRef().mRefID))
+ out.push_back(ptr);
+ }
+ }
+ }
+
+ struct ListHandlesFunctor
+ {
+ std::vector<std::string> mHandles;
+
+ bool operator() (ESM::CellRef& ref, RefData& data)
+ {
+ Ogre::SceneNode* handle = data.getBaseNode();
+ if (handle)
+ mHandles.push_back(handle->getName());
+ return true;
+ }
+ };
+
+ void World::getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out)
+ {
+ const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells();
+ for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt)
+ {
+ ListHandlesFunctor functor;
+ (*cellIt)->forEach<ListHandlesFunctor>(functor);
+
+ for (std::vector<std::string>::iterator it = functor.mHandles.begin(); it != functor.mHandles.end(); ++it)
+ if (Misc::StringUtils::ciEqual(searchPtrViaHandle(*it).getCellRef().mOwner, npc.getCellRef().mRefID))
+ out.push_back(searchPtrViaHandle(*it));
+ }
+ }
+
+ void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
+ {
+ OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle());
+
+ physicActor->enableCollisions(enable);
+ }
+
+ bool World::findInteriorPosition(const std::string &name, ESM::Position &pos)
+ {
+ typedef MWWorld::CellRefList<ESM::Door>::List DoorList;
+
+ pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
+ pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
+
+ MWWorld::CellStore *cellStore = getInterior(name);
+
+ if (0 == cellStore) {
+ return false;
+ }
+ const DoorList &doors = cellStore->mDoors.mList;
+ for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) {
+ if (!it->mRef.mTeleport) {
+ continue;
+ }
+
+ MWWorld::CellStore *source = 0;
+
+ // door to exterior
+ if (it->mRef.mDestCell.empty()) {
+ int x, y;
+ const float *pos = it->mRef.mDoorDest.pos;
+ positionToIndex(pos[0], pos[1], x, y);
+ source = getExterior(x, y);
+ }
+ // door to interior
+ else {
+ source = getInterior(it->mRef.mDestCell);
+ }
+ if (0 != source) {
+ // Find door leading to our current teleport door
+ // and use it destination to position inside cell.
+ const DoorList &doors = source->mDoors.mList;
+ for (DoorList::const_iterator jt = doors.begin(); jt != doors.end(); ++jt) {
+ if (it->mRef.mTeleport &&
+ Misc::StringUtils::ciEqual(name, jt->mRef.mDestCell))
+ {
+ /// \note Using _any_ door pointed to the interior,
+ /// not the one pointed to current door.
+ pos = jt->mRef.mDoorDest;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ bool World::findExteriorPosition(const std::string &name, ESM::Position &pos)
+ {
+ pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
+
+ if (const ESM::Cell *ext = getExterior(name)) {
+ int x = ext->getGridX();
+ 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;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ void World::enableTeleporting(bool enable)
+ {
+ mTeleportEnabled = enable;
+ }
+
+ bool World::isTeleportingEnabled() const
+ {
+ return mTeleportEnabled;
+ }
+
+ void World::setWerewolf(const MWWorld::Ptr& actor, bool werewolf)
+ {
+ MWMechanics::NpcStats& npcStats = Class::get(actor).getNpcStats(actor);
+
+ // The actor does not have to change state
+ if (npcStats.isWerewolf() == werewolf)
+ return;
+
+ npcStats.setWerewolf(werewolf);
+
+ MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
+ invStore.unequipAll(actor);
+
+ 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);
+ }
+
+ // Not sure this is right
+ InventoryStore &inv = Class::get(actor).getInventoryStore(actor);
+ inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), 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);
+ }
+ }
+
+ if(actor.getRefData().getHandle() == "player")
+ {
+ // Update the GUI only when called on the player
+ MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
+ windowManager->unsetSelectedWeapon();
+
+ if (werewolf)
+ {
+ windowManager->forceHide(MWGui::GW_Inventory);
+ windowManager->forceHide(MWGui::GW_Magic);
+ }
+ else
+ {
+ windowManager->unsetForceHide(MWGui::GW_Inventory);
+ windowManager->unsetForceHide(MWGui::GW_Magic);
+ }
+ }
+
+ mRendering->rebuildPtr(actor);
+ }
+
+ void World::applyWerewolfAcrobatics(const Ptr &actor)
+ {
+ const Store<ESM::GameSetting> &gmst = getStore().get<ESM::GameSetting>();
+ MWMechanics::NpcStats &stats = Class::get(actor).getNpcStats(actor);
+
+ stats.getSkill(ESM::Skill::Acrobatics).setModified(gmst.find("fWerewolfAcrobatics")->getFloat(), 0);
+ }
+
+}
diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp
new file mode 100644
index 0000000000..71391239b3
--- /dev/null
+++ b/apps/openmw/mwworld/worldimp.hpp
@@ -0,0 +1,445 @@
+#ifndef GAME_MWWORLD_WORLDIMP_H
+#define GAME_MWWORLD_WORLDIMP_H
+
+#include "../mwrender/debugging.hpp"
+
+#include "ptr.hpp"
+#include "scene.hpp"
+#include "esmstore.hpp"
+#include "physicssystem.hpp"
+#include "cells.hpp"
+#include "localscripts.hpp"
+#include "timestamp.hpp"
+#include "fallback.hpp"
+
+#include "../mwbase/world.hpp"
+
+namespace Ogre
+{
+ class Vector3;
+}
+
+namespace ESM
+{
+ struct Position;
+}
+
+namespace Files
+{
+ class Collections;
+}
+
+namespace Render
+{
+ class OgreRenderer;
+}
+
+namespace MWRender
+{
+ class SkyManager;
+ class CellRender;
+ class Animation;
+}
+
+namespace MWWorld
+{
+ class WeatherManager;
+ class Player;
+
+ /// \brief The game world and its visual representation
+
+ class World : public MWBase::World
+ {
+ MWWorld::Fallback mFallback;
+ MWRender::RenderingManager* mRendering;
+
+ MWWorld::WeatherManager* mWeatherManager;
+
+ MWWorld::Scene *mWorldScene;
+ MWWorld::Player *mPlayer;
+ std::vector<ESM::ESMReader> mEsm;
+ MWWorld::ESMStore mStore;
+ LocalScripts mLocalScripts;
+ MWWorld::Globals *mGlobalVariables;
+ MWWorld::PhysicsSystem *mPhysics;
+ bool mSky;
+
+ Cells mCells;
+
+ OEngine::Physic::PhysicEngine* mPhysEngine;
+
+ // not implemented
+ World (const World&);
+ World& operator= (const World&);
+
+ Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore);
+
+ int mActivationDistanceOverride;
+ std::string mFacedHandle;
+ float mFacedDistance;
+
+ std::map<MWWorld::Ptr, int> mDoorStates;
+ ///< only holds doors that are currently moving. 0 means closing, 1 opening
+
+ int getDaysPerMonth (int month) const;
+
+ void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust);
+
+ 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);
+
+ void updateWindowManager ();
+ void performUpdateSceneQueries ();
+ void updateFacedHandle ();
+
+ float getMaxActivationDistance ();
+ float getNpcActivationDistance ();
+ float getObjectActivationDistance ();
+
+ void removeContainerScripts(const Ptr& reference);
+ void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell);
+ void PCDropped (const Ptr& item);
+
+ void processDoors(float duration);
+ ///< Run physics simulation and modify \a world accordingly.
+
+ void doPhysics(float duration);
+ ///< Run physics simulation and modify \a world accordingly.
+
+ void ensureNeededRecords();
+
+ int mPlayIntro;
+
+ bool mTeleportEnabled;
+
+ public:
+
+ World (OEngine::Render::OgreRenderer& renderer,
+ const Files::Collections& fileCollections,
+ const std::vector<std::string>& master, const std::vector<std::string>& plugins,
+ const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir,
+ ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, int mActivationDistanceOverride);
+
+ virtual ~World();
+
+ virtual void startNewGame();
+
+ virtual OEngine::Render::Fader* getFader();
+ ///< \ŧodo remove this function. Rendering details should not be exposed.
+
+ virtual CellStore *getExterior (int x, int y);
+
+ virtual CellStore *getInterior (const std::string& name);
+
+ virtual void setWaterHeight(const float height);
+
+ virtual void toggleWater();
+
+ virtual void adjustSky();
+
+ virtual void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches);
+
+ virtual const Fallback *getFallback() const;
+
+ virtual Player& getPlayer();
+
+ virtual const MWWorld::ESMStore& getStore() const;
+
+ virtual std::vector<ESM::ESMReader>& getEsmReader();
+
+ virtual LocalScripts& getLocalScripts();
+
+ virtual bool hasCellChanged() const;
+ ///< Has the player moved to a different cell, since the last frame?
+
+ virtual bool isCellExterior() const;
+
+ virtual bool isCellQuasiExterior() const;
+
+ virtual Ogre::Vector2 getNorthVector (CellStore* cell);
+ ///< get north vector (OGRE coordinates) for given interior cell
+
+ virtual std::vector<DoorMarker> getDoorMarkers (MWWorld::CellStore* cell);
+ ///< 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);
+ ///< see MWRender::LocalMap::getInteriorMapPosition
+
+ virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior);
+ ///< see MWRender::LocalMap::isPositionExplored
+
+ virtual Globals::Data& getGlobalVariable (const std::string& name);
+
+ virtual Globals::Data getGlobalVariable (const std::string& name) const;
+
+ virtual char getGlobalVariableType (const std::string& name) const;
+ ///< Return ' ', if there is no global variable with this name.
+
+ virtual std::vector<std::string> getGlobals () const;
+
+ virtual std::string getCurrentCellName () const;
+
+ virtual void removeRefScript (MWWorld::RefData *ref);
+ //< Remove the script attached to ref from mLocalScripts
+
+ virtual Ptr getPtr (const std::string& name, bool activeOnly);
+ ///< Return a pointer to a liveCellRef with the given name.
+ /// \param activeOnly do non search inactive cells.
+
+ virtual Ptr getPtrViaHandle (const std::string& handle);
+ ///< Return a pointer to a liveCellRef with the given Ogre handle.
+
+ virtual Ptr searchPtrViaHandle (const std::string& handle);
+ ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found
+
+ virtual void adjustPosition (const Ptr& ptr);
+ ///< Adjust position after load to be on ground. Must be called after model load.
+
+ virtual void enable (const Ptr& ptr);
+
+ virtual void disable (const Ptr& ptr);
+
+ virtual void advanceTime (double hours);
+ ///< Advance in-game time.
+
+ virtual void setHour (double hour);
+ ///< Set in-game time hour.
+
+ virtual void setMonth (int month);
+ ///< Set in-game time month.
+
+ virtual void setDay (int day);
+ ///< Set in-game time day.
+
+ virtual int getDay();
+ virtual int getMonth();
+
+ virtual TimeStamp getTimeStamp() const;
+ ///< Return current in-game time stamp.
+
+ virtual bool toggleSky();
+ ///< \return Resulting mode
+
+ virtual void changeWeather (const std::string& region, unsigned int id);
+
+ virtual int getCurrentWeather() const;
+
+ virtual int getMasserPhase() const;
+
+ virtual int getSecundaPhase() const;
+
+ virtual void setMoonColour (bool red);
+
+ virtual void modRegion(const std::string &regionid, const std::vector<char> &chances);
+
+ virtual float getTimeScaleFactor() const;
+
+ virtual void changeToInteriorCell (const std::string& cellName,
+ const ESM::Position& position);
+ ///< Move to interior cell.
+
+ virtual void changeToExteriorCell (const ESM::Position& position);
+ ///< Move to exterior cell.
+
+ virtual const ESM::Cell *getExterior (const std::string& cellName) const;
+ ///< Return a cell matching the given name or a 0-pointer, if there is no such cell.
+
+ virtual void markCellAsUnchanged();
+
+ virtual MWWorld::Ptr getFacedObject();
+ ///< Return pointer to the object the player is looking at, if it is within activation range
+
+ /// Returns a pointer to the object the provided object would hit (if within the
+ /// specified distance), and the point where the hit occurs. This will attempt to
+ /// use the "Head" node as a basis.
+ virtual std::pair<MWWorld::Ptr,Ogre::Vector3> getHitContact(const MWWorld::Ptr &ptr, float distance);
+
+ virtual void deleteObject (const Ptr& ptr);
+
+ virtual void moveObject (const Ptr& ptr, float x, float y, float z);
+ virtual void moveObject (const Ptr& ptr, CellStore &newCell, float x, float y, float z);
+
+ virtual void scaleObject (const Ptr& ptr, float scale);
+
+ /// Rotates object, uses degrees
+ /// \param adjust indicates rotation should be set or adjusted
+ virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false);
+
+ 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);
+ ///< 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)
+ const;
+ ///< Convert cell numbers to position.
+
+ virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const;
+ ///< Convert position to cell numbers
+
+ virtual void queueMovement(const Ptr &ptr, const Ogre::Vector3 &velocity);
+ ///< Queues movement for \a ptr (in local space), to be applied in the next call to
+ /// doPhysics.
+
+ virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2);
+ ///< cast a Ray and return true if there is an object in the ray path.
+
+ virtual bool toggleCollisionMode();
+ ///< Toggle collision mode for player. If disabled player object should ignore
+ /// collisions and gravity.
+ ///< \return Resulting mode
+
+ virtual bool toggleRenderMode (RenderMode mode);
+ ///< Toggle a render mode.
+ ///< \return Resulting mode
+
+ virtual const ESM::Potion *createRecord (const ESM::Potion& record);
+ ///< Create a new record (of type potion) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Spell *createRecord (const ESM::Spell& record);
+ ///< Create a new record (of type spell) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Class *createRecord (const ESM::Class& record);
+ ///< Create a new record (of type class) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Cell *createRecord (const ESM::Cell& record);
+ ///< Create a new record (of type cell) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::NPC *createRecord(const ESM::NPC &record);
+ ///< Create a new record (of type npc) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Armor *createRecord (const ESM::Armor& record);
+ ///< Create a new record (of type armor) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Weapon *createRecord (const ESM::Weapon& record);
+ ///< Create a new record (of type weapon) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Clothing *createRecord (const ESM::Clothing& record);
+ ///< Create a new record (of type clothing) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Enchantment *createRecord (const ESM::Enchantment& record);
+ ///< Create a new record (of type enchantment) in the ESM store.
+ /// \return pointer to created record
+
+ virtual const ESM::Book *createRecord (const ESM::Book& record);
+ ///< Create a new record (of type book) in the ESM store.
+ /// \return pointer to created record
+
+ 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
+ /// @param object
+ /// @param cursor X (relative 0-1)
+ /// @param cursor Y (relative 0-1)
+ /// @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 bool canPlaceObject(float cursorX, float cursorY);
+ ///< @return true if it is possible to place on object at specified cursor location
+
+ virtual void processChangedSettings(const Settings::CategorySettingVector& settings);
+
+ virtual bool isFlying(const MWWorld::Ptr &ptr) const;
+ ///Is the head of the creature underwater?
+ virtual bool isSubmerged(const MWWorld::Ptr &object) const;
+ virtual bool isSwimming(const MWWorld::Ptr &object) const;
+ virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const;
+ virtual bool isOnGround(const MWWorld::Ptr &ptr) const;
+
+ virtual void togglePOV() {
+ mRendering->togglePOV();
+ }
+
+ virtual void togglePreviewMode(bool enable) {
+ mRendering->togglePreviewMode(enable);
+ }
+
+ virtual bool toggleVanityMode(bool enable) {
+ return mRendering->toggleVanityMode(enable);
+ }
+
+ virtual void allowVanityMode(bool allow) {
+ mRendering->allowVanityMode(allow);
+ }
+
+ virtual void togglePlayerLooking(bool enable) {
+ mRendering->togglePlayerLooking(enable);
+ }
+
+ virtual void changeVanityModeScale(float factor) {
+ mRendering->changeVanityModeScale(factor);
+ }
+
+ virtual bool vanityRotateCamera(float * rot);
+ virtual void setCameraDistance(float dist, bool adjust = false, bool override = true);
+
+ virtual void setupPlayer();
+ virtual void renderPlayer();
+
+ virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door);
+ ///< if activated, should this door be opened or closed?
+ virtual void activateDoor(const MWWorld::Ptr& door);
+ ///< activate (open or close) an non-teleport door
+
+ virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object
+ virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object
+ virtual float getWindSpeed();
+
+ virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out);
+ ///< get all containers in active cells owned by this Npc
+ virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out);
+ ///< get all items in active cells owned by this Npc
+
+ virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable);
+
+ virtual void setupExternalRendering (MWRender::ExternalRendering& rendering);
+
+ virtual int canRest();
+ ///< check if the player is allowed to rest \n
+ /// 0 - yes \n
+ /// 1 - only waiting \n
+ /// 2 - player is underwater \n
+ /// 3 - enemies are nearby (not implemented)
+
+ /// \todo Probably shouldn't be here
+ virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr);
+
+ /// \todo this does not belong here
+ virtual void playVideo(const std::string& name, bool allowSkipping);
+ virtual void stopVideo();
+ virtual void frameStarted (float dt, bool paused);
+
+ /// Find center of exterior cell above land surface
+ /// \return false if exterior with given name not exists, true otherwise
+ virtual bool findExteriorPosition(const std::string &name, ESM::Position &pos);
+
+ /// Find position in interior cell near door entrance
+ /// \return false if interior with given name not exists, true otherwise
+ virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos);
+
+ /// Enables or disables use of teleport spell effects (recall, intervention, etc).
+ virtual void enableTeleporting(bool enable);
+
+ /// Returns true if teleport spell effects are allowed.
+ virtual bool isTeleportingEnabled() const;
+
+ virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf);
+
+ virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor);
+ };
+}
+
+#endif
diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt
new file mode 100644
index 0000000000..6820103402
--- /dev/null
+++ b/apps/openmw_test_suite/CMakeLists.txt
@@ -0,0 +1,29 @@
+# TODO: This should not be needed, check how it was done in FindGTEST
+set(GMOCK_ROOT "/usr/include")
+set(GMOCK_BUILD "/usr/lib")
+
+find_package(GTest REQUIRED)
+find_package(GMock REQUIRED)
+
+if (GTEST_FOUND AND GMOCK_FOUND)
+
+ include_directories(${GTEST_INCLUDE_DIRS})
+ include_directories(${GMOCK_INCLUDE_DIRS})
+
+ file(GLOB UNITTEST_SRC_FILES
+ components/misc/test_*.cpp
+ components/file_finder/test_*.cpp
+ )
+
+ source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
+
+ add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
+
+ target_link_libraries(openmw_test_suite ${GMOCK_BOTH_LIBRARIES} ${GTEST_BOTH_LIBRARIES} components)
+ # Fix for not visible pthreads functions for linker with glibc 2.15
+ if (UNIX AND NOT APPLE)
+ target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})
+ endif()
+endif()
+
+
diff --git a/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp b/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp
new file mode 100644
index 0000000000..2d151988b9
--- /dev/null
+++ b/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp
@@ -0,0 +1,66 @@
+#include <gtest/gtest.h>
+#include <fstream>
+#include "components/file_finder/file_finder.hpp"
+
+struct FileFinderTest : public ::testing::Test
+{
+ protected:
+ FileFinderTest()
+ : mTestDir("./filefinder_test_dir/")
+ , mTestFile("test.txt")
+ , mTestFileUppercase("TEST.TXT")
+ , mTestFileNotFound("foobarbaz.txt")
+ {
+ }
+
+ virtual void SetUp()
+ {
+ boost::filesystem::create_directory(boost::filesystem::path(mTestDir));
+
+ std::ofstream ofs(std::string(mTestDir + mTestFile).c_str(), std::ofstream::out);
+ ofs << std::endl;
+ ofs.close();
+ }
+
+ virtual void TearDown()
+ {
+ boost::filesystem::remove_all(boost::filesystem::path(mTestDir));
+ }
+
+ std::string mTestDir;
+ std::string mTestFile;
+ std::string mTestFileUppercase;
+ std::string mTestFileNotFound;
+};
+
+TEST_F(FileFinderTest, FileFinder_has_file)
+{
+ FileFinder::FileFinder fileFinder(mTestDir);
+ ASSERT_TRUE(fileFinder.has(mTestFile));
+ ASSERT_TRUE(fileFinder.has(mTestFileUppercase));
+ ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile));
+ ASSERT_TRUE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile));
+}
+
+TEST_F(FileFinderTest, FileFinder_does_not_have_file)
+{
+ FileFinder::FileFinder fileFinder(mTestDir);
+ ASSERT_FALSE(fileFinder.has(mTestFileNotFound));
+ ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty());
+}
+
+TEST_F(FileFinderTest, FileFinderStrict_has_file)
+{
+ FileFinder::FileFinderStrict fileFinder(mTestDir);
+ ASSERT_TRUE(fileFinder.has(mTestFile));
+ ASSERT_FALSE(fileFinder.has(mTestFileUppercase));
+ ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile));
+ ASSERT_FALSE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile));
+}
+
+TEST_F(FileFinderTest, FileFinderStrict_does_not_have_file)
+{
+ FileFinder::FileFinderStrict fileFinder(mTestDir);
+ ASSERT_FALSE(fileFinder.has(mTestFileNotFound));
+ ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty());
+}
diff --git a/apps/openmw_test_suite/components/file_finder/test_search.cpp b/apps/openmw_test_suite/components/file_finder/test_search.cpp
new file mode 100644
index 0000000000..63745b6256
--- /dev/null
+++ b/apps/openmw_test_suite/components/file_finder/test_search.cpp
@@ -0,0 +1,74 @@
+#include <gtest/gtest.h>
+#include <boost/filesystem.hpp>
+#include <fstream>
+
+#include "components/file_finder/search.hpp"
+
+struct SearchTest : public ::testing::Test
+{
+ protected:
+ SearchTest()
+ : mTestDir("./search_test_dir/")
+ {
+ }
+
+ virtual void SetUp()
+ {
+ boost::filesystem::create_directory(boost::filesystem::path(mTestDir));
+
+ std::ofstream ofs(std::string(mTestDir + "test2.txt").c_str(), std::ofstream::out);
+ ofs << std::endl;
+ ofs.close();
+ }
+
+ virtual void TearDown()
+ {
+ boost::filesystem::remove_all(boost::filesystem::path(mTestDir));
+ }
+
+ std::string mTestDir;
+};
+
+TEST_F(SearchTest, file_not_found)
+{
+ struct Result : public FileFinder::ReturnPath
+ {
+ Result(const boost::filesystem::path& expectedPath)
+ : mExpectedPath(expectedPath)
+ {
+ }
+
+ void add(const boost::filesystem::path& p)
+ {
+ ASSERT_FALSE(p == mExpectedPath);
+ }
+
+ private:
+ boost::filesystem::path mExpectedPath;
+
+ } r(boost::filesystem::path(mTestDir + "test.txt"));
+
+ FileFinder::find(mTestDir, r, false);
+}
+
+TEST_F(SearchTest, file_found)
+{
+ struct Result : public FileFinder::ReturnPath
+ {
+ Result(const boost::filesystem::path& expectedPath)
+ : mExpectedPath(expectedPath)
+ {
+ }
+
+ void add(const boost::filesystem::path& p)
+ {
+ ASSERT_TRUE(p == mExpectedPath);
+ }
+
+ private:
+ boost::filesystem::path mExpectedPath;
+
+ } r(boost::filesystem::path(mTestDir + "test2.txt"));
+
+ FileFinder::find(mTestDir, r, false);
+}
diff --git a/apps/openmw_test_suite/components/misc/test_slicearray.cpp b/apps/openmw_test_suite/components/misc/test_slicearray.cpp
new file mode 100644
index 0000000000..ab63e56c4f
--- /dev/null
+++ b/apps/openmw_test_suite/components/misc/test_slicearray.cpp
@@ -0,0 +1,33 @@
+#include <gtest/gtest.h>
+#include "components/misc/slice_array.hpp"
+
+struct SliceArrayTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ }
+
+ virtual void TearDown()
+ {
+ }
+};
+
+TEST_F(SliceArrayTest, hello_string)
+{
+ Misc::SString s("hello");
+ ASSERT_EQ(sizeof("hello") - 1, s.length);
+ ASSERT_FALSE(s=="hel");
+ ASSERT_FALSE(s=="hell");
+ ASSERT_TRUE(s=="hello");
+}
+
+TEST_F(SliceArrayTest, othello_string_with_offset_2_and_size_4)
+{
+ Misc::SString s("othello" + 2, 4);
+ ASSERT_EQ(sizeof("hell") - 1, s.length);
+ ASSERT_FALSE(s=="hel");
+ ASSERT_TRUE(s=="hell");
+ ASSERT_FALSE(s=="hello");
+}
+
diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp
new file mode 100644
index 0000000000..44587c445f
--- /dev/null
+++ b/apps/openmw_test_suite/components/misc/test_stringops.cpp
@@ -0,0 +1,79 @@
+#include <gtest/gtest.h>
+#include "components/misc/stringops.hpp"
+
+struct StringOpsTest : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ }
+
+ virtual void TearDown()
+ {
+ }
+};
+
+TEST_F(StringOpsTest, begins_matching)
+{
+ ASSERT_TRUE(Misc::begins("abc", "a"));
+ ASSERT_TRUE(Misc::begins("abc", "ab"));
+ ASSERT_TRUE(Misc::begins("abc", "abc"));
+ ASSERT_TRUE(Misc::begins("abcd", "abc"));
+}
+
+TEST_F(StringOpsTest, begins_not_matching)
+{
+ ASSERT_FALSE(Misc::begins("abc", "b"));
+ ASSERT_FALSE(Misc::begins("abc", "bc"));
+ ASSERT_FALSE(Misc::begins("abc", "bcd"));
+ ASSERT_FALSE(Misc::begins("abc", "abcd"));
+}
+
+TEST_F(StringOpsTest, ibegins_matching)
+{
+ ASSERT_TRUE(Misc::ibegins("Abc", "a"));
+ ASSERT_TRUE(Misc::ibegins("aBc", "ab"));
+ ASSERT_TRUE(Misc::ibegins("abC", "abc"));
+ ASSERT_TRUE(Misc::ibegins("abcD", "abc"));
+}
+
+TEST_F(StringOpsTest, ibegins_not_matching)
+{
+ ASSERT_FALSE(Misc::ibegins("abc", "b"));
+ ASSERT_FALSE(Misc::ibegins("abc", "bc"));
+ ASSERT_FALSE(Misc::ibegins("abc", "bcd"));
+ ASSERT_FALSE(Misc::ibegins("abc", "abcd"));
+}
+
+TEST_F(StringOpsTest, ends_matching)
+{
+ ASSERT_TRUE(Misc::ends("abc", "c"));
+ ASSERT_TRUE(Misc::ends("abc", "bc"));
+ ASSERT_TRUE(Misc::ends("abc", "abc"));
+ ASSERT_TRUE(Misc::ends("abcd", "abcd"));
+}
+
+TEST_F(StringOpsTest, ends_not_matching)
+{
+ ASSERT_FALSE(Misc::ends("abc", "b"));
+ ASSERT_FALSE(Misc::ends("abc", "ab"));
+ ASSERT_FALSE(Misc::ends("abc", "bcd"));
+ ASSERT_FALSE(Misc::ends("abc", "abcd"));
+}
+
+TEST_F(StringOpsTest, iends_matching)
+{
+ ASSERT_TRUE(Misc::iends("Abc", "c"));
+ ASSERT_TRUE(Misc::iends("aBc", "bc"));
+ ASSERT_TRUE(Misc::iends("abC", "abc"));
+ ASSERT_TRUE(Misc::iends("abcD", "abcd"));
+}
+
+TEST_F(StringOpsTest, iends_not_matching)
+{
+ ASSERT_FALSE(Misc::iends("abc", "b"));
+ ASSERT_FALSE(Misc::iends("abc", "ab"));
+ ASSERT_FALSE(Misc::iends("abc", "bcd"));
+ ASSERT_FALSE(Misc::iends("abc", "abcd"));
+}
+
diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp
new file mode 100644
index 0000000000..81476325ea
--- /dev/null
+++ b/apps/openmw_test_suite/openmw_test_suite.cpp
@@ -0,0 +1,12 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+
+int main(int argc, char** argv) {
+ // The following line causes Google Mock to throw an exception on failure,
+ // which will be interpreted by your testing framework as a test failure.
+ ::testing::GTEST_FLAG(throw_on_failure) = false;
+ ::testing::InitGoogleMock(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS
new file mode 100644
index 0000000000..8ee3ea36b2
--- /dev/null
+++ b/cmake/COPYING-CMAKE-SCRIPTS
@@ -0,0 +1,27 @@
+The following files are derived from the Thermite project
+(http://www.thermite3d.org) and are covered under the license below.
+
+FindMYGUI.cmake, FindOGRE.cmake, FindOIS.cmake, FindBullet.cmake
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmake/FindAudiere.cmake b/cmake/FindAudiere.cmake
new file mode 100644
index 0000000000..79427309c0
--- /dev/null
+++ b/cmake/FindAudiere.cmake
@@ -0,0 +1,60 @@
+# Locate Audiere
+# This module defines
+# AUDIERE_LIBRARY
+# AUDIERE_FOUND, if false, do not try to link to Audiere
+# AUDIERE_INCLUDE_DIR, where to find the headers
+#
+# Created by Nicolay Korslund for OpenMW (http://openmw.com)
+#
+# More or less a direct ripoff of FindOpenAL.cmake by Eric Wing.
+
+#=============================================================================
+# Copyright 2005-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distributed this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+
+FIND_PATH(AUDIERE_INCLUDE_DIR audiere.h
+ HINTS
+ PATH_SUFFIXES include
+ PATHS
+ $ENV{AUDIERE_DIR}
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+
+FIND_LIBRARY(AUDIERE_LIBRARY
+ NAMES audiere
+ HINTS
+ PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
+ PATHS
+ $ENV{AUDIERE_DIR}
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+SET(AUDIERE_FOUND "NO")
+IF(AUDIERE_LIBRARY AND AUDIERE_INCLUDE_DIR)
+ SET(AUDIERE_FOUND "YES")
+ENDIF(AUDIERE_LIBRARY AND AUDIERE_INCLUDE_DIR)
+
diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake
new file mode 100644
index 0000000000..97feddffeb
--- /dev/null
+++ b/cmake/FindBullet.cmake
@@ -0,0 +1,80 @@
+# - Try to find the Bullet physics engine
+#
+# This module defines the following variables
+#
+# BULLET_FOUND - Was bullet found
+# BULLET_INCLUDE_DIRS - the Bullet include directories
+# BULLET_LIBRARIES - Link to this, by default it includes
+# all bullet components (Dynamics,
+# Collision, LinearMath, & SoftBody)
+#
+# This module accepts the following variables
+#
+# BULLET_ROOT - Can be set to bullet install path or Windows build path
+#
+
+# Copyright (c) 2009, Philip Lowman <philip at yhbt.com>
+#
+# Redistribution AND use is allowed according to the terms of the New
+# BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+set(BULLET_ROOT $ENV{BULLET_ROOT})
+
+macro(_FIND_BULLET_LIBRARY _var)
+ find_library(${_var}
+ NAMES
+ ${ARGN}
+ PATHS
+ ${BULLET_ROOT}
+ ${BULLET_ROOT}/lib/Debug
+ ${BULLET_ROOT}/lib/Release
+ ${BULLET_ROOT}/out/release8/libs
+ ${BULLET_ROOT}/out/debug8/libs
+ PATH_SUFFIXES lib
+ )
+ mark_as_advanced(${_var})
+endmacro()
+
+macro(_BULLET_APPEND_LIBRARIES _list _release)
+ set(_debug ${_release}_DEBUG)
+ if(${_debug})
+ set(${_list} ${${_list}} optimized ${${_release}} debug ${${_debug}})
+ else()
+ set(${_list} ${${_list}} ${${_release}})
+ endif()
+endmacro()
+
+find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h
+ PATHS
+ ${BULLET_ROOT}/include
+ ${BULLET_ROOT}/src
+ PATH_SUFFIXES bullet
+)
+
+# Find the libraries
+
+_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics)
+_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d)
+_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
+# all listed variables are TRUE
+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)
+
+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/FindCg.cmake b/cmake/FindCg.cmake
new file mode 100644
index 0000000000..4bd348c46f
--- /dev/null
+++ b/cmake/FindCg.cmake
@@ -0,0 +1,53 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find Cg
+# Once done, this will define
+#
+# Cg_FOUND - system has Cg
+# Cg_INCLUDE_DIRS - the Cg include directories
+# Cg_LIBRARIES - link these to use Cg
+
+include(FindPkgMacros)
+findpkg_begin(Cg)
+
+# Get path, convert backslashes as ${ENV_${var}}
+getenv_path(Cg_HOME)
+getenv_path(OGRE_SOURCE)
+getenv_path(OGRE_HOME)
+
+# construct search paths
+set(Cg_PREFIX_PATH ${Cg_HOME} ${ENV_Cg_HOME}
+ ${OGRE_SOURCE}/Dependencies
+ ${ENV_OGRE_SOURCE}/Dependencies
+ ${OGRE_HOME} ${ENV_OGRE_HOME}
+ /opt/nvidia-cg-toolkit)
+create_search_paths(Cg)
+# redo search if prefix path changed
+clear_if_changed(Cg_PREFIX_PATH
+ Cg_LIBRARY_FWK
+ Cg_LIBRARY_REL
+ Cg_LIBRARY_DBG
+ Cg_INCLUDE_DIR
+)
+
+set(Cg_LIBRARY_NAMES Cg)
+get_debug_names(Cg_LIBRARY_NAMES)
+
+use_pkgconfig(Cg_PKGC Cg)
+
+findpkg_framework(Cg)
+
+find_path(Cg_INCLUDE_DIR NAMES cg.h HINTS ${Cg_FRAMEWORK_INCLUDES} ${Cg_INC_SEARCH_PATH} ${Cg_PKGC_INCLUDE_DIRS} PATH_SUFFIXES Cg)
+find_library(Cg_LIBRARY_REL NAMES ${Cg_LIBRARY_NAMES} HINTS ${Cg_LIB_SEARCH_PATH} ${Cg_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
+find_library(Cg_LIBRARY_DBG NAMES ${Cg_LIBRARY_NAMES_DBG} HINTS ${Cg_LIB_SEARCH_PATH} ${Cg_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
+make_library_set(Cg_LIBRARY)
+
+findpkg_finish(Cg)
+add_parent_dir(Cg_INCLUDE_DIRS Cg_INCLUDE_DIR)
diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake
new file mode 100644
index 0000000000..a3509597b2
--- /dev/null
+++ b/cmake/FindFFmpeg.cmake
@@ -0,0 +1,156 @@
+# vim: ts=2 sw=2
+# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC)
+#
+# Once done this will define
+# FFMPEG_FOUND - System has the all required components.
+# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers.
+# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components.
+# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components.
+#
+# For each of the components it will additionaly set.
+# - AVCODEC
+# - AVDEVICE
+# - AVFORMAT
+# - AVUTIL
+# - POSTPROCESS
+# - SWSCALE
+# the following variables will be defined
+# <component>_FOUND - System has <component>
+# <component>_INCLUDE_DIRS - Include directory necessary for using the <component> headers
+# <component>_LIBRARIES - Link these to use <component>
+# <component>_DEFINITIONS - Compiler switches required for using <component>
+# <component>_VERSION - The components version
+#
+# Copyright (c) 2006, Matthias Kretz, <kretz@kde.org>
+# Copyright (c) 2008, Alexander Neundorf, <neundorf@kde.org>
+# Copyright (c) 2011, Michael Jansen, <kde@michael-jansen.biz>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+include(FindPackageHandleStandardArgs)
+
+# The default components were taken from a survey over other FindFFMPEG.cmake files
+if (NOT FFmpeg_FIND_COMPONENTS)
+ set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE)
+endif ()
+
+#
+### Macro: set_component_found
+#
+# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present.
+#
+macro(set_component_found _component )
+ if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS)
+ # message(STATUS " - ${_component} found.")
+ set(${_component}_FOUND TRUE)
+ else ()
+ # message(STATUS " - ${_component} not found.")
+ endif ()
+endmacro()
+
+#
+### Macro: find_component
+#
+# Checks for the given component by invoking pkgconfig and then looking up the libraries and
+# include directories.
+#
+macro(find_component _component _pkgconfig _library _header)
+
+ if (NOT WIN32)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(PC_${_component} ${_pkgconfig})
+ endif ()
+ endif (NOT WIN32)
+
+ find_path(${_component}_INCLUDE_DIRS ${_header}
+ HINTS
+ ${FFMPEGSDK_INC}
+ ${PC_LIB${_component}_INCLUDEDIR}
+ ${PC_LIB${_component}_INCLUDE_DIRS}
+ PATH_SUFFIXES
+ ffmpeg
+ )
+
+ find_library(${_component}_LIBRARIES NAMES ${_library}
+ HINTS
+ ${FFMPEGSDK_LIB}
+ ${PC_LIB${_component}_LIBDIR}
+ ${PC_LIB${_component}_LIBRARY_DIRS}
+ )
+
+ set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.")
+ set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.")
+
+ set_component_found(${_component})
+
+ mark_as_advanced(
+ ${_component}_INCLUDE_DIRS
+ ${_component}_LIBRARIES
+ ${_component}_DEFINITIONS
+ ${_component}_VERSION)
+
+endmacro()
+
+
+# Check for cached results. If there are skip the costly part.
+if (NOT FFMPEG_LIBRARIES)
+
+ set (FFMPEGSDK $ENV{FFMPEG_HOME})
+ if (FFMPEGSDK)
+ set (FFMPEGSDK_INC "${FFMPEGSDK}/include")
+ set (FFMPEGSDK_LIB "${FFMPEGSDK}/lib")
+ endif ()
+
+ # Check for all possible component.
+ find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h)
+ find_component(AVFORMAT libavformat avformat libavformat/avformat.h)
+ find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h)
+ find_component(AVUTIL libavutil avutil libavutil/avutil.h)
+ find_component(SWSCALE libswscale swscale libswscale/swscale.h)
+ find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h)
+
+ # Check if the required components were found and add their stuff to the FFMPEG_* vars.
+ foreach (_component ${FFmpeg_FIND_COMPONENTS})
+ if (${_component}_FOUND)
+ # message(STATUS "Required component ${_component} present.")
+ set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES})
+ set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS})
+ list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS})
+ else ()
+ # message(STATUS "Required component ${_component} missing.")
+ endif ()
+ endforeach ()
+
+ # Build the include path with duplicates removed.
+ if (FFMPEG_INCLUDE_DIRS)
+ list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
+ endif ()
+
+ # cache the vars.
+ set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE)
+ set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE)
+ set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE)
+
+ mark_as_advanced(FFMPEG_INCLUDE_DIRS
+ FFMPEG_LIBRARIES
+ FFMPEG_DEFINITIONS)
+
+endif ()
+
+# Now set the noncached _FOUND vars for the components.
+foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE)
+ set_component_found(${_component})
+endforeach ()
+
+# Compile the list of required vars
+set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS)
+foreach (_component ${FFmpeg_FIND_COMPONENTS})
+ list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS)
+endforeach ()
+
+# Give a nice error message if some of the required vars are missing.
+find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS})
diff --git a/cmake/FindFreeImage.cmake b/cmake/FindFreeImage.cmake
new file mode 100644
index 0000000000..3b21a17d64
--- /dev/null
+++ b/cmake/FindFreeImage.cmake
@@ -0,0 +1,47 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find FreeImage
+# Once done, this will define
+#
+# FreeImage_FOUND - system has FreeImage
+# FreeImage_INCLUDE_DIRS - the FreeImage include directories
+# FreeImage_LIBRARIES - link these to use FreeImage
+
+include(FindPkgMacros)
+findpkg_begin(FreeImage)
+
+# Get path, convert backslashes as ${ENV_${var}}
+getenv_path(FREEIMAGE_HOME)
+
+# construct search paths
+set(FreeImage_PREFIX_PATH ${FREEIMAGE_HOME} ${ENV_FREEIMAGE_HOME})
+create_search_paths(FreeImage)
+# redo search if prefix path changed
+clear_if_changed(FreeImage_PREFIX_PATH
+ FreeImage_LIBRARY_FWK
+ FreeImage_LIBRARY_REL
+ FreeImage_LIBRARY_DBG
+ FreeImage_INCLUDE_DIR
+)
+
+set(FreeImage_LIBRARY_NAMES freeimage)
+get_debug_names(FreeImage_LIBRARY_NAMES)
+
+use_pkgconfig(FreeImage_PKGC freeimage)
+
+findpkg_framework(FreeImage)
+
+find_path(FreeImage_INCLUDE_DIR NAMES FreeImage.h HINTS ${FreeImage_INC_SEARCH_PATH} ${FreeImage_PKGC_INCLUDE_DIRS})
+find_library(FreeImage_LIBRARY_REL NAMES ${FreeImage_LIBRARY_NAMES} HINTS ${FreeImage_LIB_SEARCH_PATH} ${FreeImage_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
+find_library(FreeImage_LIBRARY_DBG NAMES ${FreeImage_LIBRARY_NAMES_DBG} HINTS ${FreeImage_LIB_SEARCH_PATH} ${FreeImage_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
+make_library_set(FreeImage_LIBRARY)
+
+findpkg_finish(FreeImage)
+
diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake
new file mode 100644
index 0000000000..fc36d548e2
--- /dev/null
+++ b/cmake/FindFreetype.cmake
@@ -0,0 +1,69 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find FreeType
+# Once done, this will define
+#
+# FREETYPE_FOUND - system has FreeType
+# FREETYPE_INCLUDE_DIRS - the FreeType include directories
+# FREETYPE_LIBRARIES - link these to use FreeType
+
+include(FindPkgMacros)
+findpkg_begin(FREETYPE)
+
+# Get path, convert backslashes as ${ENV_${var}}
+getenv_path(FREETYPE_HOME)
+
+# construct search paths
+set(FREETYPE_PREFIX_PATH ${FREETYPE_HOME} ${ENV_FREETYPE_HOME})
+create_search_paths(FREETYPE)
+# redo search if prefix path changed
+clear_if_changed(FREETYPE_PREFIX_PATH
+ FREETYPE_LIBRARY_FWK
+ FREETYPE_LIBRARY_REL
+ FREETYPE_LIBRARY_DBG
+ FREETYPE_INCLUDE_DIR
+)
+
+set(FREETYPE_LIBRARY_NAMES freetype2311 freetype239 freetype238 freetype235 freetype219 freetype)
+get_debug_names(FREETYPE_LIBRARY_NAMES)
+
+use_pkgconfig(FREETYPE_PKGC freetype2)
+
+# prefer static library over framework
+set(CMAKE_FIND_FRAMEWORK "LAST")
+
+message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
+findpkg_framework(FREETYPE)
+message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
+
+find_path(FREETYPE_INCLUDE_DIR NAMES freetype/freetype.h HINTS ${FREETYPE_INC_SEARCH_PATH} ${FREETYPE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES freetype2)
+find_path(FREETYPE_FT2BUILD_INCLUDE_DIR NAMES ft2build.h HINTS ${FREETYPE_INC_SEARCH_PATH} ${FREETYPE_PKGC_INCLUDE_DIRS})
+
+if (SYMBIAN)
+set(ORIGINAL_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH})
+set(CMAKE_PREFIX_PATH ${CMAKE_SYSYEM_OUT_DIR})
+message(STATUS "Lib will be searched in Symbian out dir: ${CMAKE_SYSYEM_OUT_DIR}")
+endif (SYMBIAN)
+find_library(FREETYPE_LIBRARY_REL NAMES ${FREETYPE_LIBRARY_NAMES} HINTS ${FREETYPE_LIB_SEARCH_PATH} ${FREETYPE_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
+find_library(FREETYPE_LIBRARY_DBG NAMES ${FREETYPE_LIBRARY_NAMES_DBG} HINTS ${FREETYPE_LIB_SEARCH_PATH} ${FREETYPE_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
+if (SYMBIAN)
+set(CMAKE_PREFIX_PATH ${ORIGINAL_CMAKE_PREFIX_PATH})
+endif (SYMBIAN)
+
+make_library_set(FREETYPE_LIBRARY)
+
+findpkg_finish(FREETYPE)
+mark_as_advanced(FREETYPE_FT2BUILD_INCLUDE_DIR)
+if (NOT FREETYPE_FT2BUILD_INCLUDE_DIR STREQUAL FREETYPE_INCLUDE_DIR)
+ set(FREETYPE_INCLUDE_DIRS ${FREETYPE_INCLUDE_DIRS} ${FREETYPE_FT2BUILD_INCLUDE_DIR})
+endif ()
+
+# Reset framework finding
+set(CMAKE_FIND_FRAMEWORK "FIRST")
diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake
new file mode 100644
index 0000000000..eda7d4d72f
--- /dev/null
+++ b/cmake/FindGMock.cmake
@@ -0,0 +1,91 @@
+# Locate the Google C++ Mocking Framework.
+#
+# Defines the following variables:
+#
+# GMOCK_FOUND - Found the Google Mocking framework
+# GMOCK_INCLUDE_DIRS - Include directories
+#
+# Also defines the library variables below as normal
+# variables. These contain debug/optimized keywords when
+# a debugging library is found.
+#
+# GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock-main
+# GMOCK_LIBRARIES - libgmock
+# GMOCK_MAIN_LIBRARIES - libgmock-main
+#
+# Accepts the following variables as input:
+#
+# GMOCK_ROOT - (as CMake or env. variable)
+# The root directory of the gmock install prefix
+#
+#-----------------------
+# Example Usage:
+#
+# enable_testing(true)
+# find_package(GMock REQUIRED)
+# include_directories(${GMOCK_INCLUDE_DIRS})
+#
+# add_executable(foo foo.cc)
+# target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES})
+#
+# add_test(AllTestsInFoo foo)
+#
+
+#set (GMOCK_FOUND FALSE)
+
+
+#set (GMOCK_ROOT $ENV{GMOCK_ROOT} CACHE PATH "Path to the gmock root directory.")
+if (NOT EXISTS ${GMOCK_ROOT})
+ message (FATAL_ERROR "GMOCK_ROOT does not exist.")
+endif ()
+
+#set (GMOCK_BUILD ${GMOCK_ROOT}/build CACHE PATH "Path to the gmock build directory.")
+if (NOT EXISTS ${GMOCK_BUILD})
+ message (FATAL_ERROR "GMOCK_BUILD does not exist.")
+endif ()
+
+# Find the include directory
+find_path(GMOCK_INCLUDE_DIRS gmock/gmock.h
+ HINTS
+ $ENV{GMOCK_ROOT}/include
+ ${GMOCK_ROOT}/include
+)
+mark_as_advanced(GMOCK_INCLUDE_DIRS)
+
+function(_gmock_find_library _name)
+ find_library(${_name}
+ NAMES ${ARGN}
+ HINTS
+ $ENV{GMOCK_BUILD}
+ ${GMOCK_BUILD}
+ )
+ mark_as_advanced(${_name})
+endfunction()
+
+# Find the gmock libraries
+if (MSVC)
+ _gmock_find_library (GMOCK_LIBRARIES_DEBUG gmock ${GMOCK_BUILD}/Debug)
+ _gmock_find_library (GMOCK_LIBRARIES_RELEASE gmock ${GMOCK_BUILD}/Release)
+ _gmock_find_library (GMOCK_MAIN_LIBRARIES_DEBUG gmock_main ${GMOCK_BUILD}/Debug)
+ _gmock_find_library (GMOCK_MAIN_LIBRARIES_RELEASE gmock_main ${GMOCK_BUILD}/Release)
+ set (GMOCK_LIBRARIES
+ debug ${GMOCK_LIBRARIES_DEBUG}
+ optimized ${GMOCK_LIBRARIES_RELEASE}
+ )
+ set (GMOCK_MAIN_LIBRARIES
+ debug ${GMOCK_MAIN_LIBRARIES_DEBUG}
+ optimized ${GMOCK_MAIN_LIBRARIES_RELEASE}
+ )
+else ()
+ _gmock_find_library (GMOCK_LIBRARIES gmock ${GMOCK_BUILD})
+ _gmock_find_library (GMOCK_MAIN_LIBRARIES gmock_main ${GMOCK_BUILD} ${GMOCK_BUILD}/Debug)
+endif ()
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARIES GMOCK_INCLUDE_DIRS GMOCK_MAIN_LIBRARIES)
+
+if(GMOCK_FOUND)
+ set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR})
+ set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES})
+endif()
+
+
diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake
new file mode 100644
index 0000000000..4f4e98a1cd
--- /dev/null
+++ b/cmake/FindLIBUNSHIELD.cmake
@@ -0,0 +1,48 @@
+# Locate LIBUNSHIELD
+# This module defines
+# LIBUNSHIELD_LIBRARY
+# LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield
+# LIBUNSHIELD_INCLUDE_DIR, where to find the headers
+#
+# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake
+#
+# Ripped off from other sources. In fact, this file is so generic (I
+# just did a search and replace on another file) that I wonder why the
+# CMake guys haven't wrapped this entire thing in a single
+# function. Do we really need to repeat this stuff for every single
+# library when they all work the same? </today's rant>
+
+FIND_PATH(LIBUNSHIELD_INCLUDE_DIR libunshield.h
+ HINTS
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+ /usr/include
+)
+
+FIND_LIBRARY(LIBUNSHIELD_LIBRARY
+ unshield
+ HINTS
+# PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+ /usr/lib
+)
+
+IF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR)
+ SET(LIBUNSHIELD_FOUND "YES")
+ENDIF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR)
+
diff --git a/cmake/FindMPG123.cmake b/cmake/FindMPG123.cmake
new file mode 100644
index 0000000000..51e562c910
--- /dev/null
+++ b/cmake/FindMPG123.cmake
@@ -0,0 +1,47 @@
+# Locate MPG123
+# This module defines
+# MPG123_LIBRARY
+# MPG123_FOUND, if false, do not try to link to Mpg123
+# MPG123_INCLUDE_DIR, where to find the headers
+#
+# Created by Nicolay Korslund for OpenMW (http://openmw.com)
+#
+# Ripped off from other sources. In fact, this file is so generic (I
+# just did a search and replace on another file) that I wonder why the
+# CMake guys haven't wrapped this entire thing in a single
+# function. Do we really need to repeat this stuff for every single
+# library when they all work the same? </today's rant>
+
+FIND_PATH(MPG123_INCLUDE_DIR mpg123.h
+ HINTS
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+
+FIND_LIBRARY(MPG123_LIBRARY
+ NAMES mpg123
+ HINTS
+ PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+SET(MPG123_FOUND "NO")
+IF(MPG123_LIBRARY AND MPG123_INCLUDE_DIR)
+ SET(MPG123_FOUND "YES")
+ENDIF(MPG123_LIBRARY AND MPG123_INCLUDE_DIR)
+
diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake
new file mode 100644
index 0000000000..e2fefac7dc
--- /dev/null
+++ b/cmake/FindMyGUI.cmake
@@ -0,0 +1,164 @@
+# - Find MyGUI includes and library
+#
+# This module defines
+# MYGUI_INCLUDE_DIRS
+# MYGUI_LIBRARIES, the libraries to link against to use MYGUI.
+# MYGUI_LIB_DIR, the location of the libraries
+# MYGUI_FOUND, If false, do not try to use MYGUI
+#
+# Copyright © 2007, Matt Williams
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+CMAKE_POLICY(PUSH)
+include(FindPkgMacros)
+
+# IF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS)
+ # SET(MYGUI_FIND_QUIETLY TRUE)
+# ENDIF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS)
+
+IF (WIN32) #Windows
+ MESSAGE(STATUS "Looking for MyGUI")
+SET(MYGUISDK $ENV{MYGUI_HOME})
+ IF (MYGUISDK)
+findpkg_begin ( "MYGUI" )
+ MESSAGE(STATUS "Using MyGUI in MyGUI SDK")
+STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" )
+
+find_path ( MYGUI_INCLUDE_DIRS
+MyGUI.h
+"${MYGUISDK}/MyGUIEngine/include"
+NO_DEFAULT_PATH )
+
+find_path ( MYGUI_PLATFORM_INCLUDE_DIRS
+MyGUI_OgrePlatform.h
+"${MYGUISDK}/Platforms/Ogre/OgrePlatform/include"
+NO_DEFAULT_PATH )
+
+SET ( MYGUI_LIB_DIR ${MYGUISDK}/lib ${MYGUISDK}/*/lib )
+
+find_library ( MYGUI_LIBRARIES_REL NAMES
+MyGUIEngine.lib
+MyGUI.OgrePlatform.lib
+HINTS
+${MYGUI_LIB_DIR}
+PATH_SUFFIXES "" release relwithdebinfo minsizerel )
+
+find_library ( MYGUI_LIBRARIES_DBG NAMES
+MyGUIEngine_d.lib
+MyGUI.OgrePlatform_d.lib
+HINTS
+${MYGUI_LIB_DIR}
+PATH_SUFFIXES "" debug )
+
+find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES
+MyGUI.OgrePlatform.lib
+HINTS
+${MYGUI_LIB_DIR}
+PATH_SUFFIXES "" release relwithdebinfo minsizerel )
+
+find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES
+MyGUI.OgrePlatform_d.lib
+HINTS
+${MYGUI_LIB_DIR}
+PATH_SUFFIXES "" debug )
+
+make_library_set ( MYGUI_LIBRARIES )
+make_library_set ( MYGUI_PLATFORM_LIBRARIES )
+
+MESSAGE ("${MYGUI_LIBRARIES}")
+MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}")
+
+#findpkg_finish ( "MYGUI" )
+
+ ENDIF (MYGUISDK)
+ IF (OGRESOURCE)
+ MESSAGE(STATUS "Using MyGUI in OGRE dependencies")
+ STRING(REGEX REPLACE "[\\]" "/" OGRESDK "${OGRESOURCE}" )
+ SET(MYGUI_INCLUDE_DIRS ${OGRESOURCE}/OgreMain/include/MYGUI)
+ SET(MYGUI_LIB_DIR ${OGRESOURCE}/lib)
+ SET(MYGUI_LIBRARIES debug Debug/MyGUIEngine_d optimized Release/MyGUIEngine)
+ ENDIF (OGRESOURCE)
+ELSE (WIN32) #Unix
+ CMAKE_MINIMUM_REQUIRED(VERSION 2.4.7 FATAL_ERROR)
+ FIND_PACKAGE(PkgConfig)
+ IF(MYGUI_STATIC)
+ # don't use pkgconfig on OS X, find freetype & append it's libs to resulting MYGUI_LIBRARIES
+ IF (NOT APPLE)
+ PKG_SEARCH_MODULE(MYGUI MYGUIStatic MyGUIStatic)
+ IF (MYGUI_INCLUDE_DIRS)
+ SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS})
+ SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR})
+ SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "")
+ SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform")
+ ELSE (MYGUI_INCLUDE_DIRS)
+ FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI)
+ FIND_LIBRARY(MYGUI_LIBRARIES myguistatic PATHS /usr/lib /usr/local/lib)
+ SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform")
+ SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES})
+ STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}")
+ STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}")
+ ENDIF (MYGUI_INCLUDE_DIRS)
+ ELSE (NOT APPLE)
+ SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR} ${OGRE_DEPENDENCIES_DIR})
+ FIND_PACKAGE(freetype)
+ FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI)
+ FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib)
+ SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform")
+ SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES})
+ STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}")
+ STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}")
+ ENDIF (NOT APPLE)
+ ELSE(MYGUI_STATIC)
+ PKG_SEARCH_MODULE(MYGUI MYGUI MyGUI)
+ IF (MYGUI_INCLUDE_DIRS)
+ SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS})
+ SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR})
+ SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "")
+ SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform")
+ ELSE (MYGUI_INCLUDE_DIRS)
+ FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI)
+ FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib)
+ SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform")
+ SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES})
+ STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}")
+ STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}")
+ ENDIF (MYGUI_INCLUDE_DIRS)
+ ENDIF(MYGUI_STATIC)
+ENDIF (WIN32)
+
+#Do some preparation
+IF (NOT WIN32) # This does not work on Windows for paths with spaces in them
+ SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS)
+ SEPARATE_ARGUMENTS(MYGUI_LIBRARIES)
+ SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES)
+ENDIF (NOT WIN32)
+
+SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${FREETYPE_LIBRARIES})
+
+SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "")
+SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "")
+SET(MYGUI_PLATFORM_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "")
+SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "")
+
+IF (NOT APPLE OR NOT MYGUI_STATIC) # we need explicit freetype libs only on OS X for static build, for other cases just make it TRUE
+ SET(FREETYPE_LIBRARIES TRUE)
+ENDIF (NOT APPLE OR NOT MYGUI_STATIC)
+
+IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND FREETYPE_LIBRARIES)
+ SET(MYGUI_FOUND TRUE)
+ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES AND FREETYPE_LIBRARIES)
+
+IF (MYGUI_FOUND)
+ MARK_AS_ADVANCED(MYGUI_LIB_DIR)
+ IF (NOT MYGUI_FIND_QUIETLY)
+ MESSAGE(STATUS " libraries : ${MYGUI_LIBRARIES} from ${MYGUI_LIB_DIR}")
+ MESSAGE(STATUS " includes : ${MYGUI_INCLUDE_DIRS}")
+ ENDIF (NOT MYGUI_FIND_QUIETLY)
+ELSE (MYGUI_FOUND)
+ IF (MYGUI_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find MYGUI")
+ ENDIF (MYGUI_FIND_REQUIRED)
+ENDIF (MYGUI_FOUND)
+
+CMAKE_POLICY(POP)
diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake
new file mode 100644
index 0000000000..fb4a090c6e
--- /dev/null
+++ b/cmake/FindOGRE.cmake
@@ -0,0 +1,554 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find OGRE
+# If you have multiple versions of Ogre installed, use the CMake or
+# the environment variable OGRE_HOME to point to the path where the
+# desired Ogre version can be found.
+# By default this script will look for a dynamic Ogre build. If you
+# need to link against static Ogre libraries, set the CMake variable
+# OGRE_STATIC to TRUE.
+#
+# Once done, this will define
+#
+# OGRE_FOUND - system has OGRE
+# OGRE_INCLUDE_DIRS - the OGRE include directories
+# OGRE_LIBRARIES - link these to use the OGRE core
+# OGRE_BINARY_REL - location of the main Ogre binary (win32 non-static only, release)
+# OGRE_BINARY_DBG - location of the main Ogre binaries (win32 non-static only, debug)
+#
+# Additionally this script searches for the following optional
+# parts of the Ogre package:
+# Plugin_BSPSceneManager, Plugin_CgProgramManager,
+# Plugin_OctreeSceneManager, Plugin_OctreeZone,
+# Plugin_ParticleFX, Plugin_PCZSceneManager,
+# RenderSystem_GL, RenderSystem_Direct3D9, RenderSystem_Direct3D10,
+# Paging, Terrain
+#
+# For each of these components, the following variables are defined:
+#
+# OGRE_${COMPONENT}_FOUND - ${COMPONENT} is available
+# OGRE_${COMPONENT}_INCLUDE_DIRS - additional include directories for ${COMPONENT}
+# OGRE_${COMPONENT}_LIBRARIES - link these to use ${COMPONENT}
+# OGRE_${COMPONENT}_BINARY_REL - location of the component binary (win32 non-static only, release)
+# OGRE_${COMPONENT}_BINARY_DBG - location of the component binary (win32 non-static only, debug)
+#
+# Finally, the following variables are defined:
+#
+# OGRE_PLUGIN_DIR_REL - The directory where the release versions of
+# the OGRE plugins are located
+# OGRE_PLUGIN_DIR_DBG - The directory where the debug versions of
+# the OGRE plugins are located
+# OGRE_MEDIA_DIR - The directory where the OGRE sample media is
+# located, if available
+
+include(FindPkgMacros)
+include(PreprocessorUtils)
+findpkg_begin(OGRE)
+
+
+# Get path, convert backslashes as ${ENV_${var}}
+getenv_path(OGRE_HOME)
+getenv_path(OGRE_SDK)
+getenv_path(OGRE_SOURCE)
+getenv_path(OGRE_BUILD)
+getenv_path(OGRE_DEPENDENCIES_DIR)
+getenv_path(PROGRAMFILES)
+
+# Determine whether to search for a dynamic or static build
+if (OGRE_STATIC)
+ set(OGRE_LIB_SUFFIX "Static")
+else ()
+ set(OGRE_LIB_SUFFIX "")
+endif ()
+
+
+set(OGRE_LIBRARY_NAMES "OgreMain${OGRE_LIB_SUFFIX}")
+get_debug_names(OGRE_LIBRARY_NAMES)
+
+# construct search paths from environmental hints and
+# OS specific guesses
+if (WIN32)
+ set(OGRE_PREFIX_GUESSES
+ ${ENV_PROGRAMFILES}/OGRE
+ C:/OgreSDK
+ )
+elseif (UNIX)
+ set(OGRE_PREFIX_GUESSES
+ /opt/ogre
+ /opt/OGRE
+ /usr/lib${LIB_SUFFIX}/ogre
+ /usr/lib${LIB_SUFFIX}/OGRE
+ /usr/local/lib${LIB_SUFFIX}/ogre
+ /usr/local/lib${LIB_SUFFIX}/OGRE
+ $ENV{HOME}/ogre
+ $ENV{HOME}/OGRE
+ )
+endif ()
+set(OGRE_PREFIX_PATH
+ ${OGRE_HOME} ${OGRE_SDK} ${ENV_OGRE_HOME} ${ENV_OGRE_SDK}
+ ${OGRE_PREFIX_GUESSES}
+)
+create_search_paths(OGRE)
+# If both OGRE_BUILD and OGRE_SOURCE are set, prepare to find Ogre in a build dir
+set(OGRE_PREFIX_SOURCE ${OGRE_SOURCE} ${ENV_OGRE_SOURCE})
+set(OGRE_PREFIX_BUILD ${OGRE_BUILD} ${ENV_OGRE_BUILD})
+set(OGRE_PREFIX_DEPENDENCIES_DIR ${OGRE_DEPENDENCIES_DIR} ${ENV_OGRE_DEPENDENCIES_DIR})
+if (OGRE_PREFIX_SOURCE AND OGRE_PREFIX_BUILD)
+ foreach(dir ${OGRE_PREFIX_SOURCE})
+ set(OGRE_INC_SEARCH_PATH ${dir}/OgreMain/include ${dir}/Dependencies/include ${dir}/iPhoneDependencies/include ${OGRE_INC_SEARCH_PATH})
+ set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${dir}/Dependencies/lib ${dir}/iPhoneDependencies/lib ${OGRE_LIB_SEARCH_PATH})
+ set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH})
+ endforeach(dir)
+ foreach(dir ${OGRE_PREFIX_BUILD})
+ set(OGRE_INC_SEARCH_PATH ${dir}/include ${OGRE_INC_SEARCH_PATH})
+ set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${OGRE_LIB_SEARCH_PATH})
+ set(OGRE_BIN_SEARCH_PATH ${dir}/bin ${OGRE_BIN_SEARCH_PATH})
+ set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH})
+ endforeach(dir)
+
+ if (OGRE_PREFIX_DEPENDENCIES_DIR)
+ set(OGRE_INC_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/include ${OGRE_INC_SEARCH_PATH})
+ set(OGRE_LIB_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/lib ${OGRE_LIB_SEARCH_PATH})
+ set(OGRE_BIN_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/bin ${OGRE_BIN_SEARCH_PATH})
+ endif()
+else()
+ set(OGRE_PREFIX_SOURCE "NOTFOUND")
+ set(OGRE_PREFIX_BUILD "NOTFOUND")
+endif ()
+
+# redo search if any of the environmental hints changed
+set(OGRE_COMPONENTS Paging Terrain
+ Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager
+ Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX
+ RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES)
+set(OGRE_RESET_VARS
+ OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR
+ OGRE_LIBRARY_FWK OGRE_LIBRARY_REL OGRE_LIBRARY_DBG
+ OGRE_PLUGIN_DIR_DBG OGRE_PLUGIN_DIR_REL OGRE_MEDIA_DIR)
+foreach (comp ${OGRE_COMPONENTS})
+ set(OGRE_RESET_VARS ${OGRE_RESET_VARS}
+ OGRE_${comp}_INCLUDE_DIR OGRE_${comp}_LIBRARY_FWK
+ OGRE_${comp}_LIBRARY_DBG OGRE_${comp}_LIBRARY_REL
+ )
+endforeach (comp)
+set(OGRE_PREFIX_WATCH ${OGRE_PREFIX_PATH} ${OGRE_PREFIX_SOURCE} ${OGRE_PREFIX_BUILD})
+clear_if_changed(OGRE_PREFIX_WATCH ${OGRE_RESET_VARS})
+
+# try to locate Ogre via pkg-config
+use_pkgconfig(OGRE_PKGC "OGRE${OGRE_LIB_SUFFIX}")
+
+if(NOT OGRE_BUILD_PLATFORM_IPHONE AND APPLE)
+ # try to find framework on OSX
+ findpkg_framework(OGRE)
+else()
+ set(OGRE_LIBRARY_FWK "")
+endif()
+
+# locate Ogre include files
+find_path(OGRE_CONFIG_INCLUDE_DIR NAMES OgreBuildSettings.h HINTS ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE")
+find_path(OGRE_INCLUDE_DIR NAMES OgreRoot.h HINTS ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE")
+set(OGRE_INCOMPATIBLE FALSE)
+
+if (OGRE_INCLUDE_DIR)
+ if (NOT OGRE_CONFIG_INCLUDE_DIR)
+ set(OGRE_CONFIG_INCLUDE_DIR ${OGRE_INCLUDE_DIR})
+ endif ()
+ # determine Ogre version
+ file(READ ${OGRE_INCLUDE_DIR}/OgrePrerequisites.h OGRE_TEMP_VERSION_CONTENT)
+ get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MAJOR OGRE_VERSION_MAJOR)
+ get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MINOR OGRE_VERSION_MINOR)
+ get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_PATCH OGRE_VERSION_PATCH)
+ get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_NAME OGRE_VERSION_NAME)
+ set(OGRE_VERSION "${OGRE_VERSION_MAJOR}.${OGRE_VERSION_MINOR}.${OGRE_VERSION_PATCH}")
+ pkg_message(OGRE "Found Ogre ${OGRE_VERSION_NAME} (${OGRE_VERSION})")
+
+ # determine configuration settings
+ set(OGRE_CONFIG_HEADERS
+ ${OGRE_CONFIG_INCLUDE_DIR}/OgreBuildSettings.h
+ ${OGRE_CONFIG_INCLUDE_DIR}/OgreConfig.h
+ )
+ foreach(CFG_FILE ${OGRE_CONFIG_HEADERS})
+ if (EXISTS ${CFG_FILE})
+ set(OGRE_CONFIG_HEADER ${CFG_FILE})
+ break()
+ endif()
+ endforeach()
+ if (OGRE_CONFIG_HEADER)
+ file(READ ${OGRE_CONFIG_HEADER} OGRE_TEMP_CONFIG_CONTENT)
+ has_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_STATIC_LIB OGRE_CONFIG_STATIC)
+ get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_SUPPORT OGRE_CONFIG_THREADS)
+ get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_PROVIDER OGRE_CONFIG_THREAD_PROVIDER)
+ get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_NO_FREEIMAGE OGRE_CONFIG_FREEIMAGE)
+ if (OGRE_CONFIG_STATIC AND OGRE_STATIC)
+ elseif (OGRE_CONFIG_STATIC OR OGRE_STATIC)
+ pkg_message(OGRE "Build type (static, dynamic) does not match the requested one.")
+ set(OGRE_INCOMPATIBLE TRUE)
+ endif ()
+ else ()
+ pkg_message(OGRE "Could not determine Ogre build configuration.")
+ set(OGRE_INCOMPATIBLE TRUE)
+ endif ()
+else ()
+ set(OGRE_INCOMPATIBLE FALSE)
+endif ()
+
+find_library(OGRE_LIBRARY_REL NAMES ${OGRE_LIBRARY_NAMES} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel")
+find_library(OGRE_LIBRARY_DBG NAMES ${OGRE_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "debug")
+make_library_set(OGRE_LIBRARY)
+
+if(APPLE)
+ set(OGRE_LIBRARY_DBG ${OGRE_LIB_SEARCH_PATH})
+endif()
+if (OGRE_INCOMPATIBLE)
+ set(OGRE_LIBRARY "NOTFOUND")
+endif ()
+
+set(OGRE_INCLUDE_DIR ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INCLUDE_DIR})
+list(REMOVE_DUPLICATES OGRE_INCLUDE_DIR)
+findpkg_finish(OGRE)
+add_parent_dir(OGRE_INCLUDE_DIRS OGRE_INCLUDE_DIR)
+if (OGRE_SOURCE)
+ # If working from source rather than SDK, add samples include
+ set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} "${OGRE_SOURCE}/Samples/Common/include")
+endif()
+
+mark_as_advanced(OGRE_CONFIG_INCLUDE_DIR OGRE_MEDIA_DIR OGRE_PLUGIN_DIR_REL OGRE_PLUGIN_DIR_DBG)
+
+if (NOT OGRE_FOUND)
+ return()
+endif ()
+
+
+# look for required Ogre dependencies in case of static build and/or threading
+if (OGRE_STATIC)
+ set(OGRE_DEPS_FOUND TRUE)
+ find_package(Cg QUIET)
+ find_package(DirectX QUIET)
+ find_package(FreeImage QUIET)
+ find_package(Freetype QUIET)
+ find_package(OpenGL QUIET)
+ find_package(OpenGLES QUIET)
+ find_package(ZLIB QUIET)
+ find_package(ZZip QUIET)
+ if (UNIX AND NOT APPLE)
+ find_package(X11 QUIET)
+ find_library(XAW_LIBRARY NAMES Xaw Xaw7 PATHS ${DEP_LIB_SEARCH_DIR} ${X11_LIB_SEARCH_PATH})
+ if (NOT XAW_LIBRARY OR NOT X11_Xt_FOUND)
+ set(X11_FOUND FALSE)
+ endif ()
+ endif ()
+ if (APPLE AND NOT OGRE_BUILD_PLATFORM_IPHONE)
+ find_package(Cocoa QUIET)
+ find_package(Carbon QUIET)
+ if (NOT Cocoa_FOUND OR NOT Carbon_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ endif ()
+ if (APPLE AND OGRE_BUILD_PLATFORM_IPHONE)
+ find_package(iPhoneSDK QUIET)
+ if (NOT iPhoneSDK_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ endif ()
+
+ set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES}
+ ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES}
+ ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB}
+ ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES})
+
+ if (NOT ZLIB_FOUND OR NOT ZZip_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ if (NOT FreeImage_FOUND AND NOT OGRE_CONFIG_FREEIMAGE)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ if (NOT FREETYPE_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ if (UNIX AND NOT APPLE)
+ if (NOT X11_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ endif ()
+ endif ()
+
+ if (OGRE_CONFIG_THREADS)
+ if (OGRE_CONFIG_THREAD_PROVIDER EQUAL 1)
+ find_package(Boost COMPONENTS thread QUIET)
+ if (NOT Boost_THREAD_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ else ()
+ set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${Boost_LIBRARIES})
+ set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
+ endif ()
+ elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 2)
+ find_package(POCO QUIET)
+ if (NOT POCO_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ else ()
+ set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${POCO_LIBRARIES})
+ set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${POCO_INCLUDE_DIRS})
+ endif ()
+ elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 3)
+ find_package(TBB QUIET)
+ if (NOT TBB_FOUND)
+ set(OGRE_DEPS_FOUND FALSE)
+ else ()
+ set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${TBB_LIBRARIES})
+ set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${TBB_INCLUDE_DIRS})
+ endif ()
+ endif ()
+ endif ()
+
+ if (NOT OGRE_DEPS_FOUND)
+ pkg_message(OGRE "Could not find all required dependencies for the Ogre package.")
+ set(OGRE_FOUND FALSE)
+ endif ()
+endif ()
+
+if (NOT OGRE_FOUND)
+ return()
+endif ()
+
+
+get_filename_component(OGRE_LIBRARY_DIR_REL "${OGRE_LIBRARY_REL}" PATH)
+get_filename_component(OGRE_LIBRARY_DIR_DBG "${OGRE_LIBRARY_DBG}" PATH)
+set(OGRE_LIBRARY_DIRS ${OGRE_LIBRARY_DIR_REL} ${OGRE_LIBRARY_DIR_DBG})
+
+# find binaries
+if (NOT OGRE_STATIC)
+ if (WIN32)
+ find_file(OGRE_BINARY_REL NAMES "OgreMain.dll" HINTS ${OGRE_BIN_SEARCH_PATH}
+ PATH_SUFFIXES "" release relwithdebinfo minsizerel)
+ find_file(OGRE_BINARY_DBG NAMES "OgreMain_d.dll" HINTS ${OGRE_BIN_SEARCH_PATH}
+ PATH_SUFFIXES "" debug )
+ endif()
+ mark_as_advanced(OGRE_BINARY_REL OGRE_BINARY_DBG)
+endif()
+
+
+#########################################################
+# Find Ogre components
+#########################################################
+
+set(OGRE_COMPONENT_SEARCH_PATH_REL
+ ${OGRE_LIBRARY_DIR_REL}/..
+ ${OGRE_LIBRARY_DIR_REL}/../..
+ ${OGRE_BIN_SEARCH_PATH}
+)
+set(OGRE_COMPONENT_SEARCH_PATH_DBG
+ ${OGRE_LIBRARY_DIR_DBG}/..
+ ${OGRE_LIBRARY_DIR_DBG}/../..
+ ${OGRE_BIN_SEARCH_PATH}
+)
+
+macro(ogre_find_component COMPONENT HEADER)
+ findpkg_begin(OGRE_${COMPONENT})
+ find_path(OGRE_${COMPONENT}_INCLUDE_DIR NAMES ${HEADER} HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE} PATH_SUFFIXES ${COMPONENT} OGRE/${COMPONENT} Components/${COMPONENT}/include)
+ set(OGRE_${COMPONENT}_LIBRARY_NAMES "Ogre${COMPONENT}${OGRE_LIB_SUFFIX}")
+ get_debug_names(OGRE_${COMPONENT}_LIBRARY_NAMES)
+ find_library(OGRE_${COMPONENT}_LIBRARY_REL NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES} HINTS ${OGRE_LIBRARY_DIR_REL} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel")
+ find_library(OGRE_${COMPONENT}_LIBRARY_DBG NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIBRARY_DIR_DBG} PATH_SUFFIXES "" "debug")
+ make_library_set(OGRE_${COMPONENT}_LIBRARY)
+ findpkg_finish(OGRE_${COMPONENT})
+ if (OGRE_${COMPONENT}_FOUND)
+ # find binaries
+ if (NOT OGRE_STATIC)
+ if (WIN32)
+ find_file(OGRE_${COMPONENT}_BINARY_REL NAMES "Ogre${COMPONENT}.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_REL} PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release)
+ find_file(OGRE_${COMPONENT}_BINARY_DBG NAMES "Ogre${COMPONENT}_d.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_DBG} PATH_SUFFIXES "" bin bin/debug debug)
+ endif()
+ mark_as_advanced(OGRE_${COMPONENT}_BINARY_REL OGRE_${COMPONENT}_BINARY_DBG)
+ endif()
+ endif()
+endmacro()
+
+# look for Paging component
+ogre_find_component(Paging OgrePaging.h)
+# look for Terrain component
+ogre_find_component(Terrain OgreTerrain.h)
+# look for Property component
+ogre_find_component(Property OgreProperty.h)
+# look for RTShaderSystem component
+ogre_find_component(RTShaderSystem OgreRTShaderSystem.h)
+
+
+#########################################################
+# Find Ogre plugins
+#########################################################
+
+macro(ogre_find_plugin PLUGIN HEADER)
+ # On Unix, the plugins might have no prefix
+ if (CMAKE_FIND_LIBRARY_PREFIXES)
+ set(TMP_CMAKE_LIB_PREFIX ${CMAKE_FIND_LIBRARY_PREFIXES})
+ set(CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES} "")
+ endif()
+
+ # strip RenderSystem_ or Plugin_ prefix from plugin name
+ string(REPLACE "RenderSystem_" "" PLUGIN_TEMP ${PLUGIN})
+ string(REPLACE "Plugin_" "" PLUGIN_NAME ${PLUGIN_TEMP})
+
+ # header files for plugins are not usually needed, but find them anyway if they are present
+ set(OGRE_PLUGIN_PATH_SUFFIXES
+ PlugIns PlugIns/${PLUGIN_NAME} Plugins Plugins/${PLUGIN_NAME} ${PLUGIN}
+ RenderSystems RenderSystems/${PLUGIN_NAME} ${ARGN})
+ find_path(OGRE_${PLUGIN}_INCLUDE_DIR NAMES ${HEADER}
+ HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_PREFIX_SOURCE}
+ PATH_SUFFIXES ${OGRE_PLUGIN_PATH_SUFFIXES})
+ # find link libraries for plugins
+ set(OGRE_${PLUGIN}_LIBRARY_NAMES "${PLUGIN}${OGRE_LIB_SUFFIX}")
+ get_debug_names(OGRE_${PLUGIN}_LIBRARY_NAMES)
+ set(OGRE_${PLUGIN}_LIBRARY_FWK ${OGRE_LIBRARY_FWK})
+ # Search for release plugins in OGRE dir with version suffix
+ find_library(OGRE_${PLUGIN}_LIBRARY_REL NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES}
+ HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE-${OGRE_VERSION} opt release release/opt relwithdebinfo relwithdebinfo/opt minsizerel minsizerel/opt)
+ if(NOT EXISTS "${OGRE_${PLUGIN}_LIBRARY_REL}")
+ # Search for release plugins in OGRE dir without version suffix
+ find_library(OGRE_${PLUGIN}_LIBRARY_REL NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES}
+ HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt release release/opt relwithdebinfo relwithdebinfo/opt minsizerel minsizerel/opt)
+ endif()
+ # Search for debug plugins in OGRE dir with version suffix
+ find_library(OGRE_${PLUGIN}_LIBRARY_DBG NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES_DBG}
+ HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE-${OGRE_VERSION} opt debug debug/opt)
+ if(NOT EXISTS "${OGRE_${PLUGIN}_LIBRARY_DBG}")
+ # Search for debug plugins in OGRE dir without version suffix
+ find_library(OGRE_${PLUGIN}_LIBRARY_DBG NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES_DBG}
+ HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt debug debug/opt)
+ endif()
+ make_library_set(OGRE_${PLUGIN}_LIBRARY)
+
+ if (OGRE_${PLUGIN}_LIBRARY OR OGRE_${PLUGIN}_INCLUDE_DIR)
+ set(OGRE_${PLUGIN}_FOUND TRUE)
+ if (OGRE_${PLUGIN}_INCLUDE_DIR)
+ set(OGRE_${PLUGIN}_INCLUDE_DIRS ${OGRE_${PLUGIN}_INCLUDE_DIR})
+ endif()
+ set(OGRE_${PLUGIN}_LIBRARIES ${OGRE_${PLUGIN}_LIBRARY})
+ endif ()
+
+ mark_as_advanced(OGRE_${PLUGIN}_INCLUDE_DIR OGRE_${PLUGIN}_LIBRARY_REL OGRE_${PLUGIN}_LIBRARY_DBG OGRE_${PLUGIN}_LIBRARY_FWK)
+
+ # look for plugin dirs
+ if (OGRE_${PLUGIN}_FOUND)
+ if (NOT OGRE_PLUGIN_DIR_REL OR NOT OGRE_PLUGIN_DIR_DBG)
+ if (WIN32)
+ set(OGRE_PLUGIN_SEARCH_PATH_REL
+ ${OGRE_LIBRARY_DIR_REL}/..
+ ${OGRE_LIBRARY_DIR_REL}/../..
+ ${OGRE_BIN_SEARCH_PATH}
+ )
+ set(OGRE_PLUGIN_SEARCH_PATH_DBG
+ ${OGRE_LIBRARY_DIR_DBG}/..
+ ${OGRE_LIBRARY_DIR_DBG}/../..
+ ${OGRE_BIN_SEARCH_PATH}
+ )
+ find_path(OGRE_PLUGIN_DIR_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_REL}
+ PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release)
+ find_path(OGRE_PLUGIN_DIR_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_DBG}
+ PATH_SUFFIXES "" bin bin/debug debug)
+ elseif (UNIX)
+ get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_REL} PATH)
+ # For some reason this fails
+ #set(OGRE_PLUGIN_DIR_REL ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (release)")
+ set(OGRE_PLUGIN_DIR_REL ${OGRE_PLUGIN_DIR_TMP})
+ get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_DBG} PATH)
+ # Same here
+ #set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (debug)")
+ set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP})
+ endif ()
+ endif ()
+
+ # find binaries
+ if (NOT OGRE_STATIC)
+ if (WIN32)
+ find_file(OGRE_${PLUGIN}_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_DIR_REL})
+ find_file(OGRE_${PLUGIN}_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_DIR_DBG})
+ endif()
+ mark_as_advanced(OGRE_${PLUGIN}_REL OGRE_${PLUGIN}_DBG)
+ endif()
+
+ endif ()
+
+ if (TMP_CMAKE_LIB_PREFIX)
+ set(CMAKE_FIND_LIBRARY_PREFIXES ${TMP_CMAKE_LIB_PREFIX})
+ endif ()
+endmacro(ogre_find_plugin)
+
+ogre_find_plugin(Plugin_PCZSceneManager OgrePCZSceneManager.h PCZ PlugIns/PCZSceneManager/include)
+ogre_find_plugin(Plugin_OctreeZone OgreOctreeZone.h PCZ PlugIns/OctreeZone/include)
+ogre_find_plugin(Plugin_BSPSceneManager OgreBspSceneManager.h PlugIns/BSPSceneManager/include)
+ogre_find_plugin(Plugin_CgProgramManager OgreCgProgram.h PlugIns/CgProgramManager/include)
+ogre_find_plugin(Plugin_OctreeSceneManager OgreOctreeSceneManager.h PlugIns/OctreeSceneManager/include)
+ogre_find_plugin(Plugin_ParticleFX OgreParticleFXPrerequisites.h PlugIns/ParticleFX/include)
+ogre_find_plugin(RenderSystem_GL OgreGLRenderSystem.h RenderSystems/GL/include)
+ogre_find_plugin(RenderSystem_GLES OgreGLESRenderSystem.h RenderSystems/GLES/include)
+ogre_find_plugin(RenderSystem_Direct3D9 OgreD3D9RenderSystem.h RenderSystems/Direct3D9/include)
+ogre_find_plugin(RenderSystem_Direct3D10 OgreD3D10RenderSystem.h RenderSystems/Direct3D10/include)
+ogre_find_plugin(RenderSystem_Direct3D11 OgreD3D11RenderSystem.h RenderSystems/Direct3D11/include)
+
+if (OGRE_STATIC)
+ # check if dependencies for plugins are met
+ if (NOT DirectX_FOUND)
+ set(OGRE_RenderSystem_Direct3D9_FOUND FALSE)
+ endif ()
+ if (NOT DirectX_D3D10_FOUND)
+ set(OGRE_RenderSystem_Direct3D10_FOUND FALSE)
+ endif ()
+ if (NOT DirectX_D3D11_FOUND)
+ set(OGRE_RenderSystem_Direct3D11_FOUND FALSE)
+ endif ()
+ if (NOT OPENGL_FOUND)
+ set(OGRE_RenderSystem_GL_FOUND FALSE)
+ endif ()
+ if (NOT OPENGLES_FOUND AND NOT OPENGLES2_FOUND)
+ set(OGRE_RenderSystem_GLES_FOUND FALSE)
+ endif ()
+ if (NOT Cg_FOUND)
+ set(OGRE_Plugin_CgProgramManager_FOUND FALSE)
+ endif ()
+
+ set(OGRE_RenderSystem_Direct3D9_LIBRARIES ${OGRE_RenderSystem_Direct3D9_LIBRARIES}
+ ${DirectX_LIBRARIES}
+ )
+ set(OGRE_RenderSystem_Direct3D10_LIBRARIES ${OGRE_RenderSystem_Direct3D10_LIBRARIES}
+ ${DirectX_D3D10_LIBRARIES}
+ )
+ set(OGRE_RenderSystem_Direct3D11_LIBRARIES ${OGRE_RenderSystem_Direct3D11_LIBRARIES}
+ ${DirectX_D3D11_LIBRARIES}
+ )
+ set(OGRE_RenderSystem_GL_LIBRARIES ${OGRE_RenderSystem_GL_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+ )
+ set(OGRE_RenderSystem_GLES_LIBRARIES ${OGRE_RenderSystem_GLES_LIBRARIES}
+ ${OPENGLES_LIBRARIES}
+ )
+ set(OGRE_Plugin_CgProgramManager_LIBRARIES ${OGRE_Plugin_CgProgramManager_LIBRARIES}
+ ${Cg_LIBRARIES}
+ )
+endif ()
+
+# look for the media directory
+set(OGRE_MEDIA_SEARCH_PATH
+ ${OGRE_SOURCE}
+ ${OGRE_LIBRARY_DIR_REL}/..
+ ${OGRE_LIBRARY_DIR_DBG}/..
+ ${OGRE_LIBRARY_DIR_REL}/../..
+ ${OGRE_LIBRARY_DIR_DBG}/../..
+ ${OGRE_PREFIX_SOURCE}
+)
+set(OGRE_MEDIA_SEARCH_SUFFIX
+ Samples/Media
+ Media
+ media
+ share/OGRE/media
+)
+
+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/cmake/FindPkgMacros.cmake b/cmake/FindPkgMacros.cmake
new file mode 100644
index 0000000000..473b27b2a7
--- /dev/null
+++ b/cmake/FindPkgMacros.cmake
@@ -0,0 +1,161 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+##################################################################
+# Provides some common functionality for the FindPackage modules
+##################################################################
+
+# Begin processing of package
+macro(findpkg_begin PREFIX)
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message(STATUS "Looking for ${PREFIX}...")
+ endif ()
+endmacro(findpkg_begin)
+
+# Display a status message unless FIND_QUIETLY is set
+macro(pkg_message PREFIX)
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message(STATUS ${ARGN})
+ endif ()
+endmacro(pkg_message)
+
+# Get environment variable, define it as ENV_$var and make sure backslashes are converted to forward slashes
+macro(getenv_path VAR)
+ set(ENV_${VAR} $ENV{${VAR}})
+ # replace won't work if var is blank
+ if (ENV_${VAR})
+ string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} )
+ endif ()
+endmacro(getenv_path)
+
+# Construct search paths for includes and libraries from a PREFIX_PATH
+macro(create_search_paths PREFIX)
+ foreach(dir ${${PREFIX}_PREFIX_PATH})
+ set(${PREFIX}_INC_SEARCH_PATH ${${PREFIX}_INC_SEARCH_PATH}
+ ${dir}/include ${dir}/Include ${dir}/include/${PREFIX} ${dir}/Headers)
+ set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH}
+ ${dir}/lib ${dir}/Lib ${dir}/lib/${PREFIX} ${dir}/Libs)
+ set(${PREFIX}_BIN_SEARCH_PATH ${${PREFIX}_BIN_SEARCH_PATH}
+ ${dir}/bin)
+ endforeach(dir)
+ set(${PREFIX}_FRAMEWORK_SEARCH_PATH ${${PREFIX}_PREFIX_PATH})
+endmacro(create_search_paths)
+
+# clear cache variables if a certain variable changed
+macro(clear_if_changed TESTVAR)
+ # test against internal check variable
+ # HACK: Apparently, adding a variable to the cache cleans up the list
+ # a bit. We need to also remove any empty strings from the list, but
+ # at the same time ensure that we are actually dealing with a list.
+ list(APPEND ${TESTVAR} "")
+ list(REMOVE_ITEM ${TESTVAR} "")
+ if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}")
+ message(STATUS "${TESTVAR} changed.")
+ foreach(var ${ARGN})
+ set(${var} "NOTFOUND" CACHE STRING "x" FORCE)
+ endforeach(var)
+ endif ()
+ set(${TESTVAR}_INT_CHECK ${${TESTVAR}} CACHE INTERNAL "x" FORCE)
+endmacro(clear_if_changed)
+
+# Try to get some hints from pkg-config, if available
+macro(use_pkgconfig PREFIX PKGNAME)
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(${PREFIX} ${PKGNAME})
+ endif ()
+endmacro (use_pkgconfig)
+
+# Couple a set of release AND debug libraries (or frameworks)
+macro(make_library_set PREFIX)
+ if (${PREFIX}_FWK)
+ set(${PREFIX} ${${PREFIX}_FWK})
+ elseif (${PREFIX}_REL AND ${PREFIX}_DBG)
+ set(${PREFIX} optimized ${${PREFIX}_REL} debug ${${PREFIX}_DBG})
+ elseif (${PREFIX}_REL)
+ set(${PREFIX} ${${PREFIX}_REL})
+ elseif (${PREFIX}_DBG)
+ set(${PREFIX} ${${PREFIX}_DBG})
+ endif ()
+endmacro(make_library_set)
+
+# Generate debug names from given release names
+macro(get_debug_names PREFIX)
+ foreach(i ${${PREFIX}})
+ set(${PREFIX}_DBG ${${PREFIX}_DBG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i})
+ endforeach(i)
+endmacro(get_debug_names)
+
+# Add the parent dir from DIR to VAR
+macro(add_parent_dir VAR DIR)
+ get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE)
+ set(${VAR} ${${VAR}} ${${DIR}_TEMP})
+endmacro(add_parent_dir)
+
+# Do the final processing for the package find.
+macro(findpkg_finish PREFIX)
+ # skip if already processed during this run
+ if (NOT ${PREFIX}_FOUND)
+ if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY)
+ set(${PREFIX}_FOUND TRUE)
+ set(${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR})
+ set(${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY})
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message(STATUS "Found ${PREFIX}: ${${PREFIX}_LIBRARIES}")
+ endif ()
+ else ()
+ if (NOT ${PREFIX}_FIND_QUIETLY)
+ message(STATUS "Could not locate ${PREFIX}")
+ endif ()
+ if (${PREFIX}_FIND_REQUIRED)
+ message(FATAL_ERROR "Required library ${PREFIX} not found! Install the library (including dev packages) and try again. If the library is already installed, set the missing variables manually in cmake.")
+ endif ()
+ endif ()
+
+ mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_REL ${PREFIX}_LIBRARY_DBG ${PREFIX}_LIBRARY_FWK)
+ endif ()
+endmacro(findpkg_finish)
+
+
+# Slightly customised framework finder
+MACRO(findpkg_framework fwk)
+ IF(APPLE)
+ SET(${fwk}_FRAMEWORK_PATH
+ ${${fwk}_FRAMEWORK_SEARCH_PATH}
+ ${CMAKE_FRAMEWORK_PATH}
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /System/Library/Frameworks
+ /Network/Library/Frameworks
+ /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/
+ ${CMAKE_CURRENT_SOURCE_DIR}/lib/Release
+ ${CMAKE_CURRENT_SOURCE_DIR}/lib/Debug
+ )
+ # These could be arrays of paths, add each individually to the search paths
+ foreach(i ${OGRE_PREFIX_PATH})
+ set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/Release ${i}/lib/Debug)
+ endforeach(i)
+
+ foreach(i ${OGRE_PREFIX_BUILD})
+ set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/Release ${i}/lib/Debug)
+ endforeach(i)
+
+ FOREACH(dir ${${fwk}_FRAMEWORK_PATH})
+ SET(fwkpath ${dir}/${fwk}.framework)
+ IF(EXISTS ${fwkpath})
+ SET(${fwk}_FRAMEWORK_INCLUDES ${${fwk}_FRAMEWORK_INCLUDES}
+ ${fwkpath}/Headers ${fwkpath}/PrivateHeaders)
+ SET(${fwk}_FRAMEWORK_PATH ${dir})
+ if (NOT ${fwk}_LIBRARY_FWK)
+ SET(${fwk}_LIBRARY_FWK "-framework ${fwk}")
+ endif ()
+ ENDIF(EXISTS ${fwkpath})
+ ENDFOREACH(dir)
+ ENDIF(APPLE)
+ENDMACRO(findpkg_framework)
diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake
new file mode 100644
index 0000000000..0dc02f5b60
--- /dev/null
+++ b/cmake/FindSDL.cmake
@@ -0,0 +1,177 @@
+# Locate SDL library
+# This module defines
+# SDL_LIBRARY, the name of the library to link against
+# SDL_FOUND, if false, do not try to link to SDL
+# SDL_INCLUDE_DIR, where to find SDL.h
+#
+# This module responds to the the flag:
+# SDL_BUILDING_LIBRARY
+# If this is defined, then no SDL_main will be linked in because
+# only applications need main().
+# Otherwise, it is assumed you are building an application and this
+# module will attempt to locate and set the the proper link flags
+# as part of the returned SDL_LIBRARY variable.
+#
+# Don't forget to include SDLmain.h and SDLmain.m your project for the
+# OS X framework based version. (Other versions link to -lSDLmain which
+# this module will try to find on your behalf.) Also for OS X, this
+# module will automatically add the -framework Cocoa on your behalf.
+#
+#
+# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration
+# and no SDL_LIBRARY, it means CMake did not find your SDL library
+# (SDL.dll, libsdl.so, SDL.framework, etc).
+# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again.
+# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value
+# as appropriate. These values are used to generate the final SDL_LIBRARY
+# variable, but when these values are unset, SDL_LIBRARY does not get created.
+#
+#
+# $SDLDIR is an environment variable that would
+# correspond to the ./configure --prefix=$SDLDIR
+# used in building SDL.
+# l.e.galup 9-20-02
+#
+# Modified by Eric Wing.
+# Added code to assist with automated building by using environmental variables
+# and providing a more controlled/consistent search behavior.
+# Added new modifications to recognize OS X frameworks and
+# additional Unix paths (FreeBSD, etc).
+# Also corrected the header search path to follow "proper" SDL guidelines.
+# Added a search for SDLmain which is needed by some platforms.
+# Added a search for threads which is needed by some platforms.
+# Added needed compile switches for MinGW.
+#
+# On OSX, this will prefer the Framework version (if found) over others.
+# People will have to manually change the cache values of
+# SDL_LIBRARY to override this selection or set the CMake environment
+# CMAKE_INCLUDE_PATH to modify the search paths.
+#
+# Note that the header path has changed from SDL/SDL.h to just SDL.h
+# This needed to change because "proper" SDL convention
+# is #include "SDL.h", not <SDL/SDL.h>. This is done for portability
+# reasons because not all systems place things in SDL/ (see FreeBSD).
+
+#=============================================================================
+# Copyright 2003-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+FIND_PATH(SDL_INCLUDE_DIR SDL.h
+ HINTS
+ $ENV{SDLDIR}
+ PATH_SUFFIXES include/SDL include
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local/include/SDL12
+ /usr/local/include/SDL11 # FreeBSD ports
+ /usr/include/SDL12
+ /usr/include/SDL11
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}")
+
+# SDL-1.1 is the name used by FreeBSD ports...
+# don't confuse it for the version number.
+FIND_LIBRARY(SDL_LIBRARY_TEMP
+ NAMES SDL SDL-1.1
+ HINTS
+ $ENV{SDLDIR}
+ PATH_SUFFIXES lib64 lib
+ PATHS
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}")
+
+IF(NOT SDL_BUILDING_LIBRARY)
+ IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework")
+ # Non-OS X framework versions expect you to also dynamically link to
+ # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms
+ # seem to provide SDLmain for compatibility even though they don't
+ # necessarily need it.
+ FIND_LIBRARY(SDLMAIN_LIBRARY
+ NAMES SDLmain SDLmain-1.1
+ HINTS
+ $ENV{SDLDIR}
+ PATH_SUFFIXES lib64 lib
+ PATHS
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+ )
+ ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework")
+ENDIF(NOT SDL_BUILDING_LIBRARY)
+
+# SDL may require threads on your system.
+# The Apple build may not need an explicit flag because one of the
+# frameworks may already provide it.
+# But for non-OSX systems, I will use the CMake Threads package.
+IF(NOT APPLE)
+ FIND_PACKAGE(Threads)
+ENDIF(NOT APPLE)
+
+# MinGW needs an additional library, mwindows
+# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows
+# (Actually on second look, I think it only needs one of the m* libraries.)
+IF(MINGW)
+ SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
+ENDIF(MINGW)
+
+SET(SDL_FOUND "NO")
+IF(SDL_LIBRARY_TEMP)
+ # For SDLmain
+ IF(NOT SDL_BUILDING_LIBRARY)
+ IF(SDLMAIN_LIBRARY)
+ SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP})
+ ENDIF(SDLMAIN_LIBRARY)
+ ENDIF(NOT SDL_BUILDING_LIBRARY)
+
+ # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa.
+ # CMake doesn't display the -framework Cocoa string in the UI even
+ # though it actually is there if I modify a pre-used variable.
+ # I think it has something to do with the CACHE STRING.
+ # So I use a temporary variable until the end so I can set the
+ # "real" variable in one-shot.
+ IF(APPLE)
+ SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa")
+ ENDIF(APPLE)
+
+ # For threads, as mentioned Apple doesn't need this.
+ # In fact, there seems to be a problem if I used the Threads package
+ # and try using this line, so I'm just skipping it entirely for OS X.
+ IF(NOT APPLE)
+ SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
+ ENDIF(NOT APPLE)
+
+ # For MinGW library
+ IF(MINGW)
+ SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP})
+ ENDIF(MINGW)
+
+ # Set the final string here so the GUI reflects the final state.
+ SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found")
+ # Set the temp variable to INTERNAL so it is not seen in the CMake GUI
+ SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "")
+
+ SET(SDL_FOUND "YES")
+ENDIF(SDL_LIBRARY_TEMP)
+
+#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}")
+
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
new file mode 100644
index 0000000000..70e607a89f
--- /dev/null
+++ b/cmake/FindSDL2.cmake
@@ -0,0 +1,193 @@
+# Locate SDL2 library
+# This module defines
+# SDL2_LIBRARY, the name of the library to link against
+# SDL2_FOUND, if false, do not try to link to SDL2
+# SDL2_INCLUDE_DIR, where to find SDL.h
+#
+# This module responds to the the flag:
+# SDL2_BUILDING_LIBRARY
+# If this is defined, then no SDL2_main will be linked in because
+# only applications need main().
+# Otherwise, it is assumed you are building an application and this
+# module will attempt to locate and set the the proper link flags
+# as part of the returned SDL2_LIBRARY variable.
+#
+# Don't forget to include SDL2main.h and SDL2main.m your project for the
+# OS X framework based version. (Other versions link to -lSDL2main which
+# this module will try to find on your behalf.) Also for OS X, this
+# module will automatically add the -framework Cocoa on your behalf.
+#
+#
+# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
+# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
+# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
+# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
+# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
+# as appropriate. These values are used to generate the final SDL2_LIBRARY
+# variable, but when these values are unset, SDL2_LIBRARY does not get created.
+#
+#
+# $SDL2DIR is an environment variable that would
+# correspond to the ./configure --prefix=$SDL2DIR
+# used in building SDL2.
+# l.e.galup 9-20-02
+#
+# Modified by Eric Wing.
+# Added code to assist with automated building by using environmental variables
+# and providing a more controlled/consistent search behavior.
+# Added new modifications to recognize OS X frameworks and
+# additional Unix paths (FreeBSD, etc).
+# Also corrected the header search path to follow "proper" SDL2 guidelines.
+# Added a search for SDL2main which is needed by some platforms.
+# Added a search for threads which is needed by some platforms.
+# Added needed compile switches for MinGW.
+#
+# On OSX, this will prefer the Framework version (if found) over others.
+# People will have to manually change the cache values of
+# SDL2_LIBRARY to override this selection or set the CMake environment
+# CMAKE_INCLUDE_PATH to modify the search paths.
+#
+# Note that the header path has changed from SDL2/SDL.h to just SDL.h
+# This needed to change because "proper" SDL2 convention
+# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
+# reasons because not all systems place things in SDL2/ (see FreeBSD).
+#
+# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake
+# module with the minor edit of changing "SDL" to "SDL2" where necessary. This
+# was not created for redistribution, and exists temporarily pending official
+# SDL2 CMake modules.
+
+#=============================================================================
+# Copyright 2003-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+# License text for the above reference.)
+
+
+FIND_PATH(SDL2_INCLUDE_DIR SDL.h
+ HINTS
+ $ENV{SDL2DIR}
+ PATH_SUFFIXES include/SDL2 include
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local/include/SDL2
+ /usr/include/SDL2
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+#MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}")
+
+FIND_LIBRARY(SDL2_LIBRARY_TEMP
+ NAMES SDL2
+ HINTS
+ $ENV{SDL2DIR}
+ PATH_SUFFIXES lib64 lib
+ PATHS
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+#MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}")
+
+IF(NOT SDL2_BUILDING_LIBRARY)
+ IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
+ # Non-OS X framework versions expect you to also dynamically link to
+ # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
+ # seem to provide SDL2main for compatibility even though they don't
+ # necessarily need it.
+ FIND_LIBRARY(SDL2MAIN_LIBRARY
+ NAMES SDL2main
+ HINTS
+ $ENV{SDL2DIR}
+ PATH_SUFFIXES lib64 lib
+ PATHS
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+ )
+ ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
+ENDIF(NOT SDL2_BUILDING_LIBRARY)
+
+# SDL2 may require threads on your system.
+# The Apple build may not need an explicit flag because one of the
+# frameworks may already provide it.
+# But for non-OSX systems, I will use the CMake Threads package.
+IF(NOT APPLE)
+ FIND_PACKAGE(Threads)
+ENDIF(NOT APPLE)
+
+# MinGW needs an additional library, mwindows
+# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows
+# (Actually on second look, I think it only needs one of the m* libraries.)
+IF(MINGW)
+ SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
+ENDIF(MINGW)
+
+SET(SDL2_FOUND "NO")
+IF(SDL2_LIBRARY_TEMP)
+ # For SDL2main
+ IF(NOT SDL2_BUILDING_LIBRARY)
+ IF(SDL2MAIN_LIBRARY)
+ SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP})
+ ENDIF(SDL2MAIN_LIBRARY)
+ ENDIF(NOT SDL2_BUILDING_LIBRARY)
+
+ # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
+ # CMake doesn't display the -framework Cocoa string in the UI even
+ # though it actually is there if I modify a pre-used variable.
+ # I think it has something to do with the CACHE STRING.
+ # So I use a temporary variable until the end so I can set the
+ # "real" variable in one-shot.
+ IF(APPLE)
+ SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
+ ENDIF(APPLE)
+
+ # For threads, as mentioned Apple doesn't need this.
+ # In fact, there seems to be a problem if I used the Threads package
+ # and try using this line, so I'm just skipping it entirely for OS X.
+ IF(NOT APPLE)
+ SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
+ ENDIF(NOT APPLE)
+
+ # For MinGW library
+ IF(MINGW)
+ SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
+ ENDIF(MINGW)
+
+ IF(WIN32)
+ SET(SDL2_LIBRARY_TEMP winmm imm32 version msimg32 ${SDL2_LIBRARY_TEMP})
+ ENDIF(WIN32)
+
+ # Set the final string here so the GUI reflects the final state.
+ SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found")
+ # Set the temp variable to INTERNAL so it is not seen in the CMake GUI
+ SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "")
+
+ SET(SDL2_FOUND "YES")
+ENDIF(SDL2_LIBRARY_TEMP)
+
+INCLUDE(FindPackageHandleStandardArgs)
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2
+ REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR)
+
+IF(SDL2_STATIC)
+ if (UNIX AND NOT APPLE)
+ EXECUTE_PROCESS(COMMAND sdl2-config --static-libs OUTPUT_VARIABLE SDL2_LINK_FLAGS)
+ STRING(REGEX REPLACE "(\r?\n)+$" "" SDL2_LINK_FLAGS "${SDL2_LINK_FLAGS}")
+ SET(SDL2_LIBRARY ${SDL2_LINK_FLAGS})
+ ENDIF()
+ENDIF(SDL2_STATIC)
diff --git a/cmake/FindSNDFILE.cmake b/cmake/FindSNDFILE.cmake
new file mode 100644
index 0000000000..5c7664b502
--- /dev/null
+++ b/cmake/FindSNDFILE.cmake
@@ -0,0 +1,41 @@
+# Locate SNDFILE
+# This module defines
+# SNDFILE_LIBRARY
+# SNDFILE_FOUND, if false, do not try to link to Sndfile
+# SNDFILE_INCLUDE_DIR, where to find the headers
+#
+# Created by Nicolay Korslund for OpenMW (http://openmw.com)
+
+FIND_PATH(SNDFILE_INCLUDE_DIR sndfile.h
+ HINTS
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+
+FIND_LIBRARY(SNDFILE_LIBRARY
+ NAMES sndfile
+ HINTS
+ PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /usr/local
+ /usr
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+SET(SNDFILE_FOUND "NO")
+IF(SNDFILE_LIBRARY AND SNDFILE_INCLUDE_DIR)
+ SET(SNDFILE_FOUND "YES")
+ENDIF(SNDFILE_LIBRARY AND SNDFILE_INCLUDE_DIR)
+
diff --git a/cmake/FindZZip.cmake b/cmake/FindZZip.cmake
new file mode 100644
index 0000000000..68fe043f36
--- /dev/null
+++ b/cmake/FindZZip.cmake
@@ -0,0 +1,48 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find zziplib
+# Once done, this will define
+#
+# ZZip_FOUND - system has ZZip
+# ZZip_INCLUDE_DIRS - the ZZip include directories
+# ZZip_LIBRARIES - link these to use ZZip
+
+include(FindPkgMacros)
+findpkg_begin(ZZip)
+
+# Get path, convert backslashes as ${ENV_${var}}
+getenv_path(ZZIP_HOME)
+
+
+# construct search paths
+set(ZZip_PREFIX_PATH ${ZZIP_HOME} ${ENV_ZZIP_HOME})
+create_search_paths(ZZip)
+# redo search if prefix path changed
+clear_if_changed(ZZip_PREFIX_PATH
+ ZZip_LIBRARY_FWK
+ ZZip_LIBRARY_REL
+ ZZip_LIBRARY_DBG
+ ZZip_INCLUDE_DIR
+)
+
+set(ZZip_LIBRARY_NAMES zzip zziplib)
+get_debug_names(ZZip_LIBRARY_NAMES)
+
+use_pkgconfig(ZZip_PKGC zziplib)
+
+findpkg_framework(ZZip)
+
+find_path(ZZip_INCLUDE_DIR NAMES zzip/zzip.h HINTS ${ZZip_INC_SEARCH_PATH} ${ZZip_PKGC_INCLUDE_DIRS})
+find_library(ZZip_LIBRARY_REL NAMES ${ZZip_LIBRARY_NAMES} HINTS ${ZZip_LIB_SEARCH_PATH} ${ZZip_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
+find_library(ZZip_LIBRARY_DBG NAMES ${ZZip_LIBRARY_NAMES_DBG} HINTS ${ZZip_LIB_SEARCH_PATH} ${ZZip_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
+make_library_set(ZZip_LIBRARY)
+
+findpkg_finish(ZZip)
+
diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake
new file mode 100644
index 0000000000..f66dbf2c47
--- /dev/null
+++ b/cmake/OpenMWMacros.cmake
@@ -0,0 +1,95 @@
+
+macro (add_openmw_dir dir)
+set (files)
+foreach (u ${ARGN})
+file (GLOB ALL "${dir}/${u}.[ch]pp")
+foreach (f ${ALL})
+list (APPEND files "${f}")
+list (APPEND OPENMW_FILES "${f}")
+endforeach (f)
+endforeach (u)
+source_group ("apps\\openmw\\${dir}" FILES ${files})
+endmacro (add_openmw_dir)
+
+macro (add_component_dir dir)
+set (files)
+foreach (u ${ARGN})
+file (GLOB ALL "${dir}/${u}.[ch]pp")
+foreach (f ${ALL})
+list (APPEND files "${f}")
+list (APPEND COMPONENT_FILES "${f}")
+endforeach (f)
+endforeach (u)
+source_group ("components\\${dir}" FILES ${files})
+endmacro (add_component_dir)
+
+macro (add_component_qt_dir dir)
+set (files)
+foreach (u ${ARGN})
+file (GLOB ALL "${dir}/${u}.[ch]pp")
+foreach (f ${ALL})
+list (APPEND files "${f}")
+list (APPEND COMPONENT_FILES "${f}")
+endforeach (f)
+file (GLOB MOC_H "${dir}/${u}.hpp")
+foreach (fi ${MOC_H})
+list (APPEND COMPONENT_MOC_FILES "${fi}")
+endforeach (fi)
+endforeach (u)
+source_group ("components\\${dir}" FILES ${files})
+endmacro (add_component_qt_dir)
+
+macro (copy_all_files source_dir destination_dir files)
+foreach (f ${files})
+get_filename_component(filename ${f} NAME)
+configure_file(${source_dir}/${f} ${destination_dir}/${filename} COPYONLY)
+endforeach (f)
+endmacro (copy_all_files)
+
+macro (add_file project type file)
+list (APPEND ${project}${type} ${file})
+endmacro (add_file)
+
+macro (add_unit project dir unit)
+add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp")
+add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp")
+endmacro (add_unit)
+
+macro (add_qt_unit project dir unit)
+add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp")
+add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp")
+add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp")
+endmacro (add_qt_unit)
+
+macro (add_hdr project dir unit)
+add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp")
+endmacro (add_hdr)
+
+macro (add_qt_hdr project dir unit)
+add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp")
+add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp")
+endmacro (add_qt_hdr)
+
+macro (opencs_units dir)
+foreach (u ${ARGN})
+add_qt_unit (OPENCS ${dir} ${u})
+endforeach (u)
+endmacro (opencs_units)
+
+macro (opencs_units_noqt dir)
+foreach (u ${ARGN})
+add_unit (OPENCS ${dir} ${u})
+endforeach (u)
+endmacro (opencs_units_noqt)
+
+macro (opencs_hdrs dir)
+foreach (u ${ARGN})
+add_qt_hdr (OPENCS ${dir} ${u})
+endforeach (u)
+endmacro (opencs_hdrs)
+
+macro (opencs_hdrs_noqt dir)
+foreach (u ${ARGN})
+add_hdr (OPENCS ${dir} ${u})
+endforeach (u)
+endmacro (opencs_hdrs_noqt)
diff --git a/cmake/PreprocessorUtils.cmake b/cmake/PreprocessorUtils.cmake
new file mode 100644
index 0000000000..38462a98d4
--- /dev/null
+++ b/cmake/PreprocessorUtils.cmake
@@ -0,0 +1,60 @@
+#-------------------------------------------------------------------
+# This file is part of the CMake build system for OGRE
+# (Object-oriented Graphics Rendering Engine)
+# For the latest info, see http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+macro(get_preprocessor_entry CONTENTS KEYWORD VARIABLE)
+ string(REGEX MATCH
+ "# *define +${KEYWORD} +((\"([^\n]*)\")|([^ \n]*))"
+ PREPROC_TEMP_VAR
+ ${${CONTENTS}}
+ )
+ if (CMAKE_MATCH_3)
+ set(${VARIABLE} ${CMAKE_MATCH_3})
+ else ()
+ set(${VARIABLE} ${CMAKE_MATCH_4})
+ endif ()
+endmacro()
+
+macro(has_preprocessor_entry CONTENTS KEYWORD VARIABLE)
+ string(REGEX MATCH
+ "\n *# *define +(${KEYWORD})"
+ PREPROC_TEMP_VAR
+ ${${CONTENTS}}
+ )
+ if (CMAKE_MATCH_1)
+ set(${VARIABLE} TRUE)
+ else ()
+ set(${VARIABLE} FALSE)
+ endif ()
+endmacro()
+
+macro(replace_preprocessor_entry VARIABLE KEYWORD NEW_VALUE)
+ string(REGEX REPLACE
+ "(// *)?# *define +${KEYWORD} +[^ \n]*"
+ "#define ${KEYWORD} ${NEW_VALUE}"
+ ${VARIABLE}_TEMP
+ ${${VARIABLE}}
+ )
+ set(${VARIABLE} ${${VARIABLE}_TEMP})
+endmacro()
+
+macro(set_preprocessor_entry VARIABLE KEYWORD ENABLE)
+ if (${ENABLE})
+ set(TMP_REPLACE_STR "#define ${KEYWORD}")
+ else ()
+ set(TMP_REPLACE_STR "// #define ${KEYWORD}")
+ endif ()
+ string(REGEX REPLACE
+ "(// *)?# *define +${KEYWORD} *\n"
+ ${TMP_REPLACE_STR}
+ ${VARIABLE}_TEMP
+ ${${VARIABLE}}
+ )
+ set(${VARIABLE} ${${VARIABLE}_TEMP})
+endmacro()
+
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
new file mode 100644
index 0000000000..04423dc6f0
--- /dev/null
+++ b/components/CMakeLists.txt
@@ -0,0 +1,102 @@
+project (Components)
+
+# source files
+
+add_component_dir (settings
+ settings
+ )
+
+add_component_dir (nifoverrides
+ nifoverrides
+ )
+
+add_component_dir (bsa
+ bsa_archive bsa_file
+ )
+
+add_component_dir (nif
+ controlled effect niftypes record controller extra node record_ptr data niffile property
+ )
+
+add_component_dir (nifogre
+ ogrenifloader skeleton material mesh
+ )
+
+add_component_dir (nifbullet
+ bulletnifloader
+ )
+
+add_component_dir (to_utf8
+ to_utf8
+ )
+
+add_component_dir (file_finder
+ file_finder filename_less search
+ )
+
+add_component_dir (esm
+ attr defs esmcommon esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell
+ loadclas loadclot loadcont loadcrea loadcrec loaddial loaddoor loadench loadfact loadglob loadgmst
+ loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc
+ loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat
+ loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter
+ )
+
+add_component_dir (misc
+ slice_array stringops
+ )
+
+add_component_dir (files
+ linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager
+ filelibrary ogreplugin constrainedfiledatastream lowlevelfile
+ )
+
+add_component_dir (compiler
+ context controlparser errorhandler exception exprparser extensions fileparser generator
+ lineparser literals locals output parser scanner scriptparser skipparser streamerrorhandler
+ stringparser tokenloc nullerrorhandler opcodes extensions0
+ )
+
+add_component_dir (interpreter
+ context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes
+ miscopcodes opcodes runtime scriptopcodes spatialopcodes types defines
+ )
+
+add_component_dir (translation
+ translation
+ )
+
+add_component_dir (terrain
+ quadtreenode chunk world storage material
+ )
+
+add_component_dir (loadinglistener
+ loadinglistener
+ )
+
+find_package(Qt4 COMPONENTS QtCore QtGui)
+
+if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY)
+ add_component_qt_dir (fileorderlist
+ model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile
+ utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort
+ )
+
+ include(${QT_USE_FILE})
+ QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES})
+endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY)
+
+include_directories(${BULLET_INCLUDE_DIRS})
+
+add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS})
+
+target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES})
+
+# Fix for not visible pthreads functions for linker with glibc 2.15
+if (UNIX AND NOT APPLE)
+target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT})
+endif()
+
+
+# Make the variable accessible for other subdirectories
+set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE)
diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp
new file mode 100644
index 0000000000..8f07b9e503
--- /dev/null
+++ b/components/bsa/bsa_archive.cpp
@@ -0,0 +1,382 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (cpp_bsaarchive.cpp) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#include "bsa_archive.hpp"
+
+#include <OgreFileSystem.h>
+#include <OgreArchive.h>
+#include <OgreArchiveFactory.h>
+#include <OgreArchiveManager.h>
+#include "bsa_file.hpp"
+
+#include "../files/constrainedfiledatastream.hpp"
+
+using namespace Ogre;
+
+static bool fsstrict = false;
+
+static char strict_normalize_char(char ch)
+{
+ return ch == '\\' ? '/' : ch;
+}
+
+static char nonstrict_normalize_char(char ch)
+{
+ return ch == '\\' ? '/' : std::tolower(ch);
+}
+
+template<typename T1, typename T2>
+static std::string normalize_path(T1 begin, T2 end)
+{
+ std::string normalized;
+ normalized.reserve(std::distance(begin, end));
+ char (*normalize_char)(char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char;
+ std::transform(begin, end, std::back_inserter(normalized), normalize_char);
+ return normalized;
+}
+
+/// An OGRE Archive wrapping a BSAFile archive
+class DirArchive: public Ogre::Archive
+{
+ typedef std::map <std::string, std::string> index;
+
+ index mIndex;
+
+ index::const_iterator lookup_filename (std::string const & filename) const
+ {
+ std::string normalized = normalize_path (filename.begin (), filename.end ());
+ return mIndex.find (normalized);
+ }
+
+public:
+
+ DirArchive(const String& name)
+ : Archive(name, "Dir")
+ {
+ typedef boost::filesystem::recursive_directory_iterator directory_iterator;
+
+ directory_iterator end;
+
+ size_t prefix = name.size ();
+
+ if (name.size () > 0 && name [prefix - 1] != '\\' && name [prefix - 1] != '/')
+ ++prefix;
+
+ for (directory_iterator i (name); i != end; ++i)
+ {
+ if(boost::filesystem::is_directory (*i))
+ continue;
+
+ std::string proper = i->path ().string ();
+
+ std::string searchable = normalize_path (proper.begin () + prefix, proper.end ());
+
+ mIndex.insert (std::make_pair (searchable, proper));
+ }
+ }
+
+ bool isCaseSensitive() const { return fsstrict; }
+
+ // The archive is loaded in the constructor, and never unloaded.
+ void load() {}
+ void unload() {}
+
+ DataStreamPtr open(const String& filename, bool readonly = true) const
+ {
+ index::const_iterator i = lookup_filename (filename);
+
+ if (i == mIndex.end ())
+ {
+ std::ostringstream os;
+ os << "The file '" << filename << "' could not be found.";
+ throw std::runtime_error (os.str ());
+ }
+
+ return openConstrainedFileDataStream (i->second.c_str ());
+ }
+
+ StringVectorPtr list(bool recursive = true, bool dirs = false)
+ {
+ return find ("*", recursive, dirs);
+ }
+
+ FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
+ {
+ return findFileInfo ("*", recursive, dirs);
+ }
+
+ StringVectorPtr find(const String& pattern, bool recursive = true,
+ bool dirs = false)
+ {
+ std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
+ StringVectorPtr ptr = StringVectorPtr(new StringVector());
+ for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter)
+ {
+ if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
+ (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
+ ptr->push_back(iter->first);
+ }
+ return ptr;
+ }
+
+ bool exists(const String& filename)
+ {
+ return lookup_filename(filename) != mIndex.end ();
+ }
+
+ time_t getModifiedTime(const String&) { return 0; }
+
+ FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
+ bool dirs = false) const
+ {
+ std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
+ FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
+
+ index::const_iterator i = mIndex.find(normalizedPattern);
+ if(i != mIndex.end())
+ {
+ std::string::size_type pt = i->first.rfind('/');
+ if(pt == std::string::npos)
+ pt = 0;
+
+ FileInfo fi;
+ fi.archive = const_cast<DirArchive*>(this);
+ fi.path = i->first.substr(0, pt);
+ fi.filename = i->first.substr((i->first[pt]=='/') ? pt+1 : pt);
+ fi.compressedSize = fi.uncompressedSize = 0;
+
+ ptr->push_back(fi);
+ }
+ else
+ {
+ for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter)
+ {
+ if(Ogre::StringUtil::match(iter->first, normalizedPattern) ||
+ (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern)))
+ {
+ std::string::size_type pt = iter->first.rfind('/');
+ if(pt == std::string::npos)
+ pt = 0;
+
+ FileInfo fi;
+ fi.archive = const_cast<DirArchive*>(this);
+ fi.path = iter->first.substr(0, pt);
+ fi.filename = iter->first.substr((iter->first[pt]=='/') ? pt+1 : pt);
+ fi.compressedSize = fi.uncompressedSize = 0;
+
+ ptr->push_back(fi);
+ }
+ }
+ }
+
+ return ptr;
+ }
+};
+
+class BSAArchive : public Archive
+{
+ Bsa::BSAFile arc;
+
+ static const char *extractFilename(const Bsa::BSAFile::FileStruct &entry)
+ {
+ return entry.name;
+ }
+
+public:
+ BSAArchive(const String& name)
+ : Archive(name, "BSA")
+ { arc.open(name); }
+
+ bool isCaseSensitive() const { return false; }
+
+ // The archive is loaded in the constructor, and never unloaded.
+ void load() {}
+ void unload() {}
+
+ DataStreamPtr open(const String& filename, bool readonly = true) const
+ {
+ // Get a non-const reference to arc. This is a hack and it's all
+ // OGRE's fault. You should NOT expect an open() command not to
+ // have any side effects on the archive, and hence this function
+ // should not have been declared const in the first place.
+ Bsa::BSAFile *narc = const_cast<Bsa::BSAFile*>(&arc);
+
+ // Open the file
+ return narc->getFile(filename.c_str());
+ }
+
+ bool exists(const String& filename) {
+ return arc.exists(filename.c_str());
+ }
+
+ time_t getModifiedTime(const String&) { return 0; }
+
+ // This is never called as far as I can see.
+ StringVectorPtr list(bool recursive = true, bool dirs = false)
+ {
+ return find ("*", recursive, dirs);
+ }
+
+ // Also never called.
+ FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false)
+ {
+ return findFileInfo ("*", recursive, dirs);
+ }
+
+ StringVectorPtr find(const String& pattern, bool recursive = true,
+ bool dirs = false)
+ {
+ std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
+ const Bsa::BSAFile::FileList &filelist = arc.getList();
+ StringVectorPtr ptr = StringVectorPtr(new StringVector());
+ for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter)
+ {
+ std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
+ if(Ogre::StringUtil::match(ent, normalizedPattern) ||
+ (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
+ ptr->push_back(iter->name);
+ }
+ return ptr;
+ }
+
+ FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true,
+ bool dirs = false) const
+ {
+ std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end());
+ FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList());
+ const Bsa::BSAFile::FileList &filelist = arc.getList();
+
+ for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter)
+ {
+ std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name));
+ if(Ogre::StringUtil::match(ent, normalizedPattern) ||
+ (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern)))
+ {
+ std::string::size_type pt = ent.rfind('/');
+ if(pt == std::string::npos)
+ pt = 0;
+
+ FileInfo fi;
+ fi.archive = const_cast<BSAArchive*>(this);
+ fi.path = std::string(iter->name, pt);
+ fi.filename = std::string(iter->name + ((ent[pt]=='/') ? pt+1 : pt));
+ fi.compressedSize = fi.uncompressedSize = iter->fileSize;
+
+ ptr->push_back(fi);
+ }
+ }
+
+ return ptr;
+ }
+};
+
+// An archive factory for BSA archives
+class BSAArchiveFactory : public ArchiveFactory
+{
+public:
+ const String& getType() const
+ {
+ static String name = "BSA";
+ return name;
+ }
+
+ Archive *createInstance( const String& name )
+ {
+ return new BSAArchive(name);
+ }
+
+ virtual Archive* createInstance(const String& name, bool readOnly)
+ {
+ return new BSAArchive(name);
+ }
+
+ void destroyInstance( Archive* arch) { delete arch; }
+};
+
+class DirArchiveFactory : public ArchiveFactory
+{
+public:
+ const String& getType() const
+ {
+ static String name = "Dir";
+ return name;
+ }
+
+ Archive *createInstance( const String& name )
+ {
+ return new DirArchive(name);
+ }
+
+ virtual Archive* createInstance(const String& name, bool readOnly)
+ {
+ return new DirArchive(name);
+ }
+
+ void destroyInstance( Archive* arch) { delete arch; }
+};
+
+
+static bool init = false;
+static bool init2 = false;
+
+static void insertBSAFactory()
+{
+ if(!init)
+ {
+ ArchiveManager::getSingleton().addArchiveFactory( new BSAArchiveFactory );
+ init = true;
+ }
+}
+
+static void insertDirFactory()
+{
+ if(!init2)
+ {
+ ArchiveManager::getSingleton().addArchiveFactory( new DirArchiveFactory );
+ init2 = true;
+ }
+}
+
+
+namespace Bsa
+{
+
+// The function below is the only publicly exposed part of this file
+
+void addBSA(const std::string& name, const std::string& group)
+{
+ insertBSAFactory();
+ ResourceGroupManager::getSingleton().
+ addResourceLocation(name, "BSA", group, true);
+}
+
+void addDir(const std::string& name, const bool& fs, const std::string& group)
+{
+ fsstrict = fs;
+ insertDirFactory();
+
+ ResourceGroupManager::getSingleton().
+ addResourceLocation(name, "Dir", group, true);
+}
+
+}
diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp
new file mode 100644
index 0000000000..18f7377ff2
--- /dev/null
+++ b/components/bsa/bsa_archive.hpp
@@ -0,0 +1,42 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (cpp_bsaarchive.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#include <string>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include <algorithm>
+
+#ifndef BSA_BSA_ARCHIVE_H
+#define BSA_BSA_ARCHIVE_H
+
+namespace Bsa
+{
+
+/// Add the given BSA file as an input archive in the Ogre resource
+/// system.
+void addBSA(const std::string& file, const std::string& group="General");
+void addDir(const std::string& file, const bool& fs, const std::string& group="General");
+
+}
+
+#endif
diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp
new file mode 100644
index 0000000000..8db4fa8888
--- /dev/null
+++ b/components/bsa/bsa_file.cpp
@@ -0,0 +1,176 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (bsa_file.cpp) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#include "bsa_file.hpp"
+
+#include <stdexcept>
+
+#include "../files/constrainedfiledatastream.hpp"
+
+using namespace std;
+using namespace Bsa;
+
+
+/// Error handling
+void BSAFile::fail(const string &msg)
+{
+ throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + filename);
+}
+
+/// Read header information from the input source
+void BSAFile::readHeader()
+{
+ /*
+ * The layout of a BSA archive is as follows:
+ *
+ * - 12 bytes header, contains 3 ints:
+ * id number - equal to 0x100
+ * dirsize - size of the directory block (see below)
+ * numfiles - number of files
+ *
+ * ---------- start of directory block -----------
+ *
+ * - 8 bytes*numfiles, each record contains:
+ * fileSize
+ * offset into data buffer (see below)
+ *
+ * - 4 bytes*numfiles, each record is an offset into the following name buffer
+ *
+ * - name buffer, indexed by the previous table, each string is
+ * null-terminated. Size is (dirsize - 12*numfiles).
+ *
+ * ---------- end of directory block -------------
+ *
+ * - 8*filenum - hash table block, we currently ignore this
+ *
+ * ----------- start of data buffer --------------
+ *
+ * - The rest of the archive is file data, indexed by the
+ * offsets in the directory block. The offsets start at 0 at
+ * the beginning of this buffer.
+ *
+ */
+ assert(!isLoaded);
+
+ std::ifstream input(filename.c_str(), std::ios_base::binary);
+
+ // Total archive size
+ size_t fsize = 0;
+ if(input.seekg(0, std::ios_base::end))
+ {
+ fsize = input.tellg();
+ input.seekg(0);
+ }
+
+ if(fsize < 12)
+ fail("File too small to be a valid BSA archive");
+
+ // Get essential header numbers
+ size_t dirsize, filenum;
+ {
+ // First 12 bytes
+ uint32_t head[3];
+
+ input.read(reinterpret_cast<char*>(head), 12);
+
+ if(head[0] != 0x100)
+ fail("Unrecognized BSA header");
+
+ // Total number of bytes used in size/offset-table + filename
+ // sections.
+ dirsize = head[1];
+
+ // Number of files
+ filenum = head[2];
+ }
+
+ // Each file must take up at least 21 bytes of data in the bsa. So
+ // if files*21 overflows the file size then we are guaranteed that
+ // the archive is corrupt.
+ if((filenum*21 > fsize -12) || (dirsize+8*filenum > fsize -12) )
+ fail("Directory information larger than entire archive");
+
+ // Read the offset info into a temporary buffer
+ vector<uint32_t> offsets(3*filenum);
+ input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum);
+
+ // Read the string table
+ stringBuf.resize(dirsize-12*filenum);
+ input.read(&stringBuf[0], stringBuf.size());
+
+ // Check our position
+ assert(input.tellg() == std::streampos(12+dirsize));
+
+ // Calculate the offset of the data buffer. All file offsets are
+ // relative to this. 12 header bytes + directory + hash table
+ // (skipped)
+ size_t fileDataOffset = 12 + dirsize + 8*filenum;
+
+ // Set up the the FileStruct table
+ files.resize(filenum);
+ for(size_t i=0;i<filenum;i++)
+ {
+ FileStruct &fs = files[i];
+ fs.fileSize = offsets[i*2];
+ fs.offset = offsets[i*2+1] + fileDataOffset;
+ fs.name = &stringBuf[offsets[2*filenum+i]];
+
+ if(fs.offset + fs.fileSize > fsize)
+ fail("Archive contains offsets outside itself");
+
+ // Add the file name to the lookup
+ lookup[fs.name] = i;
+ }
+
+ isLoaded = true;
+}
+
+/// Get the index of a given file name, or -1 if not found
+int BSAFile::getIndex(const char *str) const
+{
+ Lookup::const_iterator it = lookup.find(str);
+ if(it == lookup.end())
+ return -1;
+
+ int res = it->second;
+ assert(res >= 0 && (size_t)res < files.size());
+ return res;
+}
+
+/// Open an archive file.
+void BSAFile::open(const string &file)
+{
+ filename = file;
+ readHeader();
+}
+
+Ogre::DataStreamPtr BSAFile::getFile(const char *file)
+{
+ assert(file);
+ int i = getIndex(file);
+ if(i == -1)
+ fail("File not found: " + string(file));
+
+ const FileStruct &fs = files[i];
+ return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize);
+}
diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp
new file mode 100644
index 0000000000..6f3ab3bceb
--- /dev/null
+++ b/components/bsa/bsa_file.hpp
@@ -0,0 +1,128 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (bsa_file.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef BSA_BSA_FILE_H
+#define BSA_BSA_FILE_H
+
+#include <libs/platform/stdint.h>
+#include <libs/platform/strings.h>
+#include <string>
+#include <vector>
+#include <map>
+
+#include <OgreDataStream.h>
+
+
+namespace Bsa
+{
+
+/**
+ This class is used to read "Bethesda Archive Files", or BSAs.
+ */
+class BSAFile
+{
+public:
+ /// Represents one file entry in the archive
+ struct FileStruct
+ {
+ // File size and offset in file. We store the offset from the
+ // beginning of the file, not the offset into the data buffer
+ // (which is what is stored in the archive.)
+ uint32_t fileSize, offset;
+
+ // Zero-terminated file name
+ const char *name;
+ };
+ typedef std::vector<FileStruct> FileList;
+
+private:
+ /// Table of files in this archive
+ FileList files;
+
+ /// Filename string buffer
+ std::vector<char> stringBuf;
+
+ /// True when an archive has been loaded
+ bool isLoaded;
+
+ /// Used for error messages
+ std::string filename;
+
+ /// Case insensitive string comparison
+ struct iltstr
+ {
+ bool operator()(const char *s1, const char *s2) const
+ { return strcasecmp(s1,s2) < 0; }
+ };
+
+ /** A map used for fast file name lookup. The value is the index into
+ the files[] vector above. The iltstr ensures that file name
+ checks are case insensitive.
+ */
+ typedef std::map<const char*, int, iltstr> Lookup;
+ Lookup lookup;
+
+ /// Error handling
+ void fail(const std::string &msg);
+
+ /// Read header information from the input source
+ void readHeader();
+
+ /// Get the index of a given file name, or -1 if not found
+ int getIndex(const char *str) const;
+
+public:
+ /* -----------------------------------
+ * BSA management methods
+ * -----------------------------------
+ */
+
+ BSAFile()
+ : isLoaded(false)
+ { }
+
+ /// Open an archive file.
+ void open(const std::string &file);
+
+ /* -----------------------------------
+ * Archive file routines
+ * -----------------------------------
+ */
+
+ /// Check if a file exists
+ bool exists(const char *file) const
+ { return getIndex(file) != -1; }
+
+ /** Open a file contained in the archive. Throws an exception if the
+ file doesn't exist.
+ */
+ Ogre::DataStreamPtr getFile(const char *file);
+
+ /// Get a list of all files
+ const FileList &getList() const
+ { return files; }
+};
+
+}
+
+#endif
diff --git a/components/bsa/tests/Makefile b/components/bsa/tests/Makefile
new file mode 100644
index 0000000000..73e20d7b3a
--- /dev/null
+++ b/components/bsa/tests/Makefile
@@ -0,0 +1,15 @@
+GCC=g++
+
+all: bsa_file_test ogre_archive_test
+
+I_OGRE=$(shell pkg-config --cflags OGRE)
+L_OGRE=$(shell pkg-config --libs OGRE)
+
+bsa_file_test: bsa_file_test.cpp ../bsa_file.cpp
+ $(GCC) $^ -o $@
+
+ogre_archive_test: ogre_archive_test.cpp ../bsa_file.cpp ../bsa_archive.cpp
+ $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE)
+
+clean:
+ rm *_test
diff --git a/components/bsa/tests/bsa_file_test.cpp b/components/bsa/tests/bsa_file_test.cpp
new file mode 100644
index 0000000000..07ee73d17e
--- /dev/null
+++ b/components/bsa/tests/bsa_file_test.cpp
@@ -0,0 +1,44 @@
+#include "../bsa_file.hpp"
+
+/*
+ Test of the BSAFile class
+
+ This test requires that data/Morrowind.bsa exists in the root
+ directory of OpenMW.
+
+ */
+
+#include <iostream>
+
+using namespace std;
+using namespace Bsa;
+
+BSAFile bsa;
+
+void find(const char* file)
+{
+ cout << "Does file '" << file << "' exist?\n ";
+ if(bsa.exists(file))
+ {
+ cout << "Yes.\n ";
+ cout << bsa.getFile(file)->size() << " bytes\n";
+ }
+ else cout << "No.\n";
+}
+
+int main()
+{
+ cout << "Reading Morrowind.bsa\n";
+ bsa.open("../../data/Morrowind.bsa");
+
+ const BSAFile::FileList &files = bsa.getList();
+
+ cout << "First 10 files in archive:\n";
+ for(int i=0; i<10; i++)
+ cout << " " << files[i].name
+ << " (" << files[i].fileSize << " bytes @"
+ << files[i].offset << ")\n";
+
+ find("meshes\\r\\xnetch_betty.nif");
+ find("humdrum");
+}
diff --git a/components/bsa/tests/ogre_archive_test.cpp b/components/bsa/tests/ogre_archive_test.cpp
new file mode 100644
index 0000000000..9cd57240f8
--- /dev/null
+++ b/components/bsa/tests/ogre_archive_test.cpp
@@ -0,0 +1,34 @@
+#include <Ogre.h>
+#include <iostream>
+
+// This is a test of the BSA archive handler for OGRE.
+
+#include "../bsa_archive.hpp"
+
+using namespace Ogre;
+using namespace std;
+
+int main()
+{
+ // Disable Ogre logging
+ new LogManager;
+ Log *log = LogManager::getSingleton().createLog("");
+ log->setDebugOutputEnabled(false);
+
+ // Set up Root
+ Root *root = new Root("","","");
+
+ // Add the BSA
+ Bsa::addBSA("../../data/Morrowind.bsa");
+
+ // Pick a sample file
+ String tex = "textures\\tx_natural_cavern_wall13.dds";
+ cout << "Opening file: " << tex << endl;
+
+ // Get it from the resource system
+ DataStreamPtr data = ResourceGroupManager::getSingleton().openResource(tex, "General");
+
+ cout << "Size: " << data->size() << endl;
+
+ return 0;
+}
diff --git a/components/bsa/tests/output/bsa_file_test.out b/components/bsa/tests/output/bsa_file_test.out
new file mode 100644
index 0000000000..0d8e76cdd8
--- /dev/null
+++ b/components/bsa/tests/output/bsa_file_test.out
@@ -0,0 +1,17 @@
+Reading Morrowind.bsa
+First 10 files in archive:
+ meshes\m\probe_journeyman_01.nif (6276 bytes @126646052)
+ textures\menu_rightbuttonup_top.dds (256 bytes @218530052)
+ textures\menu_rightbuttonup_right.dds (256 bytes @218529796)
+ textures\menu_rightbuttonup_left.dds (256 bytes @218529540)
+ textures\menu_rightbuttondown_top.dds (256 bytes @218528196)
+ meshes\b\b_n_redguard_f_skins.nif (41766 bytes @17809778)
+ meshes\b\b_n_redguard_m_skins.nif (41950 bytes @18103107)
+ meshes\b\b_n_redguard_f_wrist.nif (2355 bytes @17858132)
+ meshes\b\b_n_redguard_m_foot.nif (4141 bytes @17862081)
+ meshes\b\b_n_redguard_m_knee.nif (2085 bytes @18098101)
+Does file 'meshes\r\xnetch_betty.nif' exist?
+ Yes.
+ 53714 bytes
+Does file 'humdrum' exist?
+ No.
diff --git a/components/bsa/tests/output/ogre_archive_test.out b/components/bsa/tests/output/ogre_archive_test.out
new file mode 100644
index 0000000000..748e4b1e7f
--- /dev/null
+++ b/components/bsa/tests/output/ogre_archive_test.out
@@ -0,0 +1,2 @@
+Opening file: textures\tx_natural_cavern_wall13.dds
+Size: 43808
diff --git a/components/bsa/tests/test.sh b/components/bsa/tests/test.sh
new file mode 100755
index 0000000000..2d07708adc
--- /dev/null
+++ b/components/bsa/tests/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+make || exit
+
+mkdir -p output
+
+PROGS=*_test
+
+for a in $PROGS; do
+ if [ -f "output/$a.out" ]; then
+ echo "Running $a:"
+ ./$a | diff output/$a.out -
+ else
+ echo "Creating $a.out"
+ ./$a > "output/$a.out"
+ git add "output/$a.out"
+ fi
+done
diff --git a/components/compiler/context.hpp b/components/compiler/context.hpp
new file mode 100644
index 0000000000..1b02613c59
--- /dev/null
+++ b/components/compiler/context.hpp
@@ -0,0 +1,44 @@
+#ifndef COMPILER_CONTEXT_H_INCLUDED
+#define COMPILER_CONTEXT_H_INCLUDED
+
+#include <string>
+
+namespace Compiler
+{
+ class Extensions;
+
+ class Context
+ {
+ const Extensions *mExtensions;
+
+ public:
+
+ Context() : mExtensions (0) {}
+
+ virtual ~Context() {}
+
+ virtual bool canDeclareLocals() const = 0;
+ ///< Is the compiler allowed to declare local variables?
+
+ void setExtensions (const Extensions *extensions = 0)
+ {
+ mExtensions = extensions;
+ }
+
+ const Extensions *getExtensions() const
+ {
+ return mExtensions;
+ }
+
+ virtual char getGlobalType (const std::string& name) const = 0;
+ ///< 'l: long, 's': short, 'f': float, ' ': does not exist.
+
+ virtual char getMemberType (const std::string& name, const std::string& id) const = 0;
+ ///< 'l: long, 's': short, 'f': float, ' ': does not exist.
+
+ virtual bool isId (const std::string& name) const = 0;
+ ///< Does \a name match an ID, that can be referenced?
+ };
+}
+
+#endif
diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp
new file mode 100644
index 0000000000..5d74ee9d42
--- /dev/null
+++ b/components/compiler/controlparser.cpp
@@ -0,0 +1,260 @@
+
+#include "controlparser.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <stdexcept>
+
+#include "scanner.hpp"
+#include "generator.hpp"
+
+namespace Compiler
+{
+ bool ControlParser::parseIfBody (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (keyword==Scanner::K_endif || keyword==Scanner::K_elseif ||
+ keyword==Scanner::K_else)
+ {
+ std::pair<Codes, Codes> entry;
+
+ if (mState!=IfElseBodyState)
+ mExprParser.append (entry.first);
+
+ std::copy (mCodeBlock.begin(), mCodeBlock.end(),
+ std::back_inserter (entry.second));
+
+ mIfCode.push_back (entry);
+
+ mCodeBlock.clear();
+
+ if (keyword==Scanner::K_endif)
+ {
+ // store code for if-cascade
+ Codes codes;
+
+ for (IfCodes::reverse_iterator iter (mIfCode.rbegin());
+ iter!=mIfCode.rend(); ++iter)
+ {
+ Codes block;
+
+ if (iter!=mIfCode.rbegin())
+ Generator::jump (iter->second, codes.size()+1);
+
+ if (!iter->first.empty())
+ {
+ // if or elseif
+ std::copy (iter->first.begin(), iter->first.end(),
+ std::back_inserter (block));
+ Generator::jumpOnZero (block, iter->second.size()+1);
+ }
+
+ std::copy (iter->second.begin(), iter->second.end(),
+ std::back_inserter (block));
+
+ std::swap (codes, block);
+
+ std::copy (block.begin(), block.end(), std::back_inserter (codes));
+ }
+
+ std::copy (codes.begin(), codes.end(), std::back_inserter (mCode));
+
+ mIfCode.clear();
+ mState = IfEndifState;
+ }
+ else if (keyword==Scanner::K_elseif)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ mState = IfElseifEndState;
+ }
+ else if (keyword==Scanner::K_else)
+ {
+ mState = IfElseEndState;
+ }
+
+ return true;
+ }
+ else if (keyword==Scanner::K_if || keyword==Scanner::K_while)
+ {
+ // nested
+ ControlParser parser (getErrorHandler(), getContext(), mLocals, mLiterals);
+
+ if (parser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (parser);
+
+ parser.appendCode (mCodeBlock);
+
+ return true;
+ }
+ else
+ {
+ mLineParser.reset();
+ if (mLineParser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (mLineParser);
+
+ return true;
+ }
+ }
+
+ bool ControlParser::parseWhileBody (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (keyword==Scanner::K_endwhile)
+ {
+ Codes loop;
+
+ Codes expr;
+ mExprParser.append (expr);
+
+ Generator::jump (loop, -static_cast<int> (mCodeBlock.size()-expr.size()));
+
+ std::copy (expr.begin(), expr.end(), std::back_inserter (mCode));
+
+ Codes skip;
+
+ Generator::jumpOnZero (skip, mCodeBlock.size()+loop.size()+1);
+
+ std::copy (skip.begin(), skip.end(), std::back_inserter (mCode));
+
+ std::copy (mCodeBlock.begin(), mCodeBlock.end(), std::back_inserter (mCode));
+
+ Codes loop2;
+
+ Generator::jump (loop2, -static_cast<int> (mCodeBlock.size()-expr.size()-skip.size()));
+
+ if (loop.size()!=loop2.size())
+ throw std::logic_error (
+ "internal compiler error: failed to generate a while loop");
+
+ std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode));
+
+ mState = WhileEndwhileState;
+ return true;
+ }
+ else if (keyword==Scanner::K_if || keyword==Scanner::K_while)
+ {
+ // nested
+ ControlParser parser (getErrorHandler(), getContext(), mLocals, mLiterals);
+
+ if (parser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (parser);
+
+ parser.appendCode (mCodeBlock);
+
+ return true;
+ }
+ else
+ {
+ mLineParser.reset();
+ if (mLineParser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (mLineParser);
+
+ return true;
+ }
+ }
+
+ ControlParser::ControlParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals)
+ : Parser (errorHandler, context), mLocals (locals), mLiterals (literals),
+ mLineParser (errorHandler, context, locals, literals, mCodeBlock),
+ mExprParser (errorHandler, context, locals, literals),
+ mState (StartState)
+ {
+
+ }
+
+ void ControlParser::appendCode (std::vector<Interpreter::Type_Code>& code) const
+ {
+ std::copy (mCode.begin(), mCode.end(), std::back_inserter (code));
+ }
+
+ bool ControlParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState ||
+ mState==WhileBodyState)
+ {
+ scanner.putbackName (name, loc);
+ mLineParser.reset();
+ scanner.scan (mLineParser);
+ return true;
+ }
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ bool ControlParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mState==StartState)
+ {
+ if (keyword==Scanner::K_if)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ mState = IfEndState;
+ return true;
+ }
+ else if (keyword==Scanner::K_while)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ mState = WhileEndState;
+ return true;
+ }
+ }
+ else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState)
+ {
+ if (parseIfBody (keyword, loc, scanner))
+ return true;
+ }
+ else if (mState==WhileBodyState)
+ {
+ if ( parseWhileBody (keyword, loc, scanner))
+ return true;
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+ }
+
+ bool ControlParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_newline)
+ {
+ switch (mState)
+ {
+ case IfEndState: mState = IfBodyState; return true;
+ case IfElseifEndState: mState = IfElseifBodyState; return true;
+ case IfElseEndState: mState = IfElseBodyState; return true;
+
+ case WhileEndState: mState = WhileBodyState; return true;
+
+ case IfBodyState:
+ case IfElseifBodyState:
+ case IfElseBodyState:
+ case WhileBodyState:
+
+ return true; // empty line
+
+ case IfEndifState:
+ case WhileEndwhileState:
+
+ return false;
+
+ default: ;
+ }
+
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ void ControlParser::reset()
+ {
+ mCode.clear();
+ mCodeBlock.clear();
+ mIfCode.clear();
+ mState = StartState;
+ Parser::reset();
+ }
+}
diff --git a/components/compiler/controlparser.hpp b/components/compiler/controlparser.hpp
new file mode 100644
index 0000000000..50fd2d1f9e
--- /dev/null
+++ b/components/compiler/controlparser.hpp
@@ -0,0 +1,74 @@
+#ifndef COMPILER_CONTROLPARSER_H_INCLUDED
+#define COMPILER_CONTROLPARSER_H_INCLUDED
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+#include "parser.hpp"
+#include "exprparser.hpp"
+#include "lineparser.hpp"
+
+namespace Compiler
+{
+ class Locals;
+ class Literals;
+
+ // Control structure parser
+
+ class ControlParser : public Parser
+ {
+ enum State
+ {
+ StartState,
+ IfEndState, IfBodyState,
+ IfElseifEndState, IfElseifBodyState,
+ IfElseEndState, IfElseBodyState,
+ IfEndifState,
+ WhileEndState, WhileBodyState,
+ WhileEndwhileState
+ };
+
+ typedef std::vector<Interpreter::Type_Code> Codes;
+ typedef std::vector<std::pair<Codes, Codes> > IfCodes;
+
+ Locals& mLocals;
+ Literals& mLiterals;
+ Codes mCode;
+ Codes mCodeBlock;
+ IfCodes mIfCode; // condition, body
+ LineParser mLineParser;
+ ExprParser mExprParser;
+ State mState;
+
+ bool parseIfBody (int keyword, const TokenLoc& loc, Scanner& scanner);
+
+ bool parseWhileBody (int keyword, const TokenLoc& loc, Scanner& scanner);
+
+ public:
+
+ ControlParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals);
+
+ void appendCode (std::vector<Interpreter::Type_Code>& code) const;
+ ///< store generated code in \æ code.
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ void reset();
+ ///< Reset parser to clean state.
+ };
+}
+
+#endif
diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp
new file mode 100644
index 0000000000..ee13c837d1
--- /dev/null
+++ b/components/compiler/errorhandler.cpp
@@ -0,0 +1,65 @@
+
+#include "errorhandler.hpp"
+
+namespace Compiler
+{
+ // constructor
+
+ ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0) {}
+
+ // destructor
+
+ ErrorHandler::~ErrorHandler() {}
+
+ // Was compiling successful?
+
+ bool ErrorHandler::isGood() const
+ {
+ return mErrors==0;
+ }
+
+ // Return number of errors
+
+ int ErrorHandler::countErrors() const
+ {
+ return mErrors;
+ }
+
+ // Return number of warnings
+
+ int ErrorHandler::countWarnings() const
+ {
+ return mWarnings;
+ }
+
+ // Generate a warning message.
+
+ void ErrorHandler::warning (const std::string& message, const TokenLoc& loc)
+ {
+ ++mWarnings;
+ report (message, loc, WarningMessage);
+ }
+
+ // Generate an error message.
+
+ void ErrorHandler::error (const std::string& message, const TokenLoc& loc)
+ {
+ ++mErrors;
+ report (message, loc, ErrorMessage);
+ }
+
+ // Generate an error message for an unexpected EOF.
+
+ void ErrorHandler::endOfFile()
+ {
+ ++mErrors;
+ report ("unexpected end of file", ErrorMessage);
+ }
+
+ // Remove all previous error/warning events
+
+ void ErrorHandler::reset()
+ {
+ mErrors = mWarnings = 0;
+ }
+}
diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp
new file mode 100644
index 0000000000..256065854a
--- /dev/null
+++ b/components/compiler/errorhandler.hpp
@@ -0,0 +1,68 @@
+
+#ifndef COMPILER_ERRORHANDLER_H_INCLUDED
+#define COMPILER_ERRORHANDLER_H_INCLUDED
+
+#include <string>
+
+namespace Compiler
+{
+ struct TokenLoc;
+
+ /// \brief Error handling
+ ///
+ /// This class collects errors and provides an interface for reporting them to the user.
+
+ class ErrorHandler
+ {
+ int mWarnings;
+ int mErrors;
+
+ protected:
+
+ enum Type
+ {
+ WarningMessage, ErrorMessage
+ };
+
+ private:
+
+ // mutators
+
+ virtual void report (const std::string& message, const TokenLoc& loc, Type type) = 0;
+ ///< Report error to the user.
+
+ virtual void report (const std::string& message, Type type) = 0;
+ ///< Report a file related error
+
+ public:
+
+ ErrorHandler();
+ ///< constructor
+
+ virtual ~ErrorHandler();
+ ///< destructor
+
+ bool isGood() const;
+ ///< Was compiling successful?
+
+ int countErrors() const;
+ ///< Return number of errors
+
+ int countWarnings() const;
+ ///< Return number of warnings
+
+ void warning (const std::string& message, const TokenLoc& loc);
+ ///< Generate a warning message.
+
+ void error (const std::string& message, const TokenLoc& loc);
+ ///< Generate an error message.
+
+ void endOfFile();
+ ///< Generate an error message for an unexpected EOF.
+
+ virtual void reset();
+ ///< Remove all previous error/warning events
+ };
+}
+
+#endif
diff --git a/components/compiler/exception.hpp b/components/compiler/exception.hpp
new file mode 100644
index 0000000000..a286631158
--- /dev/null
+++ b/components/compiler/exception.hpp
@@ -0,0 +1,39 @@
+#ifndef COMPILER_EXCEPTION_H_INCLUDED
+#define COMPILER_EXCEPTION_H_INCLUDED
+
+#include <exception>
+
+namespace Compiler
+{
+ /// \brief Exception: Error while parsing the source
+
+ class SourceException : public std::exception
+ {
+ public:
+
+ virtual const char *what() const throw() { return "compile error";}
+ ///< Return error message
+ };
+
+ /// \brief Exception: File error
+
+ class FileException : public SourceException
+ {
+ public:
+
+ virtual const char *what() const throw() { return "can't read file"; }
+ ///< Return error message
+ };
+
+ /// \brief Exception: EOF condition encountered
+
+ class EOFException : public SourceException
+ {
+ public:
+
+ virtual const char *what() const throw() { return "end of file"; }
+ ///< Return error message
+ };
+}
+
+#endif
diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp
new file mode 100644
index 0000000000..94240c5eb9
--- /dev/null
+++ b/components/compiler/exprparser.cpp
@@ -0,0 +1,756 @@
+
+#include "exprparser.hpp"
+
+#include <stdexcept>
+#include <cassert>
+#include <algorithm>
+#include <stack>
+#include <iterator>
+
+#include "generator.hpp"
+#include "scanner.hpp"
+#include "errorhandler.hpp"
+#include "locals.hpp"
+#include "stringparser.hpp"
+#include "extensions.hpp"
+#include "context.hpp"
+#include <components/misc/stringops.hpp>
+
+namespace Compiler
+{
+ int ExprParser::getPriority (char op) const
+ {
+ switch (op)
+ {
+ case '(':
+
+ return 0;
+
+ case 'e': // ==
+ case 'n': // !=
+ case 'l': // <
+ case 'L': // <=
+ case 'g': // <
+ case 'G': // >=
+
+ return 1;
+
+ case '+':
+ case '-':
+
+ return 2;
+
+ case '*':
+ case '/':
+
+ return 3;
+
+ case 'm':
+
+ return 4;
+ }
+
+ return 0;
+ }
+
+ char ExprParser::getOperandType (int Index) const
+ {
+ assert (!mOperands.empty());
+ assert (Index>=0);
+ assert (Index<static_cast<int> (mOperands.size()));
+ return mOperands[mOperands.size()-1-Index];
+ }
+
+ char ExprParser::getOperator() const
+ {
+ assert (!mOperators.empty());
+ return mOperators[mOperators.size()-1];
+ }
+
+ bool ExprParser::isOpen() const
+ {
+ return std::find (mOperators.begin(), mOperators.end(), '(')!=mOperators.end();
+ }
+
+ void ExprParser::popOperator()
+ {
+ assert (!mOperators.empty());
+ mOperators.resize (mOperators.size()-1);
+ }
+
+ void ExprParser::popOperand()
+ {
+ assert (!mOperands.empty());
+ mOperands.resize (mOperands.size()-1);
+ }
+
+ void ExprParser::replaceBinaryOperands()
+ {
+ char t1 = getOperandType (1);
+ char t2 = getOperandType();
+
+ popOperand();
+ popOperand();
+
+ if (t1==t2)
+ mOperands.push_back (t1);
+ else if (t1=='f' || t2=='f')
+ mOperands.push_back ('f');
+ else
+ std::logic_error ("failed to determine result operand type");
+ }
+
+ void ExprParser::pop()
+ {
+ char op = getOperator();
+
+ switch (op)
+ {
+ case 'm':
+
+ Generator::negate (mCode, getOperandType());
+ popOperator();
+ break;
+
+ case '+':
+
+ Generator::add (mCode, getOperandType (1), getOperandType());
+ popOperator();
+ replaceBinaryOperands();
+ break;
+
+ case '-':
+
+ Generator::sub (mCode, getOperandType (1), getOperandType());
+ popOperator();
+ replaceBinaryOperands();
+ break;
+
+ case '*':
+
+ Generator::mul (mCode, getOperandType (1), getOperandType());
+ popOperator();
+ replaceBinaryOperands();
+ break;
+
+ case '/':
+
+ Generator::div (mCode, getOperandType (1), getOperandType());
+ popOperator();
+ replaceBinaryOperands();
+ break;
+
+ case 'e':
+ case 'n':
+ case 'l':
+ case 'L':
+ case 'g':
+ case 'G':
+
+ Generator::compare (mCode, op, getOperandType (1), getOperandType());
+ popOperator();
+ popOperand();
+ popOperand();
+ mOperands.push_back ('l');
+ break;
+
+ default:
+
+ throw std::logic_error ("unknown operator");
+ }
+ }
+
+ void ExprParser::pushIntegerLiteral (int value)
+ {
+ mNextOperand = false;
+ mOperands.push_back ('l');
+ Generator::pushInt (mCode, mLiterals, value);
+ }
+
+ void ExprParser::pushFloatLiteral (float value)
+ {
+ mNextOperand = false;
+ mOperands.push_back ('f');
+ Generator::pushFloat (mCode, mLiterals, value);
+ }
+
+ void ExprParser::pushBinaryOperator (char c)
+ {
+ while (!mOperators.empty() && getPriority (getOperator())>=getPriority (c))
+ pop();
+
+ mOperators.push_back (c);
+ mNextOperand = true;
+ }
+
+ void ExprParser::close()
+ {
+ while (getOperator()!='(')
+ pop();
+
+ popOperator();
+ }
+
+ int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner)
+ {
+ return parseArguments (arguments, scanner, mCode);
+ }
+
+ bool ExprParser::handleMemberAccess (const std::string& name)
+ {
+ mMemberOp = false;
+
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+ std::string id = Misc::StringUtils::lowerCase (mExplicit);
+
+ char type = getContext().getMemberType (name2, id);
+
+ if (type!=' ')
+ {
+ Generator::fetchMember (mCode, mLiterals, type, name2, id);
+ mNextOperand = false;
+ mExplicit.clear();
+ mOperands.push_back (type=='f' ? 'f' : 'l');
+ return true;
+ }
+
+ return false;
+ }
+
+ ExprParser::ExprParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals, bool argument)
+ : Parser (errorHandler, context), mLocals (locals), mLiterals (literals),
+ mNextOperand (true), mFirst (true), mArgument (argument), mRefOp (false), mMemberOp (false)
+ {}
+
+ bool ExprParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!mExplicit.empty())
+ return Parser::parseInt (value, loc, scanner);
+
+ mFirst = false;
+
+ if (mNextOperand)
+ {
+ start();
+
+ pushIntegerLiteral (value);
+ mTokenLoc = loc;
+ return true;
+ }
+ else
+ {
+ // no comma was used between arguments
+ scanner.putbackInt (value, loc);
+ return false;
+ }
+ }
+
+ bool ExprParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!mExplicit.empty())
+ return Parser::parseFloat (value, loc, scanner);
+
+ mFirst = false;
+
+ if (mNextOperand)
+ {
+ start();
+
+ pushFloatLiteral (value);
+ mTokenLoc = loc;
+ return true;
+ }
+ else
+ {
+ // no comma was used between arguments
+ scanner.putbackFloat (value, loc);
+ return false;
+ }
+ }
+
+ bool ExprParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ if (!mExplicit.empty())
+ {
+ if (mMemberOp && handleMemberAccess (name))
+ return true;
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ mFirst = false;
+
+ if (mNextOperand)
+ {
+ start();
+
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+
+ char type = mLocals.getType (name2);
+
+ if (type!=' ')
+ {
+ Generator::fetchLocal (mCode, type, mLocals.getIndex (name2));
+ mNextOperand = false;
+ mOperands.push_back (type=='f' ? 'f' : 'l');
+ return true;
+ }
+
+ type = getContext().getGlobalType (name2);
+
+ if (type!=' ')
+ {
+ Generator::fetchGlobal (mCode, mLiterals, type, name2);
+ mNextOperand = false;
+ mOperands.push_back (type=='f' ? 'f' : 'l');
+ return true;
+ }
+
+ if (mExplicit.empty() && getContext().isId (name2))
+ {
+ mExplicit = name2;
+ return true;
+ }
+ }
+ else
+ {
+ // no comma was used between arguments
+ scanner.putbackName (name, loc);
+ return false;
+ }
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ bool ExprParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ mFirst = false;
+
+ if (!mExplicit.empty())
+ {
+ if (mRefOp && mNextOperand)
+ {
+ if (keyword==Scanner::K_getdisabled)
+ {
+ start();
+
+ mTokenLoc = loc;
+
+ Generator::getDisabled (mCode, mLiterals, mExplicit);
+ mOperands.push_back ('l');
+ mExplicit.clear();
+ mRefOp = false;
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_getdistance)
+ {
+ start();
+
+ mTokenLoc = loc;
+ parseArguments ("c", scanner);
+
+ Generator::getDistance (mCode, mLiterals, mExplicit);
+ mOperands.push_back ('f');
+ mExplicit.clear();
+ mRefOp = false;
+
+ mNextOperand = false;
+ return true;
+ }
+
+ // check for custom extensions
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ char returnType;
+ std::string argumentType;
+
+ if (extensions->isFunction (keyword, returnType, argumentType, true))
+ {
+ start();
+
+ mTokenLoc = loc;
+ int optionals = parseArguments (argumentType, scanner);
+
+ extensions->generateFunctionCode (keyword, mCode, mLiterals, mExplicit,
+ optionals);
+ mOperands.push_back (returnType);
+ mExplicit.clear();
+ mRefOp = false;
+
+ mNextOperand = false;
+ return true;
+ }
+ }
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+ }
+
+ if (mNextOperand)
+ {
+ if (keyword==Scanner::K_getsquareroot)
+ {
+ start();
+
+ mTokenLoc = loc;
+ parseArguments ("f", scanner);
+
+ Generator::squareRoot (mCode);
+ mOperands.push_back ('f');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_menumode)
+ {
+ start();
+
+ mTokenLoc = loc;
+
+ Generator::menuMode (mCode);
+ mOperands.push_back ('l');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_random)
+ {
+ start();
+
+ mTokenLoc = loc;
+ parseArguments ("l", scanner);
+
+ Generator::random (mCode);
+ mOperands.push_back ('l');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_scriptrunning)
+ {
+ start();
+
+ mTokenLoc = loc;
+ parseArguments ("c", scanner);
+
+ Generator::scriptRunning (mCode);
+ mOperands.push_back ('l');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_getdistance)
+ {
+ start();
+
+ mTokenLoc = loc;
+ parseArguments ("c", scanner);
+
+ Generator::getDistance (mCode, mLiterals, "");
+ mOperands.push_back ('f');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_getsecondspassed)
+ {
+ start();
+
+ mTokenLoc = loc;
+
+ Generator::getSecondsPassed (mCode);
+ mOperands.push_back ('f');
+
+ mNextOperand = false;
+ return true;
+ }
+ else if (keyword==Scanner::K_getdisabled)
+ {
+ start();
+
+ mTokenLoc = loc;
+
+ Generator::getDisabled (mCode, mLiterals, "");
+ mOperands.push_back ('l');
+
+ mNextOperand = false;
+ return true;
+ }
+ else
+ {
+ // check for custom extensions
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ start();
+
+ char returnType;
+ std::string argumentType;
+
+ if (extensions->isFunction (keyword, returnType, argumentType, false))
+ {
+ mTokenLoc = loc;
+ int optionals = parseArguments (argumentType, scanner);
+
+ extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals);
+ mOperands.push_back (returnType);
+
+ mNextOperand = false;
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ // no comma was used between arguments
+ scanner.putbackKeyword (keyword, loc);
+ return false;
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+ }
+
+ bool ExprParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!mExplicit.empty())
+ {
+ if (!mRefOp && code==Scanner::S_ref)
+ {
+ mRefOp = true;
+ return true;
+ }
+
+ if (!mMemberOp && code==Scanner::S_member)
+ {
+ mMemberOp = true;
+ return true;
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ if (code==Scanner::S_comma)
+ {
+ mTokenLoc = loc;
+
+ if (mFirst)
+ {
+ // leading comma
+ mFirst = false;
+ return true;
+ }
+
+ // end marker
+ scanner.putbackSpecial (code, loc);
+ return false;
+ }
+
+ mFirst = false;
+
+ if (code==Scanner::S_newline)
+ {
+ // end marker
+ mTokenLoc = loc;
+ scanner.putbackSpecial (code, loc);
+ return false;
+ }
+
+ if (code==Scanner::S_minus && mNextOperand)
+ {
+ // unary
+ mOperators.push_back ('m');
+ mTokenLoc = loc;
+ return true;
+ }
+
+ if (code==Scanner::S_open)
+ {
+ if (mNextOperand)
+ {
+ mOperators.push_back ('(');
+ mTokenLoc = loc;
+ return true;
+ }
+ else
+ {
+ // no comma was used between arguments
+ scanner.putbackKeyword (code, loc);
+ return false;
+ }
+ }
+
+ if (code==Scanner::S_close && !mNextOperand)
+ {
+ if (isOpen())
+ {
+ close();
+ return true;
+ }
+
+ mTokenLoc = loc;
+ scanner.putbackSpecial (code, loc);
+ return false;
+ }
+
+ if (!mNextOperand)
+ {
+ mTokenLoc = loc;
+ char c = 0; // comparison
+
+ switch (code)
+ {
+ case Scanner::S_plus: c = '+'; break;
+ case Scanner::S_minus: c = '-'; break;
+ case Scanner::S_mult: pushBinaryOperator ('*'); return true;
+ case Scanner::S_div: pushBinaryOperator ('/'); return true;
+ case Scanner::S_cmpEQ: c = 'e'; break;
+ case Scanner::S_cmpNE: c = 'n'; break;
+ case Scanner::S_cmpLT: c = 'l'; break;
+ case Scanner::S_cmpLE: c = 'L'; break;
+ case Scanner::S_cmpGT: c = 'g'; break;
+ case Scanner::S_cmpGE: c = 'G'; break;
+ }
+
+ if (c)
+ {
+ if (mArgument && !isOpen())
+ {
+ // expression ends here
+ // Thank you Morrowind for this rotten syntax :(
+ scanner.putbackSpecial (code, loc);
+ return false;
+ }
+
+ pushBinaryOperator (c);
+ return true;
+ }
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ void ExprParser::reset()
+ {
+ mOperands.clear();
+ mOperators.clear();
+ mNextOperand = true;
+ mCode.clear();
+ mFirst = true;
+ mExplicit.clear();
+ mRefOp = false;
+ mMemberOp = false;
+ Parser::reset();
+ }
+
+ char ExprParser::append (std::vector<Interpreter::Type_Code>& code)
+ {
+ if (mOperands.empty() && mOperators.empty())
+ {
+ getErrorHandler().error ("missing expression", mTokenLoc);
+ return 'l';
+ }
+
+ if (mNextOperand || mOperands.empty())
+ {
+ getErrorHandler().error ("syntax error in expression", mTokenLoc);
+ return 'l';
+ }
+
+ while (!mOperators.empty())
+ pop();
+
+ std::copy (mCode.begin(), mCode.end(), std::back_inserter (code));
+
+ assert (mOperands.size()==1);
+ return mOperands[0];
+ }
+
+ int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner,
+ std::vector<Interpreter::Type_Code>& code, bool invert)
+ {
+ bool optional = false;
+ int optionalCount = 0;
+
+ ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true);
+ StringParser stringParser (getErrorHandler(), getContext(), mLiterals);
+
+ std::stack<std::vector<Interpreter::Type_Code> > stack;
+
+ for (std::string::const_iterator iter (arguments.begin()); iter!=arguments.end();
+ ++iter)
+ {
+ if (*iter=='/')
+ {
+ optional = true;
+ }
+ else if (*iter=='S' || *iter=='c')
+ {
+ stringParser.reset();
+
+ if (optional)
+ stringParser.setOptional (true);
+
+ if (*iter=='c') stringParser.smashCase();
+ scanner.scan (stringParser);
+
+ if (optional && stringParser.isEmpty())
+ break;
+
+ if (invert)
+ {
+ std::vector<Interpreter::Type_Code> tmp;
+ stringParser.append (tmp);
+
+ stack.push (tmp);
+ }
+ else
+ stringParser.append (code);
+
+ if (optional)
+ ++optionalCount;
+ }
+ else
+ {
+ parser.reset();
+
+ if (optional)
+ parser.setOptional (true);
+
+ scanner.scan (parser);
+
+ if (optional && parser.isEmpty())
+ break;
+
+ std::vector<Interpreter::Type_Code> tmp;
+
+ char type = parser.append (tmp);
+
+ if (type!=*iter)
+ Generator::convert (tmp, type, *iter);
+
+ if (invert)
+ stack.push (tmp);
+ else
+ std::copy (tmp.begin(), tmp.end(), std::back_inserter (code));
+
+ if (optional)
+ ++optionalCount;
+ }
+ }
+
+ while (!stack.empty())
+ {
+ std::vector<Interpreter::Type_Code>& tmp = stack.top();
+
+ std::copy (tmp.begin(), tmp.end(), std::back_inserter (code));
+
+ stack.pop();
+ }
+
+ return optionalCount;
+ }
+}
diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp
new file mode 100644
index 0000000000..8ce5409d23
--- /dev/null
+++ b/components/compiler/exprparser.hpp
@@ -0,0 +1,109 @@
+#ifndef COMPILER_EXPRPARSER_H_INCLUDED
+#define COMPILER_EXPRPARSER_H_INCLUDED
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+#include "parser.hpp"
+#include "tokenloc.hpp"
+
+namespace Compiler
+{
+ class Locals;
+ class Literals;
+
+ class ExprParser : public Parser
+ {
+ Locals& mLocals;
+ Literals& mLiterals;
+ std::vector<char> mOperands;
+ std::vector<char> mOperators;
+ bool mNextOperand;
+ TokenLoc mTokenLoc;
+ std::vector<Interpreter::Type_Code> mCode;
+ bool mFirst;
+ bool mArgument;
+ std::string mExplicit;
+ bool mRefOp;
+ bool mMemberOp;
+
+ int getPriority (char op) const;
+
+ char getOperandType (int Index = 0) const;
+
+ char getOperator() const;
+
+ bool isOpen() const;
+
+ void popOperator();
+
+ void popOperand();
+
+ void replaceBinaryOperands();
+
+ void pop();
+
+ void pushIntegerLiteral (int value);
+
+ void pushFloatLiteral (float value);
+
+ void pushBinaryOperator (char c);
+
+ void close();
+
+ int parseArguments (const std::string& arguments, Scanner& scanner);
+
+ bool handleMemberAccess (const std::string& name);
+
+ public:
+
+ ExprParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals, bool argument = false);
+ ///< constructor
+ /// \param argument Parser is used to parse function- or instruction-
+ /// arguments (this influences the precedence rules).
+
+ char getType() const;
+ ///< Return type of parsed expression ('l' integer, 'f' float)
+
+ virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle an int token.
+ /// \return fetch another token?
+
+ virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a float token.
+ /// \return fetch another token?
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ void reset();
+ ///< Reset parser to clean state.
+
+ char append (std::vector<Interpreter::Type_Code>& code);
+ ///< Generate code for parsed expression.
+ /// \return Type ('l': integer, 'f': float)
+
+ int parseArguments (const std::string& arguments, Scanner& scanner,
+ std::vector<Interpreter::Type_Code>& code, bool invert = false);
+ ///< Parse sequence of arguments specified by \a arguments.
+ /// \param arguments Each character represents one arguments ('l': integer,
+ /// 'f': float, 'S': string, 'c': string (case smashed), '/': following arguments are
+ /// optional)
+ /// \param invert Store arguments in reverted order.
+ /// \return number of optional arguments
+ };
+}
+
+#endif
diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp
new file mode 100644
index 0000000000..c6a74b234b
--- /dev/null
+++ b/components/compiler/extensions.cpp
@@ -0,0 +1,217 @@
+
+#include "extensions.hpp"
+
+#include <cassert>
+#include <stdexcept>
+
+#include "generator.hpp"
+#include "literals.hpp"
+
+namespace Compiler
+{
+ Extensions::Extensions() : mNextKeywordIndex (-1) {}
+
+ int Extensions::searchKeyword (const std::string& keyword) const
+ {
+ std::map<std::string, int>::const_iterator iter = mKeywords.find (keyword);
+
+ if (iter==mKeywords.end())
+ return 0;
+
+ return iter->second;
+ }
+
+ bool Extensions::isFunction (int keyword, char& returnType, std::string& argumentType,
+ bool explicitReference) const
+ {
+ std::map<int, Function>::const_iterator iter = mFunctions.find (keyword);
+
+ if (iter==mFunctions.end())
+ return false;
+
+ if (explicitReference && iter->second.mCodeExplicit==-1)
+ return false;
+
+ returnType = iter->second.mReturn;
+ argumentType = iter->second.mArguments;
+ return true;
+ }
+
+ bool Extensions::isInstruction (int keyword, std::string& argumentType,
+ bool explicitReference) const
+ {
+ std::map<int, Instruction>::const_iterator iter = mInstructions.find (keyword);
+
+ if (iter==mInstructions.end())
+ return false;
+
+ if (explicitReference && iter->second.mCodeExplicit==-1)
+ return false;
+
+ argumentType = iter->second.mArguments;
+ return true;
+ }
+
+ void Extensions::registerFunction (const std::string& keyword, char returnType,
+ const std::string& argumentType, int code, int codeExplicit)
+ {
+ Function function;
+
+ if (argumentType.find ('/')==std::string::npos)
+ {
+ function.mSegment = 5;
+ assert (code>=33554432 && code<=67108863);
+ assert (codeExplicit==-1 || (codeExplicit>=33554432 && codeExplicit<=67108863));
+ }
+ else
+ {
+ function.mSegment = 3;
+ assert (code>=0x20000 && code<=0x2ffff);
+ assert (codeExplicit==-1 || (codeExplicit>=0x20000 && codeExplicit<=0x2ffff));
+ }
+
+ int keywordIndex = mNextKeywordIndex--;
+
+ mKeywords.insert (std::make_pair (keyword, keywordIndex));
+
+ function.mReturn = returnType;
+ function.mArguments = argumentType;
+ function.mCode = code;
+ function.mCodeExplicit = codeExplicit;
+
+ mFunctions.insert (std::make_pair (keywordIndex, function));
+ }
+
+ void Extensions::registerInstruction (const std::string& keyword,
+ const std::string& argumentType, int code, int codeExplicit)
+ {
+ Instruction instruction;
+
+ if (argumentType.find ('/')==std::string::npos)
+ {
+ instruction.mSegment = 5;
+ assert (code>=33554432 && code<=67108863);
+ assert (codeExplicit==-1 || (codeExplicit>=33554432 && codeExplicit<=67108863));
+ }
+ else
+ {
+ instruction.mSegment = 3;
+ assert (code>=0x20000 && code<=0x2ffff);
+ assert (codeExplicit==-1 || (codeExplicit>=0x20000 && codeExplicit<=0x2ffff));
+ }
+
+ int keywordIndex = mNextKeywordIndex--;
+
+ mKeywords.insert (std::make_pair (keyword, keywordIndex));
+
+ instruction.mArguments = argumentType;
+ instruction.mCode = code;
+ instruction.mCodeExplicit = codeExplicit;
+
+ mInstructions.insert (std::make_pair (keywordIndex, instruction));
+ }
+
+ void Extensions::generateFunctionCode (int keyword, std::vector<Interpreter::Type_Code>& code,
+ Literals& literals, const std::string& id, int optionalArguments) const
+ {
+ assert (optionalArguments>=0);
+
+ std::map<int, Function>::const_iterator iter = mFunctions.find (keyword);
+
+ if (iter==mFunctions.end())
+ throw std::logic_error ("unknown custom function keyword");
+
+ if (optionalArguments && iter->second.mSegment!=3)
+ throw std::logic_error ("functions with optional arguments must be placed into segment 3");
+
+ if (!id.empty())
+ {
+ if (iter->second.mCodeExplicit==-1)
+ throw std::logic_error ("explicit references not supported");
+
+ int index = literals.addString (id);
+ Generator::pushInt (code, literals, index);
+ }
+
+ switch (iter->second.mSegment)
+ {
+ case 3:
+
+ if (optionalArguments>=256)
+ throw std::logic_error ("number of optional arguments is too large for segment 3");
+
+ code.push_back (Generator::segment3 (
+ id.empty() ? iter->second.mCode : iter->second.mCodeExplicit,
+ optionalArguments));
+
+ break;
+
+ case 5:
+
+ code.push_back (Generator::segment5 (
+ id.empty() ? iter->second.mCode : iter->second.mCodeExplicit));
+
+ break;
+
+ default:
+
+ throw std::logic_error ("unsupported code segment");
+ }
+ }
+
+ void Extensions::generateInstructionCode (int keyword,
+ std::vector<Interpreter::Type_Code>& code, Literals& literals, const std::string& id,
+ int optionalArguments) const
+ {
+ assert (optionalArguments>=0);
+
+ std::map<int, Instruction>::const_iterator iter = mInstructions.find (keyword);
+
+ if (iter==mInstructions.end())
+ throw std::logic_error ("unknown custom instruction keyword");
+
+ if (optionalArguments && iter->second.mSegment!=3)
+ throw std::logic_error ("instructions with optional arguments must be placed into segment 3");
+
+ if (!id.empty())
+ {
+ if (iter->second.mCodeExplicit==-1)
+ throw std::logic_error ("explicit references not supported");
+
+ int index = literals.addString (id);
+ Generator::pushInt (code, literals, index);
+ }
+
+ switch (iter->second.mSegment)
+ {
+ case 3:
+
+ if (optionalArguments>=256)
+ throw std::logic_error ("number of optional arguments is too large for segment 3");
+
+ code.push_back (Generator::segment3 (
+ id.empty() ? iter->second.mCode : iter->second.mCodeExplicit,
+ optionalArguments));
+
+ break;
+
+ case 5:
+
+ code.push_back (Generator::segment5 (
+ id.empty() ? iter->second.mCode : iter->second.mCodeExplicit));
+
+ break;
+
+ default:
+
+ throw std::logic_error ("unsupported code segment");
+ }
+ }
+
+ void Extensions::listKeywords (std::vector<std::string>& keywords) const
+ {
+ for (std::map<std::string, int>::const_iterator iter (mKeywords.begin());
+ iter!=mKeywords.end(); ++iter)
+ keywords.push_back (iter->first);
+ }
+}
diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp
new file mode 100644
index 0000000000..53ebfa31b5
--- /dev/null
+++ b/components/compiler/extensions.hpp
@@ -0,0 +1,87 @@
+#ifndef COMPILER_EXTENSIONS_H_INCLUDED
+#define COMPILER_EXTENSINOS_H_INCLUDED
+
+#include <string>
+#include <map>
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+namespace Compiler
+{
+ class Literals;
+
+ /// \brief Collection of compiler extensions
+
+ class Extensions
+ {
+ struct Function
+ {
+ char mReturn;
+ std::string mArguments;
+ int mCode;
+ int mCodeExplicit;
+ int mSegment;
+ };
+
+ struct Instruction
+ {
+ std::string mArguments;
+ int mCode;
+ int mCodeExplicit;
+ int mSegment;
+ };
+
+ int mNextKeywordIndex;
+ std::map<std::string, int> mKeywords;
+ std::map<int, Function> mFunctions;
+ std::map<int, Instruction> mInstructions;
+
+ public:
+
+ Extensions();
+
+ int searchKeyword (const std::string& keyword) const;
+ ///< Return extension keyword code, that is assigned to the string \a keyword.
+ /// - if no match is found 0 is returned.
+ /// - keyword must be all lower case.
+
+ bool isFunction (int keyword, char& returnType, std::string& argumentType,
+ bool explicitReference) const;
+ ///< Is this keyword registered with a function? If yes, return return and argument
+ /// types.
+
+ bool isInstruction (int keyword, std::string& argumentType,
+ bool explicitReference) const;
+ ///< Is this keyword registered with a function? If yes, return argument types.
+
+ void registerFunction (const std::string& keyword, char returnType,
+ const std::string& argumentType, int code, int codeExplicit = -1);
+ ///< Register a custom function
+ /// - keyword must be all lower case.
+ /// - keyword must be unique
+ /// - if explicit references are not supported, segment5codeExplicit must be set to -1
+ /// \note Currently only segment 3 and segment 5 opcodes are supported.
+
+ void registerInstruction (const std::string& keyword,
+ const std::string& argumentType, int code, int codeExplicit = -1);
+ ///< Register a custom instruction
+ /// - keyword must be all lower case.
+ /// - keyword must be unique
+ /// - if explicit references are not supported, segment5codeExplicit must be set to -1
+ /// \note Currently only segment 3 and segment 5 opcodes are supported.
+
+ void generateFunctionCode (int keyword, std::vector<Interpreter::Type_Code>& code,
+ Literals& literals, const std::string& id, int optionalArguments) const;
+ ///< Append code for function to \a code.
+
+ void generateInstructionCode (int keyword, std::vector<Interpreter::Type_Code>& code,
+ Literals& literals, const std::string& id, int optionalArguments) const;
+ ///< Append code for function to \a code.
+
+ void listKeywords (std::vector<std::string>& keywords) const;
+ ///< Append all known keywords to \æ kaywords.
+ };
+}
+
+#endif
diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp
new file mode 100644
index 0000000000..9e0c36825a
--- /dev/null
+++ b/components/compiler/extensions0.cpp
@@ -0,0 +1,467 @@
+#include "extensions0.hpp"
+
+#include "opcodes.hpp"
+#include "extensions.hpp"
+
+namespace Compiler
+{
+ void registerExtensions (Extensions& extensions, bool consoleOnly)
+ {
+ Ai::registerExtensions (extensions);
+ Animation::registerExtensions (extensions);
+ Cell::registerExtensions (extensions);
+ Container::registerExtensions (extensions);
+ Control::registerExtensions (extensions);
+ Dialogue::registerExtensions (extensions);
+ Gui::registerExtensions (extensions);
+ Misc::registerExtensions (extensions);
+ Sky::registerExtensions (extensions);
+ Sound::registerExtensions (extensions);
+ Stats::registerExtensions (extensions);
+ Transformation::registerExtensions (extensions);
+
+ if (consoleOnly)
+ {
+ Console::registerExtensions (extensions);
+ User::registerExtensions (extensions);
+ }
+ }
+
+ namespace Ai
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate,
+ opcodeAIActivateExplicit);
+ extensions.registerInstruction ("aitravel", "fff/l", opcodeAiTravel,
+ opcodeAiTravelExplicit);
+ extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort,
+ opcodeAiEscortExplicit);
+ extensions.registerInstruction ("aiescortcell", "ccffff/l", opcodeAiEscortCell,
+ opcodeAiEscortCellExplicit);
+ extensions.registerInstruction ("aiwander", "fff/llllllllll", opcodeAiWander,
+ opcodeAiWanderExplicit);
+ extensions.registerInstruction ("aifollow", "cffff/l", opcodeAiFollow,
+ opcodeAiFollowExplicit);
+ extensions.registerInstruction ("aifollowcell", "ccffff/l", opcodeAiFollowCell,
+ opcodeAiFollowCellExplicit);
+ extensions.registerFunction ("getaipackagedone", 'l', "", opcodeGetAiPackageDone,
+ opcodeGetAiPackageDoneExplicit);
+ extensions.registerFunction ("getcurrentaipackage", 'l', "", opcodeGetCurrentAiPackage,
+ opcodeGetAiPackageDoneExplicit);
+ extensions.registerFunction ("getdetected", 'l', "c", opcodeGetDetected,
+ opcodeGetDetectedExplicit);
+ extensions.registerInstruction ("sethello", "l", opcodeSetHello, opcodeSetHelloExplicit);
+ extensions.registerInstruction ("setfight", "l", opcodeSetFight, opcodeSetFightExplicit);
+ extensions.registerInstruction ("setflee", "l", opcodeSetFlee, opcodeSetFleeExplicit);
+ extensions.registerInstruction ("setalarm", "l", opcodeSetAlarm, opcodeSetAlarmExplicit);
+ extensions.registerInstruction ("modhello", "l", opcodeModHello, opcodeModHelloExplicit);
+ extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit);
+ extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit);
+ extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit);
+ extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit);
+ extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit);
+ extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit);
+ extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit);
+ }
+ }
+
+ namespace Animation
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("skipanim", "", opcodeSkipAnim, opcodeSkipAnimExplicit);
+ extensions.registerInstruction ("playgroup", "c/l", opcodePlayAnim, opcodePlayAnimExplicit);
+ extensions.registerInstruction ("loopgroup", "cl/l", opcodeLoopAnim, opcodeLoopAnimExplicit);
+ }
+ }
+
+ namespace Cell
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerFunction ("cellchanged", 'l', "", opcodeCellChanged);
+ extensions.registerInstruction ("coc", "S", opcodeCOC);
+ extensions.registerInstruction ("centeroncell", "S", opcodeCOC);
+ extensions.registerInstruction ("coe", "ll", opcodeCOE);
+ extensions.registerInstruction ("centeronexterior", "ll", opcodeCOE);
+ extensions.registerInstruction ("setwaterlevel", "f", opcodeSetWaterLevel);
+ extensions.registerInstruction ("modwaterlevel", "f", opcodeModWaterLevel);
+ extensions.registerFunction ("getinterior", 'l', "", opcodeGetInterior);
+ extensions.registerFunction ("getpccell", 'l', "c", opcodeGetPCCell);
+ extensions.registerFunction ("getwaterlevel", 'f', "", opcodeGetWaterLevel);
+ }
+ }
+
+ namespace Console
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+
+ }
+ }
+
+ namespace Container
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("additem", "cl", opcodeAddItem, opcodeAddItemExplicit);
+ extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount,
+ opcodeGetItemCountExplicit);
+ extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem,
+ opcodeRemoveItemExplicit);
+ extensions.registerInstruction ("equip", "c", opcodeEquip, opcodeEquipExplicit);
+ extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit);
+ extensions.registerFunction ("hasitemequipped", 'l', "c", opcodeHasItemEquipped, opcodeHasItemEquippedExplicit);
+ extensions.registerFunction ("hassoulgem", 'l', "c", opcodeHasSoulGem, opcodeHasSoulGemExplicit);
+ extensions.registerFunction ("getweapontype", 'l', "", opcodeGetWeaponType, opcodeGetWeaponTypeExplicit);
+ }
+ }
+
+ namespace Control
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ std::string enable ("enable");
+ std::string disable ("disable");
+
+ for (int i=0; i<numberOfControls; ++i)
+ {
+ extensions.registerInstruction (enable + controls[i], "", opcodeEnable+i);
+ extensions.registerInstruction (disable + controls[i], "", opcodeDisable+i);
+ extensions.registerFunction (std::string("get") + controls[i] + std::string("disabled"), 'l', "", opcodeGetDisabled+i);
+ }
+
+ extensions.registerInstruction ("togglecollision", "", opcodeToggleCollision);
+ extensions.registerInstruction ("tcl", "", opcodeToggleCollision);
+
+ extensions.registerInstruction ("clearforcerun", "", opcodeClearForceRun,
+ opcodeClearForceRunExplicit);
+ extensions.registerInstruction ("forcerun", "", opcodeForceRun,
+ opcodeForceRunExplicit);
+
+ extensions.registerInstruction ("clearforcesneak", "", opcodeClearForceSneak,
+ opcodeClearForceSneakExplicit);
+ extensions.registerInstruction ("forcesneak", "", opcodeForceSneak,
+ opcodeForceSneakExplicit);
+ extensions.registerFunction ("getpcrunning", 'l', "", opcodeGetPcRunning);
+ extensions.registerFunction ("getpcsneaking", 'l', "", opcodeGetPcSneaking);
+ extensions.registerFunction ("getforcerun", 'l', "", opcodeGetForceRun, opcodeGetForceRunExplicit);
+ extensions.registerFunction ("getforcesneak", 'l', "", opcodeGetForceSneak, opcodeGetForceSneakExplicit);
+ }
+ }
+
+ namespace Dialogue
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("journal", "cl", opcodeJournal);
+ extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex);
+ extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex);
+ extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic);
+ extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice);
+ extensions.registerInstruction("forcegreeting","",opcodeForceGreeting);
+ extensions.registerInstruction("forcegreeting","",opcodeForceGreeting,
+ opcodeForceGreetingExplicit);
+ extensions.registerInstruction("goodbye", "", opcodeGoodbye);
+ extensions.registerInstruction("setreputation", "l", opcodeSetReputation,
+ opcodeSetReputationExplicit);
+ extensions.registerInstruction("modreputation", "l", opcodeModReputation,
+ opcodeModReputationExplicit);
+ extensions.registerFunction("getreputation", 'l', "", opcodeGetReputation,
+ opcodeGetReputationExplicit);
+ extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction,
+ opcodeSameFactionExplicit);
+ }
+ }
+
+ namespace Gui
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("enablebirthmenu", "", opcodeEnableBirthMenu);
+ extensions.registerInstruction ("enableclassmenu", "", opcodeEnableClassMenu);
+ extensions.registerInstruction ("enablenamemenu", "", opcodeEnableNameMenu);
+ extensions.registerInstruction ("enableracemenu", "", opcodeEnableRaceMenu);
+ extensions.registerInstruction ("enablestatreviewmenu", "",
+ opcodeEnableStatsReviewMenu);
+
+ extensions.registerInstruction ("enableinventorymenu", "", opcodeEnableInventoryMenu);
+ extensions.registerInstruction ("enablemagicmenu", "", opcodeEnableMagicMenu);
+ extensions.registerInstruction ("enablemapmenu", "", opcodeEnableMapMenu);
+ extensions.registerInstruction ("enablestatsmenu", "", opcodeEnableStatsMenu);
+
+ extensions.registerInstruction ("enablerest", "", opcodeEnableRest);
+ extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest);
+
+ extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu);
+
+ extensions.registerFunction ("getbuttonpressed", 'l', "", opcodeGetButtonPressed);
+
+ extensions.registerInstruction ("togglefogofwar", "", opcodeToggleFogOfWar);
+ extensions.registerInstruction ("tfow", "", opcodeToggleFogOfWar);
+
+ extensions.registerInstruction ("togglefullhelp", "", opcodeToggleFullHelp);
+ extensions.registerInstruction ("tfh", "", opcodeToggleFullHelp);
+
+ extensions.registerInstruction ("showmap", "S", opcodeShowMap);
+ extensions.registerInstruction ("fillmap", "", opcodeFillMap);
+ }
+ }
+
+ namespace Misc
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerFunction ("xbox", 'l', "", opcodeXBox);
+ extensions.registerFunction ("onactivate", 'l', "", opcodeOnActivate);
+ extensions.registerInstruction ("activate", "", opcodeActivate);
+ extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit);
+ extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit);
+ extensions.registerInstruction ("togglecollisionboxes", "", opcodeToggleCollisionBoxes);
+ extensions.registerInstruction ("togglecollisiongrid", "", opcodeToggleCollisionDebug);
+ extensions.registerInstruction ("tcb", "", opcodeToggleCollisionBoxes);
+ extensions.registerInstruction ("tcg", "", opcodeToggleCollisionDebug);
+ extensions.registerInstruction ("twf", "", opcodeToggleWireframe);
+ extensions.registerInstruction ("togglewireframe", "", opcodeToggleWireframe);
+ extensions.registerInstruction ("fadein", "f", opcodeFadeIn);
+ extensions.registerInstruction ("fadeout", "f", opcodeFadeOut);
+ extensions.registerInstruction ("fadeto", "ff", opcodeFadeTo);
+ extensions.registerInstruction ("togglewater", "", opcodeToggleWater);
+ extensions.registerInstruction ("twa", "", opcodeToggleWater);
+ extensions.registerInstruction ("togglepathgrid", "", opcodeTogglePathgrid);
+ extensions.registerInstruction ("tpg", "", opcodeTogglePathgrid);
+ extensions.registerInstruction ("dontsaveobject", "", opcodeDontSaveObject);
+ extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode);
+ extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode);
+ extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep);
+ extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc);
+ extensions.registerInstruction ("playbink", "Sl", opcodePlayBink);
+ extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit);
+ extensions.registerFunction ("geteffect", 'l', "S", opcodeGetEffect, opcodeGetEffectExplicit);
+ extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit);
+ extensions.registerInstruction ("removesoulgem", "c", opcodeRemoveSoulGem, opcodeRemoveSoulGemExplicit);
+ extensions.registerInstruction ("drop", "cl", opcodeDrop, opcodeDropExplicit);
+ extensions.registerInstruction ("dropsoulgem", "c", opcodeDropSoulGem, opcodeDropSoulGemExplicit);
+ extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit);
+ extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit);
+ extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit);
+ extensions.registerFunction ("getcurrenttime", 'f', "", opcodeGetCurrentTime);
+ extensions.registerInstruction ("setdelete", "l", opcodeSetDelete, opcodeSetDeleteExplicit);
+ extensions.registerFunction ("getsquareroot", 'f', "f", opcodeGetSquareRoot);
+ extensions.registerInstruction ("fall", "", opcodeFall, opcodeFallExplicit);
+ extensions.registerFunction ("getstandingpc", 'l', "", opcodeGetStandingPc, opcodeGetStandingPcExplicit);
+ extensions.registerFunction ("getstandingactor", 'l', "", opcodeGetStandingActor, opcodeGetStandingActorExplicit);
+ extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed);
+ extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit);
+ extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting);
+ extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting);
+ extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit);
+ extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit);
+ }
+ }
+
+ namespace Sky
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("togglesky", "", opcodeToggleSky);
+ extensions.registerInstruction ("ts", "", opcodeToggleSky);
+ extensions.registerInstruction ("turnmoonwhite", "", opcodeTurnMoonWhite);
+ extensions.registerInstruction ("turnmoonred", "", opcodeTurnMoonRed);
+ extensions.registerInstruction ("changeweather", "Sl", opcodeChangeWeather);
+ extensions.registerFunction ("getmasserphase", 'l', "", opcodeGetMasserPhase);
+ extensions.registerFunction ("getsecundaphase", 'l', "", opcodeGetSecundaPhase);
+ extensions.registerFunction ("getcurrentweather", 'l', "", opcodeGetCurrentWeather);
+ extensions.registerInstruction ("modregion", "S/llllllllll", opcodeModRegion);
+ }
+ }
+
+ namespace Sound
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("say", "SS", opcodeSay, opcodeSayExplicit);
+ extensions.registerFunction ("saydone", 'l', "", opcodeSayDone, opcodeSayDoneExplicit);
+ extensions.registerInstruction ("streammusic", "S", opcodeStreamMusic);
+ extensions.registerInstruction ("playsound", "c", opcodePlaySound);
+ extensions.registerInstruction ("playsoundvp", "cff", opcodePlaySoundVP);
+ extensions.registerInstruction ("playsound3d", "c", opcodePlaySound3D,
+ opcodePlaySound3DExplicit);
+ extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP,
+ opcodePlaySound3DVPExplicit);
+ extensions.registerInstruction ("playloopsound3d", "c", opcodePlayLoopSound3D,
+ opcodePlayLoopSound3DExplicit);
+ extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP,
+ opcodePlayLoopSound3DVPExplicit);
+ extensions.registerInstruction ("stopsound", "c", opcodeStopSound,
+ opcodeStopSoundExplicit);
+ extensions.registerFunction ("getsoundplaying", 'l', "c", opcodeGetSoundPlaying,
+ opcodeGetSoundPlayingExplicit);
+ }
+ }
+
+ namespace Stats
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ static const char *attributes[numberOfAttributes] =
+ {
+ "strength", "intelligence", "willpower", "agility", "speed", "endurance",
+ "personality", "luck"
+ };
+
+ static const char *dynamics[numberOfDynamics] =
+ {
+ "health", "magicka", "fatigue"
+ };
+
+ static const char *skills[numberOfSkills] =
+ {
+ "block", "armorer", "mediumarmor", "heavyarmor", "bluntweapon",
+ "longblade", "axe", "spear", "athletics", "enchant", "destruction",
+ "alteration", "illusion", "conjuration", "mysticism",
+ "restoration", "alchemy", "unarmored", "security", "sneak",
+ "acrobatics", "lightarmor", "shortblade", "marksman",
+ "mercantile", "speechcraft", "handtohand"
+ };
+
+ std::string get ("get");
+ std::string set ("set");
+ std::string mod ("mod");
+ std::string modCurrent ("modcurrent");
+ std::string getRatio ("getratio");
+
+ for (int i=0; i<numberOfAttributes; ++i)
+ {
+ extensions.registerFunction (get + attributes[i], 'l', "",
+ opcodeGetAttribute+i, opcodeGetAttributeExplicit+i);
+
+ extensions.registerInstruction (set + attributes[i], "l",
+ opcodeSetAttribute+i, opcodeSetAttributeExplicit+i);
+
+ extensions.registerInstruction (mod + attributes[i], "l",
+ opcodeModAttribute+i, opcodeModAttributeExplicit+i);
+ }
+
+ for (int i=0; i<numberOfDynamics; ++i)
+ {
+ extensions.registerFunction (get + dynamics[i], 'f', "",
+ opcodeGetDynamic+i, opcodeGetDynamicExplicit+i);
+
+ extensions.registerInstruction (set + dynamics[i], "f",
+ opcodeSetDynamic+i, opcodeSetDynamicExplicit+i);
+
+ extensions.registerInstruction (mod + dynamics[i], "f",
+ opcodeModDynamic+i, opcodeModDynamicExplicit+i);
+
+ extensions.registerInstruction (modCurrent + dynamics[i], "f",
+ opcodeModCurrentDynamic+i, opcodeModCurrentDynamicExplicit+i);
+
+ extensions.registerFunction (get + dynamics[i] + getRatio, 'f', "",
+ opcodeGetDynamicGetRatio+i, opcodeGetDynamicGetRatioExplicit+i);
+ }
+
+ for (int i=0; i<numberOfSkills; ++i)
+ {
+ extensions.registerFunction (get + skills[i], 'l', "",
+ opcodeGetSkill+i, opcodeGetSkillExplicit+i);
+
+ extensions.registerInstruction (set + skills[i], "l",
+ opcodeSetSkill+i, opcodeSetSkillExplicit+i);
+
+ extensions.registerInstruction (mod + skills[i], "l",
+ opcodeModSkill+i, opcodeModSkillExplicit+i);
+ }
+
+ extensions.registerFunction ("getpccrimelevel", 'f', "", opcodeGetPCCrimeLevel);
+ extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel);
+ extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel);
+
+ extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit);
+ extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell,
+ opcodeRemoveSpellExplicit);
+ extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit);
+
+ extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank);
+ extensions.registerInstruction("pclowerrank","/S",opcodePCLowerRank);
+ extensions.registerInstruction("pcjoinfaction","/S",opcodePCJoinFaction);
+ extensions.registerInstruction ("moddisposition","l",opcodeModDisposition,
+ opcodeModDispositionExplicit);
+ extensions.registerInstruction ("setdisposition","l",opcodeSetDisposition,
+ opcodeSetDispositionExplicit);
+ extensions.registerFunction ("getdisposition",'l', "",opcodeGetDisposition,
+ opcodeGetDispositionExplicit);
+ extensions.registerFunction("getpcrank",'l',"/S",opcodeGetPCRank,opcodeGetPCRankExplicit);
+
+ extensions.registerInstruction("setlevel", "l", opcodeSetLevel, opcodeSetLevelExplicit);
+ extensions.registerFunction("getlevel", 'l', "", opcodeGetLevel, opcodeGetLevelExplicit);
+
+ extensions.registerFunction ("getdeadcount", 'l', "c", opcodeGetDeadCount);
+
+ extensions.registerFunction ("getpcfacrep", 'l', "/c", opcodeGetPCFacRep, opcodeGetPCFacRepExplicit);
+ extensions.registerInstruction ("setpcfacrep", "l/c", opcodeSetPCFacRep, opcodeSetPCFacRepExplicit);
+ extensions.registerInstruction ("modpcfacrep", "l/c", opcodeModPCFacRep, opcodeModPCFacRepExplicit);
+
+ extensions.registerFunction ("getcommondisease", 'l', "", opcodeGetCommonDisease,
+ opcodeGetCommonDiseaseExplicit);
+ extensions.registerFunction ("getblightdisease", 'l', "", opcodeGetBlightDisease,
+ opcodeGetBlightDiseaseExplicit);
+
+ extensions.registerFunction ("getrace", 'l', "c", opcodeGetRace,
+ opcodeGetRaceExplicit);
+ extensions.registerFunction ("getwerewolfkills", 'f', "", opcodeGetWerewolfKills);
+ extensions.registerFunction ("pcexpelled", 'l', "/S", opcodePcExpelled, opcodePcExpelledExplicit);
+ extensions.registerInstruction ("pcexpell", "/S", opcodePcExpell, opcodePcExpellExplicit);
+ extensions.registerInstruction ("pcclearexpelled", "/S", opcodePcClearExpelled, opcodePcClearExpelledExplicit);
+ extensions.registerInstruction ("raiserank", "", opcodeRaiseRank, opcodeRaiseRankExplicit);
+ extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit);
+
+ extensions.registerFunction ("ondeath", 'l', "", opcodeOnDeath, opcodeOnDeathExplicit);
+
+ extensions.registerFunction ("iswerewolf", 'l', "", opcodeIsWerewolf, opcodeIsWerewolfExplicit);
+
+ extensions.registerInstruction("becomewerewolf", "", opcodeBecomeWerewolf, opcodeBecomeWerewolfExplicit);
+ extensions.registerInstruction("undowerewolf", "", opcodeUndoWerewolf, opcodeUndoWerewolfExplicit);
+ extensions.registerInstruction("setwerewolfacrobatics", "", opcodeSetWerewolfAcrobatics, opcodeSetWerewolfAcrobaticsExplicit);
+ }
+ }
+
+ namespace Transformation
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction("setscale","f",opcodeSetScale,opcodeSetScaleExplicit);
+ extensions.registerFunction("getscale",'f',"",opcodeGetScale,opcodeGetScaleExplicit);
+ extensions.registerInstruction("setangle","cf",opcodeSetAngle,opcodeSetAngleExplicit);
+ extensions.registerFunction("getangle",'f',"c",opcodeGetAngle,opcodeGetAngleExplicit);
+ extensions.registerInstruction("setpos","cf",opcodeSetPos,opcodeSetPosExplicit);
+ extensions.registerFunction("getpos",'f',"c",opcodeGetPos,opcodeGetPosExplicit);
+ extensions.registerFunction("getstartingpos",'f',"c",opcodeGetStartingPos,opcodeGetStartingPosExplicit);
+ extensions.registerInstruction("position","ffff",opcodePosition,opcodePositionExplicit);
+ extensions.registerInstruction("positioncell","ffffc",opcodePositionCell,opcodePositionCellExplicit);
+ extensions.registerInstruction("placeitemcell","ccffff",opcodePlaceItemCell);
+ extensions.registerInstruction("placeitem","cffff",opcodePlaceItem);
+ extensions.registerInstruction("placeatpc","clfl",opcodePlaceAtPc);
+ extensions.registerInstruction("placeatme","clfl",opcodePlaceAtMe,opcodePlaceAtMeExplicit);
+ extensions.registerInstruction("modscale","f",opcodeModScale,opcodeModScaleExplicit);
+ extensions.registerInstruction("rotate","cf",opcodeRotate,opcodeRotateExplicit);
+ extensions.registerInstruction("rotateworld","cf",opcodeRotateWorld,opcodeRotateWorldExplicit);
+ extensions.registerInstruction("setatstart","",opcodeSetAtStart,opcodeSetAtStartExplicit);
+ extensions.registerInstruction("move","cf",opcodeMove,opcodeMoveExplicit);
+ extensions.registerInstruction("moveworld","cf",opcodeMoveWorld,opcodeMoveWorldExplicit);
+ extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit);
+ }
+ }
+
+ namespace User
+ {
+ void registerExtensions (Extensions& extensions)
+ {
+ extensions.registerInstruction ("user1", "", opcodeUser1);
+ extensions.registerInstruction ("user2", "", opcodeUser2);
+ extensions.registerInstruction ("user3", "", opcodeUser3, opcodeUser3);
+ extensions.registerInstruction ("user4", "", opcodeUser4, opcodeUser4);
+ }
+ }
+}
diff --git a/components/compiler/extensions0.hpp b/components/compiler/extensions0.hpp
new file mode 100644
index 0000000000..d51507711f
--- /dev/null
+++ b/components/compiler/extensions0.hpp
@@ -0,0 +1,81 @@
+#ifndef COMPILER_EXTENSIONS0_H
+#define COMPILER_EXTENSIONS0_H
+
+namespace Compiler
+{
+ class Extensions;
+
+ void registerExtensions (Extensions& extensions, bool consoleOnly = false);
+
+ namespace Ai
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Animation
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Cell
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Console
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Container
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Control
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Dialogue
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Gui
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Misc
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Sky
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Sound
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Stats
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace Transformation
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+
+ namespace User
+ {
+ void registerExtensions (Extensions& extensions);
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp
new file mode 100644
index 0000000000..185af4a513
--- /dev/null
+++ b/components/compiler/fileparser.cpp
@@ -0,0 +1,134 @@
+#include "fileparser.hpp"
+
+#include <iostream>
+
+#include "tokenloc.hpp"
+#include "scanner.hpp"
+
+namespace Compiler
+{
+ FileParser::FileParser (ErrorHandler& errorHandler, Context& context)
+ : Parser (errorHandler, context),
+ mScriptParser (errorHandler, context, mLocals, true),
+ mState (BeginState)
+ {}
+
+ std::string FileParser::getName() const
+ {
+ return mName;
+ }
+
+ void FileParser::getCode (std::vector<Interpreter::Type_Code>& code) const
+ {
+ mScriptParser.getCode (code);
+ }
+
+ const Locals& FileParser::getLocals() const
+ {
+ return mLocals;
+ }
+
+ bool FileParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ if (mState==NameState)
+ {
+ mName = name;
+ mState = BeginCompleteState;
+ return true;
+ }
+
+ if (mState==EndNameState)
+ {
+ // optional repeated name after end statement
+ if (mName!=name)
+ reportWarning ("Names for script " + mName + " do not match", loc);
+
+ mState = EndCompleteState;
+ return false; // we are stopping here, because there might be more garbage on the end line,
+ // that we must ignore.
+ //
+ /// \todo allow this workaround to be disabled for newer scripts
+ }
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ bool FileParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mState==BeginState && keyword==Scanner::K_begin)
+ {
+ mState = NameState;
+ return true;
+ }
+
+ if (mState==NameState)
+ {
+ // keywords can be used as script names too. Thank you Morrowind for another
+ // syntactic perversity :(
+ mName = loc.mLiteral;
+ mState = BeginCompleteState;
+ return true;
+ }
+
+ if (mState==EndNameState)
+ {
+ // optional repeated name after end statement
+ if (mName!=loc.mLiteral)
+ reportWarning ("Names for script " + mName + " do not match", loc);
+
+ mState = EndCompleteState;
+ return false; // we are stopping here, because there might be more garbage on the end line,
+ // that we must ignore.
+ //
+ /// \todo allow this workaround to be disabled for newer scripts
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+ }
+
+ bool FileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_newline)
+ {
+ if (mState==BeginState)
+ {
+ // ignore empty lines
+ return true;
+ }
+
+ if (mState==BeginCompleteState)
+ {
+ // parse the script body
+ mScriptParser.reset();
+
+ scanner.scan (mScriptParser);
+
+ mState = EndNameState;
+ return true;
+ }
+
+ if (mState==EndCompleteState || mState==EndNameState)
+ {
+ // we are done here -> ignore the rest of the script
+ return false;
+ }
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ void FileParser::parseEOF (Scanner& scanner)
+ {
+ if (mState!=EndNameState && mState!=EndCompleteState)
+ Parser::parseEOF (scanner);
+ }
+
+ void FileParser::reset()
+ {
+ mState = BeginState;
+ mName.clear();
+ mScriptParser.reset();
+ Parser::reset();
+ }
+}
diff --git a/components/compiler/fileparser.hpp b/components/compiler/fileparser.hpp
new file mode 100644
index 0000000000..13c7fb5376
--- /dev/null
+++ b/components/compiler/fileparser.hpp
@@ -0,0 +1,60 @@
+#ifndef COMPILER_FILEPARSER_H_INCLUDED
+#define COMPILER_FILEPARSER_H_INCLUDED
+
+#include "parser.hpp"
+#include "scriptparser.hpp"
+#include "locals.hpp"
+#include "literals.hpp"
+
+namespace Compiler
+{
+ // Top-level parser, to be used for global scripts, local scripts and targeted scripts
+
+ class FileParser : public Parser
+ {
+ enum State
+ {
+ BeginState, NameState, BeginCompleteState, EndNameState,
+ EndCompleteState
+ };
+
+ ScriptParser mScriptParser;
+ State mState;
+ std::string mName;
+ Locals mLocals;
+
+ public:
+
+ FileParser (ErrorHandler& errorHandler, Context& context);
+
+ std::string getName() const;
+ ///< Return script name.
+
+ void getCode (std::vector<Interpreter::Type_Code>& code) const;
+ ///< store generated code in \æ code.
+
+ const Locals& getLocals() const;
+ ///< get local variable declarations.
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ virtual void parseEOF (Scanner& scanner);
+ ///< Handle EOF token.
+
+ void reset();
+ ///< Reset parser to clean state.
+ };
+}
+
+#endif
diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp
new file mode 100644
index 0000000000..9b02e4273f
--- /dev/null
+++ b/components/compiler/generator.cpp
@@ -0,0 +1,903 @@
+
+#include "generator.hpp"
+
+#include <cassert>
+#include <algorithm>
+#include <iterator>
+#include <stdexcept>
+
+#include "literals.hpp"
+
+namespace
+{
+ void opPushInt (Compiler::Generator::CodeContainer& code, int value)
+ {
+ code.push_back (Compiler::Generator::segment0 (0, value));
+ }
+
+ void opFetchIntLiteral (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (4));
+ }
+
+ void opFetchFloatLiteral (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (5));
+ }
+
+ void opIntToFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (3));
+ }
+
+ void opFloatToInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (6));
+ }
+
+ void opStoreLocalShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (0));
+ }
+
+ void opStoreLocalLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (1));
+ }
+
+ void opStoreLocalFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (2));
+ }
+
+ void opNegateInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (7));
+ }
+
+ void opNegateFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (8));
+ }
+
+ void opAddInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (9));
+ }
+
+ void opAddFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (10));
+ }
+
+ void opSubInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (11));
+ }
+
+ void opSubFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (12));
+ }
+
+ void opMulInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (13));
+ }
+
+ void opMulFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (14));
+ }
+
+ void opDivInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (15));
+ }
+
+ void opDivFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (16));
+ }
+
+ void opIntToFloat1 (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (17));
+ }
+
+ void opFloatToInt1 (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (18));
+ }
+
+ void opSquareRoot (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (19));
+ }
+
+ void opReturn (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (20));
+ }
+
+ void opMessageBox (Compiler::Generator::CodeContainer& code, int buttons)
+ {
+ code.push_back (Compiler::Generator::segment3 (0, buttons));
+ }
+
+ void opReport (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (58));
+ }
+
+ void opFetchLocalShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (21));
+ }
+
+ void opFetchLocalLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (22));
+ }
+
+ void opFetchLocalFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (23));
+ }
+
+ void opJumpForward (Compiler::Generator::CodeContainer& code, int offset)
+ {
+ code.push_back (Compiler::Generator::segment0 (1, offset));
+ }
+
+ void opJumpBackward (Compiler::Generator::CodeContainer& code, int offset)
+ {
+ code.push_back (Compiler::Generator::segment0 (2, offset));
+ }
+
+ void opSkipOnZero (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (24));
+ }
+
+ void opSkipOnNonZero (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (25));
+ }
+
+ void opEqualInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (26));
+ }
+
+ void opNonEqualInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (27));
+ }
+
+ void opLessThanInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (28));
+ }
+
+ void opLessOrEqualInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (29));
+ }
+
+ void opGreaterThanInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (30));
+ }
+
+ void opGreaterOrEqualInt (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (31));
+ }
+
+ void opEqualFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (32));
+ }
+
+ void opNonEqualFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (33));
+ }
+
+ void opLessThanFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (34));
+ }
+
+ void opLessOrEqualFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (35));
+ }
+
+ void opGreaterThanFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (36));
+ }
+
+ void opGreaterOrEqualFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (37));
+ }
+
+ void opMenuMode (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (38));
+ }
+
+ void opStoreGlobalShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (39));
+ }
+
+ void opStoreGlobalLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (40));
+ }
+
+ void opStoreGlobalFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (41));
+ }
+
+ void opFetchGlobalShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (42));
+ }
+
+ void opFetchGlobalLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (43));
+ }
+
+ void opFetchGlobalFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (44));
+ }
+
+ void opStoreMemberShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (59));
+ }
+
+ void opStoreMemberLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (60));
+ }
+
+ void opStoreMemberFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (61));
+ }
+
+ void opFetchMemberShort (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (62));
+ }
+
+ void opFetchMemberLong (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (63));
+ }
+
+ void opFetchMemberFloat (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (64));
+ }
+
+ void opRandom (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (45));
+ }
+
+ void opScriptRunning (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (46));
+ }
+
+ void opStartScript (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (47));
+ }
+
+ void opStopScript (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (48));
+ }
+
+ void opGetDistance (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (49));
+ }
+
+ void opGetSecondsPassed (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (50));
+ }
+
+ void opEnable (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (51));
+ }
+
+ void opDisable (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (52));
+ }
+
+ void opGetDisabled (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (53));
+ }
+
+ void opEnableExplicit (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (54));
+ }
+
+ void opDisableExplicit (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (55));
+ }
+
+ void opGetDisabledExplicit (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (56));
+ }
+
+ void opGetDistanceExplicit (Compiler::Generator::CodeContainer& code)
+ {
+ code.push_back (Compiler::Generator::segment5 (57));
+ }
+}
+
+namespace Compiler
+{
+ namespace Generator
+ {
+ void pushInt (CodeContainer& code, Literals& literals, int value)
+ {
+ int index = literals.addInteger (value);
+ opPushInt (code, index);
+ opFetchIntLiteral (code);
+ }
+
+ void pushFloat (CodeContainer& code, Literals& literals, float value)
+ {
+ int index = literals.addFloat (value);
+ opPushInt (code, index);
+ opFetchFloatLiteral (code);
+ }
+
+ void pushString (CodeContainer& code, Literals& literals, const std::string& value)
+ {
+ int index = literals.addString (value);
+ opPushInt (code, index);
+ }
+
+ void assignToLocal (CodeContainer& code, char localType,
+ int localIndex, const CodeContainer& value, char valueType)
+ {
+ opPushInt (code, localIndex);
+
+ std::copy (value.begin(), value.end(), std::back_inserter (code));
+
+ if (localType!=valueType)
+ {
+ if (localType=='f' && valueType=='l')
+ {
+ opIntToFloat (code);
+ }
+ else if ((localType=='l' || localType=='s') && valueType=='f')
+ {
+ opFloatToInt (code);
+ }
+ }
+
+ switch (localType)
+ {
+ case 'f':
+
+ opStoreLocalFloat (code);
+ break;
+
+ case 's':
+
+ opStoreLocalShort (code);
+ break;
+
+ case 'l':
+
+ opStoreLocalLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void negate (CodeContainer& code, char valueType)
+ {
+ switch (valueType)
+ {
+ case 'l':
+
+ opNegateInt (code);
+ break;
+
+ case 'f':
+
+ opNegateFloat (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void add (CodeContainer& code, char valueType1, char valueType2)
+ {
+ if (valueType1=='l' && valueType2=='l')
+ {
+ opAddInt (code);
+ }
+ else
+ {
+ if (valueType1=='l')
+ opIntToFloat1 (code);
+
+ if (valueType2=='l')
+ opIntToFloat (code);
+
+ opAddFloat (code);
+ }
+ }
+
+ void sub (CodeContainer& code, char valueType1, char valueType2)
+ {
+ if (valueType1=='l' && valueType2=='l')
+ {
+ opSubInt (code);
+ }
+ else
+ {
+ if (valueType1=='l')
+ opIntToFloat1 (code);
+
+ if (valueType2=='l')
+ opIntToFloat (code);
+
+ opSubFloat (code);
+ }
+ }
+
+ void mul (CodeContainer& code, char valueType1, char valueType2)
+ {
+ if (valueType1=='l' && valueType2=='l')
+ {
+ opMulInt (code);
+ }
+ else
+ {
+ if (valueType1=='l')
+ opIntToFloat1 (code);
+
+ if (valueType2=='l')
+ opIntToFloat (code);
+
+ opMulFloat (code);
+ }
+ }
+
+ void div (CodeContainer& code, char valueType1, char valueType2)
+ {
+ if (valueType1=='l' && valueType2=='l')
+ {
+ opDivInt (code);
+ }
+ else
+ {
+ if (valueType1=='l')
+ opIntToFloat1 (code);
+
+ if (valueType2=='l')
+ opIntToFloat (code);
+
+ opDivFloat (code);
+ }
+ }
+
+ void convert (CodeContainer& code, char fromType, char toType)
+ {
+ if (fromType!=toType)
+ {
+ if (fromType=='f' && toType=='l')
+ opFloatToInt (code);
+ else if (fromType=='l' && toType=='f')
+ opIntToFloat (code);
+ else
+ throw std::logic_error ("illegal type conversion");
+ }
+ }
+
+ void squareRoot (CodeContainer& code)
+ {
+ opSquareRoot (code);
+ }
+
+ void exit (CodeContainer& code)
+ {
+ opReturn (code);
+ }
+
+ void message (CodeContainer& code, Literals& literals, const std::string& message,
+ int buttons)
+ {
+ assert (buttons>=0);
+
+ if (buttons>=256)
+ throw std::runtime_error ("A message box can't have more than 255 buttons");
+
+ int index = literals.addString (message);
+
+ opPushInt (code, index);
+ opMessageBox (code, buttons);
+ }
+
+ void report (CodeContainer& code, Literals& literals, const std::string& message)
+ {
+ int index = literals.addString (message);
+
+ opPushInt (code, index);
+ opReport (code);
+ }
+
+ void fetchLocal (CodeContainer& code, char localType, int localIndex)
+ {
+ opPushInt (code, localIndex);
+
+ switch (localType)
+ {
+ case 'f':
+
+ opFetchLocalFloat (code);
+ break;
+
+ case 's':
+
+ opFetchLocalShort (code);
+ break;
+
+ case 'l':
+
+ opFetchLocalLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void jump (CodeContainer& code, int offset)
+ {
+ if (offset>0)
+ opJumpForward (code, offset);
+ else if (offset<0)
+ opJumpBackward (code, -offset);
+ else
+ throw std::logic_error ("inifite loop");
+ }
+
+ void jumpOnZero (CodeContainer& code, int offset)
+ {
+ opSkipOnNonZero (code);
+
+ if (offset<0)
+ --offset; // compensate for skip instruction
+
+ jump (code, offset);
+ }
+
+ void jumpOnNonZero (CodeContainer& code, int offset)
+ {
+ opSkipOnZero (code);
+
+ if (offset<0)
+ --offset; // compensate for skip instruction
+
+ jump (code, offset);
+ }
+
+ void compare (CodeContainer& code, char op, char valueType1, char valueType2)
+ {
+ if (valueType1=='l' && valueType2=='l')
+ {
+ switch (op)
+ {
+ case 'e': opEqualInt (code); break;
+ case 'n': opNonEqualInt (code); break;
+ case 'l': opLessThanInt (code); break;
+ case 'L': opLessOrEqualInt (code); break;
+ case 'g': opGreaterThanInt (code); break;
+ case 'G': opGreaterOrEqualInt (code); break;
+
+ default:
+
+ assert (0);
+ }
+ }
+ else
+ {
+ if (valueType1=='l')
+ opIntToFloat1 (code);
+
+ if (valueType2=='l')
+ opIntToFloat (code);
+
+ switch (op)
+ {
+ case 'e': opEqualFloat (code); break;
+ case 'n': opNonEqualFloat (code); break;
+ case 'l': opLessThanFloat (code); break;
+ case 'L': opLessOrEqualFloat (code); break;
+ case 'g': opGreaterThanFloat (code); break;
+ case 'G': opGreaterOrEqualFloat (code); break;
+
+ default:
+
+ assert (0);
+ }
+ }
+ }
+
+ void menuMode (CodeContainer& code)
+ {
+ opMenuMode (code);
+ }
+
+ void assignToGlobal (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name, const CodeContainer& value, char valueType)
+ {
+ int index = literals.addString (name);
+
+ opPushInt (code, index);
+
+ std::copy (value.begin(), value.end(), std::back_inserter (code));
+
+ if (localType!=valueType)
+ {
+ if (localType=='f' && (valueType=='l' || valueType=='s'))
+ {
+ opIntToFloat (code);
+ }
+ else if ((localType=='l' || localType=='s') && valueType=='f')
+ {
+ opFloatToInt (code);
+ }
+ }
+
+ switch (localType)
+ {
+ case 'f':
+
+ opStoreGlobalFloat (code);
+ break;
+
+ case 's':
+
+ opStoreGlobalShort (code);
+ break;
+
+ case 'l':
+
+ opStoreGlobalLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void fetchGlobal (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name)
+ {
+ int index = literals.addString (name);
+
+ opPushInt (code, index);
+
+ switch (localType)
+ {
+ case 'f':
+
+ opFetchGlobalFloat (code);
+ break;
+
+ case 's':
+
+ opFetchGlobalShort (code);
+ break;
+
+ case 'l':
+
+ opFetchGlobalLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void assignToMember (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name, const std::string& id, const CodeContainer& value, char valueType)
+ {
+ int index = literals.addString (name);
+
+ opPushInt (code, index);
+
+ index = literals.addString (id);
+
+ opPushInt (code, index);
+
+ std::copy (value.begin(), value.end(), std::back_inserter (code));
+
+ if (localType!=valueType)
+ {
+ if (localType=='f' && (valueType=='l' || valueType=='s'))
+ {
+ opIntToFloat (code);
+ }
+ else if ((localType=='l' || localType=='s') && valueType=='f')
+ {
+ opFloatToInt (code);
+ }
+ }
+
+ switch (localType)
+ {
+ case 'f':
+
+ opStoreMemberFloat (code);
+ break;
+
+ case 's':
+
+ opStoreMemberShort (code);
+ break;
+
+ case 'l':
+
+ opStoreMemberLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void fetchMember (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name, const std::string& id)
+ {
+ int index = literals.addString (name);
+
+ opPushInt (code, index);
+
+ index = literals.addString (id);
+
+ opPushInt (code, index);
+
+ switch (localType)
+ {
+ case 'f':
+
+ opFetchMemberFloat (code);
+ break;
+
+ case 's':
+
+ opFetchMemberShort (code);
+ break;
+
+ case 'l':
+
+ opFetchMemberLong (code);
+ break;
+
+ default:
+
+ assert (0);
+ }
+ }
+
+ void random (CodeContainer& code)
+ {
+ opRandom (code);
+ }
+
+ void scriptRunning (CodeContainer& code)
+ {
+ opScriptRunning (code);
+ }
+
+ void startScript (CodeContainer& code)
+ {
+ opStartScript (code);
+ }
+
+ void stopScript (CodeContainer& code)
+ {
+ opStopScript (code);
+ }
+
+ void getDistance (CodeContainer& code, Literals& literals, const std::string& id)
+ {
+ if (id.empty())
+ {
+ opGetDistance (code);
+ }
+ else
+ {
+ int index = literals.addString (id);
+ opPushInt (code, index);
+ opGetDistanceExplicit (code);
+ }
+ }
+
+ void getSecondsPassed (CodeContainer& code)
+ {
+ opGetSecondsPassed (code);
+ }
+
+ void getDisabled (CodeContainer& code, Literals& literals, const std::string& id)
+ {
+ if (id.empty())
+ {
+ opGetDisabled (code);
+ }
+ else
+ {
+ int index = literals.addString (id);
+ opPushInt (code, index);
+ opGetDisabledExplicit (code);
+ }
+ }
+
+ void enable (CodeContainer& code, Literals& literals, const std::string& id)
+ {
+ if (id.empty())
+ {
+ opEnable (code);
+ }
+ else
+ {
+ int index = literals.addString (id);
+ opPushInt (code, index);
+ opEnableExplicit (code);
+ }
+ }
+
+ void disable (CodeContainer& code, Literals& literals, const std::string& id)
+ {
+ if (id.empty())
+ {
+ opDisable (code);
+ }
+ else
+ {
+ int index = literals.addString (id);
+ opPushInt (code, index);
+ opDisableExplicit (code);
+ }
+ }
+ }
+}
diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp
new file mode 100644
index 0000000000..feab26c93d
--- /dev/null
+++ b/components/compiler/generator.hpp
@@ -0,0 +1,130 @@
+#ifndef COMPILER_GENERATOR_H_INCLUDED
+#define COMPILER_GENERATOR_H_INCLUDED
+
+#include <vector>
+#include <string>
+#include <cassert>
+
+#include <components/interpreter/types.hpp>
+
+namespace Compiler
+{
+ class Literals;
+
+ namespace Generator
+ {
+ typedef std::vector<Interpreter::Type_Code> CodeContainer;
+
+ inline Interpreter::Type_Code segment0 (unsigned int c, unsigned int arg0)
+ {
+ assert (c<64);
+ return (c<<24) | (arg0 & 0xffffff);
+ }
+
+ inline Interpreter::Type_Code segment1 (unsigned int c, unsigned int arg0,
+ unsigned int arg1)
+ {
+ assert (c<64);
+ return 0x40000000 | (c<<24) | ((arg0 & 0xfff)<<12) | (arg1 & 0xfff);
+ }
+
+ inline Interpreter::Type_Code segment2 (unsigned int c, unsigned int arg0)
+ {
+ assert (c<1024);
+ return 0x80000000 | (c<<20) | (arg0 & 0xfffff);
+ }
+
+ inline Interpreter::Type_Code segment3 (unsigned int c, unsigned int arg0)
+ {
+ assert (c<262144);
+ return 0xc0000000 | (c<<8) | (arg0 & 0xff);
+ }
+
+ inline Interpreter::Type_Code segment4 (unsigned int c, unsigned int arg0,
+ unsigned int arg1)
+ {
+ assert (c<1024);
+ return 0xc4000000 | (c<<16) | ((arg0 & 0xff)<<8) | (arg1 & 0xff);
+ }
+
+ inline Interpreter::Type_Code segment5 (unsigned int c)
+ {
+ assert (c<67108864);
+ return 0xc8000000 | c;
+ }
+
+ void pushInt (CodeContainer& code, Literals& literals, int value);
+
+ void pushFloat (CodeContainer& code, Literals& literals, float value);
+
+ void pushString (CodeContainer& code, Literals& literals, const std::string& value);
+
+ void assignToLocal (CodeContainer& code, char localType,
+ int localIndex, const CodeContainer& value, char valueType);
+
+ void negate (CodeContainer& code, char valueType);
+
+ void add (CodeContainer& code, char valueType1, char valueType2);
+
+ void sub (CodeContainer& code, char valueType1, char valueType2);
+
+ void mul (CodeContainer& code, char valueType1, char valueType2);
+
+ void div (CodeContainer& code, char valueType1, char valueType2);
+
+ void convert (CodeContainer& code, char fromType, char toType);
+
+ void squareRoot (CodeContainer& code);
+
+ void exit (CodeContainer& code);
+
+ void message (CodeContainer& code, Literals& literals, const std::string& message,
+ int buttons);
+
+ void report (CodeContainer& code, Literals& literals, const std::string& message);
+
+ void fetchLocal (CodeContainer& code, char localType, int localIndex);
+
+ void jump (CodeContainer& code, int offset);
+
+ void jumpOnZero (CodeContainer& code, int offset);
+
+ void jumpOnNonZero (CodeContainer& code, int offset);
+
+ void compare (CodeContainer& code, char op, char valueType1, char valueType2);
+
+ void menuMode (CodeContainer& code);
+
+ void assignToGlobal (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name, const CodeContainer& value, char valueType);
+
+ void fetchGlobal (CodeContainer& code, Literals& literals, char localType,
+ const std::string& name);
+
+ void assignToMember (CodeContainer& code, Literals& literals, char memberType,
+ const std::string& name, const std::string& id, const CodeContainer& value, char valueType);
+
+ void fetchMember (CodeContainer& code, Literals& literals, char memberType,
+ const std::string& name, const std::string& id);
+
+ void random (CodeContainer& code);
+
+ void scriptRunning (CodeContainer& code);
+
+ void startScript (CodeContainer& code);
+
+ void stopScript (CodeContainer& code);
+
+ void getDistance (CodeContainer& code, Literals& literals, const std::string& id);
+
+ void getSecondsPassed (CodeContainer& code);
+
+ void getDisabled (CodeContainer& code, Literals& literals, const std::string& id);
+
+ void enable (CodeContainer& code, Literals& literals, const std::string& id);
+
+ void disable (CodeContainer& code, Literals& literals, const std::string& id);
+ }
+}
+
+#endif
diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp
new file mode 100644
index 0000000000..3d9ac0a93a
--- /dev/null
+++ b/components/compiler/lineparser.cpp
@@ -0,0 +1,451 @@
+
+#include "lineparser.hpp"
+
+#include "scanner.hpp"
+#include "context.hpp"
+#include "errorhandler.hpp"
+#include "skipparser.hpp"
+#include "locals.hpp"
+#include "generator.hpp"
+#include "extensions.hpp"
+#include <components/misc/stringops.hpp>
+
+namespace Compiler
+{
+ void LineParser::parseExpression (Scanner& scanner, const TokenLoc& loc)
+ {
+ mExprParser.reset();
+
+ if (!mExplicit.empty())
+ {
+ mExprParser.parseName (mExplicit, loc, scanner);
+ if (mState==MemberState)
+ mExprParser.parseSpecial (Scanner::S_member, loc, scanner);
+ else
+ mExprParser.parseSpecial (Scanner::S_ref, loc, scanner);
+ }
+
+ scanner.scan (mExprParser);
+
+ char type = mExprParser.append (mCode);
+ mState = EndState;
+
+ switch (type)
+ {
+ case 'l':
+
+ Generator::report (mCode, mLiterals, "%g");
+ break;
+
+ case 'f':
+
+ Generator::report (mCode, mLiterals, "%f");
+ break;
+
+ default:
+
+ throw std::runtime_error ("unknown expression result type");
+ }
+ }
+
+ LineParser::LineParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals, std::vector<Interpreter::Type_Code>& code, bool allowExpression)
+ : Parser (errorHandler, context), mLocals (locals), mLiterals (literals), mCode (code),
+ mState (BeginState), mExprParser (errorHandler, context, locals, literals),
+ mAllowExpression (allowExpression), mButtons(0), mType(0)
+ {}
+
+ bool LineParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mAllowExpression && mState==BeginState)
+ {
+ scanner.putbackInt (value, loc);
+ parseExpression (scanner, loc);
+ return true;
+ }
+
+ return Parser::parseInt (value, loc, scanner);
+ }
+
+ bool LineParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mAllowExpression && mState==BeginState)
+ {
+ scanner.putbackFloat (value, loc);
+ parseExpression (scanner, loc);
+ return true;
+ }
+
+ return Parser::parseFloat (value, loc, scanner);
+ }
+
+ bool LineParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ if (mState==ShortState || mState==LongState || mState==FloatState)
+ {
+ if (!getContext().canDeclareLocals())
+ {
+ getErrorHandler().error ("local variables can't be declared in this context", loc);
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return false;
+ }
+
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+
+ char type = mLocals.getType (name2);
+
+ if (type!=' ')
+ {
+ /// \todo add option to make re-declared local variables an error
+ getErrorHandler().warning ("can't re-declare local variable", loc);
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ mState = EndState;
+ return true;
+ }
+
+ mLocals.declare (mState==ShortState ? 's' : (mState==LongState ? 'l' : 'f'),
+ name2);
+
+ mState = EndState;
+ return true;
+ }
+
+ if (mState==SetState)
+ {
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+ mName = name2;
+
+ // local variable?
+ char type = mLocals.getType (name2);
+ if (type!=' ')
+ {
+ mType = type;
+ mState = SetLocalVarState;
+ return true;
+ }
+
+ type = getContext().getGlobalType (name2);
+ if (type!=' ')
+ {
+ mType = type;
+ mState = SetGlobalVarState;
+ return true;
+ }
+
+ mState = SetPotentialMemberVarState;
+ return true;
+ }
+
+ if (mState==SetMemberVarState)
+ {
+ mMemberName = name;
+ char type = getContext().getMemberType (mMemberName, mName);
+
+ if (type!=' ')
+ {
+ mState = SetMemberVarState2;
+ mType = type;
+ return true;
+ }
+
+ getErrorHandler().error ("unknown variable", loc);
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return false;
+ }
+
+ if (mState==MessageState || mState==MessageCommaState)
+ {
+ std::string arguments;
+
+ for (std::size_t i=0; i<name.size(); ++i)
+ {
+ if (name[i]=='%')
+ {
+ ++i;
+ if (i<name.size())
+ {
+ if (name[i]=='G' || name[i]=='g')
+ {
+ arguments += "l";
+ }
+ else if (name[i]=='S' || name[i]=='s')
+ {
+ arguments += 'S';
+ }
+ else if (name[i]=='.' || name[i]=='f')
+ {
+ arguments += 'f';
+ }
+ }
+ }
+ }
+
+ if (!arguments.empty())
+ {
+ mExprParser.reset();
+ mExprParser.parseArguments (arguments, scanner, mCode, true);
+ }
+
+ mName = name;
+ mButtons = 0;
+
+ mState = MessageButtonState;
+ return true;
+ }
+
+ if (mState==MessageButtonState || mState==MessageButtonCommaState)
+ {
+ Generator::pushString (mCode, mLiterals, name);
+ mState = MessageButtonState;
+ ++mButtons;
+ return true;
+ }
+
+ if (mState==BeginState && getContext().isId (name))
+ {
+ mState = PotentialExplicitState;
+ mExplicit = Misc::StringUtils::lowerCase (name);
+ return true;
+ }
+
+ if (mState==BeginState && mAllowExpression)
+ {
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+
+ char type = mLocals.getType (name2);
+
+ if (type!=' ')
+ {
+ scanner.putbackName (name, loc);
+ parseExpression (scanner, loc);
+ return true;
+ }
+
+ type = getContext().getGlobalType (name2);
+
+ if (type!=' ')
+ {
+ scanner.putbackName (name, loc);
+ parseExpression (scanner, loc);
+ return true;
+ }
+ }
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (mState==BeginState || mState==ExplicitState)
+ {
+ switch (keyword)
+ {
+ case Scanner::K_enable:
+
+ Generator::enable (mCode, mLiterals, mExplicit);
+ mState = EndState;
+ return true;
+
+ case Scanner::K_disable:
+
+ Generator::disable (mCode, mLiterals, mExplicit);
+ mState = EndState;
+ return true;
+ }
+
+ // check for custom extensions
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ std::string argumentType;
+
+ if (extensions->isInstruction (keyword, argumentType, mState==ExplicitState))
+ {
+ int optionals = mExprParser.parseArguments (argumentType, scanner, mCode, true);
+
+ extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals);
+ mState = EndState;
+ return true;
+ }
+ }
+
+ if (mAllowExpression)
+ {
+ if (keyword==Scanner::K_getdisabled || keyword==Scanner::K_getdistance)
+ {
+ scanner.putbackKeyword (keyword, loc);
+ parseExpression (scanner, loc);
+ mState = EndState;
+ return true;
+ }
+
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ char returnType;
+ std::string argumentType;
+
+ if (extensions->isFunction (keyword, returnType, argumentType,
+ !mExplicit.empty()))
+ {
+ scanner.putbackKeyword (keyword, loc);
+ parseExpression (scanner, loc);
+ mState = EndState;
+ return true;
+ }
+ }
+ }
+ }
+
+ if (mState==BeginState)
+ {
+ switch (keyword)
+ {
+ case Scanner::K_short: mState = ShortState; return true;
+ case Scanner::K_long: mState = LongState; return true;
+ case Scanner::K_float: mState = FloatState; return true;
+ case Scanner::K_set: mState = SetState; return true;
+ case Scanner::K_messagebox: mState = MessageState; return true;
+
+ case Scanner::K_return:
+
+ Generator::exit (mCode);
+ mState = EndState;
+ return true;
+
+ case Scanner::K_startscript:
+
+ mExprParser.parseArguments ("c", scanner, mCode, true);
+ Generator::startScript (mCode);
+ mState = EndState;
+ return true;
+
+ case Scanner::K_stopscript:
+
+ mExprParser.parseArguments ("c", scanner, mCode, true);
+ Generator::stopScript (mCode);
+ mState = EndState;
+ return true;
+ }
+ }
+ else if (mState==SetLocalVarState && keyword==Scanner::K_to)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ std::vector<Interpreter::Type_Code> code;
+ char type = mExprParser.append (code);
+
+ Generator::assignToLocal (mCode, mLocals.getType (mName),
+ mLocals.getIndex (mName), code, type);
+
+ mState = EndState;
+ return true;
+ }
+ else if (mState==SetGlobalVarState && keyword==Scanner::K_to)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ std::vector<Interpreter::Type_Code> code;
+ char type = mExprParser.append (code);
+
+ Generator::assignToGlobal (mCode, mLiterals, mType, mName, code, type);
+
+ mState = EndState;
+ return true;
+ }
+ else if (mState==SetMemberVarState2 && keyword==Scanner::K_to)
+ {
+ mExprParser.reset();
+ scanner.scan (mExprParser);
+
+ std::vector<Interpreter::Type_Code> code;
+ char type = mExprParser.append (code);
+
+ Generator::assignToMember (mCode, mLiterals, mType, mMemberName, mName, code, type);
+
+ mState = EndState;
+ return true;
+ }
+
+ if (mAllowExpression)
+ {
+ if (keyword==Scanner::K_getsquareroot || keyword==Scanner::K_menumode ||
+ keyword==Scanner::K_random || keyword==Scanner::K_scriptrunning ||
+ keyword==Scanner::K_getsecondspassed)
+ {
+ scanner.putbackKeyword (keyword, loc);
+ parseExpression (scanner, loc);
+ mState = EndState;
+ return true;
+ }
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+ }
+
+ bool LineParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_newline && (mState==EndState || mState==BeginState))
+ return false;
+
+ if (code==Scanner::S_comma && mState==MessageState)
+ {
+ mState = MessageCommaState;
+ return true;
+ }
+
+ if (code==Scanner::S_ref && mState==PotentialExplicitState)
+ {
+ mState = ExplicitState;
+ return true;
+ }
+
+ if (code==Scanner::S_member && mState==PotentialExplicitState)
+ {
+ mState = MemberState;
+ parseExpression (scanner, loc);
+ mState = EndState;
+ return true;
+ }
+
+ if (code==Scanner::S_newline && mState==MessageButtonState)
+ {
+ Generator::message (mCode, mLiterals, mName, mButtons);
+ return false;
+ }
+
+ if (code==Scanner::S_comma && mState==MessageButtonState)
+ {
+ mState = MessageButtonCommaState;
+ return true;
+ }
+
+ if (code==Scanner::S_member && mState==SetPotentialMemberVarState)
+ {
+ mState = SetMemberVarState;
+ return true;
+ }
+
+ if (mAllowExpression && mState==BeginState &&
+ (code==Scanner::S_open || code==Scanner::S_minus))
+ {
+ scanner.putbackSpecial (code, loc);
+ parseExpression (scanner, loc);
+ mState = EndState;
+ return true;
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ void LineParser::reset()
+ {
+ mState = BeginState;
+ mName.clear();
+ mExplicit.clear();
+ }
+}
diff --git a/components/compiler/lineparser.hpp b/components/compiler/lineparser.hpp
new file mode 100644
index 0000000000..aa74cd232f
--- /dev/null
+++ b/components/compiler/lineparser.hpp
@@ -0,0 +1,79 @@
+#ifndef COMPILER_LINEPARSER_H_INCLUDED
+#define COMPILER_LINEPARSER_H_INCLUDED
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+#include "parser.hpp"
+#include "exprparser.hpp"
+
+namespace Compiler
+{
+ class Locals;
+ class Literals;
+
+ /// \brief Line parser, to be used in console scripts and as part of ScriptParser
+
+ class LineParser : public Parser
+ {
+ enum State
+ {
+ BeginState,
+ ShortState, LongState, FloatState,
+ SetState, SetLocalVarState, SetGlobalVarState, SetPotentialMemberVarState,
+ SetMemberVarState, SetMemberVarState2,
+ MessageState, MessageCommaState, MessageButtonState, MessageButtonCommaState,
+ EndState,
+ PotentialExplicitState, ExplicitState, MemberState
+ };
+
+ Locals& mLocals;
+ Literals& mLiterals;
+ std::vector<Interpreter::Type_Code>& mCode;
+ State mState;
+ std::string mName;
+ std::string mMemberName;
+ int mButtons;
+ std::string mExplicit;
+ char mType;
+ ExprParser mExprParser;
+ bool mAllowExpression;
+
+ void parseExpression (Scanner& scanner, const TokenLoc& loc);
+
+ public:
+
+ LineParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ Literals& literals, std::vector<Interpreter::Type_Code>& code,
+ bool allowExpression = false);
+ ///< \param allowExpression Allow lines consisting of a naked expression
+ /// (result is send to the messagebox interface)
+
+ virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle an int token.
+ /// \return fetch another token?
+
+ virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a float token.
+ /// \return fetch another token?
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ void reset();
+ ///< Reset parser to clean state.
+ };
+}
+
+#endif
diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp
new file mode 100644
index 0000000000..626b03afbe
--- /dev/null
+++ b/components/compiler/literals.cpp
@@ -0,0 +1,94 @@
+
+#include "literals.hpp"
+
+#include <algorithm>
+
+namespace Compiler
+{
+ int Literals::getIntegerSize() const
+ {
+ return mIntegers.size() * sizeof (Interpreter::Type_Integer);
+ }
+
+ int Literals::getFloatSize() const
+ {
+ return mFloats.size() * sizeof (Interpreter::Type_Float);
+ }
+
+ int Literals::getStringSize() const
+ {
+ int size = 0;
+
+ for (std::vector<std::string>::const_iterator iter (mStrings.begin());
+ iter!=mStrings.end(); ++iter)
+ size += static_cast<int> (iter->size()) + 1;
+
+ if (size % 4) // padding
+ size += 4 - size % 4;
+
+ return size;
+ }
+
+ void Literals::append (std::vector<Interpreter::Type_Code>& code) const
+ {
+ for (std::vector<Interpreter::Type_Integer>::const_iterator iter (mIntegers.begin());
+ iter!=mIntegers.end(); ++iter)
+ code.push_back (*reinterpret_cast<const Interpreter::Type_Code *> (&*iter));
+
+ for (std::vector<Interpreter::Type_Float>::const_iterator iter (mFloats.begin());
+ iter!=mFloats.end(); ++iter)
+ code.push_back (*reinterpret_cast<const Interpreter::Type_Code *> (&*iter));
+
+ int stringBlockSize = getStringSize();
+ int size = static_cast<int> (code.size());
+
+ code.resize (size+stringBlockSize/4);
+
+ int offset = 0;
+
+ for (std::vector<std::string>::const_iterator iter (mStrings.begin());
+ iter!=mStrings.end(); ++iter)
+ {
+ int stringSize = iter->size()+1;
+
+ std::copy (iter->c_str(), iter->c_str()+stringSize,
+ reinterpret_cast<char *> (&code[size]) + offset);
+ offset += stringSize;
+ }
+ }
+
+ int Literals::addInteger (Interpreter::Type_Integer value)
+ {
+ int index = static_cast<int> (mIntegers.size());
+
+ mIntegers.push_back (value);
+
+ return index;
+ }
+
+ int Literals::addFloat (Interpreter::Type_Float value)
+ {
+ int index = static_cast<int> (mFloats.size());
+
+ mFloats.push_back (value);
+
+ return index;
+ }
+
+ int Literals::addString (const std::string& value)
+ {
+ int index = static_cast<int> (mStrings.size());
+
+ mStrings.push_back (value);
+
+ return index;
+ }
+
+ void Literals::clear()
+ {
+ mIntegers.clear();
+ mFloats.clear();
+ mStrings.clear();
+ }
+}
+
diff --git a/components/compiler/literals.hpp b/components/compiler/literals.hpp
new file mode 100644
index 0000000000..b18c864791
--- /dev/null
+++ b/components/compiler/literals.hpp
@@ -0,0 +1,49 @@
+#ifndef COMPILER_LITERALS_H_INCLUDED
+#define COMPILER_LITERALS_H_INCLUDED
+
+#include <string>
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+namespace Compiler
+{
+ /// \brief Literal values.
+
+ class Literals
+ {
+ std::vector<Interpreter::Type_Integer> mIntegers;
+ std::vector<Interpreter::Type_Float> mFloats;
+ std::vector<std::string> mStrings;
+
+ public:
+
+ int getIntegerSize() const;
+ ///< Return size of integer block (in bytes).
+
+ int getFloatSize() const;
+ ///< Return size of float block (in bytes).
+
+ int getStringSize() const;
+ ///< Return size of string block (in bytes).
+
+ void append (std::vector<Interpreter::Type_Code>& code) const;
+ ///< Apepnd literal blocks to code.
+ /// \note code blocks will be padded for 32-bit alignment.
+
+ int addInteger (Interpreter::Type_Integer value);
+ ///< add integer liternal and return index.
+
+ int addFloat (Interpreter::Type_Float value);
+ ///< add float literal and return value.
+
+ int addString (const std::string& value);
+ ///< add string literal and return value.
+
+ void clear();
+ ///< remove all literals.
+ };
+}
+
+#endif
+
diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp
new file mode 100644
index 0000000000..d93e738494
--- /dev/null
+++ b/components/compiler/locals.cpp
@@ -0,0 +1,110 @@
+
+#include "locals.hpp"
+
+#include <cassert>
+#include <stdexcept>
+#include <algorithm>
+#include <ostream>
+#include <iterator>
+
+namespace Compiler
+{
+ const std::vector<std::string>& Locals::get (char type) const
+ {
+ switch (type)
+ {
+ case 's': return mShorts;
+ case 'l': return mLongs;
+ case 'f': return mFloats;
+ }
+
+ throw std::logic_error ("unknown variable type");
+ }
+
+ int Locals::searchIndex (char type, const std::string& name) const
+ {
+ const std::vector<std::string>& collection = get (type);
+
+ std::vector<std::string>::const_iterator iter =
+ std::find (collection.begin(), collection.end(), name);
+
+ if (iter==collection.end())
+ return -1;
+
+ return iter-collection.begin();
+ }
+
+ bool Locals::search (char type, const std::string& name) const
+ {
+ return searchIndex (type, name)!=-1;
+ }
+
+ std::vector<std::string>& Locals::get (char type)
+ {
+ switch (type)
+ {
+ case 's': return mShorts;
+ case 'l': return mLongs;
+ case 'f': return mFloats;
+ }
+
+ throw std::logic_error ("unknown variable type");
+ }
+
+ char Locals::getType (const std::string& name) const
+ {
+ if (search ('s', name))
+ return 's';
+
+ if (search ('l', name))
+ return 'l';
+
+ if (search ('f', name))
+ return 'f';
+
+ return ' ';
+ }
+
+ int Locals::getIndex (const std::string& name) const
+ {
+ int index = searchIndex ('s', name);
+
+ if (index!=-1)
+ return index;
+
+ index = searchIndex ('l', name);
+
+ if (index!=-1)
+ return index;
+
+ return searchIndex ('f', name);
+ }
+
+ void Locals::write (std::ostream& localFile) const
+ {
+ localFile
+ << get ('s').size() << ' '
+ << get ('l').size() << ' '
+ << get ('f').size() << std::endl;
+
+ std::copy (get ('s').begin(), get ('s').end(),
+ std::ostream_iterator<std::string> (localFile, " "));
+ std::copy (get ('l').begin(), get ('l').end(),
+ std::ostream_iterator<std::string> (localFile, " "));
+ std::copy (get ('f').begin(), get ('f').end(),
+ std::ostream_iterator<std::string> (localFile, " "));
+ }
+
+ void Locals::declare (char type, const std::string& name)
+ {
+ get (type).push_back (name);
+ }
+
+ void Locals::clear()
+ {
+ get ('s').clear();
+ get ('l').clear();
+ get ('f').clear();
+ }
+}
+
diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp
new file mode 100644
index 0000000000..e54b7798c5
--- /dev/null
+++ b/components/compiler/locals.hpp
@@ -0,0 +1,45 @@
+#ifndef COMPILER_LOCALS_H_INCLUDED
+#define COMPILER_LOCALS_H_INCLUDED
+
+#include <vector>
+#include <string>
+#include <iosfwd>
+
+namespace Compiler
+{
+ /// \brief Local variable declarations
+
+ class Locals
+ {
+ std::vector<std::string> mShorts;
+ std::vector<std::string> mLongs;
+ std::vector<std::string> mFloats;
+
+ int searchIndex (char type, const std::string& name) const;
+
+ bool search (char type, const std::string& name) const;
+
+ std::vector<std::string>& get (char type);
+
+ public:
+
+ char getType (const std::string& name) const;
+ ///< 's': short, 'l': long, 'f': float, ' ': does not exist.
+
+ int getIndex (const std::string& name) const;
+ ///< return index for local variable \a name (-1: does not exist).
+
+ const std::vector<std::string>& get (char type) const;
+
+ void write (std::ostream& localFile) const;
+ ///< write declarations to file.
+
+ void declare (char type, const std::string& name);
+ ///< declares a variable.
+
+ void clear();
+ ///< remove all declarations.
+ };
+}
+
+#endif
diff --git a/components/compiler/nullerrorhandler.cpp b/components/compiler/nullerrorhandler.cpp
new file mode 100644
index 0000000000..3071701e8a
--- /dev/null
+++ b/components/compiler/nullerrorhandler.cpp
@@ -0,0 +1,6 @@
+
+#include "nullerrorhandler.hpp"
+
+void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {}
+
+void Compiler::NullErrorHandler::report (const std::string& message, Type type) {} \ No newline at end of file
diff --git a/components/compiler/nullerrorhandler.hpp b/components/compiler/nullerrorhandler.hpp
new file mode 100644
index 0000000000..bb4db99a28
--- /dev/null
+++ b/components/compiler/nullerrorhandler.hpp
@@ -0,0 +1,21 @@
+
+#ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED
+#define COMPILER_NULLERRORHANDLER_H_INCLUDED
+
+#include "errorhandler.hpp"
+
+namespace Compiler
+{
+ /// \brief Error handler implementation: Ignore all error messages
+
+ class NullErrorHandler : public ErrorHandler
+ {
+ virtual void report (const std::string& message, const TokenLoc& loc, Type type);
+ ///< Report error to the user.
+
+ virtual void report (const std::string& message, Type type);
+ ///< Report a file related error
+ };
+}
+
+#endif
diff --git a/components/compiler/opcodes.cpp b/components/compiler/opcodes.cpp
new file mode 100644
index 0000000000..8617062d63
--- /dev/null
+++ b/components/compiler/opcodes.cpp
@@ -0,0 +1,13 @@
+#include "opcodes.hpp"
+
+namespace Compiler
+{
+ namespace Control
+ {
+ const char *controls[numberOfControls] =
+ {
+ "playercontrols", "playerfighting", "playerjumping", "playerlooking", "playermagic",
+ "playerviewswitch", "vanitymode"
+ };
+ }
+} \ No newline at end of file
diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp
new file mode 100644
index 0000000000..c4e2c1bc6b
--- /dev/null
+++ b/components/compiler/opcodes.hpp
@@ -0,0 +1,416 @@
+#ifndef COMPILER_OPCODES_H
+#define COMPILER_OPCODES_H
+
+namespace Compiler
+{
+ namespace Ai
+ {
+ const int opcodeAiTravel = 0x20000;
+ const int opcodeAiTravelExplicit = 0x20001;
+ const int opcodeAiEscort = 0x20002;
+ const int opcodeAiEscortExplicit = 0x20003;
+ const int opcodeGetAiPackageDone = 0x200007c;
+ const int opcodeGetAiPackageDoneExplicit = 0x200007d;
+ const int opcodeGetCurrentAiPackage = 0x20001ef;
+ const int opcodeGetCurrentAiPackageExplicit = 0x20001f0;
+ const int opcodeGetDetected = 0x20001f1;
+ const int opcodeGetDetectedExplicit = 0x20001f2;
+ const int opcodeAiWander = 0x20010;
+ const int opcodeAiWanderExplicit = 0x20011;
+ const int opcodeAIActivate = 0x2001e;
+ const int opcodeAIActivateExplicit = 0x2001f;
+ const int opcodeAiEscortCell = 0x20020;
+ const int opcodeAiEscortCellExplicit = 0x20021;
+ const int opcodeAiFollow = 0x20022;
+ const int opcodeAiFollowExplicit = 0x20023;
+ const int opcodeAiFollowCell = 0x20024;
+ const int opcodeAiFollowCellExplicit = 0x20025;
+ const int opcodeSetHello = 0x200015e;
+ const int opcodeSetHelloExplicit = 0x200015d;
+ const int opcodeSetFight = 0x200015e;
+ const int opcodeSetFightExplicit = 0x200015f;
+ const int opcodeSetFlee = 0x2000160;
+ const int opcodeSetFleeExplicit = 0x2000161;
+ const int opcodeSetAlarm = 0x2000162;
+ const int opcodeSetAlarmExplicit = 0x2000163;
+ const int opcodeModHello = 0x20001b7;
+ const int opcodeModHelloExplicit = 0x20001b8;
+ const int opcodeModFight = 0x20001b9;
+ const int opcodeModFightExplicit = 0x20001ba;
+ const int opcodeModFlee = 0x20001bb;
+ const int opcodeModFleeExplicit = 0x20001bc;
+ const int opcodeModAlarm = 0x20001bd;
+ const int opcodeModAlarmExplicit = 0x20001be;
+ const int opcodeGetHello = 0x20001bf;
+ const int opcodeGetHelloExplicit = 0x20001c0;
+ const int opcodeGetFight = 0x20001c1;
+ const int opcodeGetFightExplicit = 0x20001c2;
+ const int opcodeGetFlee = 0x20001c3;
+ const int opcodeGetFleeExplicit = 0x20001c4;
+ const int opcodeGetAlarm = 0x20001c5;
+ const int opcodeGetAlarmExplicit = 0x20001c6;
+ }
+
+ namespace Animation
+ {
+ const int opcodeSkipAnim = 0x2000138;
+ const int opcodeSkipAnimExplicit = 0x2000139;
+ const int opcodePlayAnim = 0x20006;
+ const int opcodePlayAnimExplicit = 0x20007;
+ const int opcodeLoopAnim = 0x20008;
+ const int opcodeLoopAnimExplicit = 0x20009;
+ }
+
+ namespace Cell
+ {
+ const int opcodeCellChanged = 0x2000000;
+ const int opcodeCOC = 0x2000026;
+ const int opcodeCOE = 0x200008e;
+ const int opcodeGetInterior = 0x2000131;
+ const int opcodeGetPCCell = 0x2000136;
+ const int opcodeGetWaterLevel = 0x2000141;
+ const int opcodeSetWaterLevel = 0x2000142;
+ const int opcodeModWaterLevel = 0x2000143;
+ }
+
+ namespace Console
+ {
+
+ }
+
+ namespace Container
+ {
+ const int opcodeAddItem = 0x2000076;
+ const int opcodeAddItemExplicit = 0x2000077;
+ const int opcodeGetItemCount = 0x2000078;
+ const int opcodeGetItemCountExplicit = 0x2000079;
+ const int opcodeRemoveItem = 0x200007a;
+ const int opcodeRemoveItemExplicit = 0x200007b;
+ const int opcodeEquip = 0x20001b3;
+ const int opcodeEquipExplicit = 0x20001b4;
+ const int opcodeGetArmorType = 0x20001d1;
+ const int opcodeGetArmorTypeExplicit = 0x20001d2;
+ const int opcodeHasItemEquipped = 0x20001d5;
+ const int opcodeHasItemEquippedExplicit = 0x20001d6;
+ const int opcodeHasSoulGem = 0x20001de;
+ const int opcodeHasSoulGemExplicit = 0x20001df;
+ const int opcodeGetWeaponType = 0x20001e0;
+ const int opcodeGetWeaponTypeExplicit = 0x20001e1;
+ }
+
+ namespace Control
+ {
+ const int numberOfControls = 7;
+
+ extern const char *controls[numberOfControls];
+
+ const int opcodeEnable = 0x200007e;
+ const int opcodeDisable = 0x2000085;
+ const int opcodeToggleCollision = 0x2000130;
+ const int opcodeClearForceRun = 0x2000154;
+ const int opcodeClearForceRunExplicit = 0x2000155;
+ const int opcodeForceRun = 0x2000156;
+ const int opcodeForceRunExplicit = 0x2000157;
+ const int opcodeClearForceSneak = 0x2000158;
+ const int opcodeClearForceSneakExplicit = 0x2000159;
+ const int opcodeForceSneak = 0x200015a;
+ const int opcodeForceSneakExplicit = 0x200015b;
+ const int opcodeGetDisabled = 0x2000175;
+ const int opcodeGetPcRunning = 0x20001c9;
+ const int opcodeGetPcSneaking = 0x20001ca;
+ const int opcodeGetForceRun = 0x20001cb;
+ const int opcodeGetForceSneak = 0x20001cc;
+ const int opcodeGetForceRunExplicit = 0x20001cd;
+ const int opcodeGetForceSneakExplicit = 0x20001ce;
+ }
+
+ namespace Dialogue
+ {
+ const int opcodeJournal = 0x2000133;
+ const int opcodeSetJournalIndex = 0x2000134;
+ const int opcodeGetJournalIndex = 0x2000135;
+ const int opcodeAddTopic = 0x200013a;
+ const int opcodeChoice = 0x2000a;
+ const int opcodeForceGreeting = 0x200014f;
+ const int opcodeForceGreetingExplicit = 0x2000150;
+ const int opcodeGoodbye = 0x2000152;
+ const int opcodeSetReputation = 0x20001ad;
+ const int opcodeModReputation = 0x20001ae;
+ const int opcodeSetReputationExplicit = 0x20001af;
+ const int opcodeModReputationExplicit = 0x20001b0;
+ const int opcodeGetReputation = 0x20001b1;
+ const int opcodeGetReputationExplicit = 0x20001b2;
+ const int opcodeSameFaction = 0x20001b5;
+ const int opcodeSameFactionExplicit = 0x20001b6;
+ }
+
+ namespace Gui
+ {
+ const int opcodeEnableBirthMenu = 0x200000e;
+ const int opcodeEnableClassMenu = 0x200000f;
+ const int opcodeEnableNameMenu = 0x2000010;
+ const int opcodeEnableRaceMenu = 0x2000011;
+ const int opcodeEnableStatsReviewMenu = 0x2000012;
+ const int opcodeEnableInventoryMenu = 0x2000013;
+ const int opcodeEnableMagicMenu = 0x2000014;
+ const int opcodeEnableMapMenu = 0x2000015;
+ const int opcodeEnableStatsMenu = 0x2000016;
+ const int opcodeEnableRest = 0x2000017;
+ const int opcodeShowRestMenu = 0x2000018;
+ const int opcodeGetButtonPressed = 0x2000137;
+ const int opcodeToggleFogOfWar = 0x2000145;
+ const int opcodeToggleFullHelp = 0x2000151;
+ const int opcodeShowMap = 0x20001a0;
+ const int opcodeFillMap = 0x20001a1;
+ }
+
+ namespace Misc
+ {
+ const int opcodeXBox = 0x200000c;
+ const int opcodeOnActivate = 0x200000d;
+ const int opcodeActivate = 0x2000075;
+ const int opcodeLock = 0x20004;
+ const int opcodeLockExplicit = 0x20005;
+ const int opcodeUnlock = 0x200008c;
+ const int opcodeUnlockExplicit = 0x200008d;
+ const int opcodeToggleCollisionDebug = 0x2000132;
+ const int opcodeToggleCollisionBoxes = 0x20001ac;
+ const int opcodeToggleWireframe = 0x200013b;
+ const int opcodeFadeIn = 0x200013c;
+ const int opcodeFadeOut = 0x200013d;
+ const int opcodeFadeTo = 0x200013e;
+ const int opcodeToggleWater = 0x2000144;
+ const int opcodeTogglePathgrid = 0x2000146;
+ const int opcodeDontSaveObject = 0x2000153;
+ const int opcodeToggleVanityMode = 0x2000174;
+ const int opcodeGetPcSleep = 0x200019f;
+ const int opcodeWakeUpPc = 0x20001a2;
+ const int opcodeGetLocked = 0x20001c7;
+ const int opcodeGetLockedExplicit = 0x20001c8;
+ const int opcodeGetEffect = 0x20001cf;
+ const int opcodeGetEffectExplicit = 0x20001d0;
+ const int opcodeAddSoulGem = 0x20001f3;
+ const int opcodeAddSoulGemExplicit = 0x20001f4;
+ const int opcodeRemoveSoulGem = 0x20001f5;
+ const int opcodeRemoveSoulGemExplicit = 0x20001f6;
+ const int opcodeDrop = 0x20001f8;
+ const int opcodeDropExplicit = 0x20001f9;
+ const int opcodeDropSoulGem = 0x20001fa;
+ const int opcodeDropSoulGemExplicit = 0x20001fb;
+ const int opcodeGetAttacked = 0x20001d3;
+ const int opcodeGetAttackedExplicit = 0x20001d4;
+ const int opcodeGetWeaponDrawn = 0x20001d7;
+ const int opcodeGetWeaponDrawnExplicit = 0x20001d8;
+ const int opcodeGetSpellEffects = 0x20001db;
+ const int opcodeGetSpellEffectsExplicit = 0x20001dc;
+ const int opcodeGetCurrentTime = 0x20001dd;
+ const int opcodeSetDelete = 0x20001e5;
+ const int opcodeSetDeleteExplicit = 0x20001e6;
+ const int opcodeGetSquareRoot = 0x20001e7;
+ const int opcodeFall = 0x200020a;
+ const int opcodeFallExplicit = 0x200020b;
+ const int opcodeGetStandingPc = 0x200020c;
+ const int opcodeGetStandingPcExplicit = 0x200020d;
+ const int opcodeGetStandingActor = 0x200020e;
+ const int opcodeGetStandingActorExplicit = 0x200020f;
+ const int opcodeGetWindSpeed = 0x2000212;
+ const int opcodePlayBink = 0x20001f7;
+ const int opcodeHitOnMe = 0x2000213;
+ const int opcodeHitOnMeExplicit = 0x2000214;
+ const int opcodeDisableTeleporting = 0x2000215;
+ const int opcodeEnableTeleporting = 0x2000216;
+ const int opcodeShowVars = 0x200021d;
+ const int opcodeShowVarsExplicit = 0x200021e;
+ }
+
+ namespace Sky
+ {
+ const int opcodeToggleSky = 0x2000021;
+ const int opcodeTurnMoonWhite = 0x2000022;
+ const int opcodeTurnMoonRed = 0x2000023;
+ const int opcodeGetMasserPhase = 0x2000024;
+ const int opcodeGetSecundaPhase = 0x2000025;
+ const int opcodeGetCurrentWeather = 0x200013f;
+ const int opcodeChangeWeather = 0x2000140;
+ const int opcodeModRegion = 0x20026;
+ }
+
+ namespace Sound
+ {
+ const int opcodeSay = 0x2000001;
+ const int opcodeSayDone = 0x2000002;
+ const int opcodeStreamMusic = 0x2000003;
+ const int opcodePlaySound = 0x2000004;
+ const int opcodePlaySoundVP = 0x2000005;
+ const int opcodePlaySound3D = 0x2000006;
+ const int opcodePlaySound3DVP = 0x2000007;
+ const int opcodePlayLoopSound3D = 0x2000008;
+ const int opcodePlayLoopSound3DVP = 0x2000009;
+ const int opcodeStopSound = 0x200000a;
+ const int opcodeGetSoundPlaying = 0x200000b;
+
+ const int opcodeSayExplicit = 0x2000019;
+ const int opcodeSayDoneExplicit = 0x200001a;
+ const int opcodePlaySound3DExplicit = 0x200001b;
+ const int opcodePlaySound3DVPExplicit = 0x200001c;
+ const int opcodePlayLoopSound3DExplicit = 0x200001d;
+ const int opcodePlayLoopSound3DVPExplicit = 0x200001e;
+ const int opcodeStopSoundExplicit = 0x200001f;
+ const int opcodeGetSoundPlayingExplicit = 0x2000020;
+ }
+
+ namespace Stats
+ {
+ const int numberOfAttributes = 8;
+ const int numberOfDynamics = 3;
+ const int numberOfSkills = 27;
+
+ const int opcodeGetAttribute = 0x2000027;
+ const int opcodeGetAttributeExplicit = 0x200002f;
+ const int opcodeSetAttribute = 0x2000037;
+ const int opcodeSetAttributeExplicit = 0x200003f;
+ const int opcodeModAttribute = 0x2000047;
+ const int opcodeModAttributeExplicit = 0x200004f;
+
+ const int opcodeGetDynamic = 0x2000057;
+ const int opcodeGetDynamicExplicit = 0x200005a;
+ const int opcodeSetDynamic = 0x200005d;
+ const int opcodeSetDynamicExplicit = 0x2000060;
+ const int opcodeModDynamic = 0x2000063;
+ const int opcodeModDynamicExplicit = 0x2000066;
+ const int opcodeModCurrentDynamic = 0x2000069;
+ const int opcodeModCurrentDynamicExplicit = 0x200006c;
+ const int opcodeGetDynamicGetRatio = 0x200006f;
+ const int opcodeGetDynamicGetRatioExplicit = 0x2000072;
+
+ const int opcodeGetSkill = 0x200008e;
+ const int opcodeGetSkillExplicit = 0x20000a9;
+ const int opcodeSetSkill = 0x20000c4;
+ const int opcodeSetSkillExplicit = 0x20000df;
+ const int opcodeModSkill = 0x20000fa;
+ const int opcodeModSkillExplicit = 0x2000115;
+
+ const int opcodeGetPCCrimeLevel = 0x20001ec;
+ const int opcodeSetPCCrimeLevel = 0x20001ed;
+ const int opcodeModPCCrimeLevel = 0x20001ee;
+
+ const int opcodeAddSpell = 0x2000147;
+ const int opcodeAddSpellExplicit = 0x2000148;
+ const int opcodeRemoveSpell = 0x2000149;
+ const int opcodeRemoveSpellExplicit = 0x200014a;
+ const int opcodeGetSpell = 0x200014b;
+ const int opcodeGetSpellExplicit = 0x200014c;
+
+ const int opcodePCRaiseRank = 0x2000b;
+ const int opcodePCLowerRank = 0x2000c;
+ const int opcodePCJoinFaction = 0x2000d;
+ const int opcodeGetPCRank = 0x2000e;
+ const int opcodeGetPCRankExplicit = 0x2000f;
+ const int opcodeModDisposition = 0x200014d;
+ const int opcodeModDispositionExplicit = 0x200014e;
+ const int opcodeSetDisposition = 0x20001a4;
+ const int opcodeSetDispositionExplicit = 0x20001a5;
+ const int opcodeGetDisposition = 0x20001a6;
+ const int opcodeGetDispositionExplicit = 0x20001a7;
+
+ const int opcodeGetLevel = 0x200018c;
+ const int opcodeGetLevelExplicit = 0x200018d;
+ const int opcodeSetLevel = 0x200018e;
+ const int opcodeSetLevelExplicit = 0x200018f;
+
+ const int opcodeGetDeadCount = 0x20001a3;
+
+ const int opcodeGetPCFacRep = 0x20012;
+ const int opcodeGetPCFacRepExplicit = 0x20013;
+ const int opcodeSetPCFacRep = 0x20014;
+ const int opcodeSetPCFacRepExplicit = 0x20015;
+ const int opcodeModPCFacRep = 0x20016;
+ const int opcodeModPCFacRepExplicit = 0x20017;
+
+ const int opcodeGetCommonDisease = 0x20001a8;
+ const int opcodeGetCommonDiseaseExplicit = 0x20001a9;
+ const int opcodeGetBlightDisease = 0x20001aa;
+ const int opcodeGetBlightDiseaseExplicit = 0x20001ab;
+
+ const int opcodeGetRace = 0x20001d9;
+ const int opcodeGetRaceExplicit = 0x20001da;
+
+ const int opcodePcExpelled = 0x20018;
+ const int opcodePcExpelledExplicit = 0x20019;
+ const int opcodePcExpell = 0x2001a;
+ const int opcodePcExpellExplicit = 0x2001b;
+ const int opcodePcClearExpelled = 0x2001c;
+ const int opcodePcClearExpelledExplicit = 0x2001d;
+ const int opcodeRaiseRank = 0x20001e8;
+ const int opcodeRaiseRankExplicit = 0x20001e9;
+ const int opcodeLowerRank = 0x20001ea;
+ const int opcodeLowerRankExplicit = 0x20001eb;
+ const int opcodeOnDeath = 0x20001fc;
+ const int opcodeOnDeathExplicit = 0x2000205;
+
+ const int opcodeBecomeWerewolf = 0x2000217;
+ const int opcodeBecomeWerewolfExplicit = 0x2000218;
+ const int opcodeUndoWerewolf = 0x2000219;
+ const int opcodeUndoWerewolfExplicit = 0x200021a;
+ const int opcodeSetWerewolfAcrobatics = 0x200021b;
+ const int opcodeSetWerewolfAcrobaticsExplicit = 0x200021c;
+ const int opcodeIsWerewolf = 0x20001fd;
+ const int opcodeIsWerewolfExplicit = 0x20001fe;
+
+ const int opcodeGetWerewolfKills = 0x20001e2;
+ }
+
+ namespace Transformation
+ {
+ const int opcodeSetScale = 0x2000164;
+ const int opcodeSetScaleExplicit = 0x2000165;
+ const int opcodeSetAngle = 0x2000166;
+ const int opcodeSetAngleExplicit = 0x2000167;
+ const int opcodeGetScale = 0x2000168;
+ const int opcodeGetScaleExplicit = 0x2000169;
+ const int opcodeGetAngle = 0x200016a;
+ const int opcodeGetAngleExplicit = 0x200016b;
+ const int opcodeGetPos = 0x2000190;
+ const int opcodeGetPosExplicit = 0x2000191;
+ const int opcodeSetPos = 0x2000192;
+ const int opcodeSetPosExplicit = 0x2000193;
+ const int opcodeGetStartingPos = 0x2000194;
+ const int opcodeGetStartingPosExplicit = 0x2000195;
+ const int opcodeGetStartingAngle = 0x2000210;
+ const int opcodeGetStartingAngleExplicit = 0x2000211;
+ const int opcodePosition = 0x2000196;
+ const int opcodePositionExplicit = 0x2000197;
+ const int opcodePositionCell = 0x2000198;
+ const int opcodePositionCellExplicit = 0x2000199;
+
+ const int opcodePlaceItemCell = 0x200019a;
+ const int opcodePlaceItem = 0x200019b;
+ const int opcodePlaceAtPc = 0x200019c;
+ const int opcodePlaceAtMe = 0x200019d;
+ const int opcodePlaceAtMeExplicit = 0x200019e;
+ const int opcodeModScale = 0x20001e3;
+ const int opcodeModScaleExplicit = 0x20001e4;
+ const int opcodeRotate = 0x20001ff;
+ const int opcodeRotateExplicit = 0x2000200;
+ const int opcodeRotateWorld = 0x2000201;
+ const int opcodeRotateWorldExplicit = 0x2000202;
+ const int opcodeSetAtStart = 0x2000203;
+ const int opcodeSetAtStartExplicit = 0x2000204;
+ const int opcodeMove = 0x2000206;
+ const int opcodeMoveExplicit = 0x2000207;
+ const int opcodeMoveWorld = 0x2000208;
+ const int opcodeMoveWorldExplicit = 0x2000209;
+ }
+
+ namespace User
+ {
+ const int opcodeUser1 = 0x200016c;
+ const int opcodeUser2 = 0x200016d;
+ const int opcodeUser3 = 0x200016e;
+ const int opcodeUser3Explicit = 0x200016f;
+ const int opcodeUser4 = 0x2000170;
+ const int opcodeUser4Explicit = 0x2000171;
+ }
+}
+
+#endif
diff --git a/components/compiler/output.cpp b/components/compiler/output.cpp
new file mode 100644
index 0000000000..46e04b8dc5
--- /dev/null
+++ b/components/compiler/output.cpp
@@ -0,0 +1,74 @@
+
+#include "output.hpp"
+
+#include <cassert>
+#include <algorithm>
+#include <iterator>
+
+#include "locals.hpp"
+
+namespace Compiler
+{
+ Output::Output (Locals& locals) : mLocals (locals) {}
+
+ void Output::getCode (std::vector<Interpreter::Type_Code>& code) const
+ {
+ code.clear();
+
+ // header
+ code.push_back (static_cast<Interpreter::Type_Code> (mCode.size()));
+
+ assert (mLiterals.getIntegerSize()%4==0);
+ code.push_back (static_cast<Interpreter::Type_Code> (mLiterals.getIntegerSize()/4));
+
+ assert (mLiterals.getFloatSize()%4==0);
+ code.push_back (static_cast<Interpreter::Type_Code> (mLiterals.getFloatSize()/4));
+
+ assert (mLiterals.getStringSize()%4==0);
+ code.push_back (static_cast<Interpreter::Type_Code> (mLiterals.getStringSize()/4));
+
+ // code
+ std::copy (mCode.begin(), mCode.end(), std::back_inserter (code));
+
+ // literals
+ mLiterals.append (code);
+ }
+
+ const Literals& Output::getLiterals() const
+ {
+ return mLiterals;
+ }
+
+ const std::vector<Interpreter::Type_Code>& Output::getCode() const
+ {
+ return mCode;
+ }
+
+ const Locals& Output::getLocals() const
+ {
+ return mLocals;
+ }
+
+ Literals& Output::getLiterals()
+ {
+ return mLiterals;
+ }
+
+ std::vector<Interpreter::Type_Code>& Output::getCode()
+ {
+ return mCode;
+ }
+
+ Locals& Output::getLocals()
+ {
+ return mLocals;
+ }
+
+ void Output::clear()
+ {
+ mLiterals.clear();
+ mCode.clear();
+ mLocals.clear();
+ }
+}
+
diff --git a/components/compiler/output.hpp b/components/compiler/output.hpp
new file mode 100644
index 0000000000..37b88cee92
--- /dev/null
+++ b/components/compiler/output.hpp
@@ -0,0 +1,44 @@
+#ifndef COMPILER_OUTPUT_H_INCLUDED
+#define COMPILER_OUTPUT_H_INCLUDED
+
+#include "literals.hpp"
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+namespace Compiler
+{
+ class Locals;
+
+ class Output
+ {
+ Literals mLiterals;
+ std::vector<Interpreter::Type_Code> mCode;
+ Locals& mLocals;
+
+ public:
+
+ Output (Locals& locals);
+
+ void getCode (std::vector<Interpreter::Type_Code>& code) const;
+ ///< store generated code in \æ code.
+
+ const Literals& getLiterals() const;
+
+ const Locals& getLocals() const;
+
+ const std::vector<Interpreter::Type_Code>& getCode() const;
+
+ Literals& getLiterals();
+
+ std::vector<Interpreter::Type_Code>& getCode();
+
+ Locals& getLocals();
+
+ void clear();
+ };
+}
+
+#endif
+
diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp
new file mode 100644
index 0000000000..8d11c4086d
--- /dev/null
+++ b/components/compiler/parser.cpp
@@ -0,0 +1,185 @@
+
+#include "parser.hpp"
+
+#include <cctype>
+#include <algorithm>
+#include <iterator>
+
+#include "errorhandler.hpp"
+#include "exception.hpp"
+#include "scanner.hpp"
+
+#include <components/misc/stringops.hpp>
+
+namespace Compiler
+{
+ // Report the error and throw an exception.
+
+ void Parser::reportSeriousError (const std::string& message, const TokenLoc& loc)
+ {
+ mErrorHandler.error (message, loc);
+ throw SourceException();
+ }
+
+ // Report the error
+
+ void Parser::reportError (const std::string& message, const TokenLoc& loc)
+ {
+ mErrorHandler.error (message, loc);
+ }
+
+ // Report the warning without throwing an exception.
+
+ void Parser::reportWarning (const std::string& message, const TokenLoc& loc)
+ {
+ mErrorHandler.warning (message, loc);
+ }
+
+ // Report an unexpected EOF condition.
+
+ void Parser::reportEOF()
+ {
+ mErrorHandler.endOfFile();
+ throw EOFException();
+ }
+
+ // Return error handler
+
+ ErrorHandler& Parser::getErrorHandler()
+ {
+ return mErrorHandler;
+ }
+
+ // Return context
+
+ Context& Parser::getContext()
+ {
+ return mContext;
+ }
+
+ std::string Parser::toLower (const std::string& name)
+ {
+ std::string lowerCase = Misc::StringUtils::lowerCase(name);
+
+ return lowerCase;
+ }
+
+ Parser::Parser (ErrorHandler& errorHandler, Context& context)
+ : mErrorHandler (errorHandler), mContext (context), mOptional (false), mEmpty (true)
+ {}
+
+ // destructor
+
+ Parser::~Parser() {}
+
+ // Handle an int token.
+ // \return fetch another token?
+ //
+ // - Default-implementation: Report an error.
+
+ bool Parser::parseInt (int value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!(mOptional && mEmpty))
+ reportSeriousError ("Unexpected numeric value", loc);
+ else
+ scanner.putbackInt (value, loc);
+
+ return false;
+ }
+
+ // Handle a float token.
+ // \return fetch another token?
+ //
+ // - Default-implementation: Report an error.
+
+ bool Parser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!(mOptional && mEmpty))
+ reportSeriousError ("Unexpected floating point value", loc);
+ else
+ scanner.putbackFloat (value, loc);
+
+ return false;
+ }
+
+ // Handle a name token.
+ // \return fetch another token?
+ //
+ // - Default-implementation: Report an error.
+
+ bool Parser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ if (!(mOptional && mEmpty))
+ reportSeriousError ("Unexpected name", loc);
+ else
+ scanner.putbackName (name, loc);
+
+ return false;
+ }
+
+ // Handle a keyword token.
+ // \return fetch another token?
+ //
+ // - Default-implementation: Report an error.
+
+ bool Parser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!(mOptional && mEmpty))
+ reportSeriousError ("Unexpected keyword", loc);
+ else
+ scanner.putbackKeyword (keyword, loc);
+
+ return false;
+ }
+
+ // Handle a special character token.
+ // \return fetch another token?
+ //
+ // - Default-implementation: Report an error.
+
+ bool Parser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (!(mOptional && mEmpty))
+ reportSeriousError ("Unexpected special token", loc);
+ else
+ scanner.putbackSpecial (code, loc);
+
+ return false;
+ }
+
+ bool Parser::parseComment (const std::string& comment, const TokenLoc& loc, Scanner& scanner)
+ {
+ return true;
+ }
+
+ // Handle an EOF token.
+ //
+ // - Default-implementation: Report an error.
+
+ void Parser::parseEOF (Scanner& scanner)
+ {
+ reportEOF();
+ }
+
+ void Parser::reset()
+ {
+ mOptional = false;
+ mEmpty = true;
+ }
+
+ void Parser::setOptional (bool optional)
+ {
+ mOptional = optional;
+ }
+
+ void Parser::start()
+ {
+ mEmpty = false;
+ }
+
+ bool Parser::isEmpty() const
+ {
+ return mEmpty;
+ }
+}
diff --git a/components/compiler/parser.hpp b/components/compiler/parser.hpp
new file mode 100644
index 0000000000..4fec570e9f
--- /dev/null
+++ b/components/compiler/parser.hpp
@@ -0,0 +1,112 @@
+#ifndef COMPILER_PARSER_H_INCLUDED
+#define COMPILER_PARSER_H_INCLUDED
+
+#include <string>
+
+namespace Compiler
+{
+ class Scanner;
+ struct TokenLoc;
+ class ErrorHandler;
+ class Context;
+
+ /// \brief Parser base class
+ ///
+ /// This class defines a callback-parser.
+
+ class Parser
+ {
+ ErrorHandler& mErrorHandler;
+ Context& mContext;
+ bool mOptional;
+ bool mEmpty;
+
+ protected:
+
+ void reportSeriousError (const std::string& message, const TokenLoc& loc);
+ ///< Report the error and throw a exception.
+
+ void reportError (const std::string& message, const TokenLoc& loc);
+ ///< Report the error
+
+ void reportWarning (const std::string& message, const TokenLoc& loc);
+ ///< Report the warning without throwing an exception.
+
+ void reportEOF();
+ ///< Report an unexpected EOF condition.
+
+ ErrorHandler& getErrorHandler();
+ ///< Return error handler
+
+ Context& getContext();
+ ///< Return context
+
+ static std::string toLower (const std::string& name);
+
+ public:
+
+ Parser (ErrorHandler& errorHandler, Context& context);
+ ///< constructor
+
+ virtual ~Parser();
+ ///< destructor
+
+ virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle an int token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a float token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual bool parseComment (const std::string& comment, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle comment token.
+ /// \return fetch another token?
+ ///
+ /// - Default-implementation: ignored (and return true).
+
+ virtual void parseEOF (Scanner& scanner);
+ ///< Handle EOF token.
+ ///
+ /// - Default-implementation: Report an error.
+
+ virtual void reset();
+ ///< Reset parser to clean state.
+
+ void setOptional (bool optional);
+ ///< Optional mode: If nothign has been parsed yet and an unexpected token is delivered, stop
+ /// parsing without raising an exception (after a reset the parser is in non-optional mode).
+
+ void start();
+ ///< Mark parser as non-empty (at least one token has been parser).
+
+ bool isEmpty() const;
+ ///< Has anything been parsed?
+ };
+}
+
+#endif
diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp
new file mode 100644
index 0000000000..816443c447
--- /dev/null
+++ b/components/compiler/scanner.cpp
@@ -0,0 +1,564 @@
+
+#include "scanner.hpp"
+
+#include <cassert>
+#include <cctype>
+#include <sstream>
+#include <algorithm>
+#include <iterator>
+
+#include "exception.hpp"
+#include "errorhandler.hpp"
+#include "parser.hpp"
+#include "extensions.hpp"
+
+#include <components/misc/stringops.hpp>
+
+namespace Compiler
+{
+ bool Scanner::get (char& c)
+ {
+ mStream.get (c);
+
+ if (!mStream.good())
+ return false;
+
+ mPrevLoc =mLoc;
+
+ if (c=='\n')
+ {
+ mLoc.mColumn = 0;
+ ++mLoc.mLine;
+ mLoc.mLiteral.clear();
+ }
+ else
+ {
+ ++mLoc.mColumn;
+ mLoc.mLiteral += c;
+ }
+
+ return true;
+ }
+
+ void Scanner::putback (char c)
+ {
+ mStream.putback (c);
+ mLoc = mPrevLoc;
+ }
+
+ bool Scanner::scanToken (Parser& parser)
+ {
+ switch (mPutback)
+ {
+ case Putback_Special:
+
+ mPutback = Putback_None;
+ return parser.parseSpecial (mPutbackCode, mPutbackLoc, *this);
+
+ case Putback_Integer:
+
+ mPutback = Putback_None;
+ return parser.parseInt (mPutbackInteger, mPutbackLoc, *this);
+
+ case Putback_Float:
+
+ mPutback = Putback_None;
+ return parser.parseFloat (mPutbackFloat, mPutbackLoc, *this);
+
+ case Putback_Name:
+
+ mPutback = Putback_None;
+ return parser.parseName (mPutbackName, mPutbackLoc, *this);
+
+ case Putback_Keyword:
+
+ mPutback = Putback_None;
+ return parser.parseKeyword (mPutbackCode, mPutbackLoc, *this);
+
+ case Putback_None:
+
+ break;
+ }
+
+ char c;
+
+ if (!get (c))
+ {
+ parser.parseEOF (*this);
+ return false;
+ }
+ else if (c==';')
+ {
+ std::string comment;
+
+ comment += c;
+
+ while (get (c))
+ {
+ if (c=='\n')
+ {
+ putback (c);
+ break;
+ }
+ else
+ comment += c;
+ }
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ return parser.parseComment (comment, loc, *this);
+ }
+ else if (isWhitespace (c))
+ {
+ mLoc.mLiteral.clear();
+ return true;
+ }
+ else if (c==':')
+ {
+ // treat : as a whitespace :(
+ mLoc.mLiteral.clear();
+ return true;
+ }
+ else if (std::isdigit (c))
+ {
+ bool cont = false;
+
+ if (scanInt (c, parser, cont))
+ {
+ mLoc.mLiteral.clear();
+ return cont;
+ }
+ }
+ else if (std::isalpha (c) || c=='_' || c=='"')
+ {
+ bool cont = false;
+
+ if (scanName (c, parser, cont))
+ {
+ mLoc.mLiteral.clear();
+ return cont;
+ }
+ }
+ else if (c==13) // linux compatibility hack
+ {
+ return true;
+ }
+ else
+ {
+ bool cont = false;
+
+ if (scanSpecial (c, parser, cont))
+ {
+ mLoc.mLiteral.clear();
+ return cont;
+ }
+ }
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ mErrorHandler.error ("syntax error", loc);
+ throw SourceException();
+ }
+
+ bool Scanner::scanInt (char c, Parser& parser, bool& cont)
+ {
+ assert(c != '\0');
+ std::string value;
+ value += c;
+
+ bool error = false;
+
+ while (get (c))
+ {
+ if (std::isdigit (c))
+ {
+ value += c;
+ }
+ else if (std::isalpha (c) || c=='_')
+ error = true;
+ else if (c=='.' && !error)
+ {
+ return scanFloat (value, parser, cont);
+ }
+ else
+ {
+ putback (c);
+ break;
+ }
+ }
+
+ if (error)
+ return false;
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ std::istringstream stream (value);
+
+ int intValue = 0;
+ stream >> intValue;
+
+ cont = parser.parseInt (intValue, loc, *this);
+ return true;
+ }
+
+ bool Scanner::scanFloat (const std::string& intValue, Parser& parser, bool& cont)
+ {
+ std::string value = intValue + ".";
+
+ char c;
+
+ bool empty = intValue.empty() || intValue=="-";
+ bool error = false;
+
+ while (get (c))
+ {
+ if (std::isdigit (c))
+ {
+ value += c;
+ empty = false;
+ }
+ else if (std::isalpha (c) || c=='_')
+ error = true;
+ else
+ {
+ putback (c);
+ break;
+ }
+ }
+
+ if (empty || error)
+ return false;
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ std::istringstream stream (value);
+
+ float floatValue = 0;
+ stream >> floatValue;
+
+ cont = parser.parseFloat (floatValue, loc, *this);
+ return true;
+ }
+
+ static const char *keywords[] =
+ {
+ "begin", "end",
+ "short", "long", "float",
+ "if", "endif", "else", "elseif",
+ "while", "endwhile",
+ "return",
+ "messagebox",
+ "set", "to",
+ "getsquareroot",
+ "menumode",
+ "random",
+ "startscript", "stopscript", "scriptrunning",
+ "getdistance",
+ "getsecondspassed",
+ "enable", "disable", "getdisabled",
+ 0
+ };
+
+ bool Scanner::scanName (char c, Parser& parser, bool& cont)
+ {
+ std::string name;
+
+ if (!scanName (c, name))
+ return false;
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"')
+ {
+ name = name.substr (1, name.size()-2);
+ cont = parser.parseName (name, loc, *this);
+ return true;
+ }
+
+ int i = 0;
+
+ std::string lowerCase = Misc::StringUtils::lowerCase(name);
+
+ for (; keywords[i]; ++i)
+ if (lowerCase==keywords[i])
+ break;
+
+ if (keywords[i])
+ {
+ cont = parser.parseKeyword (i, loc, *this);
+ return true;
+ }
+
+ if (mExtensions)
+ {
+ if (int keyword = mExtensions->searchKeyword (lowerCase))
+ {
+ cont = parser.parseKeyword (keyword, loc, *this);
+ return true;
+ }
+ }
+
+ cont = parser.parseName (name, loc, *this);
+
+ return true;
+ }
+
+ bool Scanner::scanName (char c, std::string& name)
+ {
+ bool first = false;
+ bool error = false;
+
+ name.clear();
+
+ putback (c);
+
+ while (get (c))
+ {
+ if (!name.empty() && name[0]=='"')
+ {
+ if (c=='"')
+ {
+ name += c;
+ break;
+ }
+// ignoring escape sequences for now, because they are messing up stupid Windows path names.
+// else if (c=='\\')
+// {
+// if (!get (c))
+// {
+// mErrorHandler.error ("incomplete escape sequence", mLoc);
+// break;
+// }
+// }
+ else if (c=='\n')
+ {
+ mErrorHandler.error ("incomplete string or name", mLoc);
+ break;
+ }
+ }
+ else if (!(c=='"' && name.empty()))
+ {
+ if (!(std::isalpha (c) || std::isdigit (c) || c=='_' || c=='`' ||
+ /// \todo add an option to disable the following hack. Also, find out who is
+ /// responsible for allowing it in the first place and meet up with that person in
+ /// a dark alley.
+ (c=='-' && !name.empty() && std::isalpha (mStream.peek()))))
+ {
+ putback (c);
+ break;
+ }
+
+ if (first && std::isdigit (c))
+ error = true;
+ }
+
+ name += c;
+ first = false;
+ }
+
+ return !error;
+ }
+
+ bool Scanner::scanSpecial (char c, Parser& parser, bool& cont)
+ {
+ int special = -1;
+
+ if (c=='\n')
+ special = S_newline;
+ else if (c=='(')
+ special = S_open;
+ else if (c==')')
+ special = S_close;
+ else if (c=='.')
+ {
+ // check, if this starts a float literal
+ if (get (c))
+ {
+ putback (c);
+
+ if (std::isdigit (c))
+ return scanFloat ("", parser, cont);
+ }
+
+ special = S_member;
+ }
+ else if (c=='=')
+ {
+ if (get (c))
+ {
+ if (c=='=')
+ special = S_cmpEQ;
+ else
+ {
+ special = S_cmpEQ;
+ putback (c);
+// return false;
+// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements.
+ }
+ }
+ else
+ {
+ putback (c);
+ return false;
+ }
+ }
+ else if (c=='!')
+ {
+ if (get (c))
+ {
+ if (c=='=')
+ special = S_cmpNE;
+ else
+ {
+ putback (c);
+ return false;
+ }
+ }
+ else
+ return false;
+ }
+ else if (c=='-')
+ {
+ if (get (c))
+ {
+ if (c=='>')
+ special = S_ref;
+ else
+ {
+ putback (c);
+ special = S_minus;
+ }
+ }
+ else
+ special = S_minus;
+ }
+ else if (c=='<')
+ {
+ if (get (c))
+ {
+ if (c=='=')
+ {
+ special = S_cmpLE;
+
+ if (get (c) && c!='=') // <== is a allowed as an alternative to <= :(
+ putback (c);
+ }
+ else
+ {
+ putback (c);
+ special = S_cmpLT;
+ }
+ }
+ else
+ special = S_cmpLT;
+ }
+ else if (c=='>')
+ {
+ if (get (c))
+ {
+ if (c=='=')
+ {
+ special = S_cmpGE;
+
+ if (get (c) && c!='=') // >== is a allowed as an alternative to >= :(
+ putback (c);
+ }
+ else
+ {
+ putback (c);
+ special = S_cmpGT;
+ }
+ }
+ else
+ special = S_cmpGT;
+ }
+ else if (c==',')
+ special = S_comma;
+ else if (c=='+')
+ special = S_plus;
+ else if (c=='*')
+ special = S_mult;
+ else if (c=='/')
+ special = S_div;
+ else
+ return false;
+
+ if (special==S_newline)
+ mLoc.mLiteral = "<newline>";
+
+ TokenLoc loc (mLoc);
+ mLoc.mLiteral.clear();
+
+ cont = parser.parseSpecial (special, loc, *this);
+
+ return true;
+ }
+
+ bool Scanner::isWhitespace (char c)
+ {
+ return c==' ' || c=='\t';
+ }
+
+ // constructor
+
+ Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream,
+ const Extensions *extensions)
+ : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions),
+ mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0)
+ {
+ }
+
+ void Scanner::scan (Parser& parser)
+ {
+ while (scanToken (parser));
+ }
+
+ void Scanner::putbackSpecial (int code, const TokenLoc& loc)
+ {
+ mPutback = Putback_Special;
+ mPutbackCode = code;
+ mPutbackLoc = loc;
+ }
+
+ void Scanner::putbackInt (int value, const TokenLoc& loc)
+ {
+ mPutback = Putback_Integer;
+ mPutbackInteger = value;
+ mPutbackLoc = loc;
+ }
+
+ void Scanner::putbackFloat (float value, const TokenLoc& loc)
+ {
+ mPutback = Putback_Float;
+ mPutbackFloat = value;
+ mPutbackLoc = loc;
+ }
+
+ void Scanner::putbackName (const std::string& name, const TokenLoc& loc)
+ {
+ mPutback = Putback_Name;
+ mPutbackName = name;
+ mPutbackLoc = loc;
+ }
+
+ void Scanner::putbackKeyword (int keyword, const TokenLoc& loc)
+ {
+ mPutback = Putback_Keyword;
+ mPutbackCode = keyword;
+ mPutbackLoc = loc;
+ }
+
+ void Scanner::listKeywords (std::vector<std::string>& keywords)
+ {
+ for (int i=0; Compiler::keywords[i]; ++i)
+ keywords.push_back (Compiler::keywords[i]);
+
+ if (mExtensions)
+ mExtensions->listKeywords (keywords);
+ }
+}
diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp
new file mode 100644
index 0000000000..19f4ca96ad
--- /dev/null
+++ b/components/compiler/scanner.hpp
@@ -0,0 +1,126 @@
+#ifndef COMPILER_SCANNER_H_INCLUDED
+#define COMPILER_SCANNER_H_INCLUDED
+
+#include <string>
+#include <iosfwd>
+#include <vector>
+
+#include "tokenloc.hpp"
+
+namespace Compiler
+{
+ class ErrorHandler;
+ class Parser;
+ class Extensions;
+
+ /// \brief Scanner
+ ///
+ /// This class translate a char-stream to a token stream (delivered via
+ /// parser-callbacks).
+
+ class Scanner
+ {
+ enum putback_type
+ {
+ Putback_None, Putback_Special, Putback_Integer, Putback_Float,
+ Putback_Name, Putback_Keyword
+ };
+
+ ErrorHandler& mErrorHandler;
+ TokenLoc mLoc;
+ TokenLoc mPrevLoc;
+ std::istream& mStream;
+ const Extensions *mExtensions;
+ putback_type mPutback;
+ int mPutbackCode;
+ int mPutbackInteger;
+ float mPutbackFloat;
+ std::string mPutbackName;
+ TokenLoc mPutbackLoc;
+
+ public:
+
+ enum keyword
+ {
+ K_begin, K_end,
+ K_short, K_long, K_float,
+ K_if, K_endif, K_else, K_elseif,
+ K_while, K_endwhile,
+ K_return,
+ K_messagebox,
+ K_set, K_to,
+ K_getsquareroot,
+ K_menumode,
+ K_random,
+ K_startscript, K_stopscript, K_scriptrunning,
+ K_getdistance,
+ K_getsecondspassed,
+ K_enable, K_disable, K_getdisabled
+ };
+
+ enum special
+ {
+ S_newline,
+ S_open, S_close,
+ S_cmpEQ, S_cmpNE, S_cmpLT, S_cmpLE, S_cmpGT, S_cmpGE,
+ S_plus, S_minus, S_mult, S_div,
+ S_comma,
+ S_ref,
+ S_member
+ };
+
+ private:
+
+ // not implemented
+
+ Scanner (const Scanner&);
+ Scanner& operator= (const Scanner&);
+
+ bool get (char& c);
+
+ void putback (char c);
+
+ bool scanToken (Parser& parser);
+
+ bool scanInt (char c, Parser& parser, bool& cont);
+
+ bool scanFloat (const std::string& intValue, Parser& parser, bool& cont);
+
+ bool scanName (char c, Parser& parser, bool& cont);
+
+ bool scanName (char c, std::string& name);
+
+ bool scanSpecial (char c, Parser& parser, bool& cont);
+
+ static bool isWhitespace (char c);
+
+ public:
+
+ Scanner (ErrorHandler& errorHandler, std::istream& inputStream,
+ const Extensions *extensions = 0);
+ ///< constructor
+
+ void scan (Parser& parser);
+ ///< Scan a token and deliver it to the parser.
+
+ void putbackSpecial (int code, const TokenLoc& loc);
+ ///< put back a special token
+
+ void putbackInt (int value, const TokenLoc& loc);
+ ///< put back an integer token
+
+ void putbackFloat (float value, const TokenLoc& loc);
+ ///< put back a float token
+
+ void putbackName (const std::string& name, const TokenLoc& loc);
+ ///< put back a name toekn
+
+ void putbackKeyword (int keyword, const TokenLoc& loc);
+ ///< put back a keyword token
+
+ void listKeywords (std::vector<std::string>& keywords);
+ ///< Append all known keywords to \æ kaywords.
+ };
+}
+
+#endif
diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp
new file mode 100644
index 0000000000..5b3d244b0d
--- /dev/null
+++ b/components/compiler/scriptparser.cpp
@@ -0,0 +1,92 @@
+
+#include "scriptparser.hpp"
+
+#include "scanner.hpp"
+#include "skipparser.hpp"
+#include "errorhandler.hpp"
+
+namespace Compiler
+{
+ ScriptParser::ScriptParser (ErrorHandler& errorHandler, Context& context,
+ Locals& locals, bool end)
+ : Parser (errorHandler, context), mOutput (locals),
+ mLineParser (errorHandler, context, locals, mOutput.getLiterals(), mOutput.getCode()),
+ mControlParser (errorHandler, context, locals, mOutput.getLiterals()),
+ mEnd (end)
+ {}
+
+ void ScriptParser::getCode (std::vector<Interpreter::Type_Code>& code) const
+ {
+ mOutput.getCode (code);
+ }
+
+ bool ScriptParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ mLineParser.reset();
+ if (mLineParser.parseName (name, loc, scanner))
+ scanner.scan (mLineParser);
+
+ return true;
+ }
+
+ bool ScriptParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (keyword==Scanner::K_while || keyword==Scanner::K_if)
+ {
+ mControlParser.reset();
+ if (mControlParser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (mControlParser);
+
+ mControlParser.appendCode (mOutput.getCode());
+
+ return true;
+ }
+
+ /// \todo add an option to disable this nonsense
+ if (keyword==Scanner::K_endif)
+ {
+ // surplus endif
+ getErrorHandler().warning ("endif without matching if/elseif", loc);
+
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return true;
+ }
+
+ if (keyword==Scanner::K_end && mEnd)
+ {
+ return false;
+ }
+
+ mLineParser.reset();
+ if (mLineParser.parseKeyword (keyword, loc, scanner))
+ scanner.scan (mLineParser);
+
+ return true;
+ }
+
+ bool ScriptParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_newline) // empty line
+ return true;
+
+ mLineParser.reset();
+ if (mLineParser.parseSpecial (code, loc, scanner))
+ scanner.scan (mLineParser);
+
+ return true;
+ }
+
+ void ScriptParser::parseEOF (Scanner& scanner)
+ {
+ if (mEnd)
+ Parser::parseEOF (scanner);
+ }
+
+ void ScriptParser::reset()
+ {
+ mLineParser.reset();
+ mOutput.clear();
+ }
+}
diff --git a/components/compiler/scriptparser.hpp b/components/compiler/scriptparser.hpp
new file mode 100644
index 0000000000..bb4809dabd
--- /dev/null
+++ b/components/compiler/scriptparser.hpp
@@ -0,0 +1,54 @@
+#ifndef COMPILER_SCRIPTPARSER_H_INCLUDED
+#define COMPILER_SCRIPTPARSER_H_INCLUDED
+
+
+#include "parser.hpp"
+#include "lineparser.hpp"
+#include "controlparser.hpp"
+#include "output.hpp"
+
+namespace Compiler
+{
+ class Locals;
+
+ // Script parser, to be used in dialogue scripts and as part of FileParser
+
+ class ScriptParser : public Parser
+ {
+ Output mOutput;
+ LineParser mLineParser;
+ ControlParser mControlParser;
+ bool mEnd;
+
+ public:
+
+ /// \param end of script is marked by end keyword.
+ ScriptParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ bool end = false);
+
+ void getCode (std::vector<Interpreter::Type_Code>& code) const;
+ ///< store generated code in \æ code.
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ virtual void parseEOF (Scanner& scanner);
+ ///< Handle EOF token.
+
+ void reset();
+ ///< Reset parser to clean state.
+ };
+}
+
+#endif
+
diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp
new file mode 100644
index 0000000000..2d09b63ba0
--- /dev/null
+++ b/components/compiler/skipparser.cpp
@@ -0,0 +1,41 @@
+
+#include "skipparser.hpp"
+
+#include "scanner.hpp"
+
+namespace Compiler
+{
+ SkipParser::SkipParser (ErrorHandler& errorHandler, Context& context)
+ : Parser (errorHandler, context)
+ {}
+
+ bool SkipParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner)
+ {
+ return true;
+ }
+
+ bool SkipParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner)
+ {
+ return true;
+ }
+
+ bool SkipParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ return true;
+ }
+
+ bool SkipParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+ {
+ return true;
+ }
+
+ bool SkipParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_newline)
+ return false;
+
+ return true;
+ }
+}
+
diff --git a/components/compiler/skipparser.hpp b/components/compiler/skipparser.hpp
new file mode 100644
index 0000000000..17f1c8d988
--- /dev/null
+++ b/components/compiler/skipparser.hpp
@@ -0,0 +1,42 @@
+#ifndef COMPILER_SKIPPARSER_H_INCLUDED
+#define COMPILER_SKIPPARSER_H_INCLUDED
+
+#include "parser.hpp"
+
+namespace Compiler
+{
+ // \brief Skip parser for skipping a line
+ //
+ // This parser is mainly intended for skipping the rest of a faulty line.
+
+ class SkipParser : public Parser
+ {
+ public:
+
+ SkipParser (ErrorHandler& errorHandler, Context& context);
+
+ virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle an int token.
+ /// \return fetch another token?
+
+ virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a float token.
+ /// \return fetch another token?
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a keyword token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+ };
+}
+
+#endif
+
diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp
new file mode 100644
index 0000000000..8a74ad0863
--- /dev/null
+++ b/components/compiler/streamerrorhandler.cpp
@@ -0,0 +1,39 @@
+
+#include "streamerrorhandler.hpp"
+
+#include "tokenloc.hpp"
+
+namespace Compiler
+{
+ // Report error to the user.
+
+ void StreamErrorHandler::report (const std::string& message, const TokenLoc& loc,
+ Type type)
+ {
+ if (type==ErrorMessage)
+ mStream << "error ";
+ else
+ mStream << "warning ";
+
+ mStream
+ << "line " << loc.mLine << ", column " << loc.mColumn
+ << " (" << loc.mLiteral << ")" << std::endl
+ << " " << message << std::endl;
+ }
+
+ // Report a file related error
+
+ void StreamErrorHandler::report (const std::string& message, Type type)
+ {
+ if (type==ErrorMessage)
+ mStream << "error ";
+ else
+ mStream << "warning ";
+
+ mStream
+ << "file:" << std::endl
+ << " " << message << std::endl;
+ }
+
+ StreamErrorHandler::StreamErrorHandler (std::ostream& ErrorStream) : mStream (ErrorStream) {}
+}
diff --git a/components/compiler/streamerrorhandler.hpp b/components/compiler/streamerrorhandler.hpp
new file mode 100644
index 0000000000..96e02b5882
--- /dev/null
+++ b/components/compiler/streamerrorhandler.hpp
@@ -0,0 +1,37 @@
+
+#ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED
+#define COMPILER_STREAMERRORHANDLER_H_INCLUDED
+
+#include <ostream>
+
+#include "errorhandler.hpp"
+
+namespace Compiler
+{
+ /// \brief Error handler implementation: Write errors into stream
+
+ class StreamErrorHandler : public ErrorHandler
+ {
+ std::ostream& mStream;
+
+ // not implemented
+
+ StreamErrorHandler (const StreamErrorHandler&);
+ StreamErrorHandler& operator= (const StreamErrorHandler&);
+
+ virtual void report (const std::string& message, const TokenLoc& loc, Type type);
+ ///< Report error to the user.
+
+ virtual void report (const std::string& message, Type type);
+ ///< Report a file related error
+
+ public:
+
+ // constructors
+
+ StreamErrorHandler (std::ostream& ErrorStream);
+ ///< constructor
+ };
+}
+
+#endif
diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp
new file mode 100644
index 0000000000..09c902131a
--- /dev/null
+++ b/components/compiler/stringparser.cpp
@@ -0,0 +1,64 @@
+
+#include "stringparser.hpp"
+
+#include <algorithm>
+#include <iterator>
+
+#include "scanner.hpp"
+#include "generator.hpp"
+#include <components/misc/stringops.hpp>
+
+namespace Compiler
+{
+ StringParser::StringParser (ErrorHandler& errorHandler, Context& context, Literals& literals)
+ : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false)
+ {
+
+ }
+
+ bool StringParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+ {
+ if (mState==StartState || mState==CommaState)
+ {
+ start();
+ if (mSmashCase)
+ Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name));
+ else
+ Generator::pushString (mCode, mLiterals, name);
+
+ return false;
+ }
+
+ return Parser::parseName (name, loc, scanner);
+ }
+
+ bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+ {
+ if (code==Scanner::S_comma && mState==StartState)
+ {
+ mState = CommaState;
+ return true;
+ }
+
+ return Parser::parseSpecial (code, loc, scanner);
+ }
+
+ void StringParser::append (std::vector<Interpreter::Type_Code>& code)
+ {
+ std::copy (mCode.begin(), mCode.end(), std::back_inserter (code));
+ }
+
+ void StringParser::reset()
+ {
+ mState = StartState;
+ mCode.clear();
+ mSmashCase = false;
+ Parser::reset();
+ }
+
+ void StringParser::smashCase()
+ {
+ mSmashCase = true;
+ }
+}
diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp
new file mode 100644
index 0000000000..f692c650b8
--- /dev/null
+++ b/components/compiler/stringparser.hpp
@@ -0,0 +1,50 @@
+#ifndef COMPILER_STRINGPARSER_H_INCLUDED
+#define COMPILER_STRINGPARSER_H_INCLUDED
+
+#include <vector>
+
+#include <components/interpreter/types.hpp>
+
+#include "parser.hpp"
+
+namespace Compiler
+{
+ class Literals;
+
+ class StringParser : public Parser
+ {
+ enum State
+ {
+ StartState, CommaState
+ };
+
+ Literals& mLiterals;
+ State mState;
+ std::vector<Interpreter::Type_Code> mCode;
+ bool mSmashCase;
+
+ public:
+
+ StringParser (ErrorHandler& errorHandler, Context& context, Literals& literals);
+
+ virtual bool parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner);
+ ///< Handle a name token.
+ /// \return fetch another token?
+
+ virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner);
+ ///< Handle a special character token.
+ /// \return fetch another token?
+
+ void append (std::vector<Interpreter::Type_Code>& code);
+ ///< Append code for parsed string.
+
+ void smashCase();
+ ///< Transform all scanned strings to lower case
+
+ void reset();
+ ///< Reset parser to clean state (this includes the smashCase function).
+ };
+}
+
+#endif
diff --git a/components/compiler/tokenloc.hpp b/components/compiler/tokenloc.hpp
new file mode 100644
index 0000000000..62b5cdee58
--- /dev/null
+++ b/components/compiler/tokenloc.hpp
@@ -0,0 +1,20 @@
+#ifndef COMPILER_TOKENLOC_H_INCLUDED
+#define COMPILER_TOKENLOC_H_INCLUDED
+
+#include <string>
+
+namespace Compiler
+{
+ /// \brief Location of a token in a source file
+
+ struct TokenLoc
+ {
+ int mColumn;
+ int mLine;
+ std::string mLiteral;
+
+ TokenLoc() : mColumn (0), mLine (0), mLiteral ("") {}
+ };
+}
+
+#endif // TOKENLOC_H_INCLUDED
diff --git a/components/doc.hpp b/components/doc.hpp
new file mode 100644
index 0000000000..5b4ffcdadc
--- /dev/null
+++ b/components/doc.hpp
@@ -0,0 +1,30 @@
+// Note: This is not a regular source file.
+
+/// \defgroup components Components
+
+/// \namespace ESMS
+/// \ingroup components
+/// \brief ESM/ESP record store
+
+/// \namespace ESM
+/// \ingroup components
+/// \brief ESM/ESP records
+
+/// \namespace FileFinder
+/// \ingroup components
+/// \brief Linux/Windows-path resolving
+
+/// \namespace ToUTF
+/// \ingroup components
+/// \brief Text encoding
+
+/// \namespace Compiler
+/// \ingroup components
+/// \brief script compiler
+
+/// \namespace Interpreter
+/// \ingroup components
+/// \brief script interpreter
+
+// TODO put nif and nifogre in different namespaces (or merge them)
+// TODO put other components into namespaces
diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp
new file mode 100644
index 0000000000..1440dbd138
--- /dev/null
+++ b/components/esm/aipackage.cpp
@@ -0,0 +1,77 @@
+#include "aipackage.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+ void AIData::blank()
+ {
+ mHello = mU1 = mFight = mFlee = mAlarm = mU2 = mU3 = mU4 = 0;
+ mServices = 0;
+ }
+
+ void AIPackageList::load(ESMReader &esm)
+ {
+ while (esm.hasMoreSubs()) {
+ // initialize every iteration
+ AIPackage pack;
+ esm.getSubName();
+ if (esm.retSubName() == 0x54444e43) { // CNDT
+ mList.back().mCellName = esm.getHString();
+ } else if (esm.retSubName() == AI_Wander) {
+ pack.mType = AI_Wander;
+ esm.getHExact(&pack.mWander, 14);
+ mList.push_back(pack);
+ } else if (esm.retSubName() == AI_Travel) {
+ pack.mType = AI_Travel;
+ esm.getHExact(&pack.mTravel, 16);
+ mList.push_back(pack);
+ } else if (esm.retSubName() == AI_Escort ||
+ esm.retSubName() == AI_Follow)
+ {
+ pack.mType =
+ (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow;
+ esm.getHExact(&pack.mTarget, 48);
+ mList.push_back(pack);
+ } else if (esm.retSubName() == AI_Activate) {
+ pack.mType = AI_Activate;
+ esm.getHExact(&pack.mActivate, 33);
+ mList.push_back(pack);
+ } else { // not AI package related data, so leave
+ return;
+ }
+ }
+ }
+
+ void AIPackageList::save(ESMWriter &esm)
+ {
+ typedef std::vector<AIPackage>::iterator PackageIter;
+ for (PackageIter it = mList.begin(); it != mList.end(); ++it) {
+ switch (it->mType) {
+ case AI_Wander:
+ esm.writeHNT("AI_W", it->mWander, sizeof(it->mWander));
+ break;
+
+ case AI_Travel:
+ esm.writeHNT("AI_T", it->mTravel, sizeof(it->mTravel));
+ break;
+
+ case AI_Activate:
+ esm.writeHNT("AI_A", it->mActivate, sizeof(it->mActivate));
+ break;
+
+ case AI_Escort:
+ case AI_Follow: {
+ const char *name = (it->mType == AI_Escort) ? "AI_E" : "AI_F";
+ esm.writeHNT(name, it->mTarget, sizeof(it->mTarget));
+ esm.writeHNOCString("CNDT", it->mCellName);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp
new file mode 100644
index 0000000000..38499b2dd8
--- /dev/null
+++ b/components/esm/aipackage.hpp
@@ -0,0 +1,101 @@
+#ifndef OPENMW_ESM_AIPACKAGE_H
+#define OPENMW_ESM_AIPACKAGE_H
+
+#include <vector>
+#include <string>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ #pragma pack(push)
+ #pragma pack(1)
+
+ struct AIData
+ {
+ // These are probabilities
+ char mHello, mU1, mFight, mFlee, mAlarm, mU2, mU3, mU4;
+ int mServices; // See the Services enum
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+ }; // 12 bytes
+
+ struct AIWander
+ {
+ short mDistance;
+ short mDuration;
+ unsigned char mTimeOfDay;
+ unsigned char mIdle[8];
+ unsigned char mUnk;
+ };
+
+ struct AITravel
+ {
+ float mX, mY, mZ;
+ int mUnk;
+ };
+
+ struct AITarget
+ {
+ float mX, mY, mZ;
+ short mDuration;
+ NAME32 mId;
+ short mUnk;
+ };
+
+ struct AIActivate
+ {
+ NAME32 mName;
+ unsigned char mUnk;
+ };
+
+ #pragma pack(pop)
+
+ enum
+ {
+ AI_Wander = 0x575f4941,
+ AI_Travel = 0x545f4941,
+ AI_Follow = 0x465f4941,
+ AI_Escort = 0x455f4941,
+ AI_Activate = 0x415f4941
+ };
+
+ /// \note Used for storaging packages in a single container
+ /// w/o manual memory allocation accordingly to policy standards
+ struct AIPackage
+ {
+ int mType;
+
+ // Anonymous union
+ union
+ {
+ AIWander mWander;
+ AITravel mTravel;
+ AITarget mTarget;
+ AIActivate mActivate;
+ };
+
+ /// \note for AITarget only, placed here to stick with union,
+ /// overhead should be not so awful
+ std::string mCellName;
+ };
+
+ struct AIPackageList
+ {
+ std::vector<AIPackage> mList;
+
+ /// \note This breaks consistency of subrecords reading:
+ /// after calling it subrecord name is already read, so
+ /// it needs to use retSubName() if needed. But, hey, there
+ /// is only one field left (XSCL) and only two records uses AI
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+ };
+}
+
+#endif
+
diff --git a/components/esm/attr.cpp b/components/esm/attr.cpp
new file mode 100644
index 0000000000..75cf8b0062
--- /dev/null
+++ b/components/esm/attr.cpp
@@ -0,0 +1,58 @@
+#include "attr.hpp"
+
+using namespace ESM;
+
+const Attribute::AttributeID Attribute::sAttributeIds[Attribute::Length] = {
+ Attribute::Strength,
+ Attribute::Intelligence,
+ Attribute::Willpower,
+ Attribute::Agility,
+ Attribute::Speed,
+ Attribute::Endurance,
+ Attribute::Personality,
+ Attribute::Luck
+};
+
+const std::string Attribute::sAttributeNames[Attribute::Length] = {
+ "Strength",
+ "Intelligence",
+ "Willpower",
+ "Agility",
+ "Speed",
+ "Endurance",
+ "Personality",
+ "Luck"
+};
+
+const std::string Attribute::sGmstAttributeIds[Attribute::Length] = {
+ "sAttributeStrength",
+ "sAttributeIntelligence",
+ "sAttributeWillpower",
+ "sAttributeAgility",
+ "sAttributeSpeed",
+ "sAttributeEndurance",
+ "sAttributePersonality",
+ "sAttributeLuck"
+};
+
+const std::string Attribute::sGmstAttributeDescIds[Attribute::Length] = {
+ "sStrDesc",
+ "sIntDesc",
+ "sWilDesc",
+ "sAgiDesc",
+ "sSpdDesc",
+ "sEndDesc",
+ "sPerDesc",
+ "sLucDesc"
+};
+
+const std::string Attribute::sAttributeIcons[Attribute::Length] = {
+ "icons\\k\\attribute_strength.dds",
+ "icons\\k\\attribute_int.dds",
+ "icons\\k\\attribute_wilpower.dds",
+ "icons\\k\\attribute_agility.dds",
+ "icons\\k\\attribute_speed.dds",
+ "icons\\k\\attribute_endurance.dds",
+ "icons\\k\\attribute_personality.dds",
+ "icons\\k\\attribute_luck.dds"
+};
diff --git a/components/esm/attr.hpp b/components/esm/attr.hpp
new file mode 100644
index 0000000000..993de6ccba
--- /dev/null
+++ b/components/esm/attr.hpp
@@ -0,0 +1,44 @@
+#ifndef OPENMW_ESM_ATTR_H
+#define OPENMW_ESM_ATTR_H
+
+#include <string>
+
+namespace ESM {
+
+/*
+ * Attribute definitions
+ */
+
+struct Attribute
+{
+ enum AttributeID
+ {
+ Strength = 0,
+ Intelligence = 1,
+ Willpower = 2,
+ Agility = 3,
+ Speed = 4,
+ Endurance = 5,
+ Personality = 6,
+ Luck = 7,
+ Length
+ };
+
+ AttributeID mId;
+ std::string mName, mDescription;
+
+ static const AttributeID sAttributeIds[Length];
+ static const std::string sAttributeNames[Length];
+ static const std::string sGmstAttributeIds[Length];
+ static const std::string sGmstAttributeDescIds[Length];
+ static const std::string sAttributeIcons[Length];
+
+ Attribute(AttributeID id, const std::string &name, const std::string &description)
+ : mId(id)
+ , mName(name)
+ , mDescription(description)
+ {
+ }
+};
+}
+#endif
diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp
new file mode 100644
index 0000000000..95cf24d331
--- /dev/null
+++ b/components/esm/cellref.cpp
@@ -0,0 +1,87 @@
+
+#include "cellref.hpp"
+
+#include "esmwriter.hpp"
+
+void ESM::CellRef::save(ESMWriter &esm)
+{
+ esm.writeHNT("FRMR", mRefnum);
+ esm.writeHNCString("NAME", mRefID);
+
+ if (mScale != 1.0) {
+ esm.writeHNT("XSCL", mScale);
+ }
+
+ esm.writeHNOCString("ANAM", mOwner);
+ esm.writeHNOCString("BNAM", mGlob);
+ esm.writeHNOCString("XSOL", mSoul);
+
+ esm.writeHNOCString("CNAM", mFaction);
+ if (mFactIndex != -2) {
+ esm.writeHNT("INDX", mFactIndex);
+ }
+
+ if (mEnchantmentCharge != -1)
+ esm.writeHNT("XCHG", mEnchantmentCharge);
+
+ if (mCharge != -1)
+ esm.writeHNT("INTV", mCharge);
+
+ if (mGoldValue != 1) {
+ esm.writeHNT("NAM9", mGoldValue);
+ }
+
+ if (mTeleport)
+ {
+ esm.writeHNT("DODT", mDoorDest);
+ esm.writeHNOCString("DNAM", mDestCell);
+ }
+
+ if (mLockLevel != -1) {
+ esm.writeHNT("FLTV", mLockLevel);
+ }
+ esm.writeHNOCString("KNAM", mKey);
+ esm.writeHNOCString("TNAM", mTrap);
+
+ if (mReferenceBlocked != -1) {
+ esm.writeHNT("UNAM", mReferenceBlocked);
+ }
+ if (mFltv != 0) {
+ esm.writeHNT("FLTV", mFltv);
+ }
+
+ esm.writeHNT("DATA", mPos, 24);
+ if (mNam0 != 0) {
+ esm.writeHNT("NAM0", mNam0);
+ }
+}
+
+void ESM::CellRef::blank()
+{
+ mRefnum = 0;
+ mRefID.clear();
+ mScale = 1;
+ mOwner.clear();
+ mGlob.clear();
+ mSoul.clear();
+ mFaction.clear();
+ mFactIndex = -1;
+ mCharge = 0;
+ mEnchantmentCharge = 0;
+ mGoldValue = 0;
+ mDestCell.clear();
+ mLockLevel = 0;
+ mKey.clear();
+ mTrap.clear();
+ mReferenceBlocked = 0;
+ mFltv = 0;
+ mNam0 = 0;
+
+ for (int i=0; i<3; ++i)
+ {
+ mDoorDest.pos[i] = 0;
+ mDoorDest.rot[i] = 0;
+ mPos.pos[i] = 0;
+ mPos.rot[i] = 0;
+ }
+} \ No newline at end of file
diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp
new file mode 100644
index 0000000000..31889914ce
--- /dev/null
+++ b/components/esm/cellref.hpp
@@ -0,0 +1,92 @@
+#ifndef OPENMW_ESM_CELLREF_H
+#define OPENMW_ESM_CELLREF_H
+
+#include <string>
+
+#include "defs.hpp"
+
+namespace ESM
+{
+ class ESMWriter;
+
+ /* Cell reference. This represents ONE object (of many) inside the
+ cell. The cell references are not loaded as part of the normal
+ loading process, but are rather loaded later on demand when we are
+ setting up a specific cell.
+ */
+
+ class CellRef
+ {
+ public:
+
+ int mRefnum; // Reference number
+ std::string mRefID; // ID of object being referenced
+
+ float mScale; // Scale applied to mesh
+
+ // The NPC that owns this object (and will get angry if you steal
+ // it)
+ std::string mOwner;
+
+ // I have no idea, looks like a link to a global variable?
+ std::string mGlob;
+
+ // ID of creature trapped in this soul gem (?)
+ std::string mSoul;
+
+ // ?? CNAM has a faction name, might be for objects/beds etc
+ // belonging to a faction.
+ std::string mFaction;
+
+ // INDX might be PC faction rank required to use the item? Sometimes
+ // is -1, which I assume means "any rank".
+ int mFactIndex;
+
+ // For weapon or armor, this is the remaining item health.
+ // For tools (lockpicks, probes, repair hammer) it is the remaining uses.
+ int mCharge;
+
+ // Remaining enchantment charge
+ float mEnchantmentCharge;
+
+ // This is 5 for Gold_005 references, 100 for Gold_100 and so on.
+ int mGoldValue;
+
+ // For doors - true if this door teleports to somewhere else, false
+ // if it should open through animation.
+ bool mTeleport;
+
+ // Teleport location for the door, if this is a teleporting door.
+ Position mDoorDest;
+
+ // Destination cell for doors (optional)
+ std::string mDestCell;
+
+ // Lock level for doors and containers
+ int mLockLevel;
+ std::string mKey, mTrap; // Key and trap ID names, if any
+
+ // This corresponds to the "Reference Blocked" checkbox in the construction set,
+ // which prevents editing that reference.
+ // -1 is not blocked, otherwise it is blocked.
+ signed char mReferenceBlocked;
+
+ // Track deleted references. 0 - not deleted, 1 - deleted, but respawns, 2 - deleted and does not respawn.
+ int mDeleted;
+
+ // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza
+ // Brindisi Dorom", where it has the value 100. Also only for
+ // activators.
+ int mFltv;
+ int mNam0;
+
+ // Position and rotation of this object within the cell
+ Position mPos;
+
+ void save(ESMWriter &esm);
+
+ void blank();
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp
new file mode 100644
index 0000000000..bd86f9ba03
--- /dev/null
+++ b/components/esm/defs.hpp
@@ -0,0 +1,87 @@
+#ifndef OPENMW_ESM_DEFS_H
+#define OPENMW_ESM_DEFS_H
+
+#include <libs/platform/stdint.h>
+
+namespace ESM
+{
+
+// Pixel color value. Standard four-byte rr,gg,bb,aa format.
+typedef int32_t Color;
+
+enum Specialization
+{
+ SPC_Combat = 0,
+ SPC_Magic = 1,
+ SPC_Stealth = 2
+};
+
+enum RangeType
+{
+ RT_Self = 0,
+ RT_Touch = 1,
+ RT_Target = 2
+};
+
+#pragma pack(push)
+#pragma pack(1)
+
+// Position and rotation
+struct Position
+{
+ float pos[3];
+ float rot[3];
+};
+#pragma pack(pop)
+
+enum RecNameInts
+{
+ REC_ACTI = 0x49544341,
+ REC_ALCH = 0x48434c41,
+ REC_APPA = 0x41505041,
+ REC_ARMO = 0x4f4d5241,
+ REC_BODY = 0x59444f42,
+ REC_BOOK = 0x4b4f4f42,
+ REC_BSGN = 0x4e475342,
+ REC_CELL = 0x4c4c4543,
+ REC_CLAS = 0x53414c43,
+ REC_CLOT = 0x544f4c43,
+ REC_CNTC = 0x43544e43,
+ REC_CONT = 0x544e4f43,
+ REC_CREA = 0x41455243,
+ REC_CREC = 0x43455243,
+ REC_DIAL = 0x4c414944,
+ REC_DOOR = 0x524f4f44,
+ REC_ENCH = 0x48434e45,
+ REC_FACT = 0x54434146,
+ REC_GLOB = 0x424f4c47,
+ REC_GMST = 0x54534d47,
+ REC_INFO = 0x4f464e49,
+ REC_INGR = 0x52474e49,
+ REC_LAND = 0x444e414c,
+ REC_LEVC = 0x4356454c,
+ REC_LEVI = 0x4956454c,
+ REC_LIGH = 0x4847494c,
+ REC_LOCK = 0x4b434f4c,
+ REC_LTEX = 0x5845544c,
+ REC_MGEF = 0x4645474d,
+ REC_MISC = 0x4353494d,
+ REC_NPC_ = 0x5f43504e,
+ REC_NPCC = 0x4343504e,
+ REC_PGRD = 0x44524750,
+ REC_PROB = 0x424f5250,
+ REC_RACE = 0x45434152,
+ REC_REGN = 0x4e474552,
+ REC_REPA = 0x41504552,
+ REC_SCPT = 0x54504353,
+ REC_SKIL = 0x4c494b53,
+ REC_SNDG = 0x47444e53,
+ REC_SOUN = 0x4e554f53,
+ REC_SPEL = 0x4c455053,
+ REC_SSCR = 0x52435353,
+ REC_STAT = 0x54415453,
+ REC_WEAP = 0x50414557
+};
+
+}
+#endif
diff --git a/components/esm/effectlist.cpp b/components/esm/effectlist.cpp
new file mode 100644
index 0000000000..88f87d6e29
--- /dev/null
+++ b/components/esm/effectlist.cpp
@@ -0,0 +1,24 @@
+#include "effectlist.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM {
+
+void EffectList::load(ESMReader &esm)
+{
+ ENAMstruct s;
+ while (esm.isNextSub("ENAM")) {
+ esm.getHT(s, 24);
+ mList.push_back(s);
+ }
+}
+
+void EffectList::save(ESMWriter &esm)
+{
+ for (std::vector<ENAMstruct>::iterator it = mList.begin(); it != mList.end(); ++it) {
+ esm.writeHNT<ENAMstruct>("ENAM", *it, 24);
+ }
+}
+
+} // end namespace
diff --git a/components/esm/effectlist.hpp b/components/esm/effectlist.hpp
new file mode 100644
index 0000000000..9f5b87aeda
--- /dev/null
+++ b/components/esm/effectlist.hpp
@@ -0,0 +1,43 @@
+#ifndef OPENMW_ESM_EFFECTLIST_H
+#define OPENMW_ESM_EFFECTLIST_H
+
+#include <vector>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ #pragma pack(push)
+ #pragma pack(1)
+
+ /** Defines a spell effect. Shared between SPEL (Spells), ALCH
+ (Potions) and ENCH (Item enchantments) records
+ */
+ struct ENAMstruct
+ {
+ // Magical effect, hard-coded ID
+ short mEffectID;
+
+ // Which skills/attributes are affected (for restore/drain spells
+ // etc.)
+ signed char mSkill, mAttribute; // -1 if N/A
+
+ // Other spell parameters
+ int mRange; // 0 - self, 1 - touch, 2 - target (RangeType enum)
+ int mArea, mDuration, mMagnMin, mMagnMax;
+ };
+ #pragma pack(pop)
+
+ struct EffectList
+ {
+
+ std::vector<ENAMstruct> mList;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+ };
+
+}
+
+#endif
diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp
new file mode 100644
index 0000000000..6f51c767ec
--- /dev/null
+++ b/components/esm/esmcommon.hpp
@@ -0,0 +1,94 @@
+#ifndef OPENMW_ESM_COMMON_H
+#define OPENMW_ESM_COMMON_H
+
+#include <string>
+#include <cstring>
+
+#include <libs/platform/stdint.h>
+#include <libs/platform/string.h>
+
+namespace ESM
+{
+enum Version
+ {
+ VER_12 = 0x3f99999a,
+ VER_13 = 0x3fa66666
+ };
+
+/* A structure used for holding fixed-length strings. In the case of
+ LEN=4, it can be more efficient to match the string as a 32 bit
+ number, therefore the struct is implemented as a union with an int.
+ */
+template <int LEN>
+union NAME_T
+{
+ char name[LEN];
+ int32_t val;
+
+ bool operator==(const char *str) const
+ {
+ for(int i=0; i<LEN; i++)
+ if(name[i] != str[i]) return false;
+ else if(name[i] == 0) return true;
+ return str[LEN] == 0;
+ }
+ bool operator!=(const char *str) const { return !((*this)==str); }
+
+ bool operator==(const std::string &str) const
+ {
+ return (*this) == str.c_str();
+ }
+ bool operator!=(const std::string &str) const { return !((*this)==str); }
+
+ bool operator==(int v) const { return v == val; }
+ bool operator!=(int v) const { return v != val; }
+
+ std::string toString() const { return std::string(name, strnlen(name, LEN)); }
+
+ void assign (const std::string& value) { std::strncpy (name, value.c_str(), LEN); }
+};
+
+typedef NAME_T<4> NAME;
+typedef NAME_T<32> NAME32;
+typedef NAME_T<64> NAME64;
+typedef NAME_T<256> NAME256;
+
+#pragma pack(push)
+#pragma pack(1)
+// Data that is only present in save game files
+struct SaveData
+{
+ float pos[6]; // Player position and rotation
+ NAME64 cell; // Cell name
+ float unk2; // Unknown value - possibly game time?
+ NAME32 player; // Player name
+};
+#pragma pack(pop)
+
+/* This struct defines a file 'context' which can be saved and later
+ restored by an ESMReader instance. It will save the position within
+ a file, and when restored will let you read from that position as
+ if you never left it.
+ */
+struct ESM_Context
+{
+ std::string filename;
+ uint32_t leftRec, leftSub;
+ size_t leftFile;
+ NAME recName, subName;
+ // When working with multiple esX files, we will generate lists of all files that
+ // actually contribute to a specific cell. Therefore, we need to store the index
+ // of the file belonging to this contest. See CellStore::(list/load)refs for details.
+ int index;
+
+ // True if subName has been read but not used.
+ bool subCached;
+
+ // File position. Only used for stored contexts, not regularly
+ // updated within the reader itself.
+ size_t filePos;
+};
+
+}
+
+#endif
diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp
new file mode 100644
index 0000000000..51d86a2eeb
--- /dev/null
+++ b/components/esm/esmreader.cpp
@@ -0,0 +1,329 @@
+#include "esmreader.hpp"
+#include <stdexcept>
+
+#include "../files/constrainedfiledatastream.hpp"
+
+namespace ESM
+{
+
+using namespace Misc;
+
+ESM_Context ESMReader::getContext()
+{
+ // Update the file position before returning
+ mCtx.filePos = mEsm->tell();
+ return mCtx;
+}
+
+ESMReader::ESMReader()
+ : mBuffer(50*1024)
+ , mRecordFlags(0)
+ , mIdx(0)
+ , mGlobalReaderList(NULL)
+ , mEncoder(NULL)
+{
+}
+
+int ESMReader::getFormat() const
+{
+ return mHeader.mFormat;
+}
+
+void ESMReader::restoreContext(const ESM_Context &rc)
+{
+ // Reopen the file if necessary
+ if (mCtx.filename != rc.filename)
+ openRaw(rc.filename);
+
+ // Copy the data
+ mCtx = rc;
+
+ // Make sure we seek to the right place
+ mEsm->seek(mCtx.filePos);
+}
+
+void ESMReader::close()
+{
+ mEsm.setNull();
+ mCtx.filename.clear();
+ mCtx.leftFile = 0;
+ mCtx.leftRec = 0;
+ mCtx.leftSub = 0;
+ mCtx.subCached = false;
+ mCtx.recName.val = 0;
+ mCtx.subName.val = 0;
+}
+
+void ESMReader::openRaw(Ogre::DataStreamPtr _esm, const std::string &name)
+{
+ close();
+ mEsm = _esm;
+ mCtx.filename = name;
+ mCtx.leftFile = mEsm->size();
+}
+
+void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name)
+{
+ openRaw(_esm, name);
+
+ if (getRecName() != "TES3")
+ fail("Not a valid Morrowind file");
+
+ getRecHeader();
+
+ mHeader.load (*this);
+}
+
+void ESMReader::open(const std::string &file)
+{
+ open (openConstrainedFileDataStream (file.c_str ()), file);
+}
+
+void ESMReader::openRaw(const std::string &file)
+{
+ openRaw (openConstrainedFileDataStream (file.c_str ()), file);
+}
+
+int64_t ESMReader::getHNLong(const char *name)
+{
+ int64_t val;
+ getHNT(val, name);
+ return val;
+}
+
+std::string ESMReader::getHNOString(const char* name)
+{
+ if (isNextSub(name))
+ return getHString();
+ return "";
+}
+
+std::string ESMReader::getHNString(const char* name)
+{
+ getSubNameIs(name);
+ return getHString();
+}
+
+std::string ESMReader::getHString()
+{
+ getSubHeader();
+
+ // Hack to make MultiMark.esp load. Zero-length strings do not
+ // occur in any of the official mods, but MultiMark makes use of
+ // them. For some reason, they break the rules, and contain a byte
+ // (value 0) even if the header says there is no data. If
+ // Morrowind accepts it, so should we.
+ if (mCtx.leftSub == 0)
+ {
+ // Skip the following zero byte
+ mCtx.leftRec--;
+ char c;
+ mEsm->read(&c, 1);
+ return "";
+ }
+
+ return getString(mCtx.leftSub);
+}
+
+void ESMReader::getHExact(void*p, int size)
+{
+ getSubHeader();
+ if (size != static_cast<int> (mCtx.leftSub))
+ fail("getHExact() size mismatch");
+ getExact(p, size);
+}
+
+// Read the given number of bytes from a named subrecord
+void ESMReader::getHNExact(void*p, int size, const char* name)
+{
+ getSubNameIs(name);
+ getHExact(p, size);
+}
+
+// Get the next subrecord name and check if it matches the parameter
+void ESMReader::getSubNameIs(const char* name)
+{
+ getSubName();
+ if (mCtx.subName != name)
+ fail(
+ "Expected subrecord " + std::string(name) + " but got "
+ + mCtx.subName.toString());
+}
+
+bool ESMReader::isNextSub(const char* name)
+{
+ if (!mCtx.leftRec)
+ return false;
+
+ getSubName();
+
+ // If the name didn't match, then mark the it as 'cached' so it's
+ // available for the next call to getSubName.
+ mCtx.subCached = (mCtx.subName != name);
+
+ // If subCached is false, then subName == name.
+ return !mCtx.subCached;
+}
+
+// Read subrecord name. This gets called a LOT, so I've optimized it
+// slightly.
+void ESMReader::getSubName()
+{
+ // If the name has already been read, do nothing
+ if (mCtx.subCached)
+ {
+ mCtx.subCached = false;
+ return;
+ }
+
+ // reading the subrecord data anyway.
+ mEsm->read(mCtx.subName.name, 4);
+ mCtx.leftRec -= 4;
+}
+
+bool ESMReader::isEmptyOrGetName()
+{
+ if (mCtx.leftRec)
+ {
+ mEsm->read(mCtx.subName.name, 4);
+ mCtx.leftRec -= 4;
+ return false;
+ }
+ return true;
+}
+
+void ESMReader::skipHSub()
+{
+ getSubHeader();
+ skip(mCtx.leftSub);
+}
+
+void ESMReader::skipHSubSize(int size)
+{
+ skipHSub();
+ if (static_cast<int> (mCtx.leftSub) != size)
+ fail("skipHSubSize() mismatch");
+}
+
+void ESMReader::getSubHeader()
+{
+ if (mCtx.leftRec < 4)
+ fail("End of record while reading sub-record header");
+
+ // Get subrecord size
+ getT(mCtx.leftSub);
+
+ // Adjust number of record bytes left
+ mCtx.leftRec -= mCtx.leftSub + 4;
+}
+
+void ESMReader::getSubHeaderIs(int size)
+{
+ getSubHeader();
+ if (size != static_cast<int> (mCtx.leftSub))
+ fail("getSubHeaderIs(): Sub header mismatch");
+}
+
+NAME ESMReader::getRecName()
+{
+ if (!hasMoreRecs())
+ fail("No more records, getRecName() failed");
+ getName(mCtx.recName);
+ mCtx.leftFile -= 4;
+
+ // Make sure we don't carry over any old cached subrecord
+ // names. This can happen in some cases when we skip parts of a
+ // record.
+ mCtx.subCached = false;
+
+ return mCtx.recName;
+}
+
+void ESMReader::skipRecord()
+{
+ skip(mCtx.leftRec);
+ mCtx.leftRec = 0;
+}
+
+void ESMReader::skipHRecord()
+{
+ if (!mCtx.leftFile)
+ return;
+ getRecHeader();
+ skipRecord();
+}
+
+void ESMReader::getRecHeader(uint32_t &flags)
+{
+ // General error checking
+ if (mCtx.leftFile < 12)
+ fail("End of file while reading record header");
+ if (mCtx.leftRec)
+ fail("Previous record contains unread bytes");
+
+ getUint(mCtx.leftRec);
+ getUint(flags);// This header entry is always zero
+ getUint(flags);
+ mCtx.leftFile -= 12;
+
+ // Check that sizes add up
+ if (mCtx.leftFile < mCtx.leftRec)
+ fail("Record size is larger than rest of file");
+
+ // Adjust number of bytes mCtx.left in file
+ mCtx.leftFile -= mCtx.leftRec;
+}
+
+/*************************************************************************
+ *
+ * Lowest level data reading and misc methods
+ *
+ *************************************************************************/
+
+void ESMReader::getExact(void*x, int size)
+{
+ int t = mEsm->read(x, size);
+ if (t != size)
+ fail("Read error");
+}
+
+std::string ESMReader::getString(int size)
+{
+ size_t s = size;
+ if (mBuffer.size() <= s)
+ // Add some extra padding to reduce the chance of having to resize
+ // again later.
+ mBuffer.resize(3*s);
+
+ // And make sure the string is zero terminated
+ mBuffer[s] = 0;
+
+ // read ESM data
+ char *ptr = &mBuffer[0];
+ getExact(ptr, size);
+
+ // Convert to UTF8 and return
+ return mEncoder->getUtf8(ptr, size);
+}
+
+void ESMReader::fail(const std::string &msg)
+{
+ using namespace std;
+
+ stringstream ss;
+
+ ss << "ESM Error: " << msg;
+ ss << "\n File: " << mCtx.filename;
+ ss << "\n Record: " << mCtx.recName.toString();
+ ss << "\n Subrecord: " << mCtx.subName.toString();
+ if (!mEsm.isNull())
+ ss << "\n Offset: 0x" << hex << mEsm->tell();
+ throw std::runtime_error(ss.str());
+}
+
+void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder)
+{
+ mEncoder = encoder;
+}
+
+}
diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp
new file mode 100644
index 0000000000..edc724cd2a
--- /dev/null
+++ b/components/esm/esmreader.hpp
@@ -0,0 +1,278 @@
+#ifndef OPENMW_ESM_READER_H
+#define OPENMW_ESM_READER_H
+
+#include <libs/platform/stdint.h>
+#include <libs/platform/string.h>
+#include <cassert>
+#include <vector>
+#include <sstream>
+
+#include <OgreDataStream.h>
+
+#include <components/misc/stringops.hpp>
+
+#include <components/to_utf8/to_utf8.hpp>
+
+#include "esmcommon.hpp"
+#include "loadtes3.hpp"
+
+namespace ESM {
+
+class ESMReader
+{
+public:
+
+ ESMReader();
+
+ /*************************************************************************
+ *
+ * Information retrieval
+ *
+ *************************************************************************/
+
+ int getVer() const { return mHeader.mData.version; }
+ float getFVer() const { if(mHeader.mData.version == VER_12) return 1.2; else return 1.3; }
+ const std::string getAuthor() const { return mHeader.mData.author.toString(); }
+ const std::string getDesc() const { return mHeader.mData.desc.toString(); }
+ const std::vector<Header::MasterData> &getMasters() const { return mHeader.mMaster; }
+ int getFormat() const;
+ const NAME &retSubName() const { return mCtx.subName; }
+ uint32_t getSubSize() const { return mCtx.leftSub; }
+
+ /*************************************************************************
+ *
+ * Opening and closing
+ *
+ *************************************************************************/
+
+ /** Save the current file position and information in a ESM_Context
+ struct
+ */
+ ESM_Context getContext();
+
+ /** Restore a previously saved context */
+ void restoreContext(const ESM_Context &rc);
+
+ /** Close the file, resets all information. After calling close()
+ the structure may be reused to load a new file.
+ */
+ void close();
+
+ /// Raw opening. Opens the file and sets everything up but doesn't
+ /// parse the header.
+ void openRaw(Ogre::DataStreamPtr _esm, const std::string &name);
+
+ /// Load ES file from a new stream, parses the header. Closes the
+ /// currently open file first, if any.
+ void open(Ogre::DataStreamPtr _esm, const std::string &name);
+
+ void open(const std::string &file);
+
+ void openRaw(const std::string &file);
+
+ /// Get the file size. Make sure that the file has been opened!
+ size_t getFileSize() { return mEsm->size(); }
+ /// Get the current position in the file. Make sure that the file has been opened!
+ size_t getFileOffset() { return mEsm->tell(); }
+
+ // This is a quick hack for multiple esm/esp files. Each plugin introduces its own
+ // terrain palette, but ESMReader does not pass a reference to the correct plugin
+ // to the individual load() methods. This hack allows to pass this reference
+ // indirectly to the load() method.
+ int mIdx;
+ void setIndex(const int index) {mIdx = index; mCtx.index = index;}
+ const int getIndex() {return mIdx;}
+
+ void setGlobalReaderList(std::vector<ESMReader> *list) {mGlobalReaderList = list;}
+ std::vector<ESMReader> *getGlobalReaderList() {return mGlobalReaderList;}
+
+ /*************************************************************************
+ *
+ * Medium-level reading shortcuts
+ *
+ *************************************************************************/
+
+ // Read data of a given type, stored in a subrecord of a given name
+ template <typename X>
+ void getHNT(X &x, const char* name)
+ {
+ getSubNameIs(name);
+ getHT(x);
+ }
+
+ // Optional version of getHNT
+ template <typename X>
+ void getHNOT(X &x, const char* name)
+ {
+ if(isNextSub(name))
+ getHT(x);
+ }
+
+ // Version with extra size checking, to make sure the compiler
+ // doesn't mess up our struct padding.
+ template <typename X>
+ void getHNT(X &x, const char* name, int size)
+ {
+ assert(sizeof(X) == size);
+ getSubNameIs(name);
+ getHT(x);
+ }
+
+ template <typename X>
+ void getHNOT(X &x, const char* name, int size)
+ {
+ assert(sizeof(X) == size);
+ if(isNextSub(name))
+ getHT(x);
+ }
+
+ int64_t getHNLong(const char *name);
+
+ // Get data of a given type/size, including subrecord header
+ template <typename X>
+ void getHT(X &x)
+ {
+ getSubHeader();
+ if (mCtx.leftSub != sizeof(X))
+ fail("getHT(): subrecord size mismatch");
+ getT(x);
+ }
+
+ // Version with extra size checking, to make sure the compiler
+ // doesn't mess up our struct padding.
+ template <typename X>
+ void getHT(X &x, int size)
+ {
+ assert(sizeof(X) == size);
+ getHT(x);
+ }
+
+ // Read a string by the given name if it is the next record.
+ std::string getHNOString(const char* name);
+
+ // Read a string with the given sub-record name
+ std::string getHNString(const char* name);
+
+ // Read a string, including the sub-record header (but not the name)
+ std::string getHString();
+
+ // Read the given number of bytes from a subrecord
+ void getHExact(void*p, int size);
+
+ // Read the given number of bytes from a named subrecord
+ void getHNExact(void*p, int size, const char* name);
+
+ /*************************************************************************
+ *
+ * Low level sub-record methods
+ *
+ *************************************************************************/
+
+ // Get the next subrecord name and check if it matches the parameter
+ void getSubNameIs(const char* name);
+
+ /** Checks if the next sub record name matches the parameter. If it
+ does, it is read into 'subName' just as if getSubName() was
+ called. If not, the read name will still be available for future
+ calls to getSubName(), isNextSub() and getSubNameIs().
+ */
+ bool isNextSub(const char* name);
+
+ // Read subrecord name. This gets called a LOT, so I've optimized it
+ // slightly.
+ void getSubName();
+
+ // This is specially optimized for LoadINFO.
+ bool isEmptyOrGetName();
+
+ // Skip current sub record, including header (but not including
+ // name.)
+ void skipHSub();
+
+ // Skip sub record and check its size
+ void skipHSubSize(int size);
+
+ /* Sub-record header. This updates leftRec beyond the current
+ sub-record as well. leftSub contains size of current sub-record.
+ */
+ void getSubHeader();
+
+ /** Get sub header and check the size
+ */
+ void getSubHeaderIs(int size);
+
+ /*************************************************************************
+ *
+ * Low level record methods
+ *
+ *************************************************************************/
+
+ // Get the next record name
+ NAME getRecName();
+
+ // Skip the rest of this record. Assumes the name and header have
+ // already been read
+ void skipRecord();
+
+ // Skip an entire record, including the header (but not the name)
+ void skipHRecord();
+
+ /* Read record header. This updatesleftFile BEYOND the data that
+ follows the header, ie beyond the entire record. You should use
+ leftRec to orient yourself inside the record itself.
+ */
+ void getRecHeader() { getRecHeader(mRecordFlags); }
+ void getRecHeader(uint32_t &flags);
+
+ bool hasMoreRecs() const { return mCtx.leftFile > 0; }
+ bool hasMoreSubs() const { return mCtx.leftRec > 0; }
+
+
+ /*************************************************************************
+ *
+ * Lowest level data reading and misc methods
+ *
+ *************************************************************************/
+
+ template <typename X>
+ void getT(X &x) { getExact(&x, sizeof(X)); }
+
+ void getExact(void*x, int size);
+ void getName(NAME &name) { getT(name); }
+ void getUint(uint32_t &u) { getT(u); }
+
+ // Read the next 'size' bytes and return them as a string. Converts
+ // them from native encoding to UTF8 in the process.
+ std::string getString(int size);
+
+ void skip(int bytes) { mEsm->seek(mEsm->tell()+bytes); }
+ uint64_t getOffset() { return mEsm->tell(); }
+
+ /// Used for error handling
+ void fail(const std::string &msg);
+
+ /// Sets font encoder for ESM strings
+ void setEncoder(ToUTF8::Utf8Encoder* encoder);
+
+ /// Get record flags of last record
+ unsigned int getRecordFlags() { return mRecordFlags; }
+
+private:
+ Ogre::DataStreamPtr mEsm;
+
+ ESM_Context mCtx;
+
+ unsigned int mRecordFlags;
+
+ // Special file signifier (see SpecialFile enum above)
+
+ // Buffer for ESM strings
+ std::vector<char> mBuffer;
+
+ Header mHeader;
+
+ std::vector<ESMReader> *mGlobalReaderList;
+ ToUTF8::Utf8Encoder* mEncoder;
+};
+}
+#endif
diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp
new file mode 100644
index 0000000000..3ea6bd350a
--- /dev/null
+++ b/components/esm/esmwriter.cpp
@@ -0,0 +1,186 @@
+#include "esmwriter.hpp"
+
+#include <cassert>
+#include <fstream>
+#include <iostream>
+
+bool count = true;
+
+namespace ESM
+{
+
+int ESMWriter::getVersion()
+{
+ return mHeader.mData.version;
+}
+
+void ESMWriter::setVersion(int ver)
+{
+ mHeader.mData.version = ver;
+}
+
+void ESMWriter::setAuthor(const std::string& auth)
+{
+ mHeader.mData.author.assign (auth);
+}
+
+void ESMWriter::setDescription(const std::string& desc)
+{
+ mHeader.mData.desc.assign (desc);
+}
+
+void ESMWriter::setRecordCount (int count)
+{
+ mHeader.mData.records = count;
+}
+
+void ESMWriter::setFormat (int format)
+{
+ mHeader.mFormat = format;
+}
+
+void ESMWriter::addMaster(const std::string& name, uint64_t size)
+{
+ Header::MasterData d;
+ d.name = name;
+ d.size = size;
+ mHeader.mMaster.push_back(d);
+}
+
+void ESMWriter::save(const std::string& file)
+{
+ std::ofstream fs(file.c_str(), std::ios_base::out | std::ios_base::trunc);
+ save(fs);
+}
+
+void ESMWriter::save(std::ostream& file)
+{
+ m_recordCount = 0;
+ m_stream = &file;
+
+ startRecord("TES3", 0);
+
+ mHeader.save (*this);
+
+ endRecord("TES3");
+}
+
+void ESMWriter::close()
+{
+ m_stream->flush();
+
+ if (!m_records.empty())
+ throw "Unclosed record remaining";
+}
+
+void ESMWriter::startRecord(const std::string& name, uint32_t flags)
+{
+ m_recordCount++;
+
+ writeName(name);
+ RecordData rec;
+ rec.name = name;
+ rec.position = m_stream->tellp();
+ rec.size = 0;
+ writeT<int>(0); // Size goes here
+ writeT<int>(0); // Unused header?
+ writeT(flags);
+ m_records.push_back(rec);
+
+ assert(m_records.back().size == 0);
+}
+
+void ESMWriter::startSubRecord(const std::string& name)
+{
+ writeName(name);
+ RecordData rec;
+ rec.name = name;
+ rec.position = m_stream->tellp();
+ rec.size = 0;
+ writeT<int>(0); // Size goes here
+ m_records.push_back(rec);
+
+ assert(m_records.back().size == 0);
+}
+
+void ESMWriter::endRecord(const std::string& name)
+{
+ RecordData rec = m_records.back();
+ assert(rec.name == name);
+ m_records.pop_back();
+
+ m_stream->seekp(rec.position);
+
+ count = false;
+ write((char*)&rec.size, sizeof(int));
+ count = true;
+
+ m_stream->seekp(0, std::ios::end);
+
+}
+
+void ESMWriter::writeHNString(const std::string& name, const std::string& data)
+{
+ startSubRecord(name);
+ writeHString(data);
+ endRecord(name);
+}
+
+void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size)
+{
+ assert(data.size() <= size);
+ startSubRecord(name);
+ writeHString(data);
+
+ if (data.size() < size)
+ {
+ for (size_t i = data.size(); i < size; ++i)
+ write("\0",1);
+ }
+
+ endRecord(name);
+}
+
+void ESMWriter::writeHString(const std::string& data)
+{
+ if (data.size() == 0)
+ write("\0", 1);
+ else
+ {
+ // Convert to UTF8 and return
+ std::string ascii = m_encoder->getLegacyEnc(data);
+
+ write(ascii.c_str(), ascii.size());
+ }
+}
+
+void ESMWriter::writeHCString(const std::string& data)
+{
+ writeHString(data);
+ if (data.size() > 0 && data[data.size()-1] != '\0')
+ write("\0", 1);
+}
+
+void ESMWriter::writeName(const std::string& name)
+{
+ assert((name.size() == 4 && name[3] != '\0'));
+ write(name.c_str(), name.size());
+}
+
+void ESMWriter::write(const char* data, size_t size)
+{
+ if (count && !m_records.empty())
+ {
+ for (std::list<RecordData>::iterator it = m_records.begin(); it != m_records.end(); ++it)
+ it->size += size;
+ }
+
+ m_stream->write(data, size);
+}
+
+void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder)
+{
+ m_encoder = encoder;
+}
+
+}
diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp
new file mode 100644
index 0000000000..be3ae33abe
--- /dev/null
+++ b/components/esm/esmwriter.hpp
@@ -0,0 +1,104 @@
+#ifndef OPENMW_ESM_WRITER_H
+#define OPENMW_ESM_WRITER_H
+
+#include <iosfwd>
+#include <list>
+
+#include <components/to_utf8/to_utf8.hpp>
+
+#include "esmcommon.hpp"
+#include "loadtes3.hpp"
+
+namespace ESM {
+
+class ESMWriter
+{
+ struct RecordData
+ {
+ std::string name;
+ std::streampos position;
+ size_t size;
+ };
+
+public:
+ int getVersion();
+ void setVersion(int ver);
+ void setEncoder(ToUTF8::Utf8Encoder *encoding); // Write strings as UTF-8?
+ void setAuthor(const std::string& author);
+ void setDescription(const std::string& desc);
+ void setRecordCount (int count);
+ void setFormat (int format);
+
+ void addMaster(const std::string& name, uint64_t size);
+
+ void save(const std::string& file);
+ void save(std::ostream& file);
+ void close();
+
+ void writeHNString(const std::string& name, const std::string& data);
+ void writeHNString(const std::string& name, const std::string& data, size_t size);
+ void writeHNCString(const std::string& name, const std::string& data)
+ {
+ startSubRecord(name);
+ writeHCString(data);
+ endRecord(name);
+ }
+ void writeHNOString(const std::string& name, const std::string& data)
+ {
+ if (!data.empty())
+ writeHNString(name, data);
+ }
+ void writeHNOCString(const std::string& name, const std::string& data)
+ {
+ if (!data.empty())
+ writeHNCString(name, data);
+ }
+
+ template<typename T>
+ void writeHNT(const std::string& name, const T& data)
+ {
+ startSubRecord(name);
+ writeT(data);
+ endRecord(name);
+ }
+
+ template<typename T>
+ void writeHNT(const std::string& name, const T& data, int size)
+ {
+ startSubRecord(name);
+ writeT(data, size);
+ endRecord(name);
+ }
+
+ template<typename T>
+ void writeT(const T& data)
+ {
+ write((char*)&data, sizeof(T));
+ }
+
+ template<typename T>
+ void writeT(const T& data, size_t size)
+ {
+ write((char*)&data, size);
+ }
+
+ void startRecord(const std::string& name, uint32_t flags);
+ void startSubRecord(const std::string& name);
+ void endRecord(const std::string& name);
+ void writeHString(const std::string& data);
+ void writeHCString(const std::string& data);
+ void writeName(const std::string& data);
+ void write(const char* data, size_t size);
+
+private:
+ std::list<RecordData> m_records;
+ std::ostream* m_stream;
+ std::streampos m_headerPos;
+ ToUTF8::Utf8Encoder* m_encoder;
+ int m_recordCount;
+
+ Header mHeader;
+};
+
+}
+#endif
diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp
new file mode 100644
index 0000000000..7d4851a5f6
--- /dev/null
+++ b/components/esm/filter.cpp
@@ -0,0 +1,23 @@
+
+#include "filter.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::Filter::load (ESMReader& esm)
+{
+ mFilter = esm.getHNString ("FILT");
+ mDescription = esm.getHNString ("DESC");
+}
+
+void ESM::Filter::save (ESMWriter& esm)
+{
+ esm.writeHNCString ("FILT", mFilter);
+ esm.writeHNCString ("DESC", mDescription);
+}
+
+void ESM::Filter::blank()
+{
+ mFilter.clear();
+ mDescription.clear();
+}
diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp
new file mode 100644
index 0000000000..0fd564361e
--- /dev/null
+++ b/components/esm/filter.hpp
@@ -0,0 +1,27 @@
+#ifndef COMPONENTS_ESM_FILTER_H
+#define COMPONENTS_ESM_FILTER_H
+
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ struct Filter
+ {
+ std::string mId;
+
+ std::string mDescription;
+
+ std::string mFilter;
+
+ void load (ESMReader& esm);
+ void save (ESMWriter& esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+ };
+}
+
+#endif
diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp
new file mode 100644
index 0000000000..fd022af7e6
--- /dev/null
+++ b/components/esm/loadacti.cpp
@@ -0,0 +1,27 @@
+#include "loadacti.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+void Activator::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+ mScript = esm.getHNOString("SCRI");
+}
+void Activator::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNOCString("SCRI", mScript);
+}
+
+ void Activator::blank()
+ {
+ mName.clear();
+ mScript.clear();
+ mModel.clear();
+ }
+}
diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp
new file mode 100644
index 0000000000..a62990590d
--- /dev/null
+++ b/components/esm/loadacti.hpp
@@ -0,0 +1,24 @@
+#ifndef OPENMW_ESM_ACTI_H
+#define OPENMW_ESM_ACTI_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Activator
+{
+ std::string mId, mName, mScript, mModel;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+}
+#endif
diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp
new file mode 100644
index 0000000000..dbb69c066f
--- /dev/null
+++ b/components/esm/loadalch.cpp
@@ -0,0 +1,38 @@
+#include "loadalch.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+void Potion::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mIcon = esm.getHNOString("TEXT"); // not ITEX here for some reason
+ mScript = esm.getHNOString("SCRI");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "ALDT", 12);
+ mEffects.load(esm);
+}
+void Potion::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("TEXT", mIcon);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("ALDT", mData, 12);
+ mEffects.save(esm);
+}
+
+ void Potion::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mAutoCalc = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ mEffects.mList.clear();
+ }
+}
diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp
new file mode 100644
index 0000000000..3ede853424
--- /dev/null
+++ b/components/esm/loadalch.hpp
@@ -0,0 +1,39 @@
+#ifndef OPENMW_ESM_ALCH_H
+#define OPENMW_ESM_ALCH_H
+
+#include <string>
+
+#include "effectlist.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Alchemy item (potions)
+ */
+
+struct Potion
+{
+ struct ALDTstruct
+ {
+ float mWeight;
+ int mValue;
+ int mAutoCalc;
+ };
+ ALDTstruct mData;
+
+ std::string mId, mName, mModel, mIcon, mScript;
+ EffectList mEffects;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+
+ };
+}
+#endif
diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp
new file mode 100644
index 0000000000..4b8d2b763c
--- /dev/null
+++ b/components/esm/loadappa.cpp
@@ -0,0 +1,51 @@
+#include "loadappa.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+void Apparatus::load(ESMReader &esm)
+{
+ // we will not treat duplicated subrecords as errors here
+ while (esm.hasMoreSubs())
+ {
+ esm.getSubName();
+ NAME subName = esm.retSubName();
+
+ if (subName == "MODL")
+ mModel = esm.getHString();
+ else if (subName == "FNAM")
+ mName = esm.getHString();
+ else if (subName == "AADT")
+ esm.getHT(mData);
+ else if (subName == "SCRI")
+ mScript = esm.getHString();
+ else if (subName == "ITEX")
+ mIcon = esm.getHString();
+ else
+ esm.fail("wrong subrecord type " + subName.toString() + " for APPA record");
+ }
+}
+
+void Apparatus::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNT("AADT", mData, 16);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNCString("ITEX", mIcon);
+}
+
+ void Apparatus::blank()
+ {
+ mData.mType = 0;
+ mData.mQuality = 0;
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ mName.clear();
+ }
+}
diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp
new file mode 100644
index 0000000000..ed9d335be6
--- /dev/null
+++ b/components/esm/loadappa.hpp
@@ -0,0 +1,44 @@
+#ifndef OPENMW_ESM_APPA_H
+#define OPENMW_ESM_APPA_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Alchemist apparatus
+ */
+
+struct Apparatus
+{
+ enum AppaType
+ {
+ MortarPestle = 0,
+ Albemic = 1,
+ Calcinator = 2,
+ Retort = 3
+ };
+
+ struct AADTstruct
+ {
+ int mType;
+ float mQuality;
+ float mWeight;
+ int mValue;
+ };
+
+ AADTstruct mData;
+ std::string mId, mModel, mIcon, mScript, mName;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp
new file mode 100644
index 0000000000..e64c8705d7
--- /dev/null
+++ b/components/esm/loadarmo.cpp
@@ -0,0 +1,68 @@
+#include "loadarmo.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void PartReferenceList::load(ESMReader &esm)
+{
+ while (esm.isNextSub("INDX"))
+ {
+ PartReference pr;
+ esm.getHT(pr.mPart); // The INDX byte
+ pr.mMale = esm.getHNOString("BNAM");
+ pr.mFemale = esm.getHNOString("CNAM");
+ mParts.push_back(pr);
+ }
+}
+
+void PartReferenceList::save(ESMWriter &esm)
+{
+ for (std::vector<PartReference>::iterator it = mParts.begin(); it != mParts.end(); ++it)
+ {
+ esm.writeHNT("INDX", it->mPart);
+ esm.writeHNOString("BNAM", it->mMale);
+ esm.writeHNOString("CNAM", it->mFemale);
+ }
+}
+
+void Armor::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+ mScript = esm.getHNOString("SCRI");
+ esm.getHNT(mData, "AODT", 24);
+ mIcon = esm.getHNOString("ITEX");
+ mParts.load(esm);
+ mEnchant = esm.getHNOString("ENAM");
+}
+
+void Armor::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNT("AODT", mData, 24);
+ esm.writeHNOCString("ITEX", mIcon);
+ mParts.save(esm);
+ esm.writeHNOCString("ENAM", mEnchant);
+}
+
+ void Armor::blank()
+ {
+ mData.mType = 0;
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mHealth = 0;
+ mData.mEnchant = 0;
+ mData.mArmor = 0;
+ mParts.mParts.clear();
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ mEnchant.clear();
+ }
+}
diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp
new file mode 100644
index 0000000000..eaef42be83
--- /dev/null
+++ b/components/esm/loadarmo.hpp
@@ -0,0 +1,98 @@
+#ifndef OPENMW_ESM_ARMO_H
+#define OPENMW_ESM_ARMO_H
+
+#include <vector>
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+enum PartReferenceType
+{
+ PRT_Head = 0,
+ PRT_Hair = 1,
+ PRT_Neck = 2,
+ PRT_Cuirass = 3,
+ PRT_Groin = 4,
+ PRT_Skirt = 5,
+ PRT_RHand = 6,
+ PRT_LHand = 7,
+ PRT_RWrist = 8,
+ PRT_LWrist = 9,
+ PRT_Shield = 10,
+ PRT_RForearm = 11,
+ PRT_LForearm = 12,
+ PRT_RUpperarm = 13,
+ PRT_LUpperarm = 14,
+ PRT_RFoot = 15,
+ PRT_LFoot = 16,
+ PRT_RAnkle = 17,
+ PRT_LAnkle = 18,
+ PRT_RKnee = 19,
+ PRT_LKnee = 20,
+ PRT_RLeg = 21,
+ PRT_LLeg = 22,
+ PRT_RPauldron = 23,
+ PRT_LPauldron = 24,
+ PRT_Weapon = 25,
+ PRT_Tail = 26,
+
+ PRT_Count = 27
+};
+
+// Reference to body parts
+struct PartReference
+{
+ char mPart;
+ std::string mMale, mFemale;
+};
+
+// A list of references to body parts
+struct PartReferenceList
+{
+ std::vector<PartReference> mParts;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+
+struct Armor
+{
+ enum Type
+ {
+ Helmet = 0,
+ Cuirass = 1,
+ LPauldron = 2,
+ RPauldron = 3,
+ Greaves = 4,
+ Boots = 5,
+ LGauntlet = 6,
+ RGauntlet = 7,
+ Shield = 8,
+ LBracer = 9,
+ RBracer = 10
+ };
+
+ struct AODTstruct
+ {
+ int mType;
+ float mWeight;
+ int mValue, mHealth, mEnchant, mArmor;
+ };
+
+ AODTstruct mData;
+ PartReferenceList mParts;
+
+ std::string mId, mName, mModel, mIcon, mScript, mEnchant;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp
new file mode 100644
index 0000000000..e95a8a8603
--- /dev/null
+++ b/components/esm/loadbody.cpp
@@ -0,0 +1,22 @@
+#include "loadbody.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void BodyPart::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mRace = esm.getHNString("FNAM");
+ esm.getHNT(mData, "BYDT", 4);
+}
+void BodyPart::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mRace);
+ esm.writeHNT("BYDT", mData, 4);
+}
+
+}
diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp
new file mode 100644
index 0000000000..3ad9b1b958
--- /dev/null
+++ b/components/esm/loadbody.hpp
@@ -0,0 +1,63 @@
+#ifndef OPENMW_ESM_BODY_H
+#define OPENMW_ESM_BODY_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct BodyPart
+{
+ enum MeshPart
+ {
+ MP_Head = 0,
+ MP_Hair = 1,
+ MP_Neck = 2,
+ MP_Chest = 3,
+ MP_Groin = 4,
+ MP_Hand = 5,
+ MP_Wrist = 6,
+ MP_Forearm = 7,
+ MP_Upperarm = 8,
+ MP_Foot = 9,
+ MP_Ankle = 10,
+ MP_Knee = 11,
+ MP_Upperleg = 12,
+ MP_Clavicle = 13,
+ MP_Tail = 14,
+
+ MP_Count = 15
+ };
+
+ enum Flags
+ {
+ BPF_Female = 1,
+ BPF_NotPlayable = 2
+ };
+
+ enum MeshType
+ {
+ MT_Skin = 0,
+ MT_Clothing = 1,
+ MT_Armor = 2
+ };
+
+ struct BYDTstruct
+ {
+ char mPart;
+ char mVampire;
+ char mFlags;
+ char mType;
+ };
+
+ BYDTstruct mData;
+ std::string mId, mModel, mRace;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp
new file mode 100644
index 0000000000..3a70ac7869
--- /dev/null
+++ b/components/esm/loadbook.cpp
@@ -0,0 +1,44 @@
+#include "loadbook.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Book::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "BKDT", 20);
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+ mText = esm.getHNOString("TEXT");
+ mEnchant = esm.getHNOString("ENAM");
+}
+void Book::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("BKDT", mData, 20);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+ esm.writeHNOString("TEXT", mText);
+ esm.writeHNOCString("ENAM", mEnchant);
+}
+
+ void Book::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mIsScroll = 0;
+ mData.mSkillID = 0;
+ mData.mEnchant = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ mEnchant.clear();
+ mText.clear();
+ }
+}
diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp
new file mode 100644
index 0000000000..68042e246e
--- /dev/null
+++ b/components/esm/loadbook.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_ESM_BOOK_H
+#define OPENMW_ESM_BOOK_H
+
+#include <string>
+
+namespace ESM
+{
+/*
+ * Books, magic scrolls, notes and so on
+ */
+
+class ESMReader;
+class ESMWriter;
+
+struct Book
+{
+ struct BKDTstruct
+ {
+ float mWeight;
+ int mValue, mIsScroll, mSkillID, mEnchant;
+ };
+
+ BKDTstruct mData;
+ std::string mName, mModel, mIcon, mScript, mEnchant, mText;
+ std::string mId;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp
new file mode 100644
index 0000000000..cb500f6748
--- /dev/null
+++ b/components/esm/loadbsgn.cpp
@@ -0,0 +1,35 @@
+#include "loadbsgn.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void BirthSign::load(ESMReader &esm)
+{
+ mName = esm.getHNString("FNAM");
+ mTexture = esm.getHNOString("TNAM");
+ mDescription = esm.getHNOString("DESC");
+
+ mPowers.load(esm);
+}
+
+void BirthSign::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNOCString("TNAM", mTexture);
+ esm.writeHNOCString("DESC", mDescription);
+
+ mPowers.save(esm);
+}
+
+ void BirthSign::blank()
+ {
+ mName.clear();
+ mDescription.clear();
+ mTexture.clear();
+ mPowers.mList.clear();
+ }
+
+}
diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp
new file mode 100644
index 0000000000..434ddf68ea
--- /dev/null
+++ b/components/esm/loadbsgn.hpp
@@ -0,0 +1,28 @@
+#ifndef OPENMW_ESM_BSGN_H
+#define OPENMW_ESM_BSGN_H
+
+#include <string>
+
+#include "spelllist.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct BirthSign
+{
+ std::string mId, mName, mDescription, mTexture;
+
+ // List of powers and abilities that come with this birth sign.
+ SpellList mPowers;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp
new file mode 100644
index 0000000000..d8d0c12912
--- /dev/null
+++ b/components/esm/loadcell.cpp
@@ -0,0 +1,304 @@
+#include "loadcell.hpp"
+
+#include <string>
+#include <sstream>
+#include <list>
+#include <boost/concept_check.hpp>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+/// Some overloaded compare operators.
+bool operator==(const MovedCellRef& ref, int pRefnum)
+{
+ return (ref.mRefnum == pRefnum);
+}
+
+bool operator==(const CellRef& ref, int pRefnum)
+{
+ return (ref.mRefnum == pRefnum);
+}
+
+
+void Cell::load(ESMReader &esm, bool saveContext)
+{
+ // Ignore this for now, it might mean we should delete the entire
+ // cell?
+ // TODO: treat the special case "another plugin moved this ref, but we want to delete it"!
+ if (esm.isNextSub("DELE")) {
+ esm.skipHSub();
+ }
+
+ esm.getHNT(mData, "DATA", 12);
+
+ // Water level
+ mWater = -1;
+ mNAM0 = 0;
+
+ if (mData.mFlags & Interior)
+ {
+ // Interior cells
+ if (esm.isNextSub("INTV"))
+ {
+ int waterl;
+ esm.getHT(waterl);
+ mWater = (float) waterl;
+ mWaterInt = true;
+ }
+ else if (esm.isNextSub("WHGT"))
+ esm.getHT(mWater);
+
+ // Quasi-exterior cells have a region (which determines the
+ // weather), pure interior cells have ambient lighting
+ // instead.
+ if (mData.mFlags & QuasiEx)
+ mRegion = esm.getHNOString("RGNN");
+ else if (esm.isNextSub("AMBI"))
+ esm.getHT(mAmbi);
+ }
+ else
+ {
+ // Exterior cells
+ mRegion = esm.getHNOString("RGNN");
+
+ mMapColor = 0;
+ esm.getHNOT(mMapColor, "NAM5");
+ }
+ if (esm.isNextSub("NAM0")) {
+ esm.getHT(mNAM0);
+ }
+
+ if (saveContext) {
+ mContextList.push_back(esm.getContext());
+ esm.skipRecord();
+ }
+}
+
+void Cell::preLoad(ESMReader &esm) //Can't be "load" because it conflicts with function in esmtool
+{
+ this->load(esm, false);
+}
+
+void Cell::postLoad(ESMReader &esm)
+{
+ // Save position of the cell references and move on
+ mContextList.push_back(esm.getContext());
+ esm.skipRecord();
+}
+
+void Cell::save(ESMWriter &esm)
+{
+ esm.writeHNT("DATA", mData, 12);
+ if (mData.mFlags & Interior)
+ {
+ if (mWater != -1) {
+ if (mWaterInt) {
+ int water =
+ (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
+ esm.writeHNT("INTV", water);
+ } else {
+ esm.writeHNT("WHGT", mWater);
+ }
+ }
+
+ if (mData.mFlags & QuasiEx)
+ esm.writeHNOCString("RGNN", mRegion);
+ else
+ esm.writeHNT("AMBI", mAmbi, 16);
+ }
+ else
+ {
+ esm.writeHNOCString("RGNN", mRegion);
+ if (mMapColor != 0)
+ esm.writeHNT("NAM5", mMapColor);
+ }
+
+ if (mNAM0 != 0)
+ esm.writeHNT("NAM0", mNAM0);
+}
+
+void Cell::restore(ESMReader &esm, int iCtx) const
+{
+ esm.restoreContext(mContextList.at (iCtx));
+}
+
+std::string Cell::getDescription() const
+{
+ if (mData.mFlags & Interior)
+ {
+ return mName;
+ }
+ else
+ {
+ std::ostringstream stream;
+ stream << mData.mX << ", " << mData.mY;
+ return stream.str();
+ }
+}
+
+bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
+{
+ // TODO: Try and document reference numbering, I don't think this has been done anywhere else.
+ if (!esm.hasMoreSubs())
+ return false;
+
+ // NOTE: We should not need this check. It is a safety check until we have checked
+ // more plugins, and how they treat these moved references.
+ if (esm.isNextSub("MVRF")) {
+ esm.skipRecord(); // skip MVRF
+ esm.skipRecord(); // skip CNDT
+ // That should be it, I haven't seen any other fields yet.
+ }
+
+ // NAM0 sometimes appears here, sometimes further on
+ ref.mNam0 = 0;
+ if (esm.isNextSub("NAM0"))
+ {
+ esm.getHT(ref.mNam0);
+ //esm.getHNOT(NAM0, "NAM0");
+ }
+
+ esm.getHNT(ref.mRefnum, "FRMR");
+ ref.mRefID = esm.getHNString("NAME");
+
+ // Identify references belonging to a parent file and adapt the ID accordingly.
+ int local = (ref.mRefnum & 0xff000000) >> 24;
+ size_t global = esm.getIndex() + 1;
+ if (local)
+ {
+ // If the most significant 8 bits are used, then this reference already exists.
+ // In this case, do not spawn a new reference, but overwrite the old one.
+ ref.mRefnum &= 0x00ffffff; // delete old plugin ID
+ const std::vector<Header::MasterData> &masters = esm.getMasters();
+ global = masters[local-1].index + 1;
+ ref.mRefnum |= global << 24; // insert global plugin ID
+ }
+ else
+ {
+ // This is an addition by the present plugin. Set the corresponding plugin index.
+ ref.mRefnum |= global << 24; // insert global plugin ID
+ }
+
+ // getHNOT will not change the existing value if the subrecord is
+ // missing
+ ref.mScale = 1.0;
+ esm.getHNOT(ref.mScale, "XSCL");
+
+ // TODO: support loading references from saves, there are tons of keys not recognized yet.
+ // The following is just an incomplete list.
+ if (esm.isNextSub("ACTN"))
+ esm.skipHSub();
+ if (esm.isNextSub("STPR"))
+ esm.skipHSub();
+ if (esm.isNextSub("ACDT"))
+ esm.skipHSub();
+ if (esm.isNextSub("ACSC"))
+ esm.skipHSub();
+ if (esm.isNextSub("ACSL"))
+ esm.skipHSub();
+ if (esm.isNextSub("CHRD"))
+ esm.skipHSub();
+ else if (esm.isNextSub("CRED")) // ???
+ esm.skipHSub();
+
+ ref.mOwner = esm.getHNOString("ANAM");
+ ref.mGlob = esm.getHNOString("BNAM");
+ ref.mSoul = esm.getHNOString("XSOL");
+
+ ref.mFaction = esm.getHNOString("CNAM");
+ ref.mFactIndex = -2;
+ esm.getHNOT(ref.mFactIndex, "INDX");
+
+ ref.mGoldValue = 1;
+ ref.mCharge = -1;
+ ref.mEnchantmentCharge = -1;
+
+ esm.getHNOT(ref.mEnchantmentCharge, "XCHG");
+
+ esm.getHNOT(ref.mCharge, "INTV");
+
+ esm.getHNOT(ref.mGoldValue, "NAM9");
+
+ // Present for doors that teleport you to another cell.
+ if (esm.isNextSub("DODT"))
+ {
+ ref.mTeleport = true;
+ esm.getHT(ref.mDoorDest);
+ ref.mDestCell = esm.getHNOString("DNAM");
+ } else {
+ ref.mTeleport = false;
+ }
+
+ // Integer, despite the name suggesting otherwise
+ ref.mLockLevel = -1;
+ esm.getHNOT(ref.mLockLevel, "FLTV");
+ ref.mKey = esm.getHNOString("KNAM");
+ ref.mTrap = esm.getHNOString("TNAM");
+
+ ref.mReferenceBlocked = -1;
+ ref.mFltv = 0;
+ esm.getHNOT(ref.mReferenceBlocked, "UNAM");
+ esm.getHNOT(ref.mFltv, "FLTV");
+
+ esm.getHNOT(ref.mPos, "DATA", 24);
+
+ // Number of references in the cell? Maximum once in each cell,
+ // but not always at the beginning, and not always right. In other
+ // words, completely useless.
+ // Update: Well, maybe not completely useless. This might actually be
+ // number_of_references + number_of_references_moved_here_Across_boundaries,
+ // and could be helpful for collecting these weird moved references.
+ if (esm.isNextSub("NAM0"))
+ {
+ esm.getHT(ref.mNam0);
+ //esm.getHNOT(NAM0, "NAM0");
+ }
+
+ if (esm.isNextSub("DELE")) {
+ esm.skipHSub();
+ ref.mDeleted = 2; // Deleted, will not respawn.
+ // TODO: find out when references do respawn.
+ } else
+ ref.mDeleted = 0;
+
+ return true;
+}
+
+bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
+{
+ esm.getHT(mref.mRefnum);
+ esm.getHNOT(mref.mTarget, "CNDT");
+
+ // Identify references belonging to a parent file and adapt the ID accordingly.
+ int local = (mref.mRefnum & 0xff000000) >> 24;
+ size_t global = esm.getIndex() + 1;
+ mref.mRefnum &= 0x00ffffff; // delete old plugin ID
+ const std::vector<Header::MasterData> &masters = esm.getMasters();
+ global = masters[local-1].index + 1;
+ mref.mRefnum |= global << 24; // insert global plugin ID
+
+ return true;
+}
+
+ void Cell::blank()
+ {
+ mName.clear();
+ mRegion.clear();
+ mWater = 0;
+ mWaterInt = false;
+ mMapColor = 0;
+ mNAM0 = 0;
+
+ mData.mFlags = 0;
+ mData.mX = 0;
+ mData.mY = 0;
+
+ mAmbi.mAmbient = 0;
+ mAmbi.mSunlight = 0;
+ mAmbi.mFog = 0;
+ mAmbi.mFogDensity = 0;
+ }
+}
diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp
new file mode 100644
index 0000000000..51288b2919
--- /dev/null
+++ b/components/esm/loadcell.hpp
@@ -0,0 +1,154 @@
+#ifndef OPENMW_ESM_CELL_H
+#define OPENMW_ESM_CELL_H
+
+#include <string>
+#include <vector>
+#include <list>
+
+#include "esmcommon.hpp"
+#include "defs.hpp"
+#include "cellref.hpp"
+
+namespace MWWorld
+{
+ class ESMStore;
+}
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/* Moved cell reference tracking object. This mainly stores the target cell
+ of the reference, so we can easily know where it has been moved when another
+ plugin tries to move it independently.
+ Unfortunately, we need to implement this here.
+ */
+class MovedCellRef
+{
+public:
+ int mRefnum;
+
+ // Target cell (if exterior)
+ int mTarget[2];
+
+ // TODO: Support moving references between exterior and interior cells!
+ // This may happen in saves, when an NPC follows the player. Tribunal
+ // introduces a henchman (which no one uses), so we may need this as well.
+};
+
+/// Overloaded copare operator used to search inside a list of cell refs.
+bool operator==(const MovedCellRef& ref, int pRefnum);
+bool operator==(const CellRef& ref, int pRefnum);
+
+typedef std::list<MovedCellRef> MovedCellRefTracker;
+typedef std::list<CellRef> CellRefTracker;
+
+/* Cells hold data about objects, creatures, statics (rocks, walls,
+ buildings) and landscape (for exterior cells). Cells frequently
+ also has other associated LAND and PGRD records. Combined, all this
+ data can be huge, and we cannot load it all at startup. Instead,
+ the strategy we use is to remember the file position of each cell
+ (using ESMReader::getContext()) and jumping back into place
+ whenever we need to load a given cell.
+ */
+struct Cell
+{
+ enum Flags
+ {
+ Interior = 0x01, // Interior cell
+ HasWater = 0x02, // Does this cell have a water surface
+ NoSleep = 0x04, // Is it allowed to sleep here (without a bed)
+ QuasiEx = 0x80 // Behave like exterior (Tribunal+), with
+ // skybox and weather
+ };
+
+ struct DATAstruct
+ {
+ int mFlags;
+ int mX, mY;
+ };
+
+ struct AMBIstruct
+ {
+ Color mAmbient, mSunlight, mFog;
+ float mFogDensity;
+ };
+
+ // Interior cells are indexed by this (it's the 'id'), for exterior
+ // cells it is optional.
+ std::string mName;
+
+ // Optional region name for exterior and quasi-exterior cells.
+ std::string mRegion;
+
+ std::vector<ESM_Context> mContextList; // File position; multiple positions for multiple plugin support
+ DATAstruct mData;
+ AMBIstruct mAmbi;
+ float mWater; // Water level
+ bool mWaterInt;
+ int mMapColor;
+ int mNAM0;
+
+ // References "leased" from another cell (i.e. a different cell
+ // introduced this ref, and it has been moved here by a plugin)
+ CellRefTracker mLeasedRefs;
+ MovedCellRefTracker mMovedRefs;
+
+ void preLoad(ESMReader &esm);
+ void postLoad(ESMReader &esm);
+
+ // This method is left in for compatibility with esmtool. Parsing moved references currently requires
+ // passing ESMStore, bit it does not know about this parameter, so we do it this way.
+ void load(ESMReader &esm, bool saveContext = true);
+ void save(ESMWriter &esm);
+
+ bool isExterior() const
+ {
+ return !(mData.mFlags & Interior);
+ }
+
+ int getGridX() const
+ {
+ return mData.mX;
+ }
+
+ int getGridY() const
+ {
+ return mData.mY;
+ }
+
+ bool hasWater() const
+ {
+ return (mData.mFlags&HasWater);
+ }
+
+ // Restore the given reader to the stored position. Will try to open
+ // the file matching the stored file name. If you want to read from
+ // somewhere other than the file system, you need to pre-open the
+ // ESMReader, and the filename must match the stored filename
+ // exactly.
+ void restore(ESMReader &esm, int iCtx) const;
+
+ std::string getDescription() const;
+ ///< Return a short string describing the cell (mostly used for debugging/logging purpose)
+
+ /* Get the next reference in this cell, if any. Returns false when
+ there are no more references in the cell.
+
+ All fields of the CellRef struct are overwritten. You can safely
+ reuse one memory location without blanking it between calls.
+ */
+ static bool getNextRef(ESMReader &esm, CellRef &ref);
+
+ /* This fetches an MVRF record, which is used to track moved references.
+ * Since they are comparably rare, we use a separate method for this.
+ */
+ static bool getNextMVRF(ESMReader &esm, MovedCellRef &mref);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp
new file mode 100644
index 0000000000..bdc4614625
--- /dev/null
+++ b/components/esm/loadclas.cpp
@@ -0,0 +1,71 @@
+#include "loadclas.hpp"
+
+#include <stdexcept>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+const Class::Specialization Class::sSpecializationIds[3] = {
+ Class::Combat,
+ Class::Magic,
+ Class::Stealth
+};
+
+const char *Class::sGmstSpecializationIds[3] = {
+ "sSpecializationCombat",
+ "sSpecializationMagic",
+ "sSpecializationStealth"
+};
+
+
+ int& Class::CLDTstruct::getSkill (int index, bool major)
+ {
+ if (index<0 || index>=5)
+ throw std::logic_error ("skill index out of range");
+
+ return mSkills[index][major ? 1 : 0];
+ }
+
+ int Class::CLDTstruct::getSkill (int index, bool major) const
+ {
+ if (index<0 || index>=5)
+ throw std::logic_error ("skill index out of range");
+
+ return mSkills[index][major ? 1 : 0];
+ }
+
+void Class::load(ESMReader &esm)
+{
+ mName = esm.getHNString("FNAM");
+ esm.getHNT(mData, "CLDT", 60);
+
+ if (mData.mIsPlayable > 1)
+ esm.fail("Unknown bool value");
+
+ mDescription = esm.getHNOString("DESC");
+}
+void Class::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNT("CLDT", mData, 60);
+ esm.writeHNOString("DESC", mDescription);
+}
+
+ void Class::blank()
+ {
+ mName.clear();
+ mDescription.clear();
+
+ mData.mAttribute[0] = mData.mAttribute[1] = 0;
+ mData.mSpecialization = 0;
+ mData.mIsPlayable = 0;
+ mData.mCalc = 0;
+
+ for (int i=0; i<5; ++i)
+ for (int i2=0; i2<2; ++i2)
+ mData.mSkills[i][i2] = 0;
+ }
+}
diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp
new file mode 100644
index 0000000000..4f85e6ee8b
--- /dev/null
+++ b/components/esm/loadclas.hpp
@@ -0,0 +1,80 @@
+#ifndef OPENMW_ESM_CLAS_H
+#define OPENMW_ESM_CLAS_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Character class definitions
+ */
+
+// These flags tells us which items should be auto-calculated for this
+// class
+struct Class
+{
+ enum AutoCalc
+ {
+ Weapon = 0x00001,
+ Armor = 0x00002,
+ Clothing = 0x00004,
+ Books = 0x00008,
+ Ingredient = 0x00010,
+ Lockpick = 0x00020,
+ Probe = 0x00040,
+ Lights = 0x00080,
+ Apparatus = 0x00100,
+ Repair = 0x00200,
+ Misc = 0x00400,
+ Spells = 0x00800,
+ MagicItems = 0x01000,
+ Potions = 0x02000,
+ Training = 0x04000,
+ Spellmaking = 0x08000,
+ Enchanting = 0x10000,
+ RepairItem = 0x20000
+ };
+
+ enum Specialization
+ {
+ Combat = 0,
+ Magic = 1,
+ Stealth = 2
+ };
+
+ static const Specialization sSpecializationIds[3];
+ static const char *sGmstSpecializationIds[3];
+
+ struct CLDTstruct
+ {
+ int mAttribute[2]; // Attributes that get class bonus
+ int mSpecialization; // 0 = Combat, 1 = Magic, 2 = Stealth
+ int mSkills[5][2]; // Minor and major skills.
+ int mIsPlayable; // 0x0001 - Playable class
+
+ // I have no idea how to autocalculate these items...
+ int mCalc;
+
+ int& getSkill (int index, bool major);
+ ///< Throws an exception for invalid values of \a index.
+
+ int getSkill (int index, bool major) const;
+ ///< Throws an exception for invalid values of \a index.
+ }; // 60 bytes
+
+ std::string mId, mName, mDescription;
+ CLDTstruct mData;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+
+};
+}
+#endif
diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp
new file mode 100644
index 0000000000..10b00970fb
--- /dev/null
+++ b/components/esm/loadclot.cpp
@@ -0,0 +1,51 @@
+#include "loadclot.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Clothing::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "CTDT", 12);
+
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+
+ mParts.load(esm);
+
+
+ mEnchant = esm.getHNOString("ENAM");
+}
+void Clothing::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("CTDT", mData, 12);
+
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+
+ mParts.save(esm);
+
+ esm.writeHNOCString("ENAM", mEnchant);
+}
+
+ void Clothing::blank()
+ {
+ mData.mType = 0;
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mEnchant = 0;
+ mParts.mParts.clear();
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mIcon.clear();
+ mEnchant.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp
new file mode 100644
index 0000000000..816d03cb23
--- /dev/null
+++ b/components/esm/loadclot.hpp
@@ -0,0 +1,54 @@
+#ifndef OPENMW_ESM_CLOT_H
+#define OPENMW_ESM_CLOT_H
+
+#include <string>
+
+#include "loadarmo.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Clothing
+ */
+
+struct Clothing
+{
+ enum Type
+ {
+ Pants = 0,
+ Shoes = 1,
+ Shirt = 2,
+ Belt = 3,
+ Robe = 4,
+ RGlove = 5,
+ LGlove = 6,
+ Skirt = 7,
+ Ring = 8,
+ Amulet = 9
+ };
+
+ struct CTDTstruct
+ {
+ int mType;
+ float mWeight;
+ short mValue;
+ short mEnchant;
+ };
+ CTDTstruct mData;
+
+ PartReferenceList mParts;
+
+ std::string mId, mName, mModel, mIcon, mEnchant, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp
new file mode 100644
index 0000000000..853c8bd500
--- /dev/null
+++ b/components/esm/loadcont.cpp
@@ -0,0 +1,65 @@
+#include "loadcont.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void InventoryList::load(ESMReader &esm)
+{
+ ContItem ci;
+ while (esm.isNextSub("NPCO"))
+ {
+ esm.getHT(ci, 36);
+ mList.push_back(ci);
+ }
+}
+
+void InventoryList::save(ESMWriter &esm)
+{
+ for (std::vector<ContItem>::iterator it = mList.begin(); it != mList.end(); ++it)
+ {
+ esm.writeHNT("NPCO", *it, 36);
+ }
+}
+
+void Container::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mWeight, "CNDT", 4);
+ esm.getHNT(mFlags, "FLAG", 4);
+
+ if (mFlags & 0xf4)
+ esm.fail("Unknown flags");
+ if (!(mFlags & 0x8))
+ esm.fail("Flag 8 not set");
+
+ mScript = esm.getHNOString("SCRI");
+
+ mInventory.load(esm);
+}
+
+void Container::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("CNDT", mWeight, 4);
+ esm.writeHNT("FLAG", mFlags, 4);
+
+ esm.writeHNOCString("SCRI", mScript);
+
+ mInventory.save(esm);
+}
+
+ void Container::blank()
+ {
+ mName.clear();
+ mModel.clear();
+ mScript.clear();
+ mWeight = 0;
+ mFlags = 0;
+ mInventory.mList.clear();
+ }
+}
diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp
new file mode 100644
index 0000000000..b2bbab73d0
--- /dev/null
+++ b/components/esm/loadcont.hpp
@@ -0,0 +1,55 @@
+#ifndef OPENMW_ESM_CONT_H
+#define OPENMW_ESM_CONT_H
+
+#include <string>
+#include <vector>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Container definition
+ */
+
+struct ContItem
+{
+ int mCount;
+ NAME32 mItem;
+};
+
+struct InventoryList
+{
+ std::vector<ContItem> mList;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+
+struct Container
+{
+ enum Flags
+ {
+ Organic = 1, // Objects cannot be placed in this container
+ Respawn = 2, // Respawns after 4 months
+ Unknown = 8
+ };
+
+ std::string mId, mName, mModel, mScript;
+
+ float mWeight; // Not sure, might be max total weight allowed?
+ int mFlags;
+ InventoryList mInventory;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp
new file mode 100644
index 0000000000..86d05b8a57
--- /dev/null
+++ b/components/esm/loadcrea.cpp
@@ -0,0 +1,82 @@
+#include "loadcrea.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM {
+
+void Creature::load(ESMReader &esm)
+{
+ mPersistent = esm.getRecordFlags() & 0x0400;
+
+ mModel = esm.getHNString("MODL");
+ mOriginal = esm.getHNOString("CNAM");
+ mName = esm.getHNOString("FNAM");
+ mScript = esm.getHNOString("SCRI");
+
+ esm.getHNT(mData, "NPDT", 96);
+
+ esm.getHNT(mFlags, "FLAG");
+ mScale = 1.0;
+ esm.getHNOT(mScale, "XSCL");
+
+ mInventory.load(esm);
+ mSpells.load(esm);
+
+ if (esm.isNextSub("AIDT"))
+ {
+ esm.getHExact(&mAiData, sizeof(mAiData));
+ mHasAI = true;
+ }
+ else
+ mHasAI = false;
+
+ mAiPackage.load(esm);
+ esm.skipRecord();
+}
+
+void Creature::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("CNAM", mOriginal);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNT("NPDT", mData, 96);
+ esm.writeHNT("FLAG", mFlags);
+ if (mScale != 1.0) {
+ esm.writeHNT("XSCL", mScale);
+ }
+
+ mInventory.save(esm);
+ mSpells.save(esm);
+ if (mHasAI) {
+ esm.writeHNT("AIDT", mAiData, sizeof(mAiData));
+ }
+ mAiPackage.save(esm);
+}
+
+ void Creature::blank()
+ {
+ mData.mType = 0;
+ mData.mLevel = 0;
+ mData.mStrength = mData.mIntelligence = mData.mWillpower = mData.mAgility =
+ mData.mSpeed = mData.mEndurance = mData.mPersonality = mData.mLuck = 0;
+ mData.mHealth = mData.mMana = mData.mFatigue = 0;
+ mData.mSoul = 0;
+ mData.mCombat = mData.mMagic = mData.mStealth = 0;
+ for (int i=0; i<6; ++i) mData.mAttack[i] = 0;
+ mData.mGold = 0;
+ mFlags = 0;
+ mScale = 0;
+ mModel.clear();
+ mName.clear();
+ mScript.clear();
+ mOriginal.clear();
+ mInventory.mList.clear();
+ mSpells.mList.clear();
+ mHasAI = false;
+ mAiData.blank();
+ mAiData.mServices = 0;
+ mAiPackage.mList.clear();
+ }
+}
diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp
new file mode 100644
index 0000000000..279e2ea3f4
--- /dev/null
+++ b/components/esm/loadcrea.hpp
@@ -0,0 +1,96 @@
+#ifndef OPENMW_ESM_CREA_H
+#define OPENMW_ESM_CREA_H
+
+#include <string>
+
+#include "loadcont.hpp"
+#include "spelllist.hpp"
+#include "aipackage.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Creature definition
+ *
+ */
+
+struct Creature
+{
+ // Default is 0x48?
+ enum Flags
+ {
+ Biped = 0x001,
+ Respawn = 0x002,
+ Weapon = 0x004, // Has weapon and shield
+ None = 0x008, // ??
+ Swims = 0x010,
+ Flies = 0x020, // Don't know what happens if several
+ Walks = 0x040, // of these are set
+ Essential = 0x080,
+ Skeleton = 0x400, // Does not have normal blood
+ Metal = 0x800 // Has 'golden' blood
+ };
+
+ enum Type
+ {
+ Creatures = 0,
+ Deadra = 1,
+ Undead = 2,
+ Humanoid = 3
+ };
+
+ struct NPDTstruct
+ {
+ int mType;
+ // For creatures we obviously have to use ints, not shorts and
+ // bytes like we use for NPCs.... this file format just makes so
+ // much sense! (Still, _much_ easier to decode than the NIFs.)
+ int mLevel;
+ int mStrength,
+ mIntelligence,
+ mWillpower,
+ mAgility,
+ mSpeed,
+ mEndurance,
+ mPersonality,
+ mLuck;
+
+ int mHealth, mMana, mFatigue; // Stats
+ int mSoul; // The creatures soul value (used with soul gems.)
+ int mCombat, mMagic, mStealth; // Don't know yet.
+ int mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3
+ int mGold;
+ }; // 96 bytes
+
+ NPDTstruct mData;
+
+ int mFlags;
+
+ bool mPersistent;
+
+ float mScale;
+
+ std::string mId, mModel, mName, mScript;
+ std::string mOriginal; // Base creature that this is a modification of
+
+ InventoryList mInventory;
+ SpellList mSpells;
+
+
+ bool mHasAI;
+ AIData mAiData;
+ AIPackageList mAiPackage;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+}
+#endif
diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp
new file mode 100644
index 0000000000..6904df15a4
--- /dev/null
+++ b/components/esm/loadcrec.hpp
@@ -0,0 +1,47 @@
+#ifndef OPENMW_ESM_CREC_H
+#define OPENMW_ESM_CREC_H
+
+#include <string>
+
+// TODO create implementation files and remove this one
+#include "esmreader.hpp"
+
+namespace ESM {
+
+class ESMReader;
+class ESMWriter;
+
+/* These two are only used in save games. They are not decoded yet.
+ */
+
+/// Changes a creature
+struct LoadCREC
+{
+ std::string mId;
+
+ void load(ESMReader &esm)
+ {
+ esm.skipRecord();
+ }
+
+ void save(ESMWriter &esm)
+ {
+ }
+};
+
+/// Changes an item list / container
+struct LoadCNTC
+{
+ std::string mId;
+
+ void load(ESMReader &esm)
+ {
+ esm.skipRecord();
+ }
+
+ void save(ESMWriter &esm)
+ {
+ }
+};
+}
+#endif
diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp
new file mode 100644
index 0000000000..fb50d5e9f5
--- /dev/null
+++ b/components/esm/loaddial.cpp
@@ -0,0 +1,39 @@
+#include "loaddial.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Dialogue::load(ESMReader &esm)
+{
+ esm.getSubNameIs("DATA");
+ esm.getSubHeader();
+ int si = esm.getSubSize();
+ if (si == 1)
+ esm.getT(mType);
+ else if (si == 4)
+ {
+ // These are just markers, their values are not used.
+ int i;
+ esm.getT(i);
+ esm.getHNT(i, "DELE");
+ mType = Deleted;
+ }
+ else
+ esm.fail("Unknown sub record size");
+}
+
+void Dialogue::save(ESMWriter &esm)
+{
+ if (mType != Deleted)
+ esm.writeHNT("DATA", mType);
+ else
+ {
+ esm.writeHNT("DATA", (int)1);
+ esm.writeHNT("DELE", (int)1);
+ }
+}
+
+}
diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp
new file mode 100644
index 0000000000..61f3f763de
--- /dev/null
+++ b/components/esm/loaddial.hpp
@@ -0,0 +1,40 @@
+#ifndef OPENMW_ESM_DIAL_H
+#define OPENMW_ESM_DIAL_H
+
+#include <string>
+#include <vector>
+
+#include "loadinfo.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Dialogue topic and journal entries. The actual data is contained in
+ * the INFO records following the DIAL.
+ */
+
+struct Dialogue
+{
+ enum Type
+ {
+ Topic = 0,
+ Voice = 1,
+ Greeting = 2,
+ Persuasion = 3,
+ Journal = 4,
+ Deleted = -1
+ };
+
+ std::string mId;
+ signed char mType;
+ std::vector<DialInfo> mInfo;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp
new file mode 100644
index 0000000000..a4c7b7d58b
--- /dev/null
+++ b/components/esm/loaddoor.cpp
@@ -0,0 +1,35 @@
+#include "loaddoor.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Door::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ mScript = esm.getHNOString("SCRI");
+ mOpenSound = esm.getHNOString("SNAM");
+ mCloseSound = esm.getHNOString("ANAM");
+}
+
+void Door::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("SNAM", mOpenSound);
+ esm.writeHNOCString("ANAM", mCloseSound);
+}
+
+ void Door::blank()
+ {
+ mName.clear();
+ mModel.clear();
+ mScript.clear();
+ mOpenSound.clear();
+ mCloseSound.clear();
+ }
+}
diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp
new file mode 100644
index 0000000000..77ffc64899
--- /dev/null
+++ b/components/esm/loaddoor.hpp
@@ -0,0 +1,23 @@
+#ifndef OPENMW_ESM_DOOR_H
+#define OPENMW_ESM_DOOR_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Door
+{
+ std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp
new file mode 100644
index 0000000000..c4e278368e
--- /dev/null
+++ b/components/esm/loadench.cpp
@@ -0,0 +1,21 @@
+#include "loadench.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Enchantment::load(ESMReader &esm)
+{
+ esm.getHNT(mData, "ENDT", 16);
+ mEffects.load(esm);
+}
+
+void Enchantment::save(ESMWriter &esm)
+{
+ esm.writeHNT("ENDT", mData, 16);
+ mEffects.save(esm);
+}
+
+}
diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp
new file mode 100644
index 0000000000..999f93ad97
--- /dev/null
+++ b/components/esm/loadench.hpp
@@ -0,0 +1,45 @@
+#ifndef OPENMW_ESM_ENCH_H
+#define OPENMW_ESM_ENCH_H
+
+#include <string>
+
+#include "effectlist.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Enchantments
+ */
+
+struct Enchantment
+{
+ enum Type
+ {
+ CastOnce = 0,
+ WhenStrikes = 1,
+ WhenUsed = 2,
+ ConstantEffect = 3
+ };
+
+ struct ENDTstruct
+ {
+ int mType;
+ int mCost;
+ int mCharge;
+ int mAutocalc; // Guessing this is 1 if we are supposed to auto
+ // calculate
+ };
+
+ std::string mId;
+ ENDTstruct mData;
+ EffectList mEffects;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp
new file mode 100644
index 0000000000..e2712d462d
--- /dev/null
+++ b/components/esm/loadfact.cpp
@@ -0,0 +1,92 @@
+#include "loadfact.hpp"
+
+#include <stdexcept>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+ int& Faction::FADTstruct::getSkill (int index, bool ignored)
+ {
+ if (index<0 || index>=6)
+ throw std::logic_error ("skill index out of range");
+
+ return mSkills[index];
+ }
+
+ int Faction::FADTstruct::getSkill (int index, bool ignored) const
+ {
+ if (index<0 || index>=6)
+ throw std::logic_error ("skill index out of range");
+
+ return mSkills[index];
+ }
+
+void Faction::load(ESMReader &esm)
+{
+ mName = esm.getHNString("FNAM");
+
+ // Read rank names. These are optional.
+ int i = 0;
+ while (esm.isNextSub("RNAM") && i < 10)
+ mRanks[i++] = esm.getHString();
+
+ // Main data struct
+ esm.getHNT(mData, "FADT", 240);
+
+ if (mData.mIsHidden > 1)
+ esm.fail("Unknown flag!");
+
+ // Read faction response values
+ while (esm.hasMoreSubs())
+ {
+ Reaction r;
+ r.mFaction = esm.getHNString("ANAM");
+ esm.getHNT(r.mReaction, "INTV");
+ mReactions.push_back(r);
+ }
+}
+void Faction::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mName);
+
+ for (int i = 0; i < 10; i++)
+ {
+ if (mRanks[i].empty())
+ break;
+
+ esm.writeHNString("RNAM", mRanks[i], 32);
+ }
+
+ esm.writeHNT("FADT", mData, 240);
+
+ for (std::vector<Reaction>::iterator it = mReactions.begin(); it != mReactions.end(); ++it)
+ {
+ esm.writeHNString("ANAM", it->mFaction);
+ esm.writeHNT("INTV", it->mReaction);
+ }
+}
+
+ void Faction::blank()
+ {
+ mName.clear();
+ mData.mAttribute[0] = mData.mAttribute[1] = 0;
+ mData.mUnknown = -1;
+ mData.mIsHidden = 0;
+
+ for (int i=0; i<10; ++i)
+ {
+ mData.mRankData[i].mAttribute1 = mData.mRankData[i].mAttribute2 = 0;
+ mData.mRankData[i].mSkill1 = mData.mRankData[i].mSkill2 = 0;
+ mData.mRankData[i].mFactReaction = 0;
+
+ mRanks[i].clear();
+ }
+
+ for (int i=0; i<6; ++i)
+ mData.mSkills[i] = 0;
+
+ mReactions.clear();
+ }
+}
diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp
new file mode 100644
index 0000000000..891b996473
--- /dev/null
+++ b/components/esm/loadfact.hpp
@@ -0,0 +1,72 @@
+#ifndef OPENMW_ESM_FACT_H
+#define OPENMW_ESM_FACT_H
+
+#include <string>
+#include <vector>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Faction definitions
+ */
+
+// Requirements for each rank
+struct RankData
+{
+ int mAttribute1, mAttribute2; // Attribute level
+
+ int mSkill1, mSkill2; // Skill level (faction skills given in
+ // skillID below.) You need one skill at
+ // level 'skill1' and two skills at level
+ // 'skill2' to advance to this rank.
+
+ int mFactReaction; // Reaction from faction members
+};
+
+struct Faction
+{
+ std::string mId, mName;
+
+ struct FADTstruct
+ {
+ // Which attributes we like
+ int mAttribute[2];
+
+ RankData mRankData[10];
+
+ int mSkills[6]; // IDs of skills this faction require
+ int mUnknown; // Always -1?
+ int mIsHidden; // 1 - hidden from player
+
+ int& getSkill (int index, bool ignored = false);
+ ///< Throws an exception for invalid values of \a index.
+
+ int getSkill (int index, bool ignored = false) const;
+ ///< Throws an exception for invalid values of \a index.
+ }; // 240 bytes
+
+ FADTstruct mData;
+
+ struct Reaction
+ {
+ std::string mFaction;
+ int mReaction;
+ };
+
+ std::vector<Reaction> mReactions;
+
+ // Name of faction ranks (may be empty for NPC factions)
+ std::string mRanks[10];
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp
new file mode 100644
index 0000000000..ccb519acd4
--- /dev/null
+++ b/components/esm/loadglob.cpp
@@ -0,0 +1,24 @@
+#include "loadglob.hpp"
+
+namespace ESM
+{
+ void Global::load (ESMReader &esm)
+ {
+ mValue.read (esm, ESM::Variant::Format_Global);
+ }
+
+ void Global::save (ESMWriter &esm)
+ {
+ mValue.write (esm, ESM::Variant::Format_Global);
+ }
+
+ void Global::blank()
+ {
+ mValue.setType (ESM::VT_None);
+ }
+
+ bool operator== (const Global& left, const Global& right)
+ {
+ return left.mId==right.mId && left.mValue==right.mValue;
+ }
+}
diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp
new file mode 100644
index 0000000000..72e16c0ce5
--- /dev/null
+++ b/components/esm/loadglob.hpp
@@ -0,0 +1,33 @@
+#ifndef OPENMW_ESM_GLOB_H
+#define OPENMW_ESM_GLOB_H
+
+#include <string>
+
+#include "variant.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Global script variables
+ */
+
+struct Global
+{
+ std::string mId;
+ Variant mValue;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+bool operator== (const Global& left, const Global& right);
+
+}
+#endif
diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp
new file mode 100644
index 0000000000..fe1cc1b047
--- /dev/null
+++ b/components/esm/loadgmst.cpp
@@ -0,0 +1,39 @@
+#include "loadgmst.hpp"
+
+namespace ESM
+{
+ void GameSetting::load (ESMReader &esm)
+ {
+ mValue.read (esm, ESM::Variant::Format_Gmst);
+ }
+
+ void GameSetting::save (ESMWriter &esm)
+ {
+ mValue.write (esm, ESM::Variant::Format_Gmst);
+ }
+
+ int GameSetting::getInt() const
+ {
+ return mValue.getInteger();
+ }
+
+ float GameSetting::getFloat() const
+ {
+ return mValue.getFloat();
+ }
+
+ std::string GameSetting::getString() const
+ {
+ return mValue.getString();
+ }
+
+ void GameSetting::blank()
+ {
+ mValue.setType (ESM::VT_None);
+ }
+
+ bool operator== (const GameSetting& left, const GameSetting& right)
+ {
+ return left.mValue==right.mValue;
+ }
+}
diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp
new file mode 100644
index 0000000000..a6e0c2ecbe
--- /dev/null
+++ b/components/esm/loadgmst.hpp
@@ -0,0 +1,46 @@
+#ifndef OPENMW_ESM_GMST_H
+#define OPENMW_ESM_GMST_H
+
+#include <string>
+
+#include "variant.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Game setting, with automatic cleaning of "dirty" entries.
+ *
+ */
+
+struct GameSetting
+{
+ std::string mId;
+
+ Variant mValue;
+
+ void load(ESMReader &esm);
+
+ /// \todo remove the get* functions (redundant, since mValue as equivalent functions now).
+
+ int getInt() const;
+ ///< Throws an exception if GMST is not of type int or float.
+
+ float getFloat() const;
+ ///< Throws an exception if GMST is not of type int or float.
+
+ std::string getString() const;
+ ///< Throwns an exception if GMST is not of type string.
+
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+ bool operator== (const GameSetting& left, const GameSetting& right);
+}
+#endif
diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp
new file mode 100644
index 0000000000..90f8fcf35b
--- /dev/null
+++ b/components/esm/loadinfo.cpp
@@ -0,0 +1,156 @@
+#include "loadinfo.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void DialInfo::load(ESMReader &esm)
+{
+ mId = esm.getHNString("INAM");
+ mPrev = esm.getHNString("PNAM");
+ mNext = esm.getHNString("NNAM");
+
+ // Not present if deleted
+ if (esm.isNextSub("DATA")) {
+ esm.getHT(mData, 12);
+ }
+
+ // What follows is somewhat spaghetti-ish, but it's worth if for
+ // an extra speedup. INFO is by far the most common record type.
+
+ // subName is a reference to the original, so it changes whenever
+ // a new sub name is read. esm.isEmptyOrGetName() will get the
+ // next name for us, or return true if there are no more records.
+ esm.getSubName();
+ const NAME &subName = esm.retSubName();
+
+ if (subName.val == REC_ONAM)
+ {
+ mActor = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_RNAM)
+ {
+ mRace = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_CNAM)
+ {
+ mClass = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+
+ mFactionLess = false;
+ if (subName.val == REC_FNAM)
+ {
+ mNpcFaction = esm.getHString();
+ if (mNpcFaction == "FFFF")
+ mFactionLess = true;
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_ANAM)
+ {
+ mCell = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_DNAM)
+ {
+ mPcFaction = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_SNAM)
+ {
+ mSound = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+ if (subName.val == REC_NAME)
+ {
+ mResponse = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+
+ while (subName.val == REC_SCVR)
+ {
+ SelectStruct ss;
+
+ ss.mSelectRule = esm.getHString();
+
+ ss.mValue.read (esm, Variant::Format_Info);
+
+ mSelects.push_back(ss);
+
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+
+ if (subName.val == REC_BNAM)
+ {
+ mResultScript = esm.getHString();
+ if (esm.isEmptyOrGetName())
+ return;
+ }
+
+ mQuestStatus = QS_None;
+
+ if (subName.val == REC_QSTN)
+ mQuestStatus = QS_Name;
+ else if (subName.val == REC_QSTF)
+ mQuestStatus = QS_Finished;
+ else if (subName.val == REC_QSTR)
+ mQuestStatus = QS_Restart;
+ else if (subName.val == REC_DELE)
+ mQuestStatus = QS_Deleted;
+ else
+ esm.fail(
+ "Don't know what to do with " + subName.toString()
+ + " in INFO " + mId);
+
+ if (mQuestStatus != QS_None)
+ // Skip rest of record
+ esm.skipRecord();
+}
+
+void DialInfo::save(ESMWriter &esm)
+{
+ 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("ANAM", mCell);
+ esm.writeHNOCString("DNAM", mPcFaction);
+ esm.writeHNOCString("SNAM", mSound);
+ esm.writeHNOString("NAME", mResponse);
+
+ for (std::vector<SelectStruct>::iterator it = mSelects.begin(); it != mSelects.end(); ++it)
+ {
+ esm.writeHNString("SCVR", it->mSelectRule);
+ it->mValue.write (esm, Variant::Format_Info);
+ }
+
+ esm.writeHNOString("BNAM", mResultScript);
+
+ switch(mQuestStatus)
+ {
+ case QS_Name: esm.writeHNT("QSTN",'\1'); break;
+ case QS_Finished: esm.writeHNT("QSTF", '\1'); break;
+ case QS_Restart: esm.writeHNT("QSTR", '\1'); break;
+ case QS_Deleted: esm.writeHNT("DELE", '\1'); break;
+ default: break;
+ }
+}
+
+}
diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp
new file mode 100644
index 0000000000..2361ed9eb5
--- /dev/null
+++ b/components/esm/loadinfo.hpp
@@ -0,0 +1,106 @@
+#ifndef OPENMW_ESM_INFO_H
+#define OPENMW_ESM_INFO_H
+
+#include <string>
+#include <vector>
+
+#include "defs.hpp"
+#include "variant.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Dialogue information. A series of these follow after DIAL records,
+ * and form a linked list of dialogue items.
+ */
+
+struct DialInfo
+{
+ enum Gender
+ {
+ Male = 0,
+ Female = 1,
+ NA = -1
+ };
+
+ struct DATAstruct
+ {
+ int mUnknown1;
+ int mDisposition;
+ signed char mRank; // Rank of NPC
+ signed char mGender; // See Gender enum
+ signed char mPCrank; // Player rank
+ signed char mUnknown2;
+ }; // 12 bytes
+ DATAstruct mData;
+
+ // The rules for whether or not we will select this dialog item.
+ struct SelectStruct
+ {
+ std::string mSelectRule; // This has a complicated format
+ Variant mValue;
+ };
+
+ // Journal quest indices (introduced with the quest system in Tribunal)
+ enum QuestStatus
+ {
+ QS_None,
+ QS_Name,
+ QS_Finished,
+ QS_Restart,
+ QS_Deleted
+ };
+
+ // Rules for when to include this item in the final list of options
+ // visible to the player.
+ std::vector<SelectStruct> mSelects;
+
+ // Id of this, previous and next INFO items
+ std::string mId, mPrev, mNext;
+
+ // Various references used in determining when to select this item.
+ std::string mActor, mRace, mClass, mNpcFaction, mPcFaction, mCell;
+
+ // Sound and text associated with this item
+ std::string mSound, mResponse;
+
+ // Result script (uncomiled) to run whenever this dialog item is
+ // selected
+ std::string mResultScript;
+
+ // ONLY include this item the NPC is not part of any faction.
+ bool mFactionLess;
+
+ // Status of this quest item
+ QuestStatus mQuestStatus;
+
+ // Hexadecimal versions of the various subrecord names.
+ enum SubNames
+ {
+ REC_ONAM = 0x4d414e4f,
+ REC_RNAM = 0x4d414e52,
+ REC_CNAM = 0x4d414e43,
+ REC_FNAM = 0x4d414e46,
+ REC_ANAM = 0x4d414e41,
+ REC_DNAM = 0x4d414e44,
+ REC_SNAM = 0x4d414e53,
+ REC_NAME = 0x454d414e,
+ REC_SCVR = 0x52564353,
+
+ REC_BNAM = 0x4d414e42,
+ REC_QSTN = 0x4e545351,
+ REC_QSTF = 0x46545351,
+ REC_QSTR = 0x52545351,
+ REC_DELE = 0x454c4544
+ };
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+
+}
+#endif
diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp
new file mode 100644
index 0000000000..7e31a4116d
--- /dev/null
+++ b/components/esm/loadingr.cpp
@@ -0,0 +1,65 @@
+#include "loadingr.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Ingredient::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+ esm.getHNT(mData, "IRDT", 56);
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+ // horrible hack to fix broken data in records
+ for (int i=0; i<4; ++i)
+ {
+ if (mData.mEffectID[i] != 85 &&
+ mData.mEffectID[i] != 22 &&
+ mData.mEffectID[i] != 17 &&
+ mData.mEffectID[i] != 79 &&
+ mData.mEffectID[i] != 74)
+ {
+ mData.mAttributes[i] = -1;
+ }
+
+ // is this relevant in cycle from 0 to 4?
+ if (mData.mEffectID[i] != 89 &&
+ mData.mEffectID[i] != 26 &&
+ mData.mEffectID[i] != 21 &&
+ mData.mEffectID[i] != 83 &&
+ mData.mEffectID[i] != 78)
+ {
+ mData.mSkills[i] = -1;
+ }
+ }
+}
+
+void Ingredient::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNT("IRDT", mData, 56);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+}
+
+ void Ingredient::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ for (int i=0; i<4; ++i)
+ {
+ mData.mEffectID[i] = 0;
+ mData.mSkills[i] = 0;
+ mData.mAttributes[i] = 0;
+ }
+
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp
new file mode 100644
index 0000000000..5e286535f4
--- /dev/null
+++ b/components/esm/loadingr.hpp
@@ -0,0 +1,37 @@
+#ifndef OPENMW_ESM_INGR_H
+#define OPENMW_ESM_INGR_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Alchemy ingredient
+ */
+
+struct Ingredient
+{
+ struct IRDTstruct
+ {
+ float mWeight;
+ int mValue;
+ int mEffectID[4]; // Effect, 0 or -1 means none
+ int mSkills[4]; // SkillEnum related to effect
+ int mAttributes[4]; // Attribute related to effect
+ };
+
+ IRDTstruct mData;
+ std::string mId, mName, mModel, mIcon, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp
new file mode 100644
index 0000000000..60c475040f
--- /dev/null
+++ b/components/esm/loadland.cpp
@@ -0,0 +1,245 @@
+#include "loadland.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Land::LandData::save(ESMWriter &esm)
+{
+ if (mDataTypes & Land::DATA_VNML) {
+ esm.writeHNT("VNML", mNormals, sizeof(VNML));
+ }
+ if (mDataTypes & Land::DATA_VHGT) {
+ static VHGT offsets;
+ offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE;
+ offsets.mUnk1 = mUnk1;
+ offsets.mUnk2 = mUnk2;
+
+ float prevY = mHeights[0], prevX;
+ int number = 0; // avoid multiplication
+ for (int i = 0; i < LAND_SIZE; ++i) {
+ float diff = (mHeights[number] - prevY) / HEIGHT_SCALE;
+ offsets.mHeightData[number] =
+ (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
+
+ prevX = prevY = mHeights[number];
+ ++number;
+
+ for (int j = 1; j < LAND_SIZE; ++j) {
+ diff = (mHeights[number] - prevX) / HEIGHT_SCALE;
+ offsets.mHeightData[number] =
+ (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
+
+ prevX = mHeights[number];
+ ++number;
+ }
+ }
+ esm.writeHNT("VHGT", offsets, sizeof(VHGT));
+ }
+ if (mDataTypes & Land::DATA_WNAM) {
+ esm.writeHNT("WNAM", mWnam, 81);
+ }
+ if (mDataTypes & Land::DATA_VCLR) {
+ esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS);
+ }
+ if (mDataTypes & Land::DATA_VTEX) {
+ static uint16_t vtex[LAND_NUM_TEXTURES];
+ transposeTextureData(mTextures, vtex);
+ esm.writeHNT("VTEX", vtex, sizeof(vtex));
+ }
+}
+
+void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out)
+{
+ int readPos = 0; //bit ugly, but it works
+ for ( int y1 = 0; y1 < 4; y1++ )
+ for ( int x1 = 0; x1 < 4; x1++ )
+ for ( int y2 = 0; y2 < 4; y2++)
+ for ( int x2 = 0; x2 < 4; x2++ )
+ out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++];
+}
+
+Land::Land()
+ : mFlags(0)
+ , mX(0)
+ , mY(0)
+ , mEsm(NULL)
+ , mDataTypes(0)
+ , mDataLoaded(false)
+ , mLandData(NULL)
+ , mPlugin(0)
+ , mHasData(false)
+{
+}
+
+Land::~Land()
+{
+ delete mLandData;
+}
+
+void Land::load(ESMReader &esm)
+{
+ mEsm = &esm;
+ mPlugin = mEsm->getIndex();
+
+ // Get the grid location
+ esm.getSubNameIs("INTV");
+ esm.getSubHeaderIs(8);
+ esm.getT<int>(mX);
+ esm.getT<int>(mY);
+
+ esm.getHNT(mFlags, "DATA");
+
+ // Store the file position
+ mContext = esm.getContext();
+
+ mHasData = false;
+
+ // Skip these here. Load the actual data when the cell is loaded.
+ if (esm.isNextSub("VNML"))
+ {
+ esm.skipHSubSize(12675);
+ mDataTypes |= DATA_VNML;
+ }
+ if (esm.isNextSub("VHGT"))
+ {
+ esm.skipHSubSize(4232);
+ mDataTypes |= DATA_VHGT;
+ }
+ if (esm.isNextSub("WNAM"))
+ {
+ esm.skipHSubSize(81);
+ mDataTypes |= DATA_WNAM;
+ }
+ if (esm.isNextSub("VCLR"))
+ {
+ esm.skipHSubSize(12675);
+ mDataTypes |= DATA_VCLR;
+ }
+ if (esm.isNextSub("VTEX"))
+ {
+ esm.skipHSubSize(512);
+ mDataTypes |= DATA_VTEX;
+ }
+
+ // We need all three of VNML, VHGT and VTEX in order to use the
+ // landscape. (Though Morrowind seems to accept terrain without VTEX/VCLR entries)
+ mHasData = mDataTypes & (DATA_VNML|DATA_VHGT|DATA_WNAM);
+
+ mDataLoaded = 0;
+ mLandData = NULL;
+}
+
+void Land::save(ESMWriter &esm)
+{
+ esm.startSubRecord("INTV");
+ esm.writeT(mX);
+ esm.writeT(mY);
+ esm.endRecord("INTV");
+
+ esm.writeHNT("DATA", mFlags);
+
+ // TODO: Land!
+ bool wasLoaded = mDataLoaded;
+ if (mDataTypes) {
+ // Try to load all available data before saving
+ loadData(mDataTypes);
+ }
+ if (mDataLoaded)
+ mLandData->save(esm);
+
+ if (!wasLoaded)
+ unloadData(); // Don't need to keep the data loaded if it wasn't already
+}
+
+/// \todo remove memory allocation when only defaults needed
+void Land::loadData(int flags)
+{
+ // Try to load only available data
+ int actual = flags & mDataTypes;
+ // Return if all required data is loaded
+ if (flags == 0 || (actual != 0 && (mDataLoaded & actual) == actual)) {
+ return;
+ }
+ // Create storage if nothing is loaded
+ if (mLandData == NULL) {
+ mLandData = new LandData;
+ mLandData->mDataTypes = mDataTypes;
+ }
+ mEsm->restoreContext(mContext);
+
+ memset(mLandData->mNormals, 0, LAND_NUM_VERTS * 3);
+
+ if (mEsm->isNextSub("VNML")) {
+ condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(VNML));
+ }
+
+ if (mEsm->isNextSub("VHGT")) {
+ static VHGT vhgt;
+ if (condLoad(actual, DATA_VHGT, &vhgt, sizeof(vhgt))) {
+ float rowOffset = vhgt.mHeightOffset;
+ for (int y = 0; y < LAND_SIZE; y++) {
+ rowOffset += vhgt.mHeightData[y * LAND_SIZE];
+
+ mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
+
+ float colOffset = rowOffset;
+ for (int x = 1; x < LAND_SIZE; x++) {
+ colOffset += vhgt.mHeightData[y * LAND_SIZE + x];
+ mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
+ }
+ }
+ mLandData->mUnk1 = vhgt.mUnk1;
+ mLandData->mUnk2 = vhgt.mUnk2;
+ }
+ } else if ((flags & DATA_VHGT) && (mDataLoaded & DATA_VHGT) == 0) {
+ for (int i = 0; i < LAND_NUM_VERTS; ++i) {
+ mLandData->mHeights[i] = -256.0f * HEIGHT_SCALE;
+ }
+ mDataLoaded |= DATA_VHGT;
+ }
+
+ if (mEsm->isNextSub("WNAM")) {
+ condLoad(actual, DATA_WNAM, mLandData->mWnam, 81);
+ }
+ if (mEsm->isNextSub("VCLR")) {
+ mLandData->mUsingColours = true;
+ condLoad(actual, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS);
+ } else {
+ mLandData->mUsingColours = false;
+ }
+ if (mEsm->isNextSub("VTEX")) {
+ static uint16_t vtex[LAND_NUM_TEXTURES];
+ if (condLoad(actual, DATA_VTEX, vtex, sizeof(vtex))) {
+ LandData::transposeTextureData(vtex, mLandData->mTextures);
+ }
+ } else if ((flags & DATA_VTEX) && (mDataLoaded & DATA_VTEX) == 0) {
+ memset(mLandData->mTextures, 0, sizeof(mLandData->mTextures));
+ mDataLoaded |= DATA_VTEX;
+ }
+}
+
+void Land::unloadData()
+{
+ if (mDataLoaded)
+ {
+ delete mLandData;
+ mLandData = NULL;
+ mDataLoaded = 0;
+ }
+}
+
+bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size)
+{
+ if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
+ mEsm->getHExact(ptr, size);
+ mDataLoaded |= dataFlag;
+ return true;
+ }
+ mEsm->skipHSubSize(size);
+ return false;
+}
+
+}
diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp
new file mode 100644
index 0000000000..9c1fd1f5c6
--- /dev/null
+++ b/components/esm/loadland.hpp
@@ -0,0 +1,126 @@
+#ifndef OPENMW_ESM_LAND_H
+#define OPENMW_ESM_LAND_H
+
+#include <libs/platform/stdint.h>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Landscape data.
+ */
+
+struct Land
+{
+ Land();
+ ~Land();
+
+ int mFlags; // Only first four bits seem to be used, don't know what
+ // they mean.
+ int mX, mY; // Map coordinates.
+ int mPlugin; // Plugin index, used to reference the correct material palette.
+
+ // File context. This allows the ESM reader to be 'reset' to this
+ // location later when we are ready to load the full data set.
+ ESMReader* mEsm;
+ ESM_Context mContext;
+
+ bool mHasData;
+ int mDataTypes;
+ int mDataLoaded;
+
+ enum
+ {
+ DATA_VNML = 1,
+ DATA_VHGT = 2,
+ DATA_WNAM = 4,
+ DATA_VCLR = 8,
+ DATA_VTEX = 16
+ };
+
+ // number of vertices per side
+ static const int LAND_SIZE = 65;
+
+ // cell terrain size in world coords
+ static const int REAL_SIZE = 8192;
+
+ // total number of vertices
+ static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE;
+
+ static const int HEIGHT_SCALE = 8;
+
+ //number of textures per side of land
+ static const int LAND_TEXTURE_SIZE = 16;
+
+ //total number of textures per land
+ static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE;
+
+#pragma pack(push,1)
+ struct VHGT
+ {
+ float mHeightOffset;
+ int8_t mHeightData[LAND_NUM_VERTS];
+ short mUnk1;
+ char mUnk2;
+ };
+#pragma pack(pop)
+
+ typedef signed char VNML[LAND_NUM_VERTS * 3];
+
+ struct LandData
+ {
+ float mHeightOffset;
+ float mHeights[LAND_NUM_VERTS];
+ VNML mNormals;
+ uint16_t mTextures[LAND_NUM_TEXTURES];
+
+ bool mUsingColours;
+ char mColours[3 * LAND_NUM_VERTS];
+ int mDataTypes;
+
+ uint8_t mWnam[81];
+ short mUnk1;
+ uint8_t mUnk2;
+
+ void save(ESMWriter &esm);
+ static void transposeTextureData(uint16_t *in, uint16_t *out);
+ };
+
+ LandData *mLandData;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ /**
+ * Actually loads data
+ */
+ void loadData(int flags);
+
+ /**
+ * Frees memory allocated for land data
+ */
+ void unloadData();
+
+ /// Check if given data type is loaded
+ /// \todo reimplement this
+ bool isDataLoaded(int flags) {
+ return (mDataLoaded & flags) == flags;
+ }
+
+ private:
+ Land(const Land& land);
+ Land& operator=(const Land& land);
+
+ /// Loads data and marks it as loaded
+ /// \return true if data is actually loaded from file, false otherwise
+ /// including the case when data is already loaded
+ bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size);
+};
+
+}
+#endif
diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp
new file mode 100644
index 0000000000..b54a912760
--- /dev/null
+++ b/components/esm/loadlevlist.cpp
@@ -0,0 +1,55 @@
+#include "loadlevlist.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void LeveledListBase::load(ESMReader &esm)
+{
+ esm.getHNT(mFlags, "DATA");
+ esm.getHNT(mChanceNone, "NNAM");
+
+ if (esm.isNextSub("INDX"))
+ {
+ int len;
+ esm.getHT(len);
+ mList.resize(len);
+ }
+ else
+ return;
+
+ // TODO: Merge with an existing lists here. This can be done
+ // simply by adding the lists together, making sure that they are
+ // sorted by level. A better way might be to exclude repeated
+ // items. Also, some times we don't want to merge lists, just
+ // overwrite. Figure out a way to give the user this option.
+
+ for (size_t i = 0; i < mList.size(); i++)
+ {
+ LevelItem &li = mList[i];
+ li.mId = esm.getHNString(mRecName);
+ esm.getHNT(li.mLevel, "INTV");
+ }
+}
+void LeveledListBase::save(ESMWriter &esm)
+{
+ esm.writeHNT("DATA", mFlags);
+ esm.writeHNT("NNAM", mChanceNone);
+ esm.writeHNT<int>("INDX", mList.size());
+
+ for (std::vector<LevelItem>::iterator it = mList.begin(); it != mList.end(); ++it)
+ {
+ esm.writeHNCString(mRecName, it->mId);
+ esm.writeHNT("INTV", it->mLevel);
+ }
+}
+
+ void LeveledListBase::blank()
+ {
+ mFlags = 0;
+ mChanceNone = 0;
+ mList.clear();
+ }
+}
diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp
new file mode 100644
index 0000000000..7339cac56f
--- /dev/null
+++ b/components/esm/loadlevlist.hpp
@@ -0,0 +1,77 @@
+#ifndef OPENMW_ESM_LEVLISTS_H
+#define OPENMW_ESM_LEVLISTS_H
+
+#include <string>
+#include <vector>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Leveled lists. Since these have identical layout, I only bothered
+ * to implement it once.
+ *
+ * We should later implement the ability to merge leveled lists from
+ * several files.
+ */
+
+struct LeveledListBase
+{
+ enum Flags
+ {
+
+ Each = 0x01, // Select a new item each time this
+ // list is instantiated, instead of
+ // giving several identical items
+ // (used when a container has more
+ // than one instance of one leveled
+ // list.)
+ AllLevels = 0x02 // Calculate from all levels <= player
+ // level, not just the closest below
+ // player.
+ };
+
+ int mFlags;
+ unsigned char mChanceNone; // Chance that none are selected (0-100)
+ std::string mId;
+
+ // Record name used to read references. Must be set before load() is
+ // called.
+ const char *mRecName;
+
+ struct LevelItem
+ {
+ std::string mId;
+ short mLevel;
+ };
+
+ std::vector<LevelItem> mList;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+struct CreatureLevList: LeveledListBase
+{
+ CreatureLevList()
+ {
+ mRecName = "CNAM";
+ }
+};
+
+struct ItemLevList: LeveledListBase
+{
+ ItemLevList()
+ {
+ mRecName = "INAM";
+ }
+};
+
+}
+#endif
diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp
new file mode 100644
index 0000000000..89a2b8c65b
--- /dev/null
+++ b/components/esm/loadligh.cpp
@@ -0,0 +1,43 @@
+#include "loadligh.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Light::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ mIcon = esm.getHNOString("ITEX");
+ assert(sizeof(mData) == 24);
+ esm.getHNT(mData, "LHDT", 24);
+ mScript = esm.getHNOString("SCRI");
+ mSound = esm.getHNOString("SNAM");
+}
+void Light::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNOCString("ITEX", mIcon);
+ esm.writeHNT("LHDT", mData, 24);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("SNAM", mSound);
+}
+
+ void Light::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mTime = 0;
+ mData.mRadius = 0;
+ mData.mColor = 0;
+ mData.mFlags = 0;
+ mSound.clear();
+ mScript.clear();
+ mModel.clear();
+ mIcon.clear();
+ mName.clear();
+ }
+}
diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp
new file mode 100644
index 0000000000..3f0b76d6e2
--- /dev/null
+++ b/components/esm/loadligh.hpp
@@ -0,0 +1,53 @@
+#ifndef OPENMW_ESM_LIGH_H
+#define OPENMW_ESM_LIGH_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Lights. Includes static light sources and also carryable candles
+ * and torches.
+ */
+
+struct Light
+{
+ enum Flags
+ {
+ Dynamic = 0x001,
+ Carry = 0x002, // Can be carried
+ Negative = 0x004, // Negative light - i.e. darkness
+ Flicker = 0x008,
+ Fire = 0x010,
+ OffDefault = 0x020, // Off by default
+ FlickerSlow = 0x040,
+ Pulse = 0x080,
+ PulseSlow = 0x100
+ };
+
+ struct LHDTstruct
+ {
+ float mWeight;
+ int mValue;
+ int mTime; // Duration
+ int mRadius;
+ int mColor; // 4-byte rgba value
+ int mFlags;
+ }; // Size = 24 bytes
+
+ LHDTstruct mData;
+
+ std::string mSound, mScript, mModel, mIcon, mName, mId;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp
new file mode 100644
index 0000000000..03eac52bd5
--- /dev/null
+++ b/components/esm/loadlock.cpp
@@ -0,0 +1,41 @@
+#include "loadlock.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Lockpick::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+
+ esm.getHNT(mData, "LKDT", 16);
+
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+}
+
+void Lockpick::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+
+ esm.writeHNT("LKDT", mData, 16);
+ esm.writeHNOString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+}
+
+ void Lockpick::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mQuality = 0;
+ mData.mUses = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp
new file mode 100644
index 0000000000..953066cb2f
--- /dev/null
+++ b/components/esm/loadlock.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_ESM_LOCK_H
+#define OPENMW_ESM_LOCK_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Lockpick
+{
+ struct Data
+ {
+ float mWeight;
+ int mValue;
+
+ float mQuality;
+ int mUses;
+ }; // Size = 16
+
+ Data mData;
+ std::string mId, mName, mModel, mIcon, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+}
+#endif
diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp
new file mode 100644
index 0000000000..e523e9fa7f
--- /dev/null
+++ b/components/esm/loadltex.cpp
@@ -0,0 +1,20 @@
+#include "loadltex.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void LandTexture::load(ESMReader &esm)
+{
+ esm.getHNT(mIndex, "INTV");
+ mTexture = esm.getHNString("DATA");
+}
+void LandTexture::save(ESMWriter &esm)
+{
+ esm.writeHNT("INTV", mIndex);
+ esm.writeHNCString("DATA", mTexture);
+}
+
+}
diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp
new file mode 100644
index 0000000000..6e6d987d49
--- /dev/null
+++ b/components/esm/loadltex.hpp
@@ -0,0 +1,37 @@
+#ifndef OPENMW_ESM_LTEX_H
+#define OPENMW_ESM_LTEX_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Texture used for texturing landscape.
+ *
+ * They are probably indexed by 'num', not 'id', but I don't know for
+ * sure. And num is not unique between files, so one option is to keep
+ * a separate list for each input file (that has LTEX records, of
+ * course.) We also need to resolve references to already existing
+ * land textures to save space.
+
+ * I'm not sure if it is even possible to override existing land
+ * textures, probably not. I'll have to try it, and have to mimic the
+ * behaviour of morrowind. First, check what you are allowed to do in
+ * the editor. Then make an esp which changes a commonly used land
+ * texture, and see if it affects the game.
+ */
+
+struct LandTexture
+{
+ std::string mId, mTexture;
+ int mIndex;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp
new file mode 100644
index 0000000000..060645b5f4
--- /dev/null
+++ b/components/esm/loadmgef.cpp
@@ -0,0 +1,278 @@
+#include "loadmgef.hpp"
+
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace
+{
+ const int NumberOfHardcodedFlags = 143;
+ const int HardcodedFlags[NumberOfHardcodedFlags] = {
+ 0x11c8, 0x11c0, 0x11c8, 0x11e0, 0x11e0, 0x11e0, 0x11e0, 0x11d0,
+ 0x11c0, 0x11c0, 0x11e0, 0x11c0, 0x11184, 0x11184, 0x1f0, 0x1f0,
+ 0x1f0, 0x11d2, 0x11f0, 0x11d0, 0x11d0, 0x11d1, 0x1d2, 0x1f0,
+ 0x1d0, 0x1d0, 0x1d1, 0x1f0, 0x11d0, 0x11d0, 0x11d0, 0x11d0,
+ 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x1d0, 0x1d0, 0x11c8,
+ 0x31c0, 0x11c0, 0x11c0, 0x11c0, 0x1180, 0x11d8, 0x11d8, 0x11d0,
+ 0x11d0, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180,
+ 0x11180, 0x11c4, 0x111b8, 0x1040, 0x104c, 0x104c, 0x104c, 0x104c,
+ 0x1040, 0x1040, 0x1040, 0x11c0, 0x11c0, 0x1cc, 0x1cc, 0x1cc,
+ 0x1cc, 0x1cc, 0x1c2, 0x1c0, 0x1c0, 0x1c0, 0x1c1, 0x11c2,
+ 0x11c0, 0x11c0, 0x11c0, 0x11c1, 0x11c0, 0x21192, 0x20190, 0x20190,
+ 0x20190, 0x21191, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0,
+ 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x1c0, 0x11190, 0x9048, 0x9048,
+ 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048,
+ 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x11c0, 0x1180, 0x1180,
+ 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1188, 0x5048,
+ 0x5048, 0x5048, 0x5048, 0x5048, 0x1048, 0x104c, 0x1048, 0x40,
+ 0x11c8, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048
+ };
+}
+
+namespace ESM
+{
+
+void MagicEffect::load(ESMReader &esm)
+{
+ esm.getHNT(mIndex, "INDX");
+
+ esm.getHNT(mData, "MEDT", 36);
+ if (mIndex>=0 && mIndex<NumberOfHardcodedFlags)
+ mData.mFlags |= HardcodedFlags[mIndex];
+
+ mIcon = esm.getHNOString("ITEX");
+ mParticle = esm.getHNOString("PTEX");
+
+ mBoltSound = esm.getHNOString("BSND");
+ mCastSound = esm.getHNOString("CSND");
+ mHitSound = esm.getHNOString("HSND");
+ mAreaSound = esm.getHNOString("ASND");
+
+ mCasting = esm.getHNOString("CVFX");
+ mBolt = esm.getHNOString("BVFX");
+ mHit = esm.getHNOString("HVFX");
+ mArea = esm.getHNOString("AVFX");
+
+ mDescription = esm.getHNOString("DESC");
+}
+void MagicEffect::save(ESMWriter &esm)
+{
+ esm.writeHNT("INDX", mIndex);
+
+ mData.mFlags &= 0xe00;
+ esm.writeHNT("MEDT", mData, 36);
+ if (mIndex>=0 && mIndex<NumberOfHardcodedFlags) {
+ mData.mFlags |= HardcodedFlags[mIndex];
+ }
+
+ esm.writeHNOCString("ITEX", mIcon);
+ esm.writeHNOCString("PTEX", mParticle);
+ esm.writeHNOCString("BSND", mBoltSound);
+ esm.writeHNOCString("CSND", mCastSound);
+ esm.writeHNOCString("HSND", mHitSound);
+ esm.writeHNOCString("ASND", mAreaSound);
+
+ esm.writeHNOCString("CVFX", mCasting);
+ esm.writeHNOCString("BVFX", mBolt);
+ esm.writeHNOCString("HVFX", mHit);
+ esm.writeHNOCString("AVFX", mArea);
+
+ esm.writeHNOString("DESC", mDescription);
+}
+
+
+static std::map<short,std::string> genNameMap()
+{
+ // Map effect ID to GMST name
+ // http://www.uesp.net/morrow/hints/mweffects.shtml
+ std::map<short, std::string> names;
+ names[85] ="sEffectAbsorbAttribute";
+ names[88] ="sEffectAbsorbFatigue";
+ names[86] ="sEffectAbsorbHealth";
+ names[87] ="sEffectAbsorbSpellPoints";
+ names[89] ="sEffectAbsorbSkill";
+ names[63] ="sEffectAlmsiviIntervention";
+ names[47] ="sEffectBlind";
+ names[123] ="sEffectBoundBattleAxe";
+ names[129] ="sEffectBoundBoots";
+ names[127] ="sEffectBoundCuirass";
+ names[120] ="sEffectBoundDagger";
+ names[131] ="sEffectBoundGloves";
+ names[128] ="sEffectBoundHelm";
+ names[125] ="sEffectBoundLongbow";
+ names[121] ="sEffectBoundLongsword";
+ names[122] ="sEffectBoundMace";
+ names[130] ="sEffectBoundShield";
+ names[124] ="sEffectBoundSpear";
+ names[7] ="sEffectBurden";
+ names[50] ="sEffectCalmCreature";
+ names[49] ="sEffectCalmHumanoid";
+ names[40] ="sEffectChameleon";
+ names[44] ="sEffectCharm";
+ names[118] ="sEffectCommandCreatures";
+ names[119] ="sEffectCommandHumanoids";
+ names[132] ="sEffectCorpus"; // NB this typo. (bethesda made it)
+ names[70] ="sEffectCureBlightDisease";
+ names[69] ="sEffectCureCommonDisease";
+ names[71] ="sEffectCureCorprusDisease";
+ names[73] ="sEffectCureParalyzation";
+ names[72] ="sEffectCurePoison";
+ names[22] ="sEffectDamageAttribute";
+ names[25] ="sEffectDamageFatigue";
+ names[23] ="sEffectDamageHealth";
+ names[24] ="sEffectDamageMagicka";
+ names[26] ="sEffectDamageSkill";
+ names[54] ="sEffectDemoralizeCreature";
+ names[53] ="sEffectDemoralizeHumanoid";
+ names[64] ="sEffectDetectAnimal";
+ names[65] ="sEffectDetectEnchantment";
+ names[66] ="sEffectDetectKey";
+ names[38] ="sEffectDisintegrateArmor";
+ names[37] ="sEffectDisintegrateWeapon";
+ names[57] ="sEffectDispel";
+ names[62] ="sEffectDivineIntervention";
+ names[17] ="sEffectDrainAttribute";
+ names[20] ="sEffectDrainFatigue";
+ names[18] ="sEffectDrainHealth";
+ names[19] ="sEffectDrainSpellpoints";
+ names[21] ="sEffectDrainSkill";
+ names[8] ="sEffectFeather";
+ names[14] ="sEffectFireDamage";
+ names[4] ="sEffectFireShield";
+ names[117] ="sEffectFortifyAttackBonus";
+ names[79] ="sEffectFortifyAttribute";
+ names[82] ="sEffectFortifyFatigue";
+ names[80] ="sEffectFortifyHealth";
+ names[81] ="sEffectFortifySpellpoints";
+ names[84] ="sEffectFortifyMagickaMultiplier";
+ names[83] ="sEffectFortifySkill";
+ names[52] ="sEffectFrenzyCreature";
+ names[51] ="sEffectFrenzyHumanoid";
+ names[16] ="sEffectFrostDamage";
+ names[6] ="sEffectFrostShield";
+ names[39] ="sEffectInvisibility";
+ names[9] ="sEffectJump";
+ names[10] ="sEffectLevitate";
+ names[41] ="sEffectLight";
+ names[5] ="sEffectLightningShield";
+ names[12] ="sEffectLock";
+ names[60] ="sEffectMark";
+ names[43] ="sEffectNightEye";
+ names[13] ="sEffectOpen";
+ names[45] ="sEffectParalyze";
+ names[27] ="sEffectPoison";
+ names[56] ="sEffectRallyCreature";
+ names[55] ="sEffectRallyHumanoid";
+ names[61] ="sEffectRecall";
+ names[68] ="sEffectReflect";
+ names[100] ="sEffectRemoveCurse";
+ names[95] ="sEffectResistBlightDisease";
+ names[94] ="sEffectResistCommonDisease";
+ names[96] ="sEffectResistCorprusDisease";
+ names[90] ="sEffectResistFire";
+ names[91] ="sEffectResistFrost";
+ names[93] ="sEffectResistMagicka";
+ names[98] ="sEffectResistNormalWeapons";
+ names[99] ="sEffectResistParalysis";
+ names[97] ="sEffectResistPoison";
+ names[92] ="sEffectResistShock";
+ names[74] ="sEffectRestoreAttribute";
+ names[77] ="sEffectRestoreFatigue";
+ names[75] ="sEffectRestoreHealth";
+ names[76] ="sEffectRestoreSpellPoints";
+ names[78] ="sEffectRestoreSkill";
+ names[42] ="sEffectSanctuary";
+ names[3] ="sEffectShield";
+ names[15] ="sEffectShockDamage";
+ names[46] ="sEffectSilence";
+ names[11] ="sEffectSlowFall";
+ names[58] ="sEffectSoultrap";
+ names[48] ="sEffectSound";
+ names[67] ="sEffectSpellAbsorption";
+ names[136] ="sEffectStuntedMagicka";
+ names[106] ="sEffectSummonAncestralGhost";
+ names[110] ="sEffectSummonBonelord";
+ names[108] ="sEffectSummonLeastBonewalker";
+ names[134] ="sEffectSummonCenturionSphere";
+ names[103] ="sEffectSummonClannfear";
+ names[104] ="sEffectSummonDaedroth";
+ names[105] ="sEffectSummonDremora";
+ names[114] ="sEffectSummonFlameAtronach";
+ names[115] ="sEffectSummonFrostAtronach";
+ names[113] ="sEffectSummonGoldenSaint";
+ names[109] ="sEffectSummonGreaterBonewalker";
+ names[112] ="sEffectSummonHunger";
+ names[102] ="sEffectSummonScamp";
+ names[107] ="sEffectSummonSkeletalMinion";
+ names[116] ="sEffectSummonStormAtronach";
+ names[111] ="sEffectSummonWingedTwilight";
+ names[135] ="sEffectSunDamage";
+ names[1] ="sEffectSwiftSwim";
+ names[59] ="sEffectTelekinesis";
+ names[101] ="sEffectTurnUndead";
+ names[133] ="sEffectVampirism";
+ names[0] ="sEffectWaterBreathing";
+ names[2] ="sEffectWaterWalking";
+ names[33] ="sEffectWeaknesstoBlightDisease";
+ names[32] ="sEffectWeaknesstoCommonDisease";
+ names[34] ="sEffectWeaknesstoCorprusDisease";
+ names[28] ="sEffectWeaknesstoFire";
+ names[29] ="sEffectWeaknesstoFrost";
+ names[31] ="sEffectWeaknesstoMagicka";
+ names[36] ="sEffectWeaknesstoNormalWeapons";
+ names[35] ="sEffectWeaknesstoPoison";
+ names[30] ="sEffectWeaknesstoShock";
+
+ // bloodmoon
+ names[138] ="sEffectSummonCreature01";
+ names[139] ="sEffectSummonCreature02";
+ names[140] ="sEffectSummonCreature03";
+ names[141] ="sEffectSummonCreature04";
+ names[142] ="sEffectSummonCreature05";
+
+ // tribunal
+ names[137] ="sEffectSummonFabricant";
+
+ return names;
+}
+const std::map<short,std::string> MagicEffect::sNames = genNameMap();
+
+const std::string &MagicEffect::effectIdToString(short effectID)
+{
+ std::map<short,std::string>::const_iterator name = sNames.find(effectID);
+ if(name == sNames.end())
+ throw std::runtime_error(std::string("Unimplemented effect ID ")+boost::lexical_cast<std::string>(effectID));
+
+ return name->second;
+}
+
+class FindSecond {
+ const std::string &mName;
+
+public:
+ FindSecond(const std::string &name) : mName(name) { }
+
+ bool operator()(const std::pair<short,std::string> &item) const
+ {
+ if(Misc::StringUtils::ciEqual(item.second, mName))
+ return true;
+ return false;
+ }
+};
+
+short MagicEffect::effectStringToId(const std::string &effect)
+{
+ std::map<short,std::string>::const_iterator name;
+
+ name = std::find_if(sNames.begin(), sNames.end(), FindSecond(effect));
+ if(name == sNames.end())
+ throw std::runtime_error(std::string("Unimplemented effect ")+effect);
+
+ return name->first;
+}
+
+
+}
diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp
new file mode 100644
index 0000000000..b74efb4662
--- /dev/null
+++ b/components/esm/loadmgef.hpp
@@ -0,0 +1,227 @@
+#ifndef OPENMW_ESM_MGEF_H
+#define OPENMW_ESM_MGEF_H
+
+#include <string>
+#include <map>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct MagicEffect
+{
+ enum Flags
+ {
+ TargetSkill = 0x1, // Affects a specific skill, which is specified elsewhere in the effect structure.
+ TargetAttribute = 0x2, // Affects a specific attribute, which is specified elsewhere in the effect structure.
+ NoDuration = 0x4, // Has no duration. Only runs effect once on cast.
+ NoMagnitude = 0x8, // Has no magnitude.
+ Harmful = 0x10, // Counts as a negative effect. Interpreted as useful for attack, and is treated as a bad effect in alchemy.
+ ContinuousVfx = 0x20, // The effect's hit particle VFX repeats for the full duration of the spell, rather than occuring once on hit.
+ CastSelf = 0x40, // Allows range - cast on self.
+ CastTouch = 0x80, // Allows range - cast on touch.
+ CastTarget = 0x100, // Allows range - cast on target.
+ UncappedDamage = 0x1000, // Negates multiple cap behaviours. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second.
+ NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target.
+ Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally.
+ CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells.
+ SpellMaking = 0x0200,
+ Enchanting = 0x0400,
+ Negative = 0x0800 // A harmful effect. Will determine whether
+ // eg. NPCs regard this spell as an attack. (same as 0x10?)
+ };
+
+ struct MEDTstruct
+ {
+ int mSchool; // SpellSchool, see defs.hpp
+ float mBaseCost;
+ int mFlags;
+ // Properties of the fired magic 'ball' I think
+ int mRed, mBlue, mGreen;
+ float mSpeed, mSize, mSizeCap;
+ }; // 36 bytes
+
+ static const std::map<short,std::string> sNames;
+
+ static const std::string &effectIdToString(short effectID);
+ static short effectStringToId(const std::string &effect);
+
+
+ MEDTstruct mData;
+
+ std::string mIcon, mParticle; // Textures
+ std::string mCasting, mHit, mArea; // Statics
+ std::string mBolt; // Weapon
+ std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds
+ std::string mDescription;
+
+ // Index of this magical effect. Corresponds to one of the
+ // hard-coded effects in the original engine:
+ // 0-136 in Morrowind
+ // 137 in Tribunal
+ // 138-140 in Bloodmoon (also changes 64?)
+ // 141-142 are summon effects introduced in bloodmoon, but not used
+ // there. They can be redefined in mods by setting the name in GMST
+ // sEffectSummonCreature04/05 creature id in
+ // sMagicCreature04ID/05ID.
+ int mIndex;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+
+ enum Effects
+ {
+ WaterBreathing = 0,
+ SwiftSwim = 1,
+ WaterWalking = 2,
+ Shield = 3,
+ FireShield = 4,
+ LightningShield = 5,
+ FrostShield = 6,
+ Burden = 7,
+ Feather = 8,
+ Jump = 9,
+ Levitate = 10,
+ SlowFall = 11,
+ Lock = 12,
+ Open = 13,
+ FireDamage = 14,
+ ShockDamage = 15,
+ FrostDamage = 16,
+ DrainAttribute = 17,
+ DrainHealth = 18,
+ DrainMagicka = 19,
+ DrainFatigue = 20,
+ DrainSkill = 21,
+ DamageAttribute = 22,
+ DamageHealth = 23,
+ DamageMagicka = 24,
+ DamageFatigue = 25,
+ DamageSkill = 26,
+ Poison = 27,
+ WeaknessToFire = 28,
+ WeaknessToFrost = 29,
+ WeaknessToShock = 30,
+ WeaknessToMagicka = 31,
+ WeaknessToCommonDisease = 32,
+ WeaknessToBlightDisease = 33,
+ WeaknessToCorprusDisease = 34,
+ WeaknessToPoison = 35,
+ WeaknessToNormalWeapons = 36,
+ DisintegrateWeapon = 37,
+ DisintegrateArmor = 38,
+ Invisibility = 39,
+ Chameleon = 40,
+ Light = 41,
+ Sanctuary = 42,
+ NightEye = 43,
+ Charm = 44,
+ Paralyze = 45,
+ Silence = 46,
+ Blind = 47,
+ Sound = 48,
+ CalmHumanoid = 49,
+ CalmCreature = 50,
+ FrenzyHumanoid = 51,
+ FrenzyCreature = 52,
+ DemoralizeHumanoid = 53,
+ DemoralizeCreature = 54,
+ RallyHumanoid = 55,
+ RallyCreature = 56,
+ Dispel = 57,
+ Soultrap = 58,
+ Telekinesis = 59,
+ Mark = 60,
+ Recall = 61,
+ DivineIntervention = 62,
+ AlmsiviIntervention = 63,
+ DetectAnimal = 64,
+ DetectEnchantment = 65,
+ DetectKey = 66,
+ SpellAbsorption = 67,
+ Reflect = 68,
+ CureCommonDisease = 69,
+ CureBlightDisease = 70,
+ CureCorprusDisease = 71,
+ CurePoison = 72,
+ CureParalyzation = 73,
+ RestoreAttribute = 74,
+ RestoreHealth = 75,
+ RestoreMagicka = 76,
+ RestoreFatigue = 77,
+ RestoreSkill = 78,
+ FortifyAttribute = 79,
+ FortifyHealth = 80,
+ FortifyMagicka= 81,
+ FortifyFatigue = 82,
+ FortifySkill = 83,
+ FortifyMaximumMagicka = 84,
+ AbsorbAttribute = 85,
+ AbsorbHealth = 86,
+ AbsorbMagicka = 87,
+ AbsorbFatigue = 88,
+ AbsorbSkill = 89,
+ ResistFire = 90,
+ ResistFrost = 91,
+ ResistShock = 92,
+ ResistMagicka = 93,
+ ResistCommonDisease = 94,
+ ResistBlightDisease = 95,
+ ResistCorprusDisease = 96,
+ ResistPoison = 97,
+ ResistNormalWeapons = 98,
+ ResistParalysis = 99,
+ RemoveCurse = 100,
+ TurnUndead = 101,
+ SummonScamp = 102,
+ SummonClannfear = 103,
+ SummonDaedroth = 104,
+ SummonDremora = 105,
+ SummonAncestralGhost = 106,
+ SummonSkeletalMinion = 107,
+ SummonBonewalker = 108,
+ SummonGreaterBonewalker = 109,
+ SummonBonelord = 110,
+ SummonWingedTwilight = 111,
+ SummonHunger = 112,
+ SummonGoldenSaint = 113,
+ SummonFlameAtronach = 114,
+ SummonFrostAtronach = 115,
+ SummonStormAtronach = 116,
+ FortifyAttack = 117,
+ CommandCreature = 118,
+ CommandHumanoid = 119,
+ BoundDagger = 120,
+ BoundLongsword = 121,
+ BoundMace = 122,
+ BoundBattleAxe = 123,
+ BoundSpear = 124,
+ BoundLongbow = 125,
+ ExtraSpell = 126,
+ BoundCuirass = 127,
+ BoundHelm = 128,
+ BoundBoots = 129,
+ BoundShield = 130,
+ BoundGloves = 131,
+ Corprus = 132,
+ Vampirism = 133,
+ SummonCenturionSphere = 134,
+ SunDamage = 135,
+ StuntedMagicka = 136,
+
+ // Tribunal only
+ SummonFabricant = 137,
+
+ // Bloodmoon only
+ SummonWolf = 138,
+ SummonBear = 139,
+ SummonBonewolf = 140,
+ SummonCreature04 = 141,
+ SummonCreature05 = 142
+ };
+};
+}
+#endif
diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp
new file mode 100644
index 0000000000..6006334ea4
--- /dev/null
+++ b/components/esm/loadmisc.cpp
@@ -0,0 +1,36 @@
+#include "loadmisc.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Miscellaneous::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "MCDT", 12);
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+}
+void Miscellaneous::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("MCDT", mData, 12);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+}
+
+ void Miscellaneous::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mIsKey = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp
new file mode 100644
index 0000000000..25a9c5865a
--- /dev/null
+++ b/components/esm/loadmisc.hpp
@@ -0,0 +1,38 @@
+#ifndef OPENMW_ESM_MISC_H
+#define OPENMW_ESM_MISC_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Misc inventory items, basically things that have no use but can be
+ * carried, bought and sold. It also includes keys.
+ */
+
+struct Miscellaneous
+{
+ struct MCDTstruct
+ {
+ float mWeight;
+ int mValue;
+ int mIsKey; // There are many keys in Morrowind.esm that has this
+ // set to 0. TODO: Check what this field corresponds to
+ // in the editor.
+ };
+ MCDTstruct mData;
+
+ std::string mId, mName, mModel, mIcon, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp
new file mode 100644
index 0000000000..7e17a93dc6
--- /dev/null
+++ b/components/esm/loadnpc.cpp
@@ -0,0 +1,147 @@
+#include "loadnpc.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void NPC::load(ESMReader &esm)
+{
+ mNpdt52.mGold = -10;
+
+ mPersistent = esm.getRecordFlags() & 0x0400;
+
+ mModel = esm.getHNOString("MODL");
+ mName = esm.getHNOString("FNAM");
+
+ mRace = esm.getHNString("RNAM");
+ mClass = esm.getHNString("CNAM");
+ mFaction = esm.getHNString("ANAM");
+ mHead = esm.getHNString("BNAM");
+ mHair = esm.getHNString("KNAM");
+
+ mScript = esm.getHNOString("SCRI");
+
+ esm.getSubNameIs("NPDT");
+ esm.getSubHeader();
+ if (esm.getSubSize() == 52)
+ {
+ mNpdtType = 52;
+ esm.getExact(&mNpdt52, 52);
+ }
+ else if (esm.getSubSize() == 12)
+ {
+ mNpdtType = 12;
+ esm.getExact(&mNpdt12, 12);
+ }
+ else
+ esm.fail("NPC_NPDT must be 12 or 52 bytes long");
+
+ esm.getHNT(mFlags, "FLAG");
+
+ mInventory.load(esm);
+ mSpells.load(esm);
+
+ if (esm.isNextSub("AIDT"))
+ {
+ esm.getHExact(&mAiData, sizeof(mAiData));
+ mHasAI= true;
+ }
+ else
+ mHasAI = false;
+
+ while (esm.isNextSub("DODT") || esm.isNextSub("DNAM")) {
+ if (esm.retSubName() == 0x54444f44) { // DODT struct
+ Dest dodt;
+ esm.getHExact(&dodt.mPos, 24);
+ mTransport.push_back(dodt);
+ } else if (esm.retSubName() == 0x4d414e44) { // DNAM struct
+ mTransport.back().mCellName = esm.getHString();
+ }
+ }
+ mAiPackage.load(esm);
+ esm.skipRecord();
+}
+void NPC::save(ESMWriter &esm)
+{
+ esm.writeHNOCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNCString("RNAM", mRace);
+ esm.writeHNCString("CNAM", mClass);
+ esm.writeHNCString("ANAM", mFaction);
+ esm.writeHNCString("BNAM", mHead);
+ esm.writeHNCString("KNAM", mHair);
+ esm.writeHNOCString("SCRI", mScript);
+
+ if (mNpdtType == 52)
+ esm.writeHNT("NPDT", mNpdt52, 52);
+ else if (mNpdtType == 12)
+ esm.writeHNT("NPDT", mNpdt12, 12);
+
+ esm.writeHNT("FLAG", mFlags);
+
+ mInventory.save(esm);
+ mSpells.save(esm);
+ if (mHasAI) {
+ esm.writeHNT("AIDT", mAiData, sizeof(mAiData));
+ }
+
+ typedef std::vector<Dest>::iterator DestIter;
+ for (DestIter it = mTransport.begin(); it != mTransport.end(); ++it) {
+ esm.writeHNT("DODT", it->mPos, sizeof(it->mPos));
+ esm.writeHNOCString("DNAM", it->mCellName);
+ }
+ mAiPackage.save(esm);
+}
+
+ bool NPC::isMale() const {
+ return (mFlags & Female) == 0;
+ }
+
+ void NPC::setIsMale(bool value) {
+ mFlags |= Female;
+ if (value) {
+ mFlags ^= Female;
+ }
+ }
+
+ void NPC::blank()
+ {
+ mNpdtType = 0;
+ 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;
+ mNpdt52.mReputation = 0;
+ mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0;
+ mNpdt52.mDisposition = 0;
+ mNpdt52.mFactionID = 0;
+ mNpdt52.mRank = 0;
+ mNpdt52.mUnknown = 0;
+ mNpdt52.mGold = 0;
+ mNpdt12.mLevel = 0;
+ mNpdt12.mDisposition = 0;
+ mNpdt12.mReputation = 0;
+ mNpdt12.mRank = 0;
+ mNpdt12.mUnknown1 = 0;
+ mNpdt12.mUnknown2 = 0;
+ mNpdt12.mUnknown3 = 0;
+ mNpdt12.mGold = 0;
+ mFlags = 0;
+ mInventory.mList.clear();
+ mSpells.mList.clear();
+ mAiData.blank();
+ mHasAI = false;
+ mTransport.clear();
+ mAiPackage.mList.clear();
+ mName.clear();
+ mModel.clear();
+ mRace.clear();
+ mClass.clear();
+ mFaction.clear();
+ mScript.clear();
+ mHair.clear();
+ mHead.clear();
+ }
+}
diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp
new file mode 100644
index 0000000000..009bc5ef3b
--- /dev/null
+++ b/components/esm/loadnpc.hpp
@@ -0,0 +1,130 @@
+#ifndef OPENMW_ESM_NPC_H
+#define OPENMW_ESM_NPC_H
+
+#include <string>
+#include <vector>
+
+#include "defs.hpp"
+#include "loadcont.hpp"
+#include "aipackage.hpp"
+#include "spelllist.hpp"
+
+namespace ESM {
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * NPC definition
+ */
+
+struct NPC
+{
+ // Services
+ enum Services
+ {
+ // This merchant buys:
+ Weapon = 0x00001,
+ Armor = 0x00002,
+ Clothing = 0x00004,
+ Books = 0x00008,
+ Ingredients = 0x00010,
+ Picks = 0x00020,
+ Probes = 0x00040,
+ Lights = 0x00080,
+ Apparatus = 0x00100,
+ RepairItem = 0x00200,
+ Misc = 0x00400,
+ Potions = 0x02000,
+
+ // Other services
+ Spells = 0x00800,
+ MagicItems = 0x01000,
+ Training = 0x04000, // What skills?
+ Spellmaking = 0x08000,
+ Enchanting = 0x10000,
+ Repair = 0x20000
+ };
+
+ enum Flags
+ {
+ Female = 0x0001,
+ Essential = 0x0002,
+ Respawn = 0x0004,
+ Autocalc = 0x0008,
+ Skeleton = 0x0400, // Skeleton blood effect (white)
+ Metal = 0x0800 // Metal blood effect (golden?)
+ };
+
+ #pragma pack(push)
+ #pragma pack(1)
+
+ struct NPDTstruct52
+ {
+ short mLevel;
+ unsigned char mStrength,
+ mIntelligence,
+ mWillpower,
+ mAgility,
+ mSpeed,
+ mEndurance,
+ mPersonality,
+ mLuck;
+
+ char mSkills[27];
+ char mReputation;
+ short mHealth, mMana, mFatigue;
+ char mDisposition, mFactionID, mRank;
+ char mUnknown;
+ int mGold;
+ }; // 52 bytes
+
+ struct NPDTstruct12
+ {
+ short mLevel;
+ char mDisposition, mReputation, mRank;
+ char mUnknown1, mUnknown2, mUnknown3;
+ int mGold; // ?? not certain
+ }; // 12 bytes
+
+ struct Dest
+ {
+ Position mPos;
+ std::string mCellName;
+ };
+ #pragma pack(pop)
+
+ char mNpdtType;
+ NPDTstruct52 mNpdt52;
+ NPDTstruct12 mNpdt12; // Use this if npdt52.gold == -10
+
+ int mFlags;
+
+ bool mPersistent;
+
+ InventoryList mInventory;
+ SpellList mSpells;
+
+ AIData mAiData;
+ bool mHasAI;
+
+ std::vector<Dest> mTransport;
+ AIPackageList mAiPackage;
+
+ std::string mId, mName, mModel, mRace, mClass, mFaction, mScript;
+
+ // body parts
+ std::string mHair, mHead;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ bool isMale() const;
+
+ void setIsMale(bool value);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadnpcc.hpp b/components/esm/loadnpcc.hpp
new file mode 100644
index 0000000000..79d92397f8
--- /dev/null
+++ b/components/esm/loadnpcc.hpp
@@ -0,0 +1,92 @@
+#ifndef OPENMW_ESM_NPCC_H
+#define OPENMW_ESM_NPCC_H
+
+#include <string>
+
+// TODO: create implementation files to remove this
+#include "esmreader.hpp"
+
+namespace ESM {
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * NPC change information (found in savegame files only). We can't
+ * read these yet.
+ *
+ * Some general observations about savegames:
+ *
+ * Magical items/potions/spells/etc are added normally as new ALCH,
+ * SPEL, etc. records, with unique numeric identifiers.
+ *
+ * Books with ability enhancements are listed in the save if they have
+ * been read.
+ *
+ * GLOB records set global variables.
+ *
+ * SCPT records do not define new scripts, but assign values to the
+ * variables of existing ones.
+ *
+ * STLN - stolen items, ONAM is the owner
+ *
+ * GAME - contains a GMDT (game data) of unknown format
+ *
+ * VFXM, SPLM, KLST - no clue
+ *
+ * PCDT - seems to contain a lot of DNAMs, strings?
+ *
+ * FMAP - MAPH and MAPD, probably map data.
+ *
+ * JOUR - the entire journal in html
+ *
+ * QUES - seems to contain all the quests in the game, not just the
+ * ones you have done or begun.
+ *
+ * REGN - lists all regions in the game, even unvisited ones.
+ *
+ * The DIAL/INFO blocks contain changes to characters' dialog status.
+ *
+ * Dammit there's a lot of stuff in there! Should really have
+ * suspected as much. The strategy further is to completely ignore
+ * save files for the time being.
+ *
+ * Several records have a "change" variant, like NPCC, CNTC
+ * (contents), and CREC (creature.) These seem to alter specific
+ * instances of creatures, npcs, etc. I have not identified most of
+ * their subrecords yet.
+ *
+ * Several NPCC records have names that begin with "chargen ", I don't
+ * know if it means something special yet.
+ *
+ * The CNTC blocks seem to be instances of leveled lists. When a
+ * container is supposed to contain this leveled list of this type,
+ * but is referenced elsewhere in the file by an INDX, the CNTC with
+ * the corresponding leveled list identifier and INDX will determine
+ * the container contents instead.
+ *
+ * Some classes of objects seem to be altered, and these include an
+ * INDX, which is probably an index used by specific references other
+ * places within the save file. I guess this means 'use this class for
+ * these objects, not the general class.' All the indices I have
+ * encountered so far are zero, but they have been for different
+ * classes (different containers, really) so possibly we start from
+ * zero for each class. This looks like a mess, but is probably still
+ * easier than to duplicate everything. I think WRITING this format
+ * will be harder than reading it.
+ */
+
+struct LoadNPCC
+{
+ std::string mId;
+
+ void load(ESMReader &esm)
+ {
+ esm.skipRecord();
+ }
+ void save(ESMWriter &esm)
+ {
+ }
+};
+}
+#endif
diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp
new file mode 100644
index 0000000000..882addcb9d
--- /dev/null
+++ b/components/esm/loadpgrd.cpp
@@ -0,0 +1,99 @@
+#include "loadpgrd.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Pathgrid::load(ESMReader &esm)
+{
+ esm.getHNT(mData, "DATA", 12);
+ mCell = esm.getHNString("NAME");
+
+ // keep track of total connections so we can reserve edge vector size
+ int edgeCount = 0;
+
+ if (esm.isNextSub("PGRP"))
+ {
+ esm.getSubHeader();
+ int size = esm.getSubSize();
+ // Check that the sizes match up. Size = 16 * s2 (path points)
+ if (size != static_cast<int> (sizeof(Point) * mData.mS2))
+ esm.fail("Path point subrecord size mismatch");
+ else
+ {
+ int pointCount = mData.mS2;
+ mPoints.reserve(pointCount);
+ for (int i = 0; i < pointCount; ++i)
+ {
+ Point p;
+ esm.getExact(&p, sizeof(Point));
+ mPoints.push_back(p);
+ edgeCount += p.mConnectionNum;
+ }
+ }
+ }
+
+ if (esm.isNextSub("PGRC"))
+ {
+ esm.getSubHeader();
+ int size = esm.getSubSize();
+ if (size % sizeof(int) != 0)
+ esm.fail("PGRC size not a multiple of 4");
+ else
+ {
+ int rawConnNum = size / sizeof(int);
+ std::vector<int> rawConnections;
+ rawConnections.reserve(rawConnNum);
+ for (int i = 0; i < rawConnNum; ++i)
+ {
+ int currentValue;
+ esm.getT(currentValue);
+ rawConnections.push_back(currentValue);
+ }
+
+ std::vector<int>::const_iterator rawIt = rawConnections.begin();
+ int pointIndex = 0;
+ mEdges.reserve(edgeCount);
+ for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex)
+ {
+ unsigned char connectionNum = (*it).mConnectionNum;
+ for (int i = 0; i < connectionNum; ++i) {
+ Edge edge;
+ edge.mV0 = pointIndex;
+ edge.mV1 = *rawIt;
+ ++rawIt;
+ mEdges.push_back(edge);
+ }
+ }
+ }
+ }
+}
+void Pathgrid::save(ESMWriter &esm)
+{
+ esm.writeHNT("DATA", mData, 12);
+ esm.writeHNCString("NAME", mCell);
+
+ if (!mPoints.empty())
+ {
+ esm.startSubRecord("PGRP");
+ for (PointList::iterator it = mPoints.begin(); it != mPoints.end(); ++it)
+ {
+ esm.writeT(*it);
+ }
+ esm.endRecord("PGRP");
+ }
+
+ if (!mEdges.empty())
+ {
+ esm.startSubRecord("PGRC");
+ for (std::vector<Edge>::iterator it = mEdges.begin(); it != mEdges.end(); ++it)
+ {
+ esm.writeT(it->mV1);
+ }
+ esm.endRecord("PGRC");
+ }
+}
+
+}
diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp
new file mode 100644
index 0000000000..c3f50fc4da
--- /dev/null
+++ b/components/esm/loadpgrd.hpp
@@ -0,0 +1,52 @@
+#ifndef OPENMW_ESM_PGRD_H
+#define OPENMW_ESM_PGRD_H
+
+#include <string>
+#include <vector>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Path grid.
+ */
+struct Pathgrid
+{
+ struct DATAstruct
+ {
+ int mX, mY; // Grid location, matches cell for exterior cells
+ short mS1; // ?? Usually but not always a power of 2. Doesn't seem
+ // to have any relation to the size of PGRC.
+ short mS2; // Number of path points.
+ }; // 12 bytes
+
+ struct Point // path grid point
+ {
+ int mX, mY, mZ; // Location of point
+ unsigned char mAutogenerated; // autogenerated vs. user coloring flag?
+ unsigned char mConnectionNum; // number of connections for this point
+ short mUnknown;
+ }; // 16 bytes
+
+ struct Edge // path grid edge
+ {
+ int mV0, mV1; // index of points connected with this edge
+ }; // 8 bytes
+
+ std::string mCell; // Cell name
+ DATAstruct mData;
+
+ typedef std::vector<Point> PointList;
+ PointList mPoints;
+
+ typedef std::vector<Edge> EdgeList;
+ EdgeList mEdges;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp
new file mode 100644
index 0000000000..729f8404e5
--- /dev/null
+++ b/components/esm/loadprob.cpp
@@ -0,0 +1,41 @@
+#include "loadprob.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Probe::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+
+ esm.getHNT(mData, "PBDT", 16);
+
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+}
+
+void Probe::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+
+ esm.writeHNT("PBDT", mData, 16);
+ esm.writeHNOString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+}
+
+ void Probe::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mQuality = 0;
+ mData.mUses = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp
new file mode 100644
index 0000000000..55b896bcda
--- /dev/null
+++ b/components/esm/loadprob.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_ESM_PROBE_H
+#define OPENMW_ESM_PROBE_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Probe
+{
+ struct Data
+ {
+ float mWeight;
+ int mValue;
+
+ float mQuality;
+ int mUses;
+ }; // Size = 16
+
+ Data mData;
+ std::string mId, mName, mModel, mIcon, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+}
+#endif
diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp
new file mode 100644
index 0000000000..955424e2b9
--- /dev/null
+++ b/components/esm/loadrace.cpp
@@ -0,0 +1,54 @@
+#include "loadrace.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+ int Race::MaleFemale::getValue (bool male) const
+ {
+ return male ? mMale : mFemale;
+ }
+
+ int Race::MaleFemaleF::getValue (bool male) const
+ {
+ return male ? mMale : mFemale;
+ }
+
+void Race::load(ESMReader &esm)
+{
+ mName = esm.getHNString("FNAM");
+ esm.getHNT(mData, "RADT", 140);
+ mPowers.load(esm);
+ mDescription = esm.getHNOString("DESC");
+}
+void Race::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mName);
+ esm.writeHNT("RADT", mData, 140);
+ mPowers.save(esm);
+ esm.writeHNOString("DESC", mDescription);
+}
+
+ void Race::blank()
+ {
+ mName.clear();
+ mDescription.clear();
+
+ mPowers.mList.clear();
+
+ for (int i=0; i<7; ++i)
+ {
+ mData.mBonus[i].mSkill = -1;
+ mData.mBonus[i].mBonus = 0;
+ }
+
+ for (int i=0; i<8; ++i)
+ mData.mAttributeValues[i].mMale = mData.mAttributeValues[i].mFemale = 1;
+
+ mData.mHeight.mMale = mData.mHeight.mFemale = 1;
+ mData.mWeight.mMale = mData.mWeight.mFemale = 1;
+
+ mData.mFlags = 0;
+ }
+}
diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp
new file mode 100644
index 0000000000..6ecec8ebb9
--- /dev/null
+++ b/components/esm/loadrace.hpp
@@ -0,0 +1,75 @@
+#ifndef OPENMW_ESM_RACE_H
+#define OPENMW_ESM_RACE_H
+
+#include <string>
+
+#include "spelllist.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Race definition
+ */
+
+struct Race
+{
+ struct SkillBonus
+ {
+ int mSkill; // SkillEnum
+ int mBonus;
+ };
+
+ struct MaleFemale
+ {
+ int mMale, mFemale;
+
+ int getValue (bool male) const;
+ };
+
+ struct MaleFemaleF
+ {
+ float mMale, mFemale;
+
+ int getValue (bool male) const;
+ };
+
+ enum Flags
+ {
+ Playable = 0x01,
+ Beast = 0x02
+ };
+
+ struct RADTstruct
+ {
+ // List of skills that get a bonus
+ SkillBonus mBonus[7];
+
+ // Attribute values for male/female
+ MaleFemale mAttributeValues[8];
+
+ // The actual eye level height (in game units) is (probably) given
+ // as 'height' times 128. This has not been tested yet.
+ MaleFemaleF mHeight, mWeight;
+
+ int mFlags; // 0x1 - playable, 0x2 - beast race
+
+ }; // Size = 140 bytes
+
+ RADTstruct mData;
+
+ std::string mId, mName, mDescription;
+ SpellList mPowers;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+
+}
+#endif
diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp
new file mode 100644
index 0000000000..41c7f507ae
--- /dev/null
+++ b/components/esm/loadregn.cpp
@@ -0,0 +1,62 @@
+#include "loadregn.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Region::load(ESMReader &esm)
+{
+ mName = esm.getHNString("FNAM");
+
+ if (esm.getVer() == VER_12)
+ esm.getHNExact(&mData, sizeof(mData) - 2, "WEAT");
+ else if (esm.getVer() == VER_13)
+ esm.getHNExact(&mData, sizeof(mData), "WEAT");
+ else
+ esm.fail("Don't know what to do in this version");
+
+ mSleepList = esm.getHNOString("BNAM");
+
+ esm.getHNT(mMapColor, "CNAM");
+
+ while (esm.hasMoreSubs())
+ {
+ SoundRef sr;
+ esm.getHNT(sr, "SNAM", 33);
+ mSoundList.push_back(sr);
+ }
+}
+void Region::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mName);
+
+ if (esm.getVersion() == VER_12)
+ esm.writeHNT("WEAT", mData, sizeof(mData) - 2);
+ else
+ esm.writeHNT("WEAT", mData);
+
+ esm.writeHNOCString("BNAM", mSleepList);
+
+ esm.writeHNT("CNAM", mMapColor);
+ for (std::vector<SoundRef>::iterator it = mSoundList.begin(); it != mSoundList.end(); ++it)
+ {
+ esm.writeHNT<SoundRef>("SNAM", *it);
+ }
+}
+
+ void Region::blank()
+ {
+ mName.clear();
+
+ mData.mClear = mData.mCloudy = mData.mFoggy = mData.mOvercast = mData.mRain =
+ mData.mThunder = mData.mAsh, mData.mBlight = mData.mA = mData.mB = 0;
+
+ mMapColor = 0;
+
+ mName.clear();
+ mSleepList.clear();
+ mSoundList.clear();
+ }
+}
diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp
new file mode 100644
index 0000000000..f2a3d9a108
--- /dev/null
+++ b/components/esm/loadregn.hpp
@@ -0,0 +1,56 @@
+#ifndef OPENMW_ESM_REGN_H
+#define OPENMW_ESM_REGN_H
+
+#include <string>
+#include <vector>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Region data
+ */
+
+struct Region
+{
+#pragma pack(push)
+#pragma pack(1)
+ struct WEATstruct
+ {
+ // I guess these are probabilities
+ char mClear, mCloudy, mFoggy, mOvercast, mRain, mThunder, mAsh, mBlight,
+ // Unknown weather, probably snow and something. Only
+ // present in file version 1.3.
+ mA, mB;
+ }; // 10 bytes
+
+ // Reference to a sound that is played randomly in this region
+ struct SoundRef
+ {
+ NAME32 mSound;
+ char mChance;
+ }; // 33 bytes
+#pragma pack(pop)
+
+ WEATstruct mData;
+ int mMapColor; // RGBA
+
+ // sleepList refers to a eveled list of creatures you can meet if
+ // you sleep outside in this region.
+ std::string mId, mName, mSleepList;
+
+ std::vector<SoundRef> mSoundList;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp
new file mode 100644
index 0000000000..ced6daa2e9
--- /dev/null
+++ b/components/esm/loadrepa.cpp
@@ -0,0 +1,41 @@
+#include "loadrepa.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Repair::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNString("FNAM");
+
+ esm.getHNT(mData, "RIDT", 16);
+
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+}
+
+void Repair::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNCString("FNAM", mName);
+
+ esm.writeHNT("RIDT", mData, 16);
+ esm.writeHNOString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+}
+
+ void Repair::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mQuality = 0;
+ mData.mUses = 0;
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp
new file mode 100644
index 0000000000..83812bad9c
--- /dev/null
+++ b/components/esm/loadrepa.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_ESM_REPA_H
+#define OPENMW_ESM_REPA_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Repair
+{
+ struct Data
+ {
+ float mWeight;
+ int mValue;
+
+ int mUses;
+ float mQuality;
+ }; // Size = 16
+
+ Data mData;
+ std::string mId, mName, mModel, mIcon, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+
+}
+#endif
diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp
new file mode 100644
index 0000000000..2c1b018d97
--- /dev/null
+++ b/components/esm/loadscpt.cpp
@@ -0,0 +1,96 @@
+#include "loadscpt.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+struct SCHD
+{
+ NAME32 mName;
+ Script::SCHDstruct mData;
+};
+
+void Script::load(ESMReader &esm)
+{
+ SCHD data;
+ esm.getHNT(data, "SCHD", 52);
+ mData = data.mData;
+ mId = data.mName.toString();
+
+ // List of local variables
+ if (esm.isNextSub("SCVR"))
+ {
+ int s = mData.mStringTableSize;
+ char* tmp = new char[s];
+ esm.getHExact(tmp, s);
+
+ // Set up the list of variable names
+ mVarNames.resize(mData.mNumShorts + mData.mNumLongs + mData.mNumFloats);
+
+ // The tmp buffer is a null-byte separated string list, we
+ // just have to pick out one string at a time.
+ char* str = tmp;
+ for (size_t i = 0; i < mVarNames.size(); i++)
+ {
+ mVarNames[i] = std::string(str);
+ str += mVarNames[i].size() + 1;
+
+ if (str - tmp > s)
+ esm.fail("String table overflow");
+ }
+ delete[] tmp;
+ }
+
+ // Script mData
+ mScriptData.resize(mData.mScriptDataSize);
+ esm.getHNExact(&mScriptData[0], mScriptData.size(), "SCDT");
+
+ // Script text
+ mScriptText = esm.getHNOString("SCTX");
+}
+void Script::save(ESMWriter &esm)
+{
+ std::string varNameString;
+ if (!mVarNames.empty())
+ for (std::vector<std::string>::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it)
+ varNameString.append(*it);
+
+ SCHD data;
+ memset(&data, 0, sizeof(data));
+
+ data.mData = mData;
+ memcpy(data.mName.name, mId.c_str(), mId.size());
+
+ esm.writeHNT("SCHD", data, 52);
+
+ if (!mVarNames.empty())
+ {
+ esm.startSubRecord("SCVR");
+ for (std::vector<std::string>::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it)
+ {
+ esm.writeHCString(*it);
+ }
+ esm.endRecord("SCVR");
+ }
+
+ esm.startSubRecord("SCDT");
+ esm.write(reinterpret_cast<const char * >(&mScriptData[0]), mData.mScriptDataSize);
+ esm.endRecord("SCDT");
+
+ esm.writeHNOString("SCTX", mScriptText);
+}
+
+ void Script::blank()
+ {
+ mData.mNumShorts = mData.mNumLongs = mData.mNumFloats = 0;
+ mData.mScriptDataSize = 0;
+ mData.mStringTableSize = 0;
+
+ mVarNames.clear();
+ mScriptData.clear();
+ mScriptText = "Begin " + mId + "\n\nEnd " + mId + "\n";
+ }
+
+}
diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp
new file mode 100644
index 0000000000..be7e839002
--- /dev/null
+++ b/components/esm/loadscpt.hpp
@@ -0,0 +1,65 @@
+#ifndef OPENMW_ESM_SCPT_H
+#define OPENMW_ESM_SCPT_H
+
+#include <string>
+#include <vector>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Script definitions
+ */
+
+class Script
+{
+public:
+ struct SCHDstruct
+ {
+ /* Script name.
+
+ NOTE: You should handle the name "Main" (case insensitive) with
+ care. With tribunal, modders got the ability to add 'start
+ scripts' to their mods, which is a script that is run at
+ startup and which runs throughout the game (I think.)
+
+ However, before Tribunal, there was only one startup script,
+ called "Main". If mods wanted to make their own start scripts,
+ they had to overwrite Main. This is obviously problem if
+ multiple mods to this at the same time.
+
+ Although most mods have switched to using Trib-style startup
+ scripts, some legacy mods might still overwrite Main, and this
+ can cause problems if several mods do it. I think the best
+ course of action is to NEVER overwrite main, but instead add
+ each with a separate unique name and add them to the start
+ script list. But there might be other problems with this
+ approach though.
+ */
+
+ // These describe the sizes we need to allocate for the script
+ // data.
+ int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize;
+ }; // 52 bytes
+
+ std::string mId;
+
+ SCHDstruct mData;
+
+ std::vector<std::string> mVarNames; // Variable names
+ std::vector<unsigned char> mScriptData; // Compiled bytecode
+ std::string mScriptText; // Uncompiled script
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp
new file mode 100644
index 0000000000..676a835c3b
--- /dev/null
+++ b/components/esm/loadskil.cpp
@@ -0,0 +1,174 @@
+#include "loadskil.hpp"
+
+#include <sstream>
+
+#include <components/misc/stringops.hpp>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+ const std::string Skill::sSkillNames[Length] = {
+ "Block",
+ "Armorer",
+ "Mediumarmor",
+ "Heavyarmor",
+ "Bluntweapon",
+ "Longblade",
+ "Axe",
+ "Spear",
+ "Athletics",
+ "Enchant",
+ "Destruction",
+ "Alteration",
+ "Illusion",
+ "Conjuration",
+ "Mysticism",
+ "Restoration",
+ "Alchemy",
+ "Unarmored",
+ "Security",
+ "Sneak",
+ "Acrobatics",
+ "Lightarmor",
+ "Shortblade",
+ "Marksman",
+ "Mercantile",
+ "Speechcraft",
+ "Handtohand",
+ };
+ const std::string Skill::sSkillNameIds[Length] = {
+ "sSkillBlock",
+ "sSkillArmorer",
+ "sSkillMediumarmor",
+ "sSkillHeavyarmor",
+ "sSkillBluntweapon",
+ "sSkillLongblade",
+ "sSkillAxe",
+ "sSkillSpear",
+ "sSkillAthletics",
+ "sSkillEnchant",
+ "sSkillDestruction",
+ "sSkillAlteration",
+ "sSkillIllusion",
+ "sSkillConjuration",
+ "sSkillMysticism",
+ "sSkillRestoration",
+ "sSkillAlchemy",
+ "sSkillUnarmored",
+ "sSkillSecurity",
+ "sSkillSneak",
+ "sSkillAcrobatics",
+ "sSkillLightarmor",
+ "sSkillShortblade",
+ "sSkillMarksman",
+ "sSkillMercantile",
+ "sSkillSpeechcraft",
+ "sSkillHandtohand",
+ };
+ const std::string Skill::sIconNames[Length] = {
+ "combat_block.dds",
+ "combat_armor.dds",
+ "combat_mediumarmor.dds",
+ "combat_heavyarmor.dds",
+ "combat_blunt.dds",
+ "combat_longblade.dds",
+ "combat_axe.dds",
+ "combat_spear.dds",
+ "combat_athletics.dds",
+ "magic_enchant.dds",
+ "magic_destruction.dds",
+ "magic_alteration.dds",
+ "magic_illusion.dds",
+ "magic_conjuration.dds",
+ "magic_mysticism.dds",
+ "magic_restoration.dds",
+ "magic_alchemy.dds",
+ "magic_unarmored.dds",
+ "stealth_security.dds",
+ "stealth_sneak.dds",
+ "stealth_acrobatics.dds",
+ "stealth_lightarmor.dds",
+ "stealth_shortblade.dds",
+ "stealth_marksman.dds",
+ "stealth_mercantile.dds",
+ "stealth_speechcraft.dds",
+ "stealth_handtohand.dds",
+ };
+ const boost::array<Skill::SkillEnum, Skill::Length> Skill::sSkillIds = {{
+ Block,
+ Armorer,
+ MediumArmor,
+ HeavyArmor,
+ BluntWeapon,
+ LongBlade,
+ Axe,
+ Spear,
+ Athletics,
+ Enchant,
+ Destruction,
+ Alteration,
+ Illusion,
+ Conjuration,
+ Mysticism,
+ Restoration,
+ Alchemy,
+ Unarmored,
+ Security,
+ Sneak,
+ Acrobatics,
+ LightArmor,
+ ShortBlade,
+ Marksman,
+ Mercantile,
+ Speechcraft,
+ HandToHand
+ }};
+
+void Skill::load(ESMReader &esm)
+{
+ esm.getHNT(mIndex, "INDX");
+ esm.getHNT(mData, "SKDT", 24);
+ mDescription = esm.getHNOString("DESC");
+
+ // create an ID from the index and the name (only used in the editor and likely to change in the
+ // future)
+ mId = indexToId (mIndex);
+}
+
+void Skill::save(ESMWriter &esm)
+{
+ esm.writeHNT("INDX", mIndex);
+ esm.writeHNT("SKDT", mData, 24);
+ esm.writeHNOString("DESC", mDescription);
+}
+
+ void Skill::blank()
+ {
+ mData.mAttribute = 0;
+ mData.mSpecialization = 0;
+ mData.mUseValue[0] = mData.mUseValue[1] = mData.mUseValue[2] = mData.mUseValue[3] = 1.0;
+ mDescription.clear();
+ }
+
+ std::string Skill::indexToId (int index)
+ {
+ std::ostringstream stream;
+
+ if (index!=-1)
+ {
+ stream << "#";
+
+ if (index<10)
+ stream << "0";
+
+ stream << index;
+
+ if (index>=0 && index<Length)
+ stream << sSkillNameIds[index].substr (6);
+ }
+
+ return stream.str();
+ }
+}
diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp
new file mode 100644
index 0000000000..384f874545
--- /dev/null
+++ b/components/esm/loadskil.hpp
@@ -0,0 +1,86 @@
+#ifndef OPENMW_ESM_SKIL_H
+#define OPENMW_ESM_SKIL_H
+
+#include <string>
+
+#include <boost/array.hpp>
+
+#include "defs.hpp"
+
+namespace ESM {
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Skill information
+ *
+ */
+
+struct Skill
+{
+ std::string mId;
+
+ struct SKDTstruct
+ {
+ int mAttribute; // see defs.hpp
+ int mSpecialization;// 0 - Combat, 1 - Magic, 2 - Stealth
+ float mUseValue[4]; // How much skill improves through use. Meaning
+ // of each field depends on what skill this
+ // is. We should document this better later.
+ }; // Total size: 24 bytes
+ SKDTstruct mData;
+
+ // Skill index. Skils don't have an id ("NAME") like most records,
+ // they only have a numerical index that matches one of the
+ // hard-coded skills in the game.
+ int mIndex;
+
+ std::string mDescription;
+
+ enum SkillEnum
+ {
+ Block = 0,
+ Armorer = 1,
+ MediumArmor = 2,
+ HeavyArmor = 3,
+ BluntWeapon = 4,
+ LongBlade = 5,
+ Axe = 6,
+ Spear = 7,
+ Athletics = 8,
+ Enchant = 9,
+ Destruction = 10,
+ Alteration = 11,
+ Illusion = 12,
+ Conjuration = 13,
+ Mysticism = 14,
+ Restoration = 15,
+ Alchemy = 16,
+ Unarmored = 17,
+ Security = 18,
+ Sneak = 19,
+ Acrobatics = 20,
+ LightArmor = 21,
+ ShortBlade = 22,
+ Marksman = 23,
+ Mercantile = 24,
+ Speechcraft = 25,
+ HandToHand = 26,
+ Length
+ };
+ static const std::string sSkillNames[Length];
+ static const std::string sSkillNameIds[Length];
+ static const std::string sIconNames[Length];
+ static const boost::array<SkillEnum, Length> sSkillIds;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+
+ static std::string indexToId (int index);
+};
+}
+#endif
diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp
new file mode 100644
index 0000000000..42d524226d
--- /dev/null
+++ b/components/esm/loadsndg.cpp
@@ -0,0 +1,23 @@
+#include "loadsndg.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void SoundGenerator::load(ESMReader &esm)
+{
+ esm.getHNT(mType, "DATA", 4);
+
+ mCreature = esm.getHNOString("CNAM");
+ mSound = esm.getHNOString("SNAM");
+}
+void SoundGenerator::save(ESMWriter &esm)
+{
+ esm.writeHNT("DATA", mType, 4);
+ esm.writeHNOCString("CNAM", mCreature);
+ esm.writeHNOCString("SNAM", mSound);
+}
+
+}
diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp
new file mode 100644
index 0000000000..a6226c1545
--- /dev/null
+++ b/components/esm/loadsndg.hpp
@@ -0,0 +1,39 @@
+#ifndef OPENMW_ESM_SNDG_H
+#define OPENMW_ESM_SNDG_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Sound generator. This describes the sounds a creature make.
+ */
+
+struct SoundGenerator
+{
+ enum Type
+ {
+ LeftFoot = 0,
+ RightFoot = 1,
+ SwimLeft = 2,
+ SwimRight = 3,
+ Moan = 4,
+ Roar = 5,
+ Scream = 6,
+ Land = 7
+ };
+
+ // Type
+ int mType;
+
+ std::string mId, mCreature, mSound;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+}
+#endif
diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp
new file mode 100644
index 0000000000..07af2b5e91
--- /dev/null
+++ b/components/esm/loadsoun.cpp
@@ -0,0 +1,34 @@
+#include "loadsoun.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Sound::load(ESMReader &esm)
+{
+ mSound = esm.getHNString("FNAM");
+ esm.getHNT(mData, "DATA", 3);
+ /*
+ cout << "vol=" << (int)data.volume
+ << " min=" << (int)data.minRange
+ << " max=" << (int)data.maxRange
+ << endl;
+ */
+}
+void Sound::save(ESMWriter &esm)
+{
+ esm.writeHNCString("FNAM", mSound);
+ esm.writeHNT("DATA", mData, 3);
+}
+
+ void Sound::blank()
+ {
+ mSound.clear();
+
+ mData.mVolume = 128;
+ mData.mMinRange = 0;
+ mData.mMaxRange = 255;
+ }
+}
diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp
new file mode 100644
index 0000000000..f8e38ac092
--- /dev/null
+++ b/components/esm/loadsoun.hpp
@@ -0,0 +1,29 @@
+#ifndef OPENMW_ESM_SOUN_H
+#define OPENMW_ESM_SOUN_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct SOUNstruct
+{
+ unsigned char mVolume, mMinRange, mMaxRange;
+};
+
+struct Sound
+{
+ SOUNstruct mData;
+ std::string mId, mSound;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp
new file mode 100644
index 0000000000..8149fe4cef
--- /dev/null
+++ b/components/esm/loadspel.cpp
@@ -0,0 +1,33 @@
+#include "loadspel.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Spell::load(ESMReader &esm)
+{
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "SPDT", 12);
+ mEffects.load(esm);
+}
+
+void Spell::save(ESMWriter &esm)
+{
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("SPDT", mData, 12);
+ mEffects.save(esm);
+}
+
+ void Spell::blank()
+ {
+ mData.mType = 0;
+ mData.mCost = 0;
+ mData.mFlags = 0;
+
+ mName.clear();
+
+ mEffects.mList.clear();
+ }
+}
diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp
new file mode 100644
index 0000000000..3a620962d1
--- /dev/null
+++ b/components/esm/loadspel.hpp
@@ -0,0 +1,51 @@
+#ifndef OPENMW_ESM_SPEL_H
+#define OPENMW_ESM_SPEL_H
+
+#include <string>
+
+#include "effectlist.hpp"
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+struct Spell
+{
+ enum SpellType
+ {
+ ST_Spell = 0, // Normal spell, must be cast and costs mana
+ ST_Ability = 1, // Inert ability, always in effect
+ ST_Blight = 2, // Blight disease
+ ST_Disease = 3, // Common disease
+ ST_Curse = 4, // Curse (?)
+ ST_Power = 5 // Power, can use once a day
+ };
+
+ enum Flags
+ {
+ F_Autocalc = 1,
+ F_PCStart = 2,
+ F_Always = 4 // Casting always succeeds
+ };
+
+ struct SPDTstruct
+ {
+ int mType; // SpellType
+ int mCost; // Mana cost
+ int mFlags; // Flags
+ };
+
+ SPDTstruct mData;
+ std::string mId, mName;
+ EffectList mEffects;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID/index).
+};
+}
+#endif
diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp
new file mode 100644
index 0000000000..ae50de517c
--- /dev/null
+++ b/components/esm/loadsscr.cpp
@@ -0,0 +1,20 @@
+#include "loadsscr.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void StartScript::load(ESMReader &esm)
+{
+ mData = esm.getHNString("DATA");
+ mScript = esm.getHNString("NAME");
+}
+void StartScript::save(ESMWriter &esm)
+{
+ esm.writeHNString("DATA", mData);
+ esm.writeHNString("NAME", mScript);
+}
+
+}
diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp
new file mode 100644
index 0000000000..713fe96b52
--- /dev/null
+++ b/components/esm/loadsscr.hpp
@@ -0,0 +1,31 @@
+#ifndef OPENMW_ESM_SSCR_H
+#define OPENMW_ESM_SSCR_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ Startup script. I think this is simply a 'main' script that is run
+ from the begining. The SSCR records contain a DATA identifier which
+ is totally useless (TODO: don't remember what it contains exactly,
+ document it below later.), and a NAME which is simply a script
+ reference.
+ */
+
+struct StartScript
+{
+ std::string mData;
+ std::string mId, mScript;
+
+ // Load a record and add it to the list
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+};
+
+}
+#endif
diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp
new file mode 100644
index 0000000000..c9346dafca
--- /dev/null
+++ b/components/esm/loadstat.cpp
@@ -0,0 +1,22 @@
+#include "loadstat.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Static::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+}
+void Static::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+}
+
+ void Static::blank()
+ {
+ mModel.clear();
+ }
+}
diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp
new file mode 100644
index 0000000000..1adb7d05be
--- /dev/null
+++ b/components/esm/loadstat.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_ESM_STAT_H
+#define OPENMW_ESM_STAT_H
+
+#include <string>
+
+namespace ESM {
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Definition of static object.
+ *
+ * A stat record is basically just a reference to a nif file. Some
+ * esps seem to contain copies of the STAT entries from the esms, and
+ * the esms themselves contain several identical entries. Perhaps all
+ * statics referenced in a file is also put in the file? Since we are
+ * only reading files it doesn't much matter to us, but it would if we
+ * were writing our own ESM/ESPs. You can check some files later when
+ * you decode the CELL blocks, if you want to test this hypothesis.
+ */
+
+struct Static
+{
+ std::string mId, mModel;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp
new file mode 100644
index 0000000000..74d578ba7d
--- /dev/null
+++ b/components/esm/loadtes3.cpp
@@ -0,0 +1,53 @@
+
+#include "loadtes3.hpp"
+
+#include "esmcommon.hpp"
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::Header::blank()
+{
+ mData.version = ESM::VER_13;
+ mData.type = 0;
+ mData.author.assign ("");
+ mData.desc.assign ("");
+ mData.records = 0;
+ mFormat = CurrentFormat;
+}
+
+void ESM::Header::load (ESMReader &esm)
+{
+ esm.getHNT (mData, "HEDR", 300);
+
+ if (esm.isNextSub ("FORM"))
+ {
+ esm.getHT (mFormat);
+ if (mFormat<0)
+ esm.fail ("invalid format code");
+ }
+ else
+ mFormat = 0;
+
+ while (esm.isNextSub ("MAST"))
+ {
+ MasterData m;
+ m.name = esm.getHString();
+ m.size = esm.getHNLong ("DATA");
+ mMaster.push_back (m);
+ }
+}
+
+void ESM::Header::save (ESMWriter &esm)
+{
+ esm.writeHNT ("HEDR", mData, 300);
+
+ if (mFormat>0)
+ esm.writeHNT ("FORM", mFormat);
+
+ for (std::vector<Header::MasterData>::iterator iter = mMaster.begin();
+ iter != mMaster.end(); ++iter)
+ {
+ esm.writeHNCString ("MAST", iter->name);
+ esm.writeHNT ("DATA", iter->size);
+ }
+} \ No newline at end of file
diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp
new file mode 100644
index 0000000000..b73a4c31e4
--- /dev/null
+++ b/components/esm/loadtes3.hpp
@@ -0,0 +1,55 @@
+#ifndef COMPONENT_ESM_TES3_H
+#define COMPONENT_ESM_TES3_H
+
+#include <vector>
+
+#include "esmcommon.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+#pragma pack(push)
+#pragma pack(1)
+
+ /// \brief File header record
+ struct Header
+ {
+ static const int CurrentFormat = 0; // most recent known format
+
+ struct Data
+ {
+ /* File format version. This is actually a float, the supported
+ versions are 1.2 and 1.3. These correspond to:
+ 1.2 = 0x3f99999a and 1.3 = 0x3fa66666
+ */
+ int version;
+ int type; // 0=esp, 1=esm, 32=ess (unused)
+ NAME32 author; // Author's name
+ NAME256 desc; // File description
+ int records; // Number of records? Not used.
+ };
+
+ // Defines another files (esm or esp) that this file depends upon.
+ struct MasterData
+ {
+ std::string name;
+ uint64_t size;
+ int index; // Position of the parent file in the global list of loaded files
+ };
+
+ Data mData;
+ int mFormat;
+ std::vector<MasterData> mMaster;
+
+ void blank();
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm);
+ };
+#pragma pack(pop)
+
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp
new file mode 100644
index 0000000000..2537123969
--- /dev/null
+++ b/components/esm/loadweap.cpp
@@ -0,0 +1,48 @@
+#include "loadweap.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+
+void Weapon::load(ESMReader &esm)
+{
+ mModel = esm.getHNString("MODL");
+ mName = esm.getHNOString("FNAM");
+ esm.getHNT(mData, "WPDT", 32);
+ mScript = esm.getHNOString("SCRI");
+ mIcon = esm.getHNOString("ITEX");
+ mEnchant = esm.getHNOString("ENAM");
+}
+void Weapon::save(ESMWriter &esm)
+{
+ esm.writeHNCString("MODL", mModel);
+ esm.writeHNOCString("FNAM", mName);
+ esm.writeHNT("WPDT", mData, 32);
+ esm.writeHNOCString("SCRI", mScript);
+ esm.writeHNOCString("ITEX", mIcon);
+ esm.writeHNOCString("ENAM", mEnchant);
+}
+
+ void Weapon::blank()
+ {
+ mData.mWeight = 0;
+ mData.mValue = 0;
+ mData.mType = 0;
+ mData.mHealth = 0;
+ mData.mSpeed = 0;
+ mData.mReach = 0;
+ mData.mEnchant = 0;
+ mData.mChop[0] = mData.mChop[1] = 0;
+ mData.mSlash[0] = mData.mSlash[1] = 0;
+ mData.mThrust[0] = mData.mThrust[1] = 0;
+ mData.mFlags = 0;
+
+ mName.clear();
+ mModel.clear();
+ mIcon.clear();
+ mEnchant.clear();
+ mScript.clear();
+ }
+}
diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp
new file mode 100644
index 0000000000..b62179ccb1
--- /dev/null
+++ b/components/esm/loadweap.hpp
@@ -0,0 +1,68 @@
+#ifndef OPENMW_ESM_WEAP_H
+#define OPENMW_ESM_WEAP_H
+
+#include <string>
+
+namespace ESM
+{
+
+class ESMReader;
+class ESMWriter;
+
+/*
+ * Weapon definition
+ */
+
+struct Weapon
+{
+ enum Type
+ {
+ ShortBladeOneHand = 0,
+ LongBladeOneHand = 1,
+ LongBladeTwoHand = 2,
+ BluntOneHand = 3,
+ BluntTwoClose = 4,
+ BluntTwoWide = 5,
+ SpearTwoWide = 6,
+ AxeOneHand = 7,
+ AxeTwoHand = 8,
+ MarksmanBow = 9,
+ MarksmanCrossbow = 10,
+ MarksmanThrown = 11,
+ Arrow = 12,
+ Bolt = 13
+ };
+
+ enum Flags
+ {
+ Magical = 0x01,
+ Silver = 0x02
+ };
+
+#pragma pack(push)
+#pragma pack(1)
+ struct WPDTstruct
+ {
+ float mWeight;
+ int mValue;
+ short mType;
+ short mHealth;
+ float mSpeed, mReach;
+ short mEnchant; // Enchantment points. The real value is mEnchant/10.f
+ unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max
+ int mFlags;
+ }; // 32 bytes
+#pragma pack(pop)
+
+ WPDTstruct mData;
+
+ std::string mId, mName, mModel, mIcon, mEnchant, mScript;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+
+ void blank();
+ ///< Set record to default state (does not touch the ID).
+};
+}
+#endif
diff --git a/components/esm/records.hpp b/components/esm/records.hpp
new file mode 100644
index 0000000000..7a0452eb3f
--- /dev/null
+++ b/components/esm/records.hpp
@@ -0,0 +1,51 @@
+#ifndef OPENMW_ESM_RECORDS_H
+#define OPENMW_ESM_RECORDS_H
+
+#include "defs.hpp"
+#include "loadacti.hpp"
+#include "loadalch.hpp"
+#include "loadappa.hpp"
+#include "loadarmo.hpp"
+#include "loadbody.hpp"
+#include "loadbook.hpp"
+#include "loadbsgn.hpp"
+#include "loadcell.hpp"
+#include "loadclas.hpp"
+#include "loadclot.hpp"
+#include "loadcont.hpp"
+#include "loadcrea.hpp"
+#include "loadcrec.hpp"
+#include "loadinfo.hpp"
+#include "loaddial.hpp"
+#include "loaddoor.hpp"
+#include "loadench.hpp"
+#include "loadfact.hpp"
+#include "loadglob.hpp"
+#include "loadgmst.hpp"
+#include "loadingr.hpp"
+#include "loadland.hpp"
+#include "loadlevlist.hpp"
+#include "loadligh.hpp"
+#include "loadlock.hpp"
+#include "loadrepa.hpp"
+#include "loadprob.hpp"
+#include "loadltex.hpp"
+#include "loadmgef.hpp"
+#include "loadmisc.hpp"
+#include "loadnpc.hpp"
+#include "loadnpcc.hpp"
+#include "loadpgrd.hpp"
+#include "loadrace.hpp"
+#include "loadregn.hpp"
+#include "loadscpt.hpp"
+#include "loadskil.hpp"
+#include "loadsndg.hpp"
+#include "loadsoun.hpp"
+#include "loadspel.hpp"
+#include "loadsscr.hpp"
+#include "loadstat.hpp"
+#include "loadweap.hpp"
+
+// Special records which are not loaded from ESM
+#include "attr.hpp"
+#endif
diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp
new file mode 100644
index 0000000000..dd886cf7ff
--- /dev/null
+++ b/components/esm/spelllist.cpp
@@ -0,0 +1,22 @@
+#include "spelllist.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM {
+
+void SpellList::load(ESMReader &esm)
+{
+ while (esm.isNextSub("NPCS")) {
+ mList.push_back(esm.getHString());
+ }
+}
+
+void SpellList::save(ESMWriter &esm)
+{
+ for (std::vector<std::string>::iterator it = mList.begin(); it != mList.end(); ++it) {
+ esm.writeHNString("NPCS", *it, 32);
+ }
+}
+
+}
diff --git a/components/esm/spelllist.hpp b/components/esm/spelllist.hpp
new file mode 100644
index 0000000000..52999270a0
--- /dev/null
+++ b/components/esm/spelllist.hpp
@@ -0,0 +1,25 @@
+#ifndef OPENMW_ESM_SPELLLIST_H
+#define OPENMW_ESM_SPELLLIST_H
+
+#include <vector>
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ /** A list of references to spells and spell effects. This is shared
+ between the records BSGN, NPC and RACE.
+ */
+ struct SpellList
+ {
+ std::vector<std::string> mList;
+
+ void load(ESMReader &esm);
+ void save(ESMWriter &esm);
+ };
+}
+
+#endif
+
diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp
new file mode 100644
index 0000000000..a7859d1283
--- /dev/null
+++ b/components/esm/variant.cpp
@@ -0,0 +1,282 @@
+#include "variant.hpp"
+
+#include <cassert>
+#include <stdexcept>
+
+#include "esmreader.hpp"
+#include "variantimp.hpp"
+
+ESM::Variant::Variant() : mType (VT_None), mData (0) {}
+
+ESM::Variant::~Variant()
+{
+ delete mData;
+}
+
+ESM::Variant& ESM::Variant::operator= (const Variant& variant)
+{
+ if (&variant!=this)
+ {
+ VariantDataBase *newData = variant.mData ? variant.mData->clone() : 0;
+
+ delete mData;
+
+ mType = variant.mType;
+ mData = newData;
+ }
+
+ return *this;
+}
+
+ESM::Variant::Variant (const Variant& variant)
+: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : 0)
+{}
+
+ESM::VarType ESM::Variant::getType() const
+{
+ return mType;
+}
+
+std::string ESM::Variant::getString() const
+{
+ if (!mData)
+ throw std::runtime_error ("can not convert empty variant to string");
+
+ return mData->getString();
+}
+
+int ESM::Variant::getInteger() const
+{
+ if (!mData)
+ throw std::runtime_error ("can not convert empty variant to integer");
+
+ return mData->getInteger();
+}
+
+float ESM::Variant::getFloat() const
+{
+ if (!mData)
+ throw std::runtime_error ("can not convert empty variant to float");
+
+ return mData->getFloat();
+}
+
+void ESM::Variant::read (ESMReader& esm, Format format)
+{
+ // type
+ VarType type = VT_Unknown;
+
+ if (format==Format_Global)
+ {
+ std::string typeId = esm.getHNString ("FNAM");
+
+ if (typeId == "s")
+ type = VT_Short;
+ else if (typeId == "l")
+ type = VT_Long;
+ else if (typeId == "f")
+ type = VT_Float;
+ else
+ esm.fail ("illegal global variable type " + typeId);
+ }
+ else if (format==Format_Gmst)
+ {
+ if (!esm.hasMoreSubs())
+ {
+ type = VT_None;
+ }
+ else
+ {
+ esm.getSubName();
+ NAME name = esm.retSubName();
+
+ if (name=="STRV")
+ {
+ type = VT_String;
+ }
+ else if (name=="INTV")
+ {
+ type = VT_Int;
+ }
+ else if (name=="FLTV")
+ {
+ type = VT_Float;
+ }
+ else
+ esm.fail ("invalid subrecord: " + name.toString());
+ }
+ }
+ else // info
+ {
+ esm.getSubName();
+ NAME name = esm.retSubName();
+
+ if (name=="INTV")
+ {
+ type = VT_Int;
+ }
+ else if (name=="FLTV")
+ {
+ type = VT_Float;
+ }
+ else
+ esm.fail ("invalid subrecord: " + name.toString());
+ }
+
+ setType (type);
+
+ // data
+ if (mData)
+ mData->read (esm, format, mType);
+}
+
+void ESM::Variant::write (ESMWriter& esm, Format format) const
+{
+ if (mType==VT_Unknown)
+ {
+ throw std::runtime_error ("can not serialise variant of unknown type");
+ }
+ else if (mType==VT_None)
+ {
+ if (format==Format_Global)
+ throw std::runtime_error ("can not serialise variant of type none to global format");
+
+ if (format==Format_Info)
+ throw std::runtime_error ("can not serialise variant of type none to info format");
+
+ // nothing to do here for GMST format
+ }
+ else
+ mData->write (esm, format, mType);
+}
+
+void ESM::Variant::write (std::ostream& stream) const
+{
+ switch (mType)
+ {
+ case VT_Unknown:
+
+ stream << "variant unknown";
+ break;
+
+ case VT_None:
+
+ stream << "variant none";
+ break;
+
+ case VT_Short:
+
+ stream << "variant short: " << mData->getInteger();
+ break;
+
+ case VT_Int:
+
+ stream << "variant int: " << mData->getInteger();
+ break;
+
+ case VT_Long:
+
+ stream << "variant long: " << mData->getInteger();
+ break;
+
+ case VT_Float:
+
+ stream << "variant float: " << mData->getFloat();
+ break;
+
+ case VT_String:
+
+ stream << "variant string: \"" << mData->getString() << "\"";
+ break;
+ }
+}
+
+void ESM::Variant::setType (VarType type)
+{
+ if (type!=mType)
+ {
+ VariantDataBase *newData = 0;
+
+ switch (type)
+ {
+ case VT_Unknown:
+ case VT_None:
+
+ break; // no data
+
+ case VT_Short:
+ case VT_Int:
+ case VT_Long:
+
+ newData = new VariantIntegerData (mData);
+ break;
+
+ case VT_Float:
+
+ newData = new VariantFloatData (mData);
+ break;
+
+ case VT_String:
+
+ newData = new VariantStringData (mData);
+ break;
+ }
+
+ delete mData;
+ mData = newData;
+ mType = type;
+ }
+}
+
+void ESM::Variant::setString (const std::string& value)
+{
+ if (!mData)
+ throw std::runtime_error ("can not assign string to empty variant");
+
+ mData->setString (value);
+}
+
+void ESM::Variant::setInteger (int value)
+{
+ if (!mData)
+ throw std::runtime_error ("can not assign integer to empty variant");
+
+ mData->setInteger (value);
+}
+
+void ESM::Variant::setFloat (float value)
+{
+ if (!mData)
+ throw std::runtime_error ("can not assign float to empty variant");
+
+ mData->setFloat (value);
+}
+
+bool ESM::Variant::isEqual (const Variant& value) const
+{
+ if (mType!=value.mType)
+ return false;
+
+ if (!mData)
+ return true;
+
+ assert (value.mData);
+
+ return mData->isEqual (*value.mData);
+}
+
+std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value)
+{
+ value.write (stream);
+ return stream;
+}
+
+bool ESM::operator== (const Variant& left, const Variant& right)
+{
+ return left.isEqual (right);
+}
+
+bool ESM::operator!= (const Variant& left, const Variant& right)
+{
+ return !(left==right);
+} \ No newline at end of file
diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp
new file mode 100644
index 0000000000..8c5f3b3d47
--- /dev/null
+++ b/components/esm/variant.hpp
@@ -0,0 +1,86 @@
+#ifndef OPENMW_ESM_VARIANT_H
+#define OPENMW_ESM_VARIANT_H
+
+#include <string>
+#include <iosfwd>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ enum VarType
+ {
+ VT_Unknown,
+ VT_None,
+ VT_Short, // stored as a float, kinda
+ VT_Int,
+ VT_Long, // stored as a float
+ VT_Float,
+ VT_String
+ };
+
+ class VariantDataBase;
+
+ class Variant
+ {
+ VarType mType;
+ VariantDataBase *mData;
+
+ public:
+
+ enum Format
+ {
+ Format_Global,
+ Format_Gmst,
+ Format_Info
+ };
+
+ Variant();
+
+ ~Variant();
+
+ Variant& operator= (const Variant& variant);
+
+ Variant (const Variant& variant);
+
+ VarType getType() const;
+
+ std::string getString() const;
+ ///< Will throw an exception, if value can not be represented as a string.
+
+ int getInteger() const;
+ ///< Will throw an exception, if value can not be represented as an integer (implicit
+ /// casting of float values is permitted).
+
+ float getFloat() const;
+ ///< Will throw an exception, if value can not be represented as a float value.
+
+ void read (ESMReader& esm, Format format);
+
+ void write (ESMWriter& esm, Format format) const;
+
+ void write (std::ostream& stream) const;
+ ///< Write in text format.
+
+ void setType (VarType type);
+
+ void setString (const std::string& value);
+ ///< Will throw an exception, if type is not compatible with string.
+
+ void setInteger (int value);
+ ///< Will throw an exception, if type is not compatible with integer.
+
+ void setFloat (float value);
+ ///< Will throw an exception, if type is not compatible with float.
+
+ bool isEqual (const Variant& value) const;
+ };
+
+ std::ostream& operator<<(std::ostream& stream, const Variant& value);
+
+ bool operator== (const Variant& left, const Variant& right);
+ bool operator!= (const Variant& left, const Variant& right);
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp
new file mode 100644
index 0000000000..160402aa4b
--- /dev/null
+++ b/components/esm/variantimp.cpp
@@ -0,0 +1,280 @@
+
+#include "variantimp.hpp"
+
+#include <stdexcept>
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+ESM::VariantDataBase::~VariantDataBase() {}
+
+std::string ESM::VariantDataBase::getString (bool default_) const
+{
+ if (default_)
+ return "";
+
+ throw std::runtime_error ("can not convert variant to string");
+}
+
+int ESM::VariantDataBase::getInteger (bool default_) const
+{
+ if (default_)
+ return 0;
+
+ throw std::runtime_error ("can not convert variant to integer");
+}
+
+float ESM::VariantDataBase::getFloat (bool default_) const
+{
+ if (default_)
+ return 0;
+
+ throw std::runtime_error ("can not convert variant to float");
+}
+
+void ESM::VariantDataBase::setString (const std::string& value)
+{
+ throw std::runtime_error ("conversion of string to variant not possible");
+}
+
+void ESM::VariantDataBase::setInteger (int value)
+{
+ throw std::runtime_error ("conversion of integer to variant not possible");
+}
+
+void ESM::VariantDataBase::setFloat (float value)
+{
+ throw std::runtime_error ("conversion of float to variant not possible");
+}
+
+
+
+ESM::VariantStringData::VariantStringData (const VariantDataBase *data)
+{
+ if (data)
+ mValue = data->getString (true);
+}
+
+ESM::VariantDataBase *ESM::VariantStringData::clone() const
+{
+ return new VariantStringData (*this);
+}
+
+std::string ESM::VariantStringData::getString (bool default_) const
+{
+ return mValue;
+}
+
+void ESM::VariantStringData::setString (const std::string& value)
+{
+ mValue = value;
+}
+
+void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarType type)
+{
+ if (type!=VT_String)
+ throw std::logic_error ("not a string type");
+
+ if (format==Variant::Format_Global)
+ esm.fail ("global variables of type string not supported");
+
+ if (format==Variant::Format_Info)
+ esm.fail ("info variables of type string not supported");
+
+ // GMST
+ mValue = esm.getHString();
+}
+
+void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarType type) const
+{
+ if (type!=VT_String)
+ throw std::logic_error ("not a string type");
+
+ if (format==Variant::Format_Global)
+ throw std::runtime_error ("global variables of type string not supported");
+
+ if (format==Variant::Format_Info)
+ throw std::runtime_error ("info variables of type string not supported");
+
+ // GMST
+ esm.writeHNString ("STRV", mValue);
+}
+
+bool ESM::VariantStringData::isEqual (const VariantDataBase& value) const
+{
+ return dynamic_cast<const VariantStringData&> (value).mValue==mValue;
+}
+
+
+
+ESM::VariantIntegerData::VariantIntegerData (const VariantDataBase *data) : mValue (0)
+{
+ if (data)
+ mValue = data->getInteger (true);
+}
+
+ESM::VariantDataBase *ESM::VariantIntegerData::clone() const
+{
+ return new VariantIntegerData (*this);
+}
+
+int ESM::VariantIntegerData::getInteger (bool default_) const
+{
+ return mValue;
+}
+
+float ESM::VariantIntegerData::getFloat (bool default_) const
+{
+ return mValue;
+}
+
+void ESM::VariantIntegerData::setInteger (int value)
+{
+ mValue = value;
+}
+
+void ESM::VariantIntegerData::setFloat (float value)
+{
+ mValue = static_cast<int> (value);
+}
+
+void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarType type)
+{
+ if (type!=VT_Short && type!=VT_Long && type!=VT_Int)
+ throw std::logic_error ("not an integer type");
+
+ if (format==Variant::Format_Global)
+ {
+ float value;
+ esm.getHNT (value, "FLTV");
+
+ if (type==VT_Short)
+ {
+ if (value!=value)
+ mValue = 0; // nan
+ else
+ mValue = static_cast<short> (value);
+ }
+ else if (type==VT_Long)
+ mValue = static_cast<int> (value);
+ else
+ esm.fail ("unsupported global variable integer type");
+ }
+ else if (format==Variant::Format_Gmst || format==Variant::Format_Info)
+ {
+ if (type!=VT_Int)
+ {
+ std::ostringstream stream;
+ stream
+ << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info")
+ << " variable integer type";
+ esm.fail (stream.str());
+ }
+
+ esm.getHT (mValue);
+ }
+}
+
+void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, VarType type) const
+{
+ if (type!=VT_Short && type!=VT_Long && type!=VT_Int)
+ throw std::logic_error ("not an integer type");
+
+ if (format==Variant::Format_Global)
+ {
+ if (type==VT_Short || type==VT_Long)
+ {
+ float value = mValue;
+ esm.writeHNString ("FNAM", type==VT_Short ? "s" : "l");
+ esm.writeHNT ("FLTV", value);
+ }
+ else
+ throw std::runtime_error ("unsupported global variable integer type");
+ }
+ else if (format==Variant::Format_Gmst || format==Variant::Format_Info)
+ {
+ if (type==VT_Int)
+ {
+ std::ostringstream stream;
+ stream
+ << "unsupported " <<(format==Variant::Format_Gmst ? "gmst" : "info")
+ << " variable integer type";
+ throw std::runtime_error (stream.str());
+ }
+
+ esm.writeHNT ("INTV", mValue);
+ }
+}
+
+bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const
+{
+ return dynamic_cast<const VariantIntegerData&> (value).mValue==mValue;
+}
+
+
+ESM::VariantFloatData::VariantFloatData (const VariantDataBase *data) : mValue (0)
+{
+ if (data)
+ mValue = data->getFloat (true);
+}
+
+ESM::VariantDataBase *ESM::VariantFloatData::clone() const
+{
+ return new VariantFloatData (*this);
+}
+
+int ESM::VariantFloatData::getInteger (bool default_) const
+{
+ return static_cast<int> (mValue);
+}
+
+float ESM::VariantFloatData::getFloat (bool default_) const
+{
+ return mValue;
+}
+
+void ESM::VariantFloatData::setInteger (int value)
+{
+ mValue = value;
+}
+
+void ESM::VariantFloatData::setFloat (float value)
+{
+ mValue = value;
+}
+
+void ESM::VariantFloatData::read (ESMReader& esm, Variant::Format format, VarType type)
+{
+ if (type!=VT_Float)
+ throw std::logic_error ("not a float type");
+
+ if (format==Variant::Format_Global)
+ {
+ esm.getHNT (mValue, "FLTV");
+ }
+ else if (format==Variant::Format_Gmst || format==Variant::Format_Info)
+ {
+ esm.getHT (mValue);
+ }
+}
+
+void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarType type) const
+{
+ if (type!=VT_Float)
+ throw std::logic_error ("not a float type");
+
+ if (format==Variant::Format_Global)
+ {
+ esm.writeHNString ("FNAM", "f");
+ esm.writeHNT ("FLTV", mValue);
+ }
+ else if (format==Variant::Format_Gmst || format==Variant::Format_Info)
+ {
+ esm.writeHNT ("FLTV", mValue);
+ }
+}
+
+bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const
+{
+ return dynamic_cast<const VariantFloatData&> (value).mValue==mValue;
+} \ No newline at end of file
diff --git a/components/esm/variantimp.hpp b/components/esm/variantimp.hpp
new file mode 100644
index 0000000000..1dc20c21f2
--- /dev/null
+++ b/components/esm/variantimp.hpp
@@ -0,0 +1,179 @@
+#ifndef OPENMW_ESM_VARIANTIMP_H
+#define OPENMW_ESM_VARIANTIMP_H
+
+#include <string>
+
+#include "variant.hpp"
+
+namespace ESM
+{
+ class VariantDataBase
+ {
+ public:
+
+ virtual ~VariantDataBase();
+
+ virtual VariantDataBase *clone() const = 0;
+
+ virtual std::string getString (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as a string.
+ ///
+ /// \note Numeric values are not converted to strings.
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual int getInteger (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as an integer (implicit
+ /// casting of float values is permitted).
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual float getFloat (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as a float value.
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual void setString (const std::string& value);
+ ///< Will throw an exception, if type is not compatible with string.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual void setInteger (int value);
+ ///< Will throw an exception, if type is not compatible with integer.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual void setFloat (float value);
+ ///< Will throw an exception, if type is not compatible with float.
+ ///
+ /// Default-implementation: throw an exception.
+
+ virtual void read (ESMReader& esm, Variant::Format format, VarType type) = 0;
+ ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
+
+ virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const = 0;
+ ///< If \a type is not supported by \a format, an exception is thrown.
+
+ virtual bool isEqual (const VariantDataBase& value) const = 0;
+ ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
+
+ };
+
+ class VariantStringData : public VariantDataBase
+ {
+ std::string mValue;
+
+ public:
+
+ VariantStringData (const VariantDataBase *data = 0);
+ ///< Calling the constructor with an incompatible data type will result in a silent
+ /// default initialisation.
+
+ virtual VariantDataBase *clone() const;
+
+ virtual std::string getString (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as a string.
+ ///
+ /// \note Numeric values are not converted to strings.
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+
+ virtual void setString (const std::string& value);
+ ///< Will throw an exception, if type is not compatible with string.
+
+ virtual void read (ESMReader& esm, Variant::Format format, VarType type);
+ ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
+
+ virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const;
+ ///< If \a type is not supported by \a format, an exception is thrown.
+
+ virtual bool isEqual (const VariantDataBase& value) const;
+ ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
+ };
+
+ class VariantIntegerData : public VariantDataBase
+ {
+ int mValue;
+
+ public:
+
+ VariantIntegerData (const VariantDataBase *data = 0);
+ ///< Calling the constructor with an incompatible data type will result in a silent
+ /// default initialisation.
+
+ virtual VariantDataBase *clone() const;
+
+ virtual int getInteger (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as an integer (implicit
+ /// casting of float values is permitted).
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+
+ virtual float getFloat (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as a float value.
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+
+ virtual void setInteger (int value);
+ ///< Will throw an exception, if type is not compatible with integer.
+
+ virtual void setFloat (float value);
+ ///< Will throw an exception, if type is not compatible with float.
+
+ virtual void read (ESMReader& esm, Variant::Format format, VarType type);
+ ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
+
+ virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const;
+ ///< If \a type is not supported by \a format, an exception is thrown.
+
+ virtual bool isEqual (const VariantDataBase& value) const;
+ ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
+ };
+
+ class VariantFloatData : public VariantDataBase
+ {
+ float mValue;
+
+ public:
+
+ VariantFloatData (const VariantDataBase *data = 0);
+ ///< Calling the constructor with an incompatible data type will result in a silent
+ /// default initialisation.
+
+ virtual VariantDataBase *clone() const;
+
+ virtual int getInteger (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as an integer (implicit
+ /// casting of float values is permitted).
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+
+ virtual float getFloat (bool default_ = false) const;
+ ///< Will throw an exception, if value can not be represented as a float value.
+ ///
+ /// \param default_ Return a default value instead of throwing an exception.
+
+ virtual void setInteger (int value);
+ ///< Will throw an exception, if type is not compatible with integer.
+
+ virtual void setFloat (float value);
+ ///< Will throw an exception, if type is not compatible with float.
+
+ virtual void read (ESMReader& esm, Variant::Format format, VarType type);
+ ///< If \a type is not supported by \a format, an exception is thrown via ESMReader::fail
+
+ virtual void write (ESMWriter& esm, Variant::Format format, VarType type) const;
+ ///< If \a type is not supported by \a format, an exception is thrown.
+
+ virtual bool isEqual (const VariantDataBase& value) const;
+ ///< If the (C++) type of \a value does not match the type of *this, an exception is thrown.
+ };
+}
+
+#endif
diff --git a/components/file_finder/file_finder.hpp b/components/file_finder/file_finder.hpp
new file mode 100644
index 0000000000..8a15af73af
--- /dev/null
+++ b/components/file_finder/file_finder.hpp
@@ -0,0 +1,142 @@
+#ifndef FILE_FINDER_MAIN_H
+#define FILE_FINDER_MAIN_H
+
+#include <map>
+
+#include "search.hpp"
+#include "filename_less.hpp"
+#include <components/files/multidircollection.hpp>
+
+namespace FileFinder
+{
+
+template <typename LESS>
+class FileFinderT
+{
+ typedef std::map<std::string, std::string, LESS> TableContainer;
+ TableContainer table;
+
+ struct Inserter : ReturnPath
+ {
+ FileFinderT<LESS> *owner;
+ int cut;
+
+ void add(const boost::filesystem::path &pth)
+ {
+ std::string file = pth.string();
+ std::string key = file.substr(cut);
+ owner->table[key] = file;
+ }
+ };
+
+ Inserter inserter;
+
+public:
+ FileFinderT(const boost::filesystem::path &path, bool recurse=true)
+ {
+ inserter.owner = this;
+
+ // Remember the original path length, so we can cut it away from
+ // the relative paths used as keys
+ const std::string& pstring = path.string();
+ inserter.cut = pstring.size();
+
+ // If the path does not end in a slash, then boost will add one
+ // later, which means one more character we have to remove.
+ char last = *pstring.rbegin();
+ if(last != '\\' && last != '/')
+ inserter.cut++;
+
+ // Fill the map
+ find(path, inserter, recurse);
+ }
+
+ bool has(const std::string& file) const
+ {
+ return table.find(file) != table.end();
+ }
+
+ // Find the full path from a relative path.
+ const std::string &lookup(const std::string& file) const
+ {
+ static std::string empty;
+ typename TableContainer::const_iterator it = table.find(file);
+ return (it != table.end()) ? it->second : empty;
+ }
+};
+
+template
+<
+ class LESS
+>
+struct TreeFileFinder
+{
+ typedef TreeFileFinder<LESS> finder_t;
+
+ TreeFileFinder(const Files::PathContainer& paths, bool recurse = true)
+ {
+ struct : ReturnPath
+ {
+ finder_t *owner;
+ int cut;
+
+ void add(const boost::filesystem::path &pth)
+ {
+ std::string file = pth.string();
+ std::string key = file.substr(cut);
+ owner->mTable[key] = file;
+ }
+ } inserter;
+
+ inserter.owner = this;
+
+ for (Files::PathContainer::const_iterator it = paths.begin(); it != paths.end(); ++it)
+ {
+
+ // Remember the original path length, so we can cut it away from
+ // the relative paths used as keys
+ const std::string& pstring = it->string();
+ inserter.cut = pstring.size();
+
+ // If the path does not end in a slash, then boost will add one
+ // later, which means one more character we have to remove.
+ char last = *pstring.rbegin();
+ if (last != '\\' && last != '/')
+ {
+ inserter.cut++;
+ }
+
+ // Fill the map
+ find(*it, inserter, recurse);
+ }
+ }
+
+ bool has(const std::string& file) const
+ {
+ return mTable.find(file) != mTable.end();
+ }
+
+ const std::string& lookup(const std::string& file) const
+ {
+ static std::string empty;
+ typename TableContainer::const_iterator it = mTable.find(file);
+ return (it != mTable.end()) ? it->second : empty;
+ }
+
+ private:
+ typedef std::map<std::string, std::string, LESS> TableContainer;
+ TableContainer mTable;
+
+// Inserter inserter;
+};
+
+
+// The default is to use path_less for equality checks
+typedef FileFinderT<path_less> FileFinder;
+typedef FileFinderT<path_slash> FileFinderStrict;
+
+typedef TreeFileFinder<path_less> LessTreeFileFinder;
+typedef TreeFileFinder<path_slash> StrictTreeFileFinder;
+
+} /* namespace FileFinder */
+#endif /* FILE_FINDER_MAIN_H */
diff --git a/components/file_finder/filename_less.hpp b/components/file_finder/filename_less.hpp
new file mode 100644
index 0000000000..bc3186ce98
--- /dev/null
+++ b/components/file_finder/filename_less.hpp
@@ -0,0 +1,84 @@
+#ifndef FILE_FINDER_LESS_H
+#define FILE_FINDER_LESS_H
+
+#include <libs/platform/strings.h>
+#include <string>
+
+namespace FileFinder{
+
+// Used for maps of file paths. Compares file paths, but ignores case
+// AND treats \ and / as the same character.
+struct path_less
+{
+ int compareChar(char a, char b) const
+ {
+ if(a>b) return 1;
+ else if(a<b) return -1;
+ return 0;
+ }
+
+ int comparePathChar(char a, char b) const
+ {
+ if(a >= 'a' && a <= 'z') a += 'A'-'a';
+ else if(a == '\\') a = '/';
+ if(b >= 'a' && b <= 'z') b += 'A'-'a';
+ else if(b == '\\') b = '/';
+ return compareChar(a,b);
+ }
+
+ int compareString(const char *a, const char *b) const
+ {
+ while(*a && *b)
+ {
+ int i = comparePathChar(*a,*b);
+ if(i != 0) return i;
+ a++; b++;
+ }
+ // At this point, one or both of the chars is a null terminator.
+ // Normal char comparison will get the correct final result here.
+ return compareChar(*a,*b);
+ }
+
+ bool operator() (const std::string& a, const std::string& b) const
+ {
+ return compareString(a.c_str(), b.c_str()) < 0;
+ }
+};
+
+struct path_slash
+{
+ int compareChar(char a, char b) const
+ {
+ if(a>b) return 1;
+ else if(a<b) return -1;
+ return 0;
+ }
+
+ int comparePathChar(char a, char b) const
+ {
+ if(a == '\\') a = '/';
+ if(b == '\\') b = '/';
+ return compareChar(a,b);
+ }
+
+ int compareString(const char *a, const char *b) const
+ {
+ while(*a && *b)
+ {
+ int i = comparePathChar(*a,*b);
+ if(i != 0) return i;
+ a++; b++;
+ }
+ // At this point, one or both of the chars is a null terminator.
+ // Normal char comparison will get the correct final result here.
+ return compareChar(*a,*b);
+ }
+
+ bool operator() (const std::string& a, const std::string& b) const
+ {
+ return compareString(a.c_str(), b.c_str()) < 0;
+ }
+};
+
+}
+#endif
diff --git a/components/file_finder/search.cpp b/components/file_finder/search.cpp
new file mode 100644
index 0000000000..06deaf83a6
--- /dev/null
+++ b/components/file_finder/search.cpp
@@ -0,0 +1,36 @@
+#include "search.hpp"
+
+#include <iostream>
+
+void FileFinder::find(const boost::filesystem::path & dir_path, ReturnPath &ret, bool recurse)
+{
+ if (boost::filesystem::exists(dir_path))
+ {
+ if (!recurse)
+ {
+ boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
+ for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr)
+ {
+ if (!boost::filesystem::is_directory( *itr ))
+ {
+ ret.add(*itr);
+ }
+ }
+ }
+ else
+ {
+ boost::filesystem::recursive_directory_iterator end_itr; // default construction yields past-the-end
+ for (boost::filesystem::recursive_directory_iterator itr(dir_path); itr != end_itr; ++itr)
+ {
+ if (!boost::filesystem::is_directory(*itr))
+ {
+ ret.add(*itr);
+ }
+ }
+ }
+ }
+ else
+ {
+ std::cout << "Path " << dir_path << " not found" << std::endl;
+ }
+}
diff --git a/components/file_finder/search.hpp b/components/file_finder/search.hpp
new file mode 100644
index 0000000000..4e16fb64a0
--- /dev/null
+++ b/components/file_finder/search.hpp
@@ -0,0 +1,20 @@
+#ifndef FILE_FINDER_SEARCH_H
+#define FILE_FINDER_SEARCH_H
+
+#include <boost/filesystem.hpp>
+#include <string>
+
+namespace FileFinder
+{
+ struct ReturnPath
+ {
+ virtual void add(const boost::filesystem::path &pth) = 0;
+ };
+
+ /** Search the given path and return all file paths through 'ret'. If
+ recurse==true, all files in subdirectories are returned as well.
+ */
+ void find(const boost::filesystem::path & dir_path, ReturnPath &ret, bool recurse=true);
+}
+
+#endif
diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp
new file mode 100644
index 0000000000..02a6766b02
--- /dev/null
+++ b/components/fileorderlist/model/datafilesmodel.cpp
@@ -0,0 +1,445 @@
+#include <QTextDecoder>
+#include <QTextCodec>
+#include <QFileInfo>
+#include <QDir>
+
+#include <stdexcept>
+
+#include <components/esm/esmreader.hpp>
+
+#include "esm/esmfile.hpp"
+
+#include "datafilesmodel.hpp"
+
+#include <QDebug>
+
+DataFilesModel::DataFilesModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+ mEncoding = QString("win1252");
+}
+
+DataFilesModel::~DataFilesModel()
+{
+}
+
+void DataFilesModel::setEncoding(const QString &encoding)
+{
+ mEncoding = encoding;
+}
+
+void DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state)
+{
+ setData(index, state, Qt::CheckStateRole);
+}
+
+Qt::CheckState DataFilesModel::checkState(const QModelIndex &index)
+{
+ EsmFile *file = item(index.row());
+ return mCheckStates[file->fileName()];
+}
+
+int DataFilesModel::columnCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : 9;
+}
+
+int DataFilesModel::rowCount(const QModelIndex &parent) const
+{
+ return parent.isValid() ? 0 : mFiles.count();
+}
+
+
+bool DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent)
+{
+ if (oldrow < 0 || row < 0 || oldrow == row)
+ return false;
+
+ emit layoutAboutToBeChanged();
+ //emit beginMoveRows(parent, oldrow, oldrow, parent, row);
+ mFiles.swap(oldrow, row);
+ //emit endInsertRows();
+ emit layoutChanged();
+
+ return true;
+}
+
+QVariant DataFilesModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ EsmFile *file = item(index.row());
+
+ if (!file)
+ return QVariant();
+
+ const int column = index.column();
+
+ switch (role) {
+ case Qt::DisplayRole: {
+
+ switch (column) {
+ case 0:
+ return file->fileName();
+ case 1:
+ return file->author();
+ case 2:
+ return QString("%1 kB").arg(int((file->size() + 1023) / 1024));
+ case 3:
+ //return file->modified().toString(Qt::TextDate);
+ return file->modified().toString(Qt::ISODate);
+ case 4:
+ return file->accessed().toString(Qt::TextDate);
+ case 5:
+ return file->version();
+ case 6:
+ return file->path();
+ case 7:
+ return file->masters().join(", ");
+ case 8:
+ return file->description();
+ }
+ }
+
+ case Qt::TextAlignmentRole: {
+ switch (column) {
+ case 0:
+ case 1:
+ return Qt::AlignLeft + Qt::AlignVCenter;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ return Qt::AlignRight + Qt::AlignVCenter;
+ default:
+ return Qt::AlignLeft + Qt::AlignVCenter;
+ }
+ }
+
+ case Qt::CheckStateRole: {
+ if (column != 0)
+ return QVariant();
+ return mCheckStates[file->fileName()];
+ }
+ case Qt::ToolTipRole:
+ {
+ if (column != 0)
+ return QVariant();
+
+ if (file->version() == 0.0f)
+ return QVariant(); // Data not set
+
+ QString tooltip =
+ QString("<b>Author:</b> %1<br/> \
+ <b>Version:</b> %2<br/> \
+ <br/><b>Description:</b><br/>%3<br/> \
+ <br/><b>Dependencies: </b>%4<br/>")
+ .arg(file->author())
+ .arg(QString::number(file->version()))
+ .arg(file->description())
+ .arg(file->masters().join(", "));
+
+
+ return tooltip;
+
+ }
+ default:
+ return QVariant();
+ }
+
+}
+
+Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ EsmFile *file = item(index.row());
+
+ if (!file)
+ return Qt::NoItemFlags;
+
+ if (canBeChecked(file)) {
+ if (index.column() == 0) {
+ return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+ } else {
+ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+ }
+ } else {
+ if (index.column() == 0) {
+ return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
+ } else {
+ return Qt::NoItemFlags | Qt::ItemIsSelectable;
+ }
+ }
+
+}
+
+QVariant DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation == Qt::Horizontal) {
+ switch (section) {
+ case 0: return tr("Name");
+ case 1: return tr("Author");
+ case 2: return tr("Size");
+ case 3: return tr("Modified");
+ case 4: return tr("Accessed");
+ case 5: return tr("Version");
+ case 6: return tr("Path");
+ case 7: return tr("Masters");
+ case 8: return tr("Description");
+ }
+ } else {
+ // Show row numbers
+ return ++section;
+ }
+
+ return QVariant();
+}
+
+bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid())
+ return false;
+
+ if (role == Qt::CheckStateRole) {
+ QString name = item(index.row())->fileName();
+ mCheckStates[name] = static_cast<Qt::CheckState>(value.toInt());
+
+ // Force a redraw of the view since unchecking one item can affect another
+ QModelIndex firstIndex = indexFromItem(mFiles.first());
+ QModelIndex lastIndex = indexFromItem(mFiles.last());
+
+ emit dataChanged(firstIndex, lastIndex);
+ emit checkedItemsChanged(checkedItems());
+ return true;
+ }
+
+ return false;
+}
+
+bool lessThanEsmFile(const EsmFile *e1, const EsmFile *e2)
+{
+ //Masters first then alphabetically
+ if (e1->fileName().endsWith(".esm") && !e2->fileName().endsWith(".esm"))
+ return true;
+ if (!e1->fileName().endsWith(".esm") && e2->fileName().endsWith(".esm"))
+ return false;
+
+ return e1->fileName().toLower() < e2->fileName().toLower();
+}
+
+bool lessThanDate(const EsmFile *e1, const EsmFile *e2)
+{
+ if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void DataFilesModel::sort(int column, Qt::SortOrder order)
+{
+ emit layoutAboutToBeChanged();
+
+ if (column == 3) {
+ qSort(mFiles.begin(), mFiles.end(), lessThanDate);
+ } else {
+ qSort(mFiles.begin(), mFiles.end(), lessThanEsmFile);
+ }
+
+ emit layoutChanged();
+}
+
+void DataFilesModel::addFile(EsmFile *file)
+{
+ emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count());
+ mFiles.append(file);
+ emit endInsertRows();
+}
+
+void DataFilesModel::addFiles(const QString &path)
+{
+ QDir dir(path);
+ QStringList filters;
+ filters << "*.esp" << "*.esm";
+ dir.setNameFilters(filters);
+
+ // Create a decoder for non-latin characters in esx metadata
+ QTextCodec *codec;
+
+ if (mEncoding == QLatin1String("win1252")) {
+ codec = QTextCodec::codecForName("windows-1252");
+ } else if (mEncoding == QLatin1String("win1251")) {
+ codec = QTextCodec::codecForName("windows-1251");
+ } else if (mEncoding == QLatin1String("win1250")) {
+ codec = QTextCodec::codecForName("windows-1250");
+ } else {
+ return; // This should never happen;
+ }
+
+ QTextDecoder *decoder = codec->makeDecoder();
+
+ foreach (const QString &path, dir.entryList()) {
+ QFileInfo info(dir.absoluteFilePath(path));
+ EsmFile *file = new EsmFile(path);
+
+ try {
+ ESM::ESMReader fileReader;
+ ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString()));
+ fileReader.setEncoder(&encoder);
+ fileReader.open(dir.absoluteFilePath(path).toStdString());
+
+ std::vector<ESM::Header::MasterData> mlist = fileReader.getMasters();
+
+ QStringList masters;
+
+ for (unsigned int i = 0; i < mlist.size(); ++i) {
+ QString master = QString::fromStdString(mlist[i].name);
+ masters.append(master);
+ }
+
+ file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str()));
+ file->setSize(info.size());
+ file->setDates(info.lastModified(), info.lastRead());
+ file->setVersion(fileReader.getFVer());
+ file->setPath(info.absoluteFilePath());
+ file->setMasters(masters);
+ file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str()));
+
+
+ // Put the file in the table
+ if (findItem(path) == 0)
+ addFile(file);
+ } catch(std::runtime_error &e) {
+ // An error occurred while reading the .esp
+ qWarning() << "Error reading esp: " << e.what();
+ continue;
+ }
+
+ }
+
+ delete decoder;
+}
+
+QModelIndex DataFilesModel::indexFromItem(EsmFile *item) const
+{
+ if (item)
+ return createIndex(mFiles.indexOf(item), 0);
+
+ return QModelIndex();
+}
+
+EsmFile* DataFilesModel::findItem(const QString &name)
+{
+ QList<EsmFile *>::ConstIterator it;
+ QList<EsmFile *>::ConstIterator itEnd = mFiles.constEnd();
+
+ int i = 0;
+ for (it = mFiles.constBegin(); it != itEnd; ++it) {
+ EsmFile *file = item(i);
+ ++i;
+
+ if (name == file->fileName())
+ return file;
+ }
+
+ // Not found
+ return 0;
+}
+
+EsmFile* DataFilesModel::item(int row) const
+{
+ if (row >= 0 && row < mFiles.count())
+ return mFiles.at(row);
+ else
+ return 0;
+}
+
+QStringList DataFilesModel::checkedItems()
+{
+ QStringList list;
+
+ QList<EsmFile *>::ConstIterator it;
+ QList<EsmFile *>::ConstIterator itEnd = mFiles.constEnd();
+
+ int i = 0;
+ for (it = mFiles.constBegin(); it != itEnd; ++it) {
+ EsmFile *file = item(i);
+ ++i;
+
+ QString name = file->fileName();
+
+ // Only add the items that are in the checked list and available
+ if (mCheckStates[name] == Qt::Checked && canBeChecked(file))
+ list << name;
+ }
+
+ return list;
+}
+
+QStringList DataFilesModel::checkedItemsPaths()
+{
+ QStringList list;
+
+ QList<EsmFile *>::ConstIterator it;
+ QList<EsmFile *>::ConstIterator itEnd = mFiles.constEnd();
+
+ int i = 0;
+ for (it = mFiles.constBegin(); it != itEnd; ++it) {
+ EsmFile *file = item(i);
+ ++i;
+
+ if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file))
+ list << file->path();
+ }
+
+ return list;
+}
+
+void DataFilesModel::uncheckAll()
+{
+ emit layoutAboutToBeChanged();
+ mCheckStates.clear();
+ emit layoutChanged();
+}
+
+QStringList DataFilesModel::uncheckedItems()
+{
+ QStringList list;
+ QStringList checked = checkedItems();
+
+ QList<EsmFile *>::ConstIterator it;
+ QList<EsmFile *>::ConstIterator itEnd = mFiles.constEnd();
+
+ int i = 0;
+ for (it = mFiles.constBegin(); it != itEnd; ++it) {
+ EsmFile *file = item(i);
+ ++i;
+
+ // Add the items that are not in the checked list
+ if (!checked.contains(file->fileName()))
+ list << file->fileName();
+ }
+
+ return list;
+}
+
+bool DataFilesModel::canBeChecked(EsmFile *file) const
+{
+ //element can be checked if all its dependencies are
+ bool canBeChecked = true;
+ foreach (const QString &master, file->masters())
+ {
+ if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked)
+ {
+ canBeChecked = false;
+ break;
+ }
+ }
+ return canBeChecked;
+}
diff --git a/components/fileorderlist/model/datafilesmodel.hpp b/components/fileorderlist/model/datafilesmodel.hpp
new file mode 100644
index 0000000000..0a07a536f8
--- /dev/null
+++ b/components/fileorderlist/model/datafilesmodel.hpp
@@ -0,0 +1,66 @@
+#ifndef DATAFILESMODEL_HPP
+#define DATAFILESMODEL_HPP
+
+#include <QAbstractTableModel>
+#include <QStringList>
+#include <QString>
+#include <QHash>
+
+
+class EsmFile;
+
+class DataFilesModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ explicit DataFilesModel(QObject *parent = 0);
+ virtual ~DataFilesModel();
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex());
+
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
+
+ inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
+ { return QAbstractTableModel::index(row, column, parent); }
+
+ void setEncoding(const QString &encoding);
+
+ void addFiles(const QString &path);
+
+ void uncheckAll();
+
+ QStringList checkedItems();
+ QStringList uncheckedItems();
+ QStringList checkedItemsPaths();
+
+ Qt::CheckState checkState(const QModelIndex &index);
+ void setCheckState(const QModelIndex &index, Qt::CheckState state);
+
+ QModelIndex indexFromItem(EsmFile *item) const;
+ EsmFile* findItem(const QString &name);
+ EsmFile* item(int row) const;
+
+signals:
+ void checkedItemsChanged(const QStringList &items);
+
+private:
+ bool canBeChecked(EsmFile *file) const;
+ void addFile(EsmFile *file);
+
+ QList<EsmFile *> mFiles;
+ QHash<QString, Qt::CheckState> mCheckStates;
+
+ QString mEncoding;
+
+};
+
+#endif // DATAFILESMODEL_HPP
diff --git a/components/fileorderlist/model/esm/esmfile.cpp b/components/fileorderlist/model/esm/esmfile.cpp
new file mode 100644
index 0000000000..93d83091e7
--- /dev/null
+++ b/components/fileorderlist/model/esm/esmfile.cpp
@@ -0,0 +1,50 @@
+#include "esmfile.hpp"
+
+EsmFile::EsmFile(QString fileName, ModelItem *parent)
+ : ModelItem(parent)
+{
+ mFileName = fileName;
+ mSize = 0;
+ mVersion = 0.0f;
+}
+
+void EsmFile::setFileName(const QString &fileName)
+{
+ mFileName = fileName;
+}
+
+void EsmFile::setAuthor(const QString &author)
+{
+ mAuthor = author;
+}
+
+void EsmFile::setSize(const int size)
+{
+ mSize = size;
+}
+
+void EsmFile::setDates(const QDateTime &modified, const QDateTime &accessed)
+{
+ mModified = modified;
+ mAccessed = accessed;
+}
+
+void EsmFile::setVersion(float version)
+{
+ mVersion = version;
+}
+
+void EsmFile::setPath(const QString &path)
+{
+ mPath = path;
+}
+
+void EsmFile::setMasters(const QStringList &masters)
+{
+ mMasters = masters;
+}
+
+void EsmFile::setDescription(const QString &description)
+{
+ mDescription = description;
+}
diff --git a/components/fileorderlist/model/esm/esmfile.hpp b/components/fileorderlist/model/esm/esmfile.hpp
new file mode 100644
index 0000000000..52b3fbd007
--- /dev/null
+++ b/components/fileorderlist/model/esm/esmfile.hpp
@@ -0,0 +1,54 @@
+#ifndef ESMFILE_HPP
+#define ESMFILE_HPP
+
+#include <QDateTime>
+#include <QStringList>
+
+#include "../modelitem.hpp"
+
+class EsmFile : public ModelItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QString filename READ fileName)
+
+public:
+ EsmFile(QString fileName = QString(), ModelItem *parent = 0);
+
+ ~EsmFile()
+ {}
+
+ void setFileName(const QString &fileName);
+ void setAuthor(const QString &author);
+ void setSize(const int size);
+ void setDates(const QDateTime &modified, const QDateTime &accessed);
+ void setVersion(const float version);
+ void setPath(const QString &path);
+ void setMasters(const QStringList &masters);
+ void setDescription(const QString &description);
+
+ inline QString fileName() const { return mFileName; }
+ inline QString author() const { return mAuthor; }
+ inline int size() const { return mSize; }
+ inline QDateTime modified() const { return mModified; }
+ inline QDateTime accessed() const { return mAccessed; }
+ inline float version() const { return mVersion; }
+ inline QString path() const { return mPath; }
+ inline QStringList masters() const { return mMasters; }
+ inline QString description() const { return mDescription; }
+
+
+private:
+ QString mFileName;
+ QString mAuthor;
+ int mSize;
+ QDateTime mModified;
+ QDateTime mAccessed;
+ float mVersion;
+ QString mPath;
+ QStringList mMasters;
+ QString mDescription;
+
+};
+
+
+#endif
diff --git a/components/fileorderlist/model/modelitem.cpp b/components/fileorderlist/model/modelitem.cpp
new file mode 100644
index 0000000000..0ff7e45cb9
--- /dev/null
+++ b/components/fileorderlist/model/modelitem.cpp
@@ -0,0 +1,57 @@
+#include "modelitem.hpp"
+
+ModelItem::ModelItem(ModelItem *parent)
+ : mParentItem(parent)
+ , QObject(parent)
+{
+}
+
+ModelItem::~ModelItem()
+{
+ qDeleteAll(mChildItems);
+}
+
+
+ModelItem *ModelItem::parent()
+{
+ return mParentItem;
+}
+
+int ModelItem::row() const
+{
+ if (mParentItem)
+ return 1;
+ //return mParentItem->childRow(const_cast<ModelItem*>(this));
+ //return mParentItem->mChildItems.indexOf(const_cast<ModelItem*>(this));
+
+ return -1;
+}
+
+
+int ModelItem::childCount() const
+{
+ return mChildItems.count();
+}
+
+int ModelItem::childRow(ModelItem *child) const
+{
+ Q_ASSERT(child);
+
+ return mChildItems.indexOf(child);
+}
+
+ModelItem *ModelItem::child(int row)
+{
+ return mChildItems.value(row);
+}
+
+
+void ModelItem::appendChild(ModelItem *item)
+{
+ mChildItems.append(item);
+}
+
+void ModelItem::removeChild(int row)
+{
+ mChildItems.removeAt(row);
+}
diff --git a/components/fileorderlist/model/modelitem.hpp b/components/fileorderlist/model/modelitem.hpp
new file mode 100644
index 0000000000..f4cb4322ff
--- /dev/null
+++ b/components/fileorderlist/model/modelitem.hpp
@@ -0,0 +1,32 @@
+#ifndef MODELITEM_HPP
+#define MODELITEM_HPP
+
+#include <QObject>
+#include <QList>
+
+class ModelItem : public QObject
+{
+ Q_OBJECT
+
+public:
+ ModelItem(ModelItem *parent = 0);
+ ~ModelItem();
+
+ ModelItem *parent();
+ int row() const;
+
+ int childCount() const;
+ int childRow(ModelItem *child) const;
+ ModelItem *child(int row);
+
+ void appendChild(ModelItem *child);
+ void removeChild(int row);
+
+ //virtual bool acceptChild(ModelItem *child);
+
+protected:
+ ModelItem *mParentItem;
+ QList<ModelItem*> mChildItems;
+};
+
+#endif
diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp
new file mode 100644
index 0000000000..6be152b555
--- /dev/null
+++ b/components/fileorderlist/model/pluginsproxymodel.cpp
@@ -0,0 +1,17 @@
+#include "pluginsproxymodel.hpp"
+
+PluginsProxyModel::PluginsProxyModel(QObject *parent) :
+ QSortFilterProxyModel(parent)
+{
+}
+
+PluginsProxyModel::~PluginsProxyModel()
+{
+}
+
+QVariant PluginsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != Qt::Vertical || role != Qt::DisplayRole)
+ return QSortFilterProxyModel::headerData(section, orientation, role);
+ return section + 1;
+}
diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp
new file mode 100644
index 0000000000..8fde732361
--- /dev/null
+++ b/components/fileorderlist/model/pluginsproxymodel.hpp
@@ -0,0 +1,18 @@
+#ifndef PLUGINSPROXYMODEL_HPP
+#define PLUGINSPROXYMODEL_HPP
+
+#include <QSortFilterProxyModel>
+
+class QVariant;
+
+class PluginsProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+ explicit PluginsProxyModel(QObject *parent = 0);
+ ~PluginsProxyModel();
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+};
+
+#endif // PLUGINSPROXYMODEL_HPP
diff --git a/components/fileorderlist/utils/comboboxlineedit.cpp b/components/fileorderlist/utils/comboboxlineedit.cpp
new file mode 100644
index 0000000000..4d62e1399a
--- /dev/null
+++ b/components/fileorderlist/utils/comboboxlineedit.cpp
@@ -0,0 +1,35 @@
+#include <QToolButton>
+#include <QStyle>
+
+#include "comboboxlineedit.hpp"
+
+ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent)
+ : QLineEdit(parent)
+{
+ mClearButton = new QToolButton(this);
+ QPixmap pixmap(":images/clear.png");
+ mClearButton->setIcon(QIcon(pixmap));
+ mClearButton->setIconSize(pixmap.size());
+ mClearButton->setCursor(Qt::ArrowCursor);
+ mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
+ mClearButton->hide();
+ connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear()));
+ connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&)));
+ int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+
+ setObjectName(QString("ComboBoxLineEdit"));
+ setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1));
+}
+
+void ComboBoxLineEdit::resizeEvent(QResizeEvent *)
+{
+ QSize sz = mClearButton->sizeHint();
+ int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+ mClearButton->move(rect().right() - frameWidth - sz.width(),
+ (rect().bottom() + 1 - sz.height())/2);
+}
+
+void ComboBoxLineEdit::updateClearButton(const QString& text)
+{
+ mClearButton->setVisible(!text.isEmpty());
+}
diff --git a/components/fileorderlist/utils/comboboxlineedit.hpp b/components/fileorderlist/utils/comboboxlineedit.hpp
new file mode 100644
index 0000000000..ba10731ae3
--- /dev/null
+++ b/components/fileorderlist/utils/comboboxlineedit.hpp
@@ -0,0 +1,35 @@
+/****************************************************************************
+**
+** Copyright (c) 2007 Trolltech ASA <info@trolltech.com>
+**
+** Use, modification and distribution is allowed without limitation,
+** warranty, liability or support of any kind.
+**
+****************************************************************************/
+
+#ifndef LINEEDIT_H
+#define LINEEDIT_H
+
+#include <QLineEdit>
+
+class QToolButton;
+
+class ComboBoxLineEdit : public QLineEdit
+{
+ Q_OBJECT
+
+public:
+ ComboBoxLineEdit(QWidget *parent = 0);
+
+protected:
+ void resizeEvent(QResizeEvent *);
+
+private slots:
+ void updateClearButton(const QString &text);
+
+private:
+ QToolButton *mClearButton;
+};
+
+#endif // LIENEDIT_H
+
diff --git a/components/fileorderlist/utils/lineedit.cpp b/components/fileorderlist/utils/lineedit.cpp
new file mode 100644
index 0000000000..b0f3395897
--- /dev/null
+++ b/components/fileorderlist/utils/lineedit.cpp
@@ -0,0 +1,38 @@
+#include <QToolButton>
+#include <QStyle>
+
+#include "lineedit.hpp"
+
+LineEdit::LineEdit(QWidget *parent)
+ : QLineEdit(parent)
+{
+ mClearButton = new QToolButton(this);
+ QPixmap pixmap(":images/clear.png");
+ mClearButton->setIcon(QIcon(pixmap));
+ mClearButton->setIconSize(pixmap.size());
+ mClearButton->setCursor(Qt::ArrowCursor);
+ mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
+ mClearButton->hide();
+ connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear()));
+ connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&)));
+ int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+
+ setObjectName(QString("LineEdit"));
+ setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1));
+ QSize msz = minimumSizeHint();
+ setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2),
+ qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2));
+}
+
+void LineEdit::resizeEvent(QResizeEvent *)
+{
+ QSize sz = mClearButton->sizeHint();
+ int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
+ mClearButton->move(rect().right() - frameWidth - sz.width(),
+ (rect().bottom() + 1 - sz.height())/2);
+}
+
+void LineEdit::updateClearButton(const QString& text)
+{
+ mClearButton->setVisible(!text.isEmpty());
+}
diff --git a/components/fileorderlist/utils/lineedit.hpp b/components/fileorderlist/utils/lineedit.hpp
new file mode 100644
index 0000000000..14bd7b1b4c
--- /dev/null
+++ b/components/fileorderlist/utils/lineedit.hpp
@@ -0,0 +1,35 @@
+/****************************************************************************
+**
+** Copyright (c) 2007 Trolltech ASA <info@trolltech.com>
+**
+** Use, modification and distribution is allowed without limitation,
+** warranty, liability or support of any kind.
+**
+****************************************************************************/
+
+#ifndef LINEEDIT_H
+#define LINEEDIT_H
+
+#include <QLineEdit>
+
+class QToolButton;
+
+class LineEdit : public QLineEdit
+{
+ Q_OBJECT
+
+public:
+ LineEdit(QWidget *parent = 0);
+
+protected:
+ void resizeEvent(QResizeEvent *);
+
+private slots:
+ void updateClearButton(const QString &text);
+
+private:
+ QToolButton *mClearButton;
+};
+
+#endif // LIENEDIT_H
+
diff --git a/components/fileorderlist/utils/naturalsort.cpp b/components/fileorderlist/utils/naturalsort.cpp
new file mode 100644
index 0000000000..50d1e77de0
--- /dev/null
+++ b/components/fileorderlist/utils/naturalsort.cpp
@@ -0,0 +1,105 @@
+/*
+ * This file contains code found in the QtGui module of the Qt Toolkit.
+ * See Qt's qfilesystemmodel source files for more information
+ */
+
+#include "naturalsort.hpp"
+
+static inline QChar getNextChar(const QString &s, int location)
+{
+ return (location < s.length()) ? s.at(location) : QChar();
+}
+
+/*!
+ * Natural number sort, skips spaces.
+ *
+ * Examples:
+ * 1, 2, 10, 55, 100
+ * 01.jpg, 2.jpg, 10.jpg
+ *
+ * Note on the algorithm:
+ * Only as many characters as necessary are looked at and at most they all
+ * are looked at once.
+ *
+ * Slower then QString::compare() (of course)
+ */
+int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
+{
+ for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) {
+ // skip spaces, tabs and 0's
+ QChar c1 = getNextChar(s1, l1);
+ while (c1.isSpace())
+ c1 = getNextChar(s1, ++l1);
+ QChar c2 = getNextChar(s2, l2);
+ while (c2.isSpace())
+ c2 = getNextChar(s2, ++l2);
+
+ if (c1.isDigit() && c2.isDigit()) {
+ while (c1.digitValue() == 0)
+ c1 = getNextChar(s1, ++l1);
+ while (c2.digitValue() == 0)
+ c2 = getNextChar(s2, ++l2);
+
+ int lookAheadLocation1 = l1;
+ int lookAheadLocation2 = l2;
+ int currentReturnValue = 0;
+ // find the last digit, setting currentReturnValue as we go if it isn't equal
+ for (
+ QChar lookAhead1 = c1, lookAhead2 = c2;
+ (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
+ lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
+ lookAhead2 = getNextChar(s2, ++lookAheadLocation2)
+ ) {
+ bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
+ bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
+ if (!is1ADigit && !is2ADigit)
+ break;
+ if (!is1ADigit)
+ return -1;
+ if (!is2ADigit)
+ return 1;
+ if (currentReturnValue == 0) {
+ if (lookAhead1 < lookAhead2) {
+ currentReturnValue = -1;
+ } else if (lookAhead1 > lookAhead2) {
+ currentReturnValue = 1;
+ }
+ }
+ }
+ if (currentReturnValue != 0)
+ return currentReturnValue;
+ }
+
+ if (cs == Qt::CaseInsensitive) {
+ if (!c1.isLower()) c1 = c1.toLower();
+ if (!c2.isLower()) c2 = c2.toLower();
+ }
+ int r = QString::localeAwareCompare(c1, c2);
+ if (r < 0)
+ return -1;
+ if (r > 0)
+ return 1;
+ }
+ // The two strings are the same (02 == 2) so fall back to the normal sort
+ return QString::compare(s1, s2, cs);
+}
+
+bool naturalSortLessThanCS( const QString &left, const QString &right )
+{
+ return (naturalCompare( left, right, Qt::CaseSensitive ) < 0);
+}
+
+bool naturalSortLessThanCI( const QString &left, const QString &right )
+{
+ return (naturalCompare( left, right, Qt::CaseInsensitive ) < 0);
+}
+
+bool naturalSortGreaterThanCS( const QString &left, const QString &right )
+{
+ return (naturalCompare( left, right, Qt::CaseSensitive ) > 0);
+}
+
+bool naturalSortGreaterThanCI( const QString &left, const QString &right )
+{
+ return (naturalCompare( left, right, Qt::CaseInsensitive ) > 0);
+}
diff --git a/components/fileorderlist/utils/naturalsort.hpp b/components/fileorderlist/utils/naturalsort.hpp
new file mode 100644
index 0000000000..59271547a5
--- /dev/null
+++ b/components/fileorderlist/utils/naturalsort.hpp
@@ -0,0 +1,11 @@
+#ifndef NATURALSORT_H
+#define NATURALSORT_H
+
+#include <QString>
+
+bool naturalSortLessThanCS( const QString &left, const QString &right );
+bool naturalSortLessThanCI( const QString &left, const QString &right );
+bool naturalSortGreaterThanCS( const QString &left, const QString &right );
+bool naturalSortGreaterThanCI( const QString &left, const QString &right );
+
+#endif
diff --git a/components/fileorderlist/utils/profilescombobox.cpp b/components/fileorderlist/utils/profilescombobox.cpp
new file mode 100644
index 0000000000..c3ff953ae0
--- /dev/null
+++ b/components/fileorderlist/utils/profilescombobox.cpp
@@ -0,0 +1,92 @@
+#include <QRegExpValidator>
+#include <QLineEdit>
+#include <QString>
+#include <QApplication>
+#include <QKeyEvent>
+
+#include "profilescombobox.hpp"
+#include "comboboxlineedit.hpp"
+
+ProfilesComboBox::ProfilesComboBox(QWidget *parent) :
+ QComboBox(parent)
+{
+ mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
+ setEditEnabled(true);
+ setValidator(mValidator);
+ setCompleter(0);
+
+ connect(this, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(slotIndexChanged(int)));
+
+ setInsertPolicy(QComboBox::NoInsert);
+}
+
+void ProfilesComboBox::setEditEnabled(bool editable)
+{
+ if (isEditable() == editable)
+ return;
+
+ if (!editable) {
+ disconnect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
+ disconnect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString)));
+ return setEditable(false);
+ }
+
+ // Reset the completer and validator
+ setEditable(true);
+ setValidator(mValidator);
+
+ ComboBoxLineEdit *edit = new ComboBoxLineEdit(this);
+ setLineEdit(edit);
+ setCompleter(0);
+
+ connect(lineEdit(), SIGNAL(editingFinished()), this,
+ SLOT(slotEditingFinished()));
+
+ connect(lineEdit(), SIGNAL(textChanged(QString)), this,
+ SLOT(slotTextChanged(QString)));
+}
+
+void ProfilesComboBox::slotTextChanged(const QString &text)
+{
+ QPalette *palette = new QPalette();
+ palette->setColor(QPalette::Text,Qt::red);
+
+ int index = findText(text);
+
+ if (text.isEmpty() || (index != -1 && index != currentIndex())) {
+ lineEdit()->setPalette(*palette);
+ } else {
+ lineEdit()->setPalette(QApplication::palette());
+ }
+}
+
+void ProfilesComboBox::slotEditingFinished()
+{
+ QString current = currentText();
+ QString previous = itemText(currentIndex());
+
+ if (currentIndex() == -1)
+ return;
+
+ if (current.isEmpty())
+ return;
+
+ if (current == previous)
+ return;
+
+ if (findText(current) != -1)
+ return;
+
+ setItemText(currentIndex(), current);
+ emit(profileRenamed(previous, current));
+}
+
+void ProfilesComboBox::slotIndexChanged(int index)
+{
+ if (index == -1)
+ return;
+
+ emit(profileChanged(mOldProfile, currentText()));
+ mOldProfile = itemText(index);
+}
diff --git a/components/fileorderlist/utils/profilescombobox.hpp b/components/fileorderlist/utils/profilescombobox.hpp
new file mode 100644
index 0000000000..08ead9a7ab
--- /dev/null
+++ b/components/fileorderlist/utils/profilescombobox.hpp
@@ -0,0 +1,30 @@
+#ifndef PROFILESCOMBOBOX_HPP
+#define PROFILESCOMBOBOX_HPP
+
+#include <QComboBox>
+
+class QString;
+class QRegExpValidator;
+
+class ProfilesComboBox : public QComboBox
+{
+ Q_OBJECT
+public:
+ explicit ProfilesComboBox(QWidget *parent = 0);
+ void setEditEnabled(bool editable);
+
+signals:
+ void profileChanged(const QString &previous, const QString &current);
+ void profileRenamed(const QString &oldName, const QString &newName);
+
+private slots:
+ void slotEditingFinished();
+ void slotIndexChanged(int index);
+ void slotTextChanged(const QString &text);
+
+private:
+ QString mOldProfile;
+ QRegExpValidator *mValidator;
+};
+
+#endif // PROFILESCOMBOBOX_HPP
diff --git a/components/files/collections.cpp b/components/files/collections.cpp
new file mode 100644
index 0000000000..c6195d88cf
--- /dev/null
+++ b/components/files/collections.cpp
@@ -0,0 +1,64 @@
+
+#include "collections.hpp"
+
+namespace Files
+{
+ Collections::Collections()
+ : mDirectories()
+ , mFoldCase(false)
+ , mCollections()
+ {
+ }
+
+ Collections::Collections(const Files::PathContainer& directories, bool foldCase)
+ : mDirectories(directories)
+ , mFoldCase(foldCase)
+ , mCollections()
+ {
+ }
+
+ const MultiDirCollection& Collections::getCollection(const std::string& extension) const
+ {
+ MultiDirCollectionContainer::iterator iter = mCollections.find(extension);
+ if (iter==mCollections.end())
+ {
+ std::pair<MultiDirCollectionContainer::iterator, bool> result =
+ mCollections.insert(std::make_pair(extension, MultiDirCollection(mDirectories, extension, mFoldCase)));
+
+ iter = result.first;
+ }
+
+ return iter->second;
+ }
+
+ boost::filesystem::path Collections::getPath(const std::string& file) const
+ {
+ for (Files::PathContainer::const_iterator iter = mDirectories.begin();
+ iter != mDirectories.end(); ++iter)
+ {
+ const boost::filesystem::path path = *iter / file;
+ if (boost::filesystem::exists(path))
+ return path.string();
+ }
+
+ throw std::runtime_error ("file " + file + " not found");
+ }
+
+ bool Collections::doesExist(const std::string& file) const
+ {
+ for (Files::PathContainer::const_iterator iter = mDirectories.begin();
+ iter != mDirectories.end(); ++iter)
+ {
+ const boost::filesystem::path path = *iter / file;
+ if (boost::filesystem::exists(path))
+ return true;
+ }
+
+ return false;
+ }
+
+ const Files::PathContainer& Collections::getPaths() const
+ {
+ return mDirectories;
+ }
+}
diff --git a/components/files/collections.hpp b/components/files/collections.hpp
new file mode 100644
index 0000000000..def61cf8ef
--- /dev/null
+++ b/components/files/collections.hpp
@@ -0,0 +1,43 @@
+#ifndef COMPONENTS_FILES_COLLECTION_HPP
+#define COMPONENTS_FILES_COLLECTION_HPP
+
+#include <boost/filesystem.hpp>
+
+#include "multidircollection.hpp"
+
+namespace Files
+{
+ class Collections
+ {
+ public:
+ Collections();
+
+ ///< Directories are listed with increasing priority.
+ Collections(const Files::PathContainer& directories, bool foldCase);
+
+ ///< Return a file collection for the given extension. Extension must contain the
+ /// leading dot and must be all lower-case.
+ const MultiDirCollection& getCollection(const std::string& extension) const;
+
+ boost::filesystem::path getPath(const std::string& file) const;
+ ///< Return full path (including filename) of \a file.
+ ///
+ /// If the file does not exist in any of the collection's
+ /// directories, an exception is thrown. \a file must include the
+ /// extension.
+
+ bool doesExist(const std::string& file) const;
+ ///< \return Does a file with the given name exist?
+
+ const Files::PathContainer& getPaths() const;
+
+ private:
+ typedef std::map<std::string, MultiDirCollection> MultiDirCollectionContainer;
+ Files::PathContainer mDirectories;
+
+ bool mFoldCase;
+ mutable MultiDirCollectionContainer mCollections;
+ };
+}
+
+#endif
diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp
new file mode 100644
index 0000000000..56e55a98d1
--- /dev/null
+++ b/components/files/configurationmanager.cpp
@@ -0,0 +1,165 @@
+#include "configurationmanager.hpp"
+
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <algorithm>
+
+#include <boost/bind.hpp>
+#include <boost/algorithm/string/erase.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+static const char* const openmwCfgFile = "openmw.cfg";
+
+const char* const mwToken = "?mw?";
+const char* const localToken = "?local?";
+const char* const userToken = "?user?";
+const char* const globalToken = "?global?";
+
+ConfigurationManager::ConfigurationManager()
+ : mFixedPath("openmw")
+{
+ setupTokensMapping();
+
+ boost::filesystem::create_directories(mFixedPath.getUserPath());
+
+ mLogPath = mFixedPath.getUserPath();
+}
+
+ConfigurationManager::~ConfigurationManager()
+{
+}
+
+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(globalToken, &FixedPath<>::getGlobalDataPath));
+}
+
+void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables,
+ boost::program_options::options_description& description)
+{
+ loadConfig(mFixedPath.getUserPath(), variables, description);
+ boost::program_options::notify(variables);
+
+ loadConfig(mFixedPath.getLocalPath(), variables, description);
+ boost::program_options::notify(variables);
+ loadConfig(mFixedPath.getGlobalPath(), variables, description);
+ boost::program_options::notify(variables);
+
+}
+
+void ConfigurationManager::processPaths(Files::PathContainer& dataDirs)
+{
+ std::string path;
+ for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
+ {
+ path = it->string();
+ boost::erase_all(path, "\"");
+ *it = boost::filesystem::path(path);
+
+ // Check if path contains a token
+ if (!path.empty() && *path.begin() == '?')
+ {
+ std::string::size_type pos = path.find('?', 1);
+ if (pos != std::string::npos && pos != 0)
+ {
+ TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1));
+ if (tokenIt != mTokensMapping.end())
+ {
+ boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))());
+ if (pos < path.length() - 1)
+ {
+ // There is something after the token, so we should
+ // append it to the path
+ tempPath /= path.substr(pos + 1, path.length() - pos);
+ }
+
+ *it = tempPath;
+ }
+ else
+ {
+ // Clean invalid / unknown token, it will be removed outside the loop
+ (*it).clear();
+ }
+ }
+ }
+
+ if (!boost::filesystem::is_directory(*it))
+ {
+ (*it).clear();
+ }
+ }
+
+ dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(),
+ boost::bind(&boost::filesystem::path::empty, _1)), dataDirs.end());
+}
+
+void ConfigurationManager::loadConfig(const boost::filesystem::path& path,
+ boost::program_options::variables_map& variables,
+ boost::program_options::options_description& description)
+{
+ boost::filesystem::path cfgFile(path);
+ cfgFile /= std::string(openmwCfgFile);
+ if (boost::filesystem::is_regular_file(cfgFile))
+ {
+ std::cout << "Loading config file: " << cfgFile.string() << "... ";
+
+ std::ifstream configFileStream(cfgFile.string().c_str());
+ if (configFileStream.is_open())
+ {
+ boost::program_options::store(boost::program_options::parse_config_file(
+ configFileStream, description, true), variables);
+
+ std::cout << "done." << std::endl;
+ }
+ else
+ {
+ std::cout << "failed." << std::endl;
+ }
+ }
+}
+
+const boost::filesystem::path& ConfigurationManager::getGlobalPath() const
+{
+ return mFixedPath.getGlobalPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getUserPath() const
+{
+ return mFixedPath.getUserPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getLocalPath() const
+{
+ return mFixedPath.getLocalPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getGlobalDataPath() const
+{
+ return mFixedPath.getGlobalDataPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getCachePath() const
+{
+ return mFixedPath.getCachePath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getInstallPath() const
+{
+ return mFixedPath.getInstallPath();
+}
+
+const boost::filesystem::path& ConfigurationManager::getLogPath() const
+{
+ return mLogPath;
+}
+
+} /* namespace Cfg */
diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp
new file mode 100644
index 0000000000..765f1cebf8
--- /dev/null
+++ b/components/files/configurationmanager.hpp
@@ -0,0 +1,74 @@
+#ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP
+#define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP
+
+#ifdef _WIN32
+#include <boost/tr1/tr1/unordered_map>
+#elif defined HAVE_UNORDERED_MAP
+#include <unordered_map>
+#else
+#include <tr1/unordered_map>
+#endif
+
+#include <boost/program_options.hpp>
+
+#include <components/files/fixedpath.hpp>
+#include <components/files/collections.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+/**
+ * \struct ConfigurationManager
+ */
+struct ConfigurationManager
+{
+ ConfigurationManager();
+ virtual ~ConfigurationManager();
+
+ void readConfiguration(boost::program_options::variables_map& variables,
+ boost::program_options::options_description& description);
+ void processPaths(Files::PathContainer& dataDirs);
+
+ /**< Fixed paths */
+ const boost::filesystem::path& getGlobalPath() const;
+ const boost::filesystem::path& getUserPath() const;
+ const boost::filesystem::path& getLocalPath() const;
+
+ const boost::filesystem::path& getGlobalDataPath() const;
+ const boost::filesystem::path& getUserDataPath() const;
+ const boost::filesystem::path& getLocalDataPath() const;
+ const boost::filesystem::path& getInstallPath() const;
+
+ const boost::filesystem::path& getCachePath() const;
+
+ const boost::filesystem::path& getLogPath() const;
+
+ private:
+ typedef Files::FixedPath<> FixedPathType;
+
+ typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const;
+ #if defined HAVE_UNORDERED_MAP
+ typedef std::unordered_map<std::string, path_type_f> TokensMappingContainer;
+ #else
+ typedef std::tr1::unordered_map<std::string, path_type_f> TokensMappingContainer;
+ #endif
+
+ void loadConfig(const boost::filesystem::path& path,
+ boost::program_options::variables_map& variables,
+ boost::program_options::options_description& description);
+
+ void setupTokensMapping();
+
+ FixedPathType mFixedPath;
+
+ boost::filesystem::path mLogPath;
+
+ TokensMappingContainer mTokensMapping;
+};
+
+} /* namespace Cfg */
+
+#endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */
diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp
new file mode 100644
index 0000000000..321bcf7c8c
--- /dev/null
+++ b/components/files/constrainedfiledatastream.cpp
@@ -0,0 +1,172 @@
+#include "constrainedfiledatastream.hpp"
+#include "lowlevelfile.hpp"
+
+#include <stdexcept>
+#include <cassert>
+
+#include <libs/platform/stdint.h>
+
+namespace {
+
+class ConstrainedDataStream : public Ogre::DataStream {
+public:
+
+ static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any
+ static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call
+
+ ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length)
+ {
+ mFile.open (fname.c_str ());
+ mSize = length != 0xFFFFFFFF ? length : mFile.size () - start;
+
+ mPos = 0;
+ mOrigin = start;
+ mExtent = start + mSize;
+
+ mBufferOrigin = 0;
+ mBufferExtent = 0;
+ }
+
+
+ size_t read(void* buf, size_t count)
+ {
+ assert (mPos <= mSize);
+
+ uint8_t * out = reinterpret_cast <uint8_t *> (buf);
+
+ size_t posBeg = mOrigin + mPos;
+ size_t posEnd = posBeg + count;
+
+ if (posEnd > mExtent)
+ posEnd = mExtent;
+
+ size_t posCur = posBeg;
+
+ while (posCur != posEnd)
+ {
+ size_t readLeft = posEnd - posCur;
+
+ if (posCur < mBufferOrigin || posCur >= mBufferExtent)
+ {
+ if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent))
+ {
+ assert (mFile.tell () == mBufferExtent);
+
+ if (posCur != mBufferExtent)
+ mFile.seek (posCur);
+
+ posCur += mFile.read (out, readLeft);
+
+ mBufferOrigin = mBufferExtent = posCur;
+
+ mPos = posCur - mOrigin;
+
+ return posCur - posBeg;
+ }
+ else
+ {
+ size_t newBufferOrigin;
+
+ if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize))
+ newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0);
+ else
+ newBufferOrigin = posCur;
+
+ fill (newBufferOrigin);
+ }
+ }
+
+ size_t xfer = std::min (readLeft, mBufferExtent - posCur);
+
+ memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer);
+
+ posCur += xfer;
+ out += xfer;
+ }
+
+ count = posEnd - posBeg;
+ mPos += count;
+ return count;
+ }
+
+ void skip(long count)
+ {
+ assert (mPos <= mSize);
+
+ if((count >= 0 && (size_t)count <= mSize-mPos) ||
+ (count < 0 && (size_t)-count <= mPos))
+ mPos += count;
+ }
+
+ void seek(size_t pos)
+ {
+ assert (mPos <= mSize);
+
+ if (pos < mSize)
+ mPos = pos;
+ }
+
+ virtual size_t tell() const
+ {
+ assert (mPos <= mSize);
+
+ return mPos;
+ }
+
+ virtual bool eof() const
+ {
+ assert (mPos <= mSize);
+
+ return mPos == mSize;
+ }
+
+ virtual void close()
+ {
+ mFile.close();
+ }
+
+private:
+
+ void fill (size_t newOrigin)
+ {
+ assert (mFile.tell () == mBufferExtent);
+
+ size_t newExtent = newOrigin + sBufferSize;
+
+ if (newExtent > mExtent)
+ newExtent = mExtent;
+
+ size_t oldExtent = mBufferExtent;
+
+ if (newOrigin != oldExtent)
+ mFile.seek (newOrigin);
+
+ mBufferOrigin = mBufferExtent = newOrigin;
+
+ size_t amountRequested = newExtent - newOrigin;
+
+ size_t amountRead = mFile.read (mBuffer, amountRequested);
+
+ if (amountRead != amountRequested)
+ throw std::runtime_error ("An unexpected condition occurred while reading from a file.");
+
+ mBufferExtent = newExtent;
+ }
+
+ LowLevelFile mFile;
+
+ size_t mOrigin;
+ size_t mExtent;
+ size_t mPos;
+
+ uint8_t mBuffer [sBufferSize];
+ size_t mBufferOrigin;
+ size_t mBufferExtent;
+};
+
+} // end of unnamed namespace
+
+Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length)
+{
+ return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length));
+}
diff --git a/components/files/constrainedfiledatastream.hpp b/components/files/constrainedfiledatastream.hpp
new file mode 100644
index 0000000000..367defcbcb
--- /dev/null
+++ b/components/files/constrainedfiledatastream.hpp
@@ -0,0 +1,8 @@
+#ifndef COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP
+#define COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP
+
+#include <OgreDataStream.h>
+
+Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset = 0, size_t length = 0xFFFFFFFF);
+
+#endif // COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP
diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp
new file mode 100644
index 0000000000..ce2d95f57c
--- /dev/null
+++ b/components/files/filelibrary.cpp
@@ -0,0 +1,120 @@
+#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
new file mode 100644
index 0000000000..6abe972690
--- /dev/null
+++ b/components/files/filelibrary.hpp
@@ -0,0 +1,49 @@
+#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
new file mode 100644
index 0000000000..fbc2eef056
--- /dev/null
+++ b/components/files/fileops.cpp
@@ -0,0 +1,120 @@
+#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
new file mode 100644
index 0000000000..bf1c51485f
--- /dev/null
+++ b/components/files/fileops.hpp
@@ -0,0 +1,38 @@
+#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
new file mode 100644
index 0000000000..a309dc9fb6
--- /dev/null
+++ b/components/files/fixedpath.hpp
@@ -0,0 +1,123 @@
+#ifndef COMPONENTS_FILES_FIXEDPATH_HPP
+#define COMPONENTS_FILES_FIXEDPATH_HPP
+
+#include <string>
+#include <boost/filesystem.hpp>
+
+#if defined(__linux__) || defined(__FreeBSD__)
+ #include <components/files/linuxpath.hpp>
+ namespace Files { typedef LinuxPath TargetPathType; }
+
+#elif defined(__WIN32) || defined(__WINDOWS__) || defined(_WIN32)
+ #include <components/files/windowspath.hpp>
+ namespace Files { typedef WindowsPath TargetPathType; }
+
+#elif defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__)
+ #include <components/files/macospath.hpp>
+ namespace Files { typedef MacOsPath TargetPathType; }
+
+#else
+ #error "Unknown platform!"
+#endif
+
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+/**
+ * \struct Path
+ *
+ * \tparam P - Path strategy class type (depends on target system)
+ *
+ */
+template
+<
+ class P = TargetPathType
+>
+struct FixedPath
+{
+ typedef P PathType;
+
+ /**
+ * \brief Path constructor.
+ *
+ * \param [in] application_name - Name of the application
+ */
+ FixedPath(const std::string& application_name)
+ : mPath(application_name + "/")
+ , mUserPath(mPath.getUserPath())
+ , mGlobalPath(mPath.getGlobalPath())
+ , mLocalPath(mPath.getLocalPath())
+ , mGlobalDataPath(mPath.getGlobalDataPath())
+ , mInstallPath(mPath.getInstallPath())
+ , mCachePath(mPath.getCachePath())
+ {
+ }
+
+ /**
+ * \brief Return path pointing to the user local configuration directory.
+ *
+ * \return boost::filesystem::path
+ */
+ const boost::filesystem::path& getUserPath() const
+ {
+ return mUserPath;
+ }
+
+ /**
+ * \brief Return path pointing to the global (system) configuration directory.
+ *
+ * \return boost::filesystem::path
+ */
+ const boost::filesystem::path& getGlobalPath() const
+ {
+ return mGlobalPath;
+ }
+
+ /**
+ * \brief Return path pointing to the directory where application was started.
+ *
+ * \return boost::filesystem::path
+ */
+ const boost::filesystem::path& getLocalPath() const
+ {
+ return mLocalPath;
+ }
+
+ const boost::filesystem::path& getInstallPath() const
+ {
+ return mInstallPath;
+ }
+
+ const boost::filesystem::path& getGlobalDataPath() const
+ {
+ return mGlobalDataPath;
+ }
+
+ const boost::filesystem::path& getCachePath() const
+ {
+ return mCachePath;
+ }
+
+ private:
+ PathType mPath;
+
+ boost::filesystem::path mUserPath; /**< User path */
+ boost::filesystem::path mGlobalPath; /**< Global path */
+ boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */
+
+ boost::filesystem::path mGlobalDataPath; /**< Global application data path */
+
+ boost::filesystem::path mCachePath;
+
+ boost::filesystem::path mInstallPath;
+
+};
+
+
+} /* namespace Files */
+
+#endif /* COMPONENTS_FILES_FIXEDPATH_HPP */
diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp
new file mode 100644
index 0000000000..c974a91d35
--- /dev/null
+++ b/components/files/linuxpath.cpp
@@ -0,0 +1,164 @@
+#include "linuxpath.hpp"
+
+#if defined(__linux__) || defined(__FreeBSD__)
+
+#include <cstdlib>
+#include <cstring>
+#include <pwd.h>
+#include <unistd.h>
+#include <boost/filesystem/fstream.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+LinuxPath::LinuxPath(const std::string& application_name)
+ : mName(application_name)
+{
+}
+
+boost::filesystem::path LinuxPath::getUserPath() 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 / ".config" / 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;
+}
+
+boost::filesystem::path LinuxPath::getGlobalPath() const
+{
+ boost::filesystem::path globalPath("/etc/");
+ return globalPath / mName;
+}
+
+boost::filesystem::path LinuxPath::getLocalPath() const
+{
+ return boost::filesystem::path("./");
+}
+
+boost::filesystem::path LinuxPath::getGlobalDataPath() const
+{
+ boost::filesystem::path globalDataPath("/usr/share/games/");
+ return globalDataPath / mName;
+}
+
+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;
+ }
+ }
+
+ if (homePath != NULL)
+ {
+ boost::filesystem::path wineDefaultRegistry(homePath);
+ wineDefaultRegistry /= ".wine/system.reg";
+
+ if (boost::filesystem::is_regular_file(wineDefaultRegistry))
+ {
+ boost::filesystem::ifstream file(wineDefaultRegistry);
+ bool isRegEntry = false;
+ std::string line;
+ std::string mwpath;
+
+ while (std::getline(file, line))
+ {
+ if (line[0] == '[') // we found an entry
+ {
+ if (isRegEntry)
+ {
+ break;
+ }
+
+ isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos);
+ }
+ else if (isRegEntry)
+ {
+ if (line[0] == '"') // empty line means new registry key
+ {
+ std::string key = line.substr(1, line.find('"', 1) - 1);
+ if (strcasecmp(key.c_str(), "Installed Path") == 0)
+ {
+ std::string::size_type valuePos = line.find('=') + 2;
+ mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
+
+ std::string::size_type pos = mwpath.find("\\");
+ while (pos != std::string::npos)
+ {
+ mwpath.replace(pos, 2, "/");
+ pos = mwpath.find("\\", pos + 1);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (!mwpath.empty())
+ {
+ // Change drive letter to lowercase, so we could use
+ // ~/.wine/dosdevices symlinks
+ mwpath[0] = tolower(mwpath[0]);
+ installPath /= homePath;
+ installPath /= ".wine/dosdevices/";
+ installPath /= mwpath;
+
+ if (!boost::filesystem::is_directory(installPath))
+ {
+ installPath.clear();
+ }
+ }
+ }
+ }
+
+ return installPath;
+}
+
+} /* namespace Files */
+
+#endif /* defined(__linux__) || defined(__FreeBSD__) */
diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp
new file mode 100644
index 0000000000..6acf2a2d5f
--- /dev/null
+++ b/components/files/linuxpath.hpp
@@ -0,0 +1,71 @@
+#ifndef COMPONENTS_FILES_LINUXPATH_H
+#define COMPONENTS_FILES_LINUXPATH_H
+
+#if defined(__linux__) || defined(__FreeBSD__)
+
+#include <boost/filesystem.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+/**
+ * \struct LinuxPath
+ */
+struct LinuxPath
+{
+ LinuxPath(const std::string& application_name);
+
+ /**
+ * \brief Return path to the user directory.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getUserPath() const;
+
+ /**
+ * \brief Return path to the global (system) directory where game files could be placed.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getGlobalPath() 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
+ */
+ 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;
+
+ std::string mName;
+};
+
+} /* namespace Files */
+
+#endif /* defined(__linux__) || defined(__FreeBSD__) */
+
+#endif /* COMPONENTS_FILES_LINUXPATH_H */
diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp
new file mode 100644
index 0000000000..71fd1b523c
--- /dev/null
+++ b/components/files/lowlevelfile.cpp
@@ -0,0 +1,299 @@
+#include "lowlevelfile.hpp"
+
+#include <stdexcept>
+#include <sstream>
+#include <cassert>
+
+#if FILE_API == FILE_API_POSIX
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#endif
+
+#if FILE_API == FILE_API_STDIO
+/*
+ *
+ * Implementation of LowLevelFile methods using c stdio
+ *
+ */
+
+LowLevelFile::LowLevelFile ()
+{
+ mHandle = NULL;
+}
+
+LowLevelFile::~LowLevelFile ()
+{
+ if (mHandle != NULL)
+ fclose (mHandle);
+}
+
+void LowLevelFile::open (char const * filename)
+{
+ assert (mHandle == NULL);
+
+ mHandle = fopen (filename, "rb");
+
+ if (mHandle == NULL)
+ {
+ std::ostringstream os;
+ os << "Failed to open '" << filename << "' for reading.";
+ throw std::runtime_error (os.str ());
+ }
+}
+
+void LowLevelFile::close ()
+{
+ assert (mHandle != NULL);
+
+ fclose (mHandle);
+
+ mHandle = NULL;
+}
+
+size_t LowLevelFile::size ()
+{
+ assert (mHandle != NULL);
+
+ long oldPosition = ftell (mHandle);
+
+ if (oldPosition == -1)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ if (fseek (mHandle, 0, SEEK_END) != 0)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ long Size = ftell (mHandle);
+
+ if (Size == -1)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ if (fseek (mHandle, oldPosition, SEEK_SET) != 0)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ return size_t (Size);
+}
+
+void LowLevelFile::seek (size_t Position)
+{
+ assert (mHandle != NULL);
+
+ if (fseek (mHandle, Position, SEEK_SET) != 0)
+ throw std::runtime_error ("A seek operation on a file failed.");
+}
+
+size_t LowLevelFile::tell ()
+{
+ assert (mHandle != NULL);
+
+ long Position = ftell (mHandle);
+
+ if (Position == -1)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ return size_t (Position);
+}
+
+size_t LowLevelFile::read (void * data, size_t size)
+{
+ assert (mHandle != NULL);
+
+ int amount = fread (data, 1, size, mHandle);
+
+ if (amount == 0 && ferror (mHandle))
+ throw std::runtime_error ("A read operation on a file failed.");
+
+ return amount;
+}
+
+#elif FILE_API == FILE_API_POSIX
+/*
+ *
+ * Implementation of LowLevelFile methods using posix IO calls
+ *
+ */
+
+LowLevelFile::LowLevelFile ()
+{
+ mHandle = -1;
+}
+
+LowLevelFile::~LowLevelFile ()
+{
+ if (mHandle != -1)
+ ::close (mHandle);
+}
+
+void LowLevelFile::open (char const * filename)
+{
+ assert (mHandle == -1);
+
+#ifdef O_BINARY
+ static const int openFlags = O_RDONLY | O_BINARY;
+#else
+ static const int openFlags = O_RDONLY;
+#endif
+
+ mHandle = ::open (filename, openFlags, 0);
+
+ if (mHandle == -1)
+ {
+ std::ostringstream os;
+ os << "Failed to open '" << filename << "' for reading.";
+ throw std::runtime_error (os.str ());
+ }
+}
+
+void LowLevelFile::close ()
+{
+ assert (mHandle != -1);
+
+ ::close (mHandle);
+
+ mHandle = -1;
+}
+
+size_t LowLevelFile::size ()
+{
+ assert (mHandle != -1);
+
+ size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR);
+
+ if (oldPosition == size_t (-1))
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ size_t Size = ::lseek (mHandle, 0, SEEK_END);
+
+ if (Size == size_t (-1))
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ if (lseek (mHandle, oldPosition, SEEK_SET) == -1)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ return Size;
+}
+
+void LowLevelFile::seek (size_t Position)
+{
+ assert (mHandle != -1);
+
+ if (::lseek (mHandle, Position, SEEK_SET) == -1)
+ throw std::runtime_error ("A seek operation on a file failed.");
+}
+
+size_t LowLevelFile::tell ()
+{
+ assert (mHandle != -1);
+
+ size_t Position = ::lseek (mHandle, 0, SEEK_CUR);
+
+ if (Position == size_t (-1))
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ return Position;
+}
+
+size_t LowLevelFile::read (void * data, size_t size)
+{
+ assert (mHandle != -1);
+
+ int amount = ::read (mHandle, data, size);
+
+ if (amount == -1)
+ throw std::runtime_error ("A read operation on a file failed.");
+
+ return amount;
+}
+
+#elif FILE_API == FILE_API_WIN32
+/*
+ *
+ * Implementation of LowLevelFile methods using Win32 API calls
+ *
+ */
+
+LowLevelFile::LowLevelFile ()
+{
+ mHandle = INVALID_HANDLE_VALUE;
+}
+
+LowLevelFile::~LowLevelFile ()
+{
+ if (mHandle == INVALID_HANDLE_VALUE)
+ CloseHandle (mHandle);
+}
+
+void LowLevelFile::open (char const * filename)
+{
+ assert (mHandle == INVALID_HANDLE_VALUE);
+
+ HANDLE handle = CreateFileA (filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+
+ if (handle == NULL)
+ {
+ std::ostringstream os;
+ os << "Failed to open '" << filename << "' for reading.";
+ throw std::runtime_error (os.str ());
+ }
+
+ mHandle = handle;
+}
+
+void LowLevelFile::close ()
+{
+ assert (mHandle != INVALID_HANDLE_VALUE);
+
+ CloseHandle (mHandle);
+
+ mHandle = INVALID_HANDLE_VALUE;
+}
+
+size_t LowLevelFile::size ()
+{
+ assert (mHandle != INVALID_HANDLE_VALUE);
+
+ BY_HANDLE_FILE_INFORMATION info;
+
+ if (!GetFileInformationByHandle (mHandle, &info))
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ if (info.nFileSizeHigh != 0)
+ throw std::runtime_error ("Files greater that 4GB are not supported.");
+
+ return info.nFileSizeLow;
+}
+
+void LowLevelFile::seek (size_t Position)
+{
+ assert (mHandle != INVALID_HANDLE_VALUE);
+
+ if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER)
+ if (GetLastError () != NO_ERROR)
+ throw std::runtime_error ("A seek operation on a file failed.");
+}
+
+size_t LowLevelFile::tell ()
+{
+ assert (mHandle != INVALID_HANDLE_VALUE);
+
+ DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR);
+
+ if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR)
+ throw std::runtime_error ("A query operation on a file failed.");
+
+ return value;
+}
+
+size_t LowLevelFile::read (void * data, size_t size)
+{
+ assert (mHandle != INVALID_HANDLE_VALUE);
+
+ DWORD read;
+
+ if (!ReadFile (mHandle, data, size, &read, NULL))
+ throw std::runtime_error ("A read operation on a file failed.");
+
+ return read;
+}
+
+#endif
diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp
new file mode 100644
index 0000000000..f49c466a51
--- /dev/null
+++ b/components/files/lowlevelfile.hpp
@@ -0,0 +1,56 @@
+#ifndef COMPONENTS_FILES_LOWLEVELFILE_HPP
+#define COMPONENTS_FILES_LOWLEVELFILE_HPP
+
+#include <OgrePlatform.h>
+
+#include <cstdlib>
+
+#define FILE_API_STDIO 0
+#define FILE_API_POSIX 1
+#define FILE_API_WIN32 2
+
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+#define FILE_API FILE_API_POSIX
+#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+#define FILE_API FILE_API_WIN32
+#else
+#define FILE_API FILE_API_STDIO
+#endif
+
+#if FILE_API == FILE_API_STDIO
+#include <cstdio>
+#elif FILE_API == FILE_API_POSIX
+#elif FILE_API == FILE_API_WIN32
+#include <windows.h>
+#else
+#error Unsupported File API
+#endif
+
+class LowLevelFile
+{
+public:
+
+ LowLevelFile ();
+ ~LowLevelFile ();
+
+ void open (char const * filename);
+ void close ();
+
+ size_t size ();
+
+ void seek (size_t Position);
+ size_t tell ();
+
+ size_t read (void * data, size_t size);
+
+private:
+#if FILE_API == FILE_API_STDIO
+ FILE* mHandle;
+#elif FILE_API == FILE_API_POSIX
+ int mHandle;
+#elif FILE_API == FILE_API_WIN32
+ HANDLE mHandle;
+#endif
+};
+
+#endif
diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp
new file mode 100644
index 0000000000..9edcd6ef2a
--- /dev/null
+++ b/components/files/macospath.cpp
@@ -0,0 +1,165 @@
+#include "macospath.hpp"
+
+#if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__)
+
+#include <cstdlib>
+#include <pwd.h>
+#include <unistd.h>
+#include <boost/filesystem/fstream.hpp>
+
+/**
+ * FIXME: Someone with MacOS system should check this and correct if necessary
+ */
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+MacOsPath::MacOsPath(const std::string& application_name)
+ : mName(application_name)
+{
+}
+
+boost::filesystem::path MacOsPath::getUserPath() 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/Preferences/";
+ }
+
+ return userPath / mName;
+}
+
+boost::filesystem::path MacOsPath::getGlobalPath() const
+{
+ boost::filesystem::path globalPath("/Library/Preferences/");
+ return globalPath / mName;
+}
+
+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 MacOsPath::getLocalPath() const
+{
+ return boost::filesystem::path("./");
+}
+
+boost::filesystem::path MacOsPath::getGlobalDataPath() const
+{
+ boost::filesystem::path globalDataPath("/Library/Application Support/");
+ return globalDataPath / mName;
+}
+
+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;
+ }
+ }
+
+ if (homePath != NULL)
+ {
+ boost::filesystem::path wineDefaultRegistry(homePath);
+ wineDefaultRegistry /= ".wine/system.reg";
+
+ if (boost::filesystem::is_regular_file(wineDefaultRegistry))
+ {
+ boost::filesystem::ifstream file(wineDefaultRegistry);
+ bool isRegEntry = false;
+ std::string line;
+ std::string mwpath;
+
+ while (std::getline(file, line))
+ {
+ if (line[0] == '[') // we found an entry
+ {
+ if (isRegEntry)
+ {
+ break;
+ }
+
+ isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos);
+ }
+ else if (isRegEntry)
+ {
+ if (line[0] == '"') // empty line means new registry key
+ {
+ std::string key = line.substr(1, line.find('"', 1) - 1);
+ if (strcasecmp(key.c_str(), "Installed Path") == 0)
+ {
+ std::string::size_type valuePos = line.find('=') + 2;
+ mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
+
+ std::string::size_type pos = mwpath.find("\\");
+ while (pos != std::string::npos)
+ {
+ mwpath.replace(pos, 2, "/");
+ pos = mwpath.find("\\", pos + 1);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (!mwpath.empty())
+ {
+ // Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks
+ mwpath[0] = tolower(mwpath[0]);
+ installPath /= homePath;
+ installPath /= ".wine/dosdevices/";
+ installPath /= mwpath;
+
+ if (!boost::filesystem::is_directory(installPath))
+ {
+ installPath.clear();
+ }
+ }
+ }
+ }
+
+ return installPath;
+}
+
+
+} /* namespace Files */
+
+#endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */
diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp
new file mode 100644
index 0000000000..576ec16812
--- /dev/null
+++ b/components/files/macospath.hpp
@@ -0,0 +1,66 @@
+#ifndef COMPONENTS_FILES_MACOSPATH_H
+#define COMPONENTS_FILES_MACOSPATH_H
+
+#if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__)
+
+#include <boost/filesystem.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+/**
+ * \struct MacOsPath
+ */
+struct MacOsPath
+{
+ MacOsPath(const std::string& application_name);
+
+ /**
+ * \brief Return path to the local directory.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getUserPath() const;
+
+ /**
+ * \brief Return path to the global (system) directory.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getGlobalPath() const;
+
+ /**
+ * \brief Return path to the runtime directory which is the
+ * place where an application was started.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getLocalPath() const;
+
+ /**
+ * \brief
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getCachePath() const;
+
+ /**
+ * \brief
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getGlobalDataPath() const;
+
+ boost::filesystem::path getInstallPath() const;
+
+ std::string mName;
+};
+
+} /* namespace Files */
+
+#endif /* defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__) */
+
+#endif /* COMPONENTS_FILES_MACOSPATH_H */
diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp
new file mode 100644
index 0000000000..347de96a6a
--- /dev/null
+++ b/components/files/multidircollection.cpp
@@ -0,0 +1,112 @@
+
+#include "multidircollection.hpp"
+
+#include <cctype>
+
+#include <algorithm>
+#include <stdexcept>
+#include <iostream>
+
+#include <boost/filesystem.hpp>
+
+namespace Files
+{
+ struct NameEqual
+ {
+ bool mStrict;
+
+ NameEqual (bool strict) : mStrict (strict) {}
+
+ bool operator() (const std::string& left, const std::string& right) const
+ {
+ if (mStrict)
+ return left==right;
+
+ std::size_t len = left.length();
+
+ if (len!=right.length())
+ return false;
+
+ for (std::size_t i=0; i<len; ++i)
+ {
+ char l = std::tolower (left[i]);
+ char r = std::tolower (right[i]);
+
+ if (l!=r)
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ MultiDirCollection::MultiDirCollection(const Files::PathContainer& directories,
+ const std::string& extension, bool foldCase)
+ : mFiles (NameLess (!foldCase))
+ {
+ NameEqual equal (!foldCase);
+
+ for (PathContainer::const_iterator iter = directories.begin();
+ iter!=directories.end(); ++iter)
+ {
+ if (!boost::filesystem::is_directory(*iter))
+ {
+ std::cout << "Skipping invalid directory: " << (*iter).string() << std::endl;
+ continue;
+ }
+
+ for (boost::filesystem::directory_iterator dirIter(*iter);
+ dirIter != boost::filesystem::directory_iterator(); ++dirIter)
+ {
+ boost::filesystem::path path = *dirIter;
+
+ if (!equal (extension, boost::filesystem::path (path.extension()).string()))
+ continue;
+
+ std::string filename = boost::filesystem::path (path.filename()).string();
+
+ TIter result = mFiles.find (filename);
+
+ if (result==mFiles.end())
+ {
+ mFiles.insert (std::make_pair (filename, path));
+ }
+ else if (result->first==filename)
+ {
+ mFiles[filename] = path;
+ }
+ else
+ {
+ // handle case folding
+ mFiles.erase (result->first);
+ mFiles.insert (std::make_pair (filename, path));
+ }
+ }
+ }
+ }
+
+ boost::filesystem::path MultiDirCollection::getPath (const std::string& file) const
+ {
+ TIter iter = mFiles.find (file);
+
+ if (iter==mFiles.end())
+ throw std::runtime_error ("file " + file + " not found");
+
+ return iter->second;
+ }
+
+ bool MultiDirCollection::doesExist (const std::string& file) const
+ {
+ return mFiles.find (file)!=mFiles.end();
+ }
+
+ MultiDirCollection::TIter MultiDirCollection::begin() const
+ {
+ return mFiles.begin();
+ }
+
+ MultiDirCollection::TIter MultiDirCollection::end() const
+ {
+ return mFiles.end();
+ }
+}
diff --git a/components/files/multidircollection.hpp b/components/files/multidircollection.hpp
new file mode 100644
index 0000000000..3b420d677d
--- /dev/null
+++ b/components/files/multidircollection.hpp
@@ -0,0 +1,88 @@
+#ifndef COMPONENTS_FILES_MULTIDIRSOLLECTION_HPP
+#define COMPONENTS_FILES_MULTIDIRSOLLECTION_HPP
+
+#include <map>
+#include <vector>
+#include <string>
+#include <locale>
+#include <cctype>
+
+#include <boost/filesystem/path.hpp>
+
+namespace Files
+{
+ typedef std::vector<boost::filesystem::path> PathContainer;
+
+ struct NameLess
+ {
+ bool mStrict;
+
+ NameLess (bool strict) : mStrict (strict) {}
+
+ bool operator() (const std::string& left, const std::string& right) const
+ {
+ if (mStrict)
+ return left<right;
+
+ std::size_t min = std::min (left.length(), right.length());
+ std::locale loc;
+
+ for (std::size_t i=0; i<min; ++i)
+ {
+ char l = std::tolower (left[i], loc);
+ char r = std::tolower (right[i], loc);
+
+ if (l<r)
+ return true;
+ if (l>r)
+ return false;
+ }
+
+ return left.length()<right.length();
+ }
+ };
+
+ /// \brief File collection across several directories
+ ///
+ /// This class lists all files with one specific extensions within one or more
+ /// directories. If the same file appears more than once, the file in the directory
+ /// with the higher priority is used.
+ class MultiDirCollection
+ {
+ public:
+
+ typedef std::map<std::string, boost::filesystem::path, NameLess> TContainer;
+ typedef TContainer::const_iterator TIter;
+
+ private:
+
+ TContainer mFiles;
+
+ public:
+
+ MultiDirCollection (const Files::PathContainer& directories,
+ const std::string& extension, bool foldCase);
+ ///< Directories are listed with increasing priority.
+ /// \param extension The extension that should be listed in this collection. Must
+ /// contain the leading dot.
+ /// \param foldCase Ignore filename case
+
+ boost::filesystem::path getPath (const std::string& file) const;
+ ///< Return full path (including filename) of \a file.
+ ///
+ /// If the file does not exist, an exception is thrown. \a file must include
+ /// the extension.
+
+ bool doesExist (const std::string& file) const;
+ ///< \return Does a file with the given name exist?
+
+ TIter begin() const;
+ ///< Return iterator pointing to the first file.
+
+ TIter end() const;
+ ///< Return iterator pointing past the last file.
+
+ };
+}
+
+#endif
diff --git a/components/files/ogreplugin.cpp b/components/files/ogreplugin.cpp
new file mode 100644
index 0000000000..c319f77589
--- /dev/null
+++ b/components/files/ogreplugin.cpp
@@ -0,0 +1,51 @@
+#include "ogreplugin.hpp"
+
+#include <OgrePrerequisites.h>
+#include <OgreRoot.h>
+
+namespace Files {
+
+bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) {
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ std::ostringstream verStream;
+ verStream << "." << OGRE_VERSION_MAJOR << "." << OGRE_VERSION_MINOR << "." << OGRE_VERSION_PATCH;
+ pluginName = pluginName + verStream.str();
+#endif
+
+ std::string pluginExt;
+#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ pluginExt = ".dll";
+#endif
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+ pluginExt = ".dylib";
+#endif
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+ pluginExt = ".so";
+#endif
+
+ // Append plugin suffix if debugging.
+ std::string pluginPath;
+#if defined(DEBUG)
+ pluginPath = pluginDir + "/" + pluginName + OGRE_PLUGIN_DEBUG_SUFFIX + pluginExt;
+ if (boost::filesystem::exists(pluginPath)) {
+ ogreRoot.loadPlugin(pluginPath);
+ return true;
+ }
+ else {
+#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ return false;
+#endif //OGRE_PLATFORM == OGRE_PLATFORM_WIN32
+ }
+#endif //defined(DEBUG)
+
+ pluginPath = pluginDir + "/" + pluginName + pluginExt;
+ if (boost::filesystem::exists(pluginPath)) {
+ ogreRoot.loadPlugin(pluginPath);
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+} \ No newline at end of file
diff --git a/components/files/ogreplugin.hpp b/components/files/ogreplugin.hpp
new file mode 100644
index 0000000000..6fcf613768
--- /dev/null
+++ b/components/files/ogreplugin.hpp
@@ -0,0 +1,42 @@
+#ifndef COMPONENTS_FILES_OGREPLUGIN_H
+#define COMPONENTS_FILES_OGREPLUGIN_H
+
+#include <string>
+
+#include <boost/filesystem.hpp>
+#include <boost/version.hpp>
+
+namespace Ogre {
+ class Root;
+}
+
+#if (BOOST_VERSION <= 104500)
+namespace boost {
+namespace filesystem {
+inline path absolute(const path& p, const path& base=current_path()) {
+ // call obsolete version of this function on older boost
+ return complete(p, base);
+}
+}
+}
+#endif /* (BOOST_VERSION <= 104300) */
+
+/**
+ * \namespace Files
+ */
+namespace Files {
+
+/**
+ * \brief Loads Ogre plugin with given name.
+ *
+ * \param pluginDir absolute path to plugins
+ * \param pluginName plugin name, for example "RenderSystem_GL"
+ * \param ogreRoot Ogre::Root instance
+ *
+ * \return whether plugin was located or not
+ */
+bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot);
+
+}
+
+#endif /* COMPONENTS_FILES_OGREPLUGIN_H */
diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp
new file mode 100644
index 0000000000..e8f1a2b08f
--- /dev/null
+++ b/components/files/windowspath.cpp
@@ -0,0 +1,108 @@
+#include "windowspath.hpp"
+
+#if defined(_WIN32) || defined(__WINDOWS__)
+
+#include <cstring>
+
+#include <windows.h>
+#include <shlobj.h>
+#include <Shlwapi.h>
+
+#pragma comment(lib, "Shlwapi.lib")
+
+/**
+ * FIXME: Someone with Windows system should check this and correct if necessary
+ */
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+WindowsPath::WindowsPath(const std::string& application_name)
+ : mName(application_name)
+{
+}
+
+boost::filesystem::path WindowsPath::getUserPath() const
+{
+ boost::filesystem::path userPath(".");
+
+ TCHAR path[MAX_PATH];
+ memset(path, 0, sizeof(path));
+
+ if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path)))
+ {
+ PathAppend(path, TEXT("My Games"));
+ userPath = boost::filesystem::path(path);
+ }
+
+ return userPath / mName;
+}
+
+boost::filesystem::path WindowsPath::getGlobalPath() const
+{
+ boost::filesystem::path globalPath(".");
+
+ TCHAR path[MAX_PATH];
+ memset(path, 0, sizeof(path));
+
+ if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, 0, path)))
+ {
+ globalPath = boost::filesystem::path(path);
+ }
+
+ return globalPath / mName;
+}
+
+boost::filesystem::path WindowsPath::getLocalPath() const
+{
+ return boost::filesystem::path("./");
+}
+
+boost::filesystem::path WindowsPath::getGlobalDataPath() const
+{
+ return getGlobalPath();
+}
+
+boost::filesystem::path WindowsPath::getCachePath() const
+{
+ return getUserPath() / "cache";
+}
+
+boost::filesystem::path WindowsPath::getInstallPath() const
+{
+ boost::filesystem::path installPath("");
+
+ HKEY hKey;
+
+ BOOL f64 = FALSE;
+ LPCTSTR regkey;
+ if ((IsWow64Process(GetCurrentProcess(), &f64) && f64) || sizeof(void*) == 8)
+ {
+ regkey = "SOFTWARE\\Wow6432Node\\Bethesda Softworks\\Morrowind";
+ }
+ else
+ {
+ regkey = "SOFTWARE\\Bethesda Softworks\\Morrowind";
+ }
+
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(regkey), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
+ {
+ //Key existed, let's try to read the install dir
+ std::vector<char> buf(512);
+ int len = 512;
+
+ if (RegQueryValueEx(hKey, TEXT("Installed Path"), NULL, NULL, (LPBYTE)&buf[0], (LPDWORD)&len) == ERROR_SUCCESS)
+ {
+ installPath = &buf[0];
+ }
+ }
+
+ return installPath;
+}
+
+} /* namespace Files */
+
+#endif /* defined(_WIN32) || defined(__WINDOWS__) */
diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp
new file mode 100644
index 0000000000..6044b67c21
--- /dev/null
+++ b/components/files/windowspath.hpp
@@ -0,0 +1,77 @@
+#ifndef COMPONENTS_FILES_WINDOWSPATH_HPP
+#define COMPONENTS_FILES_WINDOWSPATH_HPP
+
+#if defined(_WIN32) || defined(__WINDOWS__)
+
+#include <boost/filesystem.hpp>
+
+/**
+ * \namespace Files
+ */
+namespace Files
+{
+
+/**
+ * \struct WindowsPath
+ */
+struct WindowsPath
+{
+ /**
+ * \brief WindowsPath constructor.
+ *
+ * \param [in] application_name - The name of the application.
+ */
+ WindowsPath(const std::string& application_name);
+
+ /**
+ * \brief Returns user path i.e.:
+ * "X:\Documents And Settings\<User name>\My Documents\My Games\"
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getUserPath() const;
+
+ /**
+ * \brief Returns "X:\Program Files\"
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getGlobalPath() const;
+
+ /**
+ * \brief Return local path which is a location where
+ * an application was started
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getLocalPath() const;
+
+ /**
+ * \brief
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getCachePath() const;
+
+ /**
+ * \brief Return same path like getGlobalPath
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getGlobalDataPath() const;
+
+ /**
+ * \brief Gets the path of the installed Morrowind version if there is one.
+ *
+ * \return boost::filesystem::path
+ */
+ boost::filesystem::path getInstallPath() const;
+
+ std::string mName;
+};
+
+} /* namespace Files */
+
+#endif /* defined(_WIN32) || defined(__WINDOWS__) */
+
+#endif /* COMPONENTS_FILES_WINDOWSPATH_HPP */
diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp
new file mode 100644
index 0000000000..bdba7b6af9
--- /dev/null
+++ b/components/interpreter/context.hpp
@@ -0,0 +1,114 @@
+#ifndef INTERPRETER_CONTEXT_H_INCLUDED
+#define INTERPRETER_CONTEXT_H_INCLUDED
+
+#include <string>
+#include <vector>
+
+namespace Interpreter
+{
+ class Context
+ {
+ public:
+
+ virtual ~Context() {}
+
+ virtual int getLocalShort (int index) const = 0;
+
+ virtual int getLocalLong (int index) const = 0;
+
+ virtual float getLocalFloat (int index) const = 0;
+
+ virtual void setLocalShort (int index, int value) = 0;
+
+ virtual void setLocalLong (int index, int value) = 0;
+
+ virtual void setLocalFloat (int index, float value) = 0;
+
+ virtual void messageBox (const std::string& message,
+ const std::vector<std::string>& buttons) = 0;
+
+ void messageBox (const std::string& message)
+ {
+ std::vector<std::string> empty;
+ messageBox (message, empty);
+ }
+
+ virtual void report (const std::string& message) = 0;
+
+ virtual bool menuMode() = 0;
+
+ virtual int getGlobalShort (const std::string& name) const = 0;
+
+ virtual int getGlobalLong (const std::string& name) const = 0;
+
+ virtual float getGlobalFloat (const std::string& name) const = 0;
+
+ virtual void setGlobalShort (const std::string& name, int value) = 0;
+
+ virtual void setGlobalLong (const std::string& name, int value) = 0;
+
+ virtual void setGlobalFloat (const std::string& name, float value) = 0;
+
+ virtual std::vector<std::string> getGlobals () const = 0;
+
+ virtual char getGlobalType (const std::string& name) const = 0;
+
+ virtual std::string getActionBinding(const std::string& action) const = 0;
+
+ virtual std::string getNPCName() const = 0;
+
+ virtual std::string getNPCRace() const = 0;
+
+ virtual std::string getNPCClass() const = 0;
+
+ virtual std::string getNPCFaction() const = 0;
+
+ virtual std::string getNPCRank() const = 0;
+
+ virtual std::string getPCName() const = 0;
+
+ virtual std::string getPCRace() const = 0;
+
+ virtual std::string getPCClass() const = 0;
+
+ virtual std::string getPCRank() const = 0;
+
+ virtual std::string getPCNextRank() const = 0;
+
+ virtual int getPCBounty() const = 0;
+
+ virtual std::string getCurrentCellName() const = 0;
+
+ virtual bool isScriptRunning (const std::string& name) const = 0;
+
+ virtual void startScript (const std::string& name) = 0;
+
+ virtual void stopScript (const std::string& name) = 0;
+
+ virtual float getDistance (const std::string& name, const std::string& id = "") const
+ = 0;
+
+ virtual float getSecondsPassed() const = 0;
+
+ virtual bool isDisabled (const std::string& id = "") const = 0;
+
+ virtual void enable (const std::string& id = "") = 0;
+
+ virtual void disable (const std::string& id = "") = 0;
+
+ virtual int getMemberShort (const std::string& id, const std::string& name) const = 0;
+
+ virtual int getMemberLong (const std::string& id, const std::string& name) const = 0;
+
+ virtual float getMemberFloat (const std::string& id, const std::string& name) const = 0;
+
+ virtual void setMemberShort (const std::string& id, const std::string& name, int value) = 0;
+
+ virtual void setMemberLong (const std::string& id, const std::string& name, int value) = 0;
+
+ virtual void setMemberFloat (const std::string& id, const std::string& name, float value)
+ = 0;
+ };
+}
+
+#endif
diff --git a/components/interpreter/controlopcodes.hpp b/components/interpreter/controlopcodes.hpp
new file mode 100644
index 0000000000..caa7559890
--- /dev/null
+++ b/components/interpreter/controlopcodes.hpp
@@ -0,0 +1,76 @@
+#ifndef INTERPRETER_CONTROLOPCODES_H_INCLUDED
+#define INTERPRETER_CONTROLOPCODES_H_INCLUDED
+
+#include <stdexcept>
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+
+namespace Interpreter
+{
+ class OpReturn : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ runtime.setPC (-1);
+ }
+ };
+
+ class OpSkipZero : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ runtime.pop();
+
+ if (data==0)
+ runtime.setPC (runtime.getPC()+1);
+ }
+ };
+
+ class OpSkipNonZero : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ runtime.pop();
+
+ if (data!=0)
+ runtime.setPC (runtime.getPC()+1);
+ }
+ };
+
+ class OpJumpForward : public Opcode1
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg0)
+ {
+ if (arg0==0)
+ throw std::logic_error ("infinite loop");
+
+ runtime.setPC (runtime.getPC()+arg0-1);
+ }
+ };
+
+ class OpJumpBackward : public Opcode1
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg0)
+ {
+ if (arg0==0)
+ throw std::logic_error ("infinite loop");
+
+ runtime.setPC (runtime.getPC()-arg0-1);
+ }
+ };
+}
+
+#endif
diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp
new file mode 100644
index 0000000000..5774c96aec
--- /dev/null
+++ b/components/interpreter/defines.cpp
@@ -0,0 +1,208 @@
+#include "defines.hpp"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace Interpreter{
+
+ bool Check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start){
+ bool retval = str.find(escword) == 0;
+ if(retval){
+ (*i) += escword.length();
+ (*start) = (*i) + 1;
+ }
+ return retval;
+ }
+
+ std::vector<std::string> globals;
+
+ bool longerStr(const std::string& a, const std::string& b){
+ return a.length() > b.length();
+ }
+
+ std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context){
+ unsigned int start = 0;
+ std::ostringstream retval;
+ for(unsigned int i = 0; i < text.length(); i++){
+ if(text[i] == eschar){
+ retval << text.substr(start, i - start);
+ std::string temp = text.substr(i+1, 100);
+ transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
+
+ bool found;
+
+ if( (found = Check(temp, "actionslideright", &i, &start))){
+ retval << context.getActionBinding("#{sRight}");
+ }
+ else if((found = Check(temp, "actionreadymagic", &i, &start))){
+ retval << context.getActionBinding("#{sReady_Magic}");
+ }
+ else if((found = Check(temp, "actionprevweapon", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
+ }
+ else if((found = Check(temp, "actionnextweapon", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_PREV_WEAPON";
+ }
+ else if((found = Check(temp, "actiontogglerun", &i, &start))){
+ retval << context.getActionBinding("#{sAuto_Run}");
+ }
+ else if((found = Check(temp, "actionslideleft", &i, &start))){
+ retval << context.getActionBinding("#{sLeft}");
+ }
+ else if((found = Check(temp, "actionreadyitem", &i, &start))){
+ retval << context.getActionBinding("#{sReady_Weapon}");
+ }
+ else if((found = Check(temp, "actionprevspell", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_PREV_SPELL";
+ }
+ else if((found = Check(temp, "actionnextspell", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_NEXT_SPELL";
+ }
+ else if((found = Check(temp, "actionrestmenu", &i, &start))){
+ retval << context.getActionBinding("#{sRestKey}");
+ }
+ else if((found = Check(temp, "actionmenumode", &i, &start))){
+ retval << context.getActionBinding("#{sJournal}");
+ }
+ else if((found = Check(temp, "actionactivate", &i, &start))){
+ retval << context.getActionBinding("#{sActivate}");
+ }
+ else if((found = Check(temp, "actionjournal", &i, &start))){
+ retval << context.getActionBinding("#{sJournal}");
+ }
+ else if((found = Check(temp, "actionforward", &i, &start))){
+ retval << context.getActionBinding("#{sForward}");
+ }
+ else if((found = Check(temp, "pccrimelevel", &i, &start))){
+ retval << context.getPCBounty();
+ }
+ else if((found = Check(temp, "actioncrouch", &i, &start))){
+ retval << context.getActionBinding("#{sCrouch_Sneak}");
+ }
+ else if((found = Check(temp, "actionjump", &i, &start))){
+ retval << context.getActionBinding("#{sJump}");
+ }
+ else if((found = Check(temp, "actionback", &i, &start))){
+ retval << context.getActionBinding("#{sBack}");
+ }
+ else if((found = Check(temp, "actionuse", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_USE";
+ }
+ else if((found = Check(temp, "actionrun", &i, &start))){
+ retval << "PLACEHOLDER_ACTION_RUN";
+ }
+ else if((found = Check(temp, "pcclass", &i, &start))){
+ retval << context.getPCClass();
+ }
+ else if((found = Check(temp, "pcrace", &i, &start))){
+ retval << context.getPCRace();
+ }
+ else if((found = Check(temp, "pcname", &i, &start))){
+ retval << context.getPCName();
+ }
+ else if((found = Check(temp, "cell", &i, &start))){
+ retval << context.getCurrentCellName();
+ }
+
+ else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox
+ if( (found = Check(temp, "faction", &i, &start))){
+ retval << context.getNPCFaction();
+ }
+ else if((found = Check(temp, "nextpcrank", &i, &start))){
+ retval << context.getPCNextRank();
+ }
+ else if((found = Check(temp, "pcnextrank", &i, &start))){
+ retval << context.getPCNextRank();
+ }
+ else if((found = Check(temp, "pcrank", &i, &start))){
+ retval << context.getPCRank();
+ }
+ else if((found = Check(temp, "rank", &i, &start))){
+ retval << context.getNPCRank();
+ }
+
+ else if((found = Check(temp, "class", &i, &start))){
+ retval << context.getNPCClass();
+ }
+ else if((found = Check(temp, "race", &i, &start))){
+ retval << context.getNPCRace();
+ }
+ else if((found = Check(temp, "name", &i, &start))){
+ retval << context.getNPCName();
+ }
+ }
+ else { // In messagebox or book, not dialogue
+
+ /* empty outside dialogue */
+ if( (found = Check(temp, "faction", &i, &start)));
+ else if((found = Check(temp, "nextpcrank", &i, &start)));
+ else if((found = Check(temp, "pcnextrank", &i, &start)));
+ else if((found = Check(temp, "pcrank", &i, &start)));
+ else if((found = Check(temp, "rank", &i, &start)));
+
+ /* uses pc in messageboxes */
+ else if((found = Check(temp, "class", &i, &start))){
+ retval << context.getPCClass();
+ }
+ else if((found = Check(temp, "race", &i, &start))){
+ retval << context.getPCRace();
+ }
+ else if((found = Check(temp, "name", &i, &start))){
+ retval << context.getPCName();
+ }
+ }
+
+ /* Not a builtin, try global variables */
+ if(!found){
+ /* if list of globals is empty, grab it and sort it by descending string length */
+ if(globals.empty()){
+ globals = context.getGlobals();
+ sort(globals.begin(), globals.end(), longerStr);
+ }
+
+ for(unsigned int j = 0; j < globals.size(); j++){
+ if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name
+ std::string temp = text.substr(i+1, globals[j].length());
+ transform(temp.begin(), temp.end(), temp.begin(), ::tolower);
+ }
+
+ if((found = Check(temp, globals[j], &i, &start))){
+ char type = context.getGlobalType(globals[j]);
+
+ switch(type){
+ case 's': retval << context.getGlobalShort(globals[j]); break;
+ case 'l': retval << context.getGlobalLong(globals[j]); break;
+ case 'f': retval << context.getGlobalFloat(globals[j]); break;
+ }
+ break;
+ }
+ }
+ }
+
+ /* Not found */
+ if(!found){
+ /* leave unmodified */
+ i += 1;
+ start = i;
+ retval << eschar;
+ }
+ }
+ }
+ retval << text.substr(start, text.length() - start);
+ return retval.str ();
+ }
+
+ std::string fixDefinesDialog(std::string text, Context& context){
+ return fixDefinesReal(text, '%', false, context);
+ }
+
+ std::string fixDefinesMsgBox(std::string text, Context& context){
+ return fixDefinesReal(text, '^', false, context);
+ }
+
+ std::string fixDefinesBook(std::string text, Context& context){
+ return fixDefinesReal(text, '%', true, context);
+ }
+}
diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp
new file mode 100644
index 0000000000..00c4386b88
--- /dev/null
+++ b/components/interpreter/defines.hpp
@@ -0,0 +1,13 @@
+#ifndef INTERPRETER_DEFINES_H_INCLUDED
+#define INTERPRETER_DEFINES_H_INCLUDED
+
+#include <string>
+#include "context.hpp"
+
+namespace Interpreter{
+ std::string fixDefinesDialog(std::string text, Context& context);
+ std::string fixDefinesMsgBox(std::string text, Context& context);
+ std::string fixDefinesBook(std::string text, Context& context);
+}
+
+#endif
diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt
new file mode 100644
index 0000000000..91e0c060e7
--- /dev/null
+++ b/components/interpreter/docs/vmformat.txt
@@ -0,0 +1,131 @@
+Note: a word is considered to be 32 bit long.
+
+Header (4 words):
+word: number of words in code block
+word: number of words in integer literal block
+word: number of words in float literal block
+word: number of words in string literal block
+
+Body (variable length):
+code block
+integer literal block (contains a collection of 1 word long integers)
+float literal block (contains a collection of 1 word long floating point numbers)
+string literal block (contains a collection of strings of variable length, word-padded)
+
+Code bit-patterns:
+
+3322222222221111111111
+10987654321098765432109876543210
+00ccccccAAAAAAAAAAAAAAAAAAAAAAAA segment 0: 64 opcodes, 1 24-bit argument
+01ccccccAAAAAAAAAAAABBBBBBBBBBBB segment 1: 64 opcodes, 2 12-bit arguments
+10ccccccccccAAAAAAAAAAAAAAAAAAAA segment 2: 1024 opcodes, 1 20-bit argument
+110000ccccccccccccccccccAAAAAAAA segment 3: 262144 opcodes, 1 8-bit argument
+110001ccccccccccAAAAAAAABBBBBBBB segment 4: 1024 opcodes, 2 8-bit arguments
+110010cccccccccccccccccccccccccc segment 5: 67108864 opcodes, no arguments
+other bit-patterns reserved
+
+legent:
+c: code
+A: argument 0
+B: argument 1
+
+Segment 0:
+op 0: push arg0
+op 1: move pc ahead by arg0
+op 2: move pc back by arg0
+opcodes 3-31 unused
+opcodes 32-63 reserved for extensions
+
+Segment 1:
+opcodes 0-31 unused
+opcodes 32-63 reserved for extensions
+
+Segment 2:
+opcodes 0-511 unused
+opcodes 512-1023 reserved for extensions
+
+Segment 3:
+op 0: show message box with message string literal index in stack[0];
+ buttons (if any) in stack[arg0]..stack[1];
+ additional arguments (if any) in stack[arg0+n]..stack[arg0+1];
+ n is determined according to the message string
+ all arguments are removed from stack
+opcodes 1-131071 unused
+opcodes 131072-262143 reserved for extensions
+
+Segment 4:
+opcodes 0-511 unused
+opcodes 512-1023 reserved for extensions
+
+Segment 5:
+op 0: store stack[0] in local short stack[1] and pop twice
+op 1: store stack[0] in local long stack[1] and pop twice
+op 2: store stack[0] in local float stack[1] and pop twice
+op 3: convert stack[0] from integer to float
+op 4: replace stack[0] with integer literal index stack[0]
+op 5: replace stack[0] with float literal index stack[0]
+op 6: convert stack[0] from float to integer
+op 7: invert sign of int value stack[0]
+op 8: invert sign of float value stack[0]
+op 9: add (integer) stack[0] to stack[1], pop twice, push result
+op 10: add (float) stack[0] to stack[1], pop twice, push result
+op 11: sub (integer) stack[1] from stack[0], pop twice, push result
+op 12: sub (float) stack[1] from stack[0], pop twice, push result
+op 13: mul (integer) stack[0] with stack[1], pop twice, push result
+op 14: mul (float) stack[0] with stack[1], pop twice, push result
+op 15: div (integer) stack[1] by stack[0], pop twice, push result
+op 16: div (float) stack[1] by stack[0], pop twice, push result
+op 17: convert stack[1] from integer to float
+op 18: convert stack[1] from float to integer
+op 19: take square root of stack[0] (float)
+op 20: return
+op 21: replace stack[0] with local short stack[0]
+op 22: replace stack[0] with local long stack[0]
+op 23: replace stack[0] with local float stack[0]
+op 24: skip next instruction if stack[0]==0; pop
+op 25: skip next instruction if stack[0]!=0; pop
+op 26: compare (intger) stack[1] with stack[0]; pop twice; push 1 if equal, 0 else
+op 27: compare (intger) stack[1] with stack[0]; pop twice; push 1 if no equal, 0 else
+op 28: compare (intger) stack[1] with stack[0]; pop twice; push 1 if lesser than, 0 else
+op 29: compare (intger) stack[1] with stack[0]; pop twice; push 1 if lesser or equal, 0 else
+op 30: compare (intger) stack[1] with stack[0]; pop twice; push 1 if greater than, 0 else
+op 31: compare (intger) stack[1] with stack[0]; pop twice; push 1 if greater or equal, 0 else
+op 32: compare (float) stack[1] with stack[0]; pop twice; push 1 if equal, 0 else
+op 33: compare (float) stack[1] with stack[0]; pop twice; push 1 if no equal, 0 else
+op 34: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser than, 0 else
+op 35: compare (float) stack[1] with stack[0]; pop twice; push 1 if lesser or equal, 0 else
+op 36: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater than, 0 else
+op 37: compare (float) stack[1] with stack[0]; pop twice; push 1 if greater or equal, 0 else
+op 38: push 1 if game is in menu mode, 0 else
+op 39: store stack[0] in global short stack[1] and pop twice
+op 40: store stack[0] in global long stack[1] and pop twice
+op 41: store stack[0] in global float stack[1] and pop twice
+op 42: replace stack[0] with global short stack[0]
+op 43: replace stack[0] with global long stack[0]
+op 44: replace stack[0] with global float stack[0]
+op 45: replace stack[0] with a random integer value in the range [0, stack[0]-1]
+op 46: replace stack[0] with 1, if global script stack[0] is running, 0 else
+op 47: start script stack[0] and pop
+op 48: stop script stack[0] and pop
+op 49: replace stack[0] with distance between implicit reference and a reference of ID stack[0]
+op 50: push frame duration (float)
+op 51: enable implicit reference
+op 52: disable implicit reference
+op 53: push 1, if implicit reference is disabled, 0 else
+op 54: explicit reference = stack[0]; pop; enable explicit reference
+op 55: explicit reference = stack[0]; pop; disable explicit reference
+op 56: explicit reference = stack[0]; pop; push 1, if explicit reference is disabled, 0 else
+op 57: explicit reference = stack[0]; pop;
+ replace stack[0] with distance between explicit reference and a reference of ID stack[0]
+op 58: report string literal index in stack[0];
+ additional arguments (if any) in stack[n]..stack[1];
+ n is determined according to the message string
+ all arguments are removed from stack
+op 59: store stack[0] in member short stack[2] of object with ID stack[1]
+op 60: store stack[0] in member long stack[2] of object with ID stack[1]
+op 61: store stack[0] in member float stack[2] of object with ID stack[1]
+op 62: replace stack[0] with member short stack[1] of object with ID stack[0]
+op 63: replace stack[0] with member short stack[1] of object with ID stack[0]
+op 64: replace stack[0] with member short stack[1] of object with ID stack[0]
+opcodes 65-33554431 unused
+opcodes 33554432-67108863 reserved for extensions
diff --git a/components/interpreter/genericopcodes.hpp b/components/interpreter/genericopcodes.hpp
new file mode 100644
index 0000000000..44ef3fc6bf
--- /dev/null
+++ b/components/interpreter/genericopcodes.hpp
@@ -0,0 +1,93 @@
+#ifndef INTERPRETER_GENERICOPCODES_H_INCLUDED
+#define INTERPRETER_GENERICOPCODES_H_INCLUDED
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+
+namespace Interpreter
+{
+ class OpPushInt : public Opcode1
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg0)
+ {
+ runtime.push (static_cast<Type_Integer> (arg0));
+ }
+ };
+
+ class OpIntToFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ Type_Float floatValue = static_cast<Type_Float> (data);
+ runtime[0].mFloat = floatValue;
+ }
+ };
+
+ class OpFloatToInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[0].mFloat;
+ Type_Integer integerValue = static_cast<Type_Integer> (data);
+ runtime[0].mInteger = integerValue;
+ }
+ };
+
+ class OpNegateInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ data = -data;
+ runtime[0].mInteger = data;
+ }
+ };
+
+ class OpNegateFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[0].mFloat;
+ data = -data;
+ runtime[0].mFloat = data;
+ }
+ };
+
+ class OpIntToFloat1 : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[1].mInteger;
+ Type_Float floatValue = static_cast<Type_Float> (data);
+ runtime[1].mFloat = floatValue;
+ }
+ };
+
+ class OpFloatToInt1 : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[1].mFloat;
+ Type_Integer integerValue = static_cast<Type_Integer> (data);
+ runtime[1].mInteger = integerValue;
+ }
+ };
+}
+
+#endif
+
diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp
new file mode 100644
index 0000000000..05f71f1cca
--- /dev/null
+++ b/components/interpreter/installopcodes.cpp
@@ -0,0 +1,115 @@
+
+#include "installopcodes.hpp"
+
+#include <functional>
+
+#include "interpreter.hpp"
+#include "genericopcodes.hpp"
+#include "localopcodes.hpp"
+#include "mathopcodes.hpp"
+#include "controlopcodes.hpp"
+#include "miscopcodes.hpp"
+#include "scriptopcodes.hpp"
+#include "spatialopcodes.hpp"
+
+namespace Interpreter
+{
+ void installOpcodes (Interpreter& interpreter)
+ {
+ // generic
+ interpreter.installSegment0 (0, new OpPushInt);
+ interpreter.installSegment5 (3, new OpIntToFloat);
+ interpreter.installSegment5 (6, new OpFloatToInt);
+ interpreter.installSegment5 (7, new OpNegateInt);
+ interpreter.installSegment5 (8, new OpNegateFloat);
+ interpreter.installSegment5 (17, new OpIntToFloat1);
+ interpreter.installSegment5 (18, new OpFloatToInt1);
+
+ // local variables, global variables & literals
+ interpreter.installSegment5 (0, new OpStoreLocalShort);
+ interpreter.installSegment5 (1, new OpStoreLocalLong);
+ interpreter.installSegment5 (2, new OpStoreLocalFloat);
+ interpreter.installSegment5 (4, new OpFetchIntLiteral);
+ interpreter.installSegment5 (5, new OpFetchFloatLiteral);
+ interpreter.installSegment5 (21, new OpFetchLocalShort);
+ interpreter.installSegment5 (22, new OpFetchLocalLong);
+ interpreter.installSegment5 (23, new OpFetchLocalFloat);
+ interpreter.installSegment5 (39, new OpStoreGlobalShort);
+ interpreter.installSegment5 (40, new OpStoreGlobalLong);
+ interpreter.installSegment5 (41, new OpStoreGlobalFloat);
+ interpreter.installSegment5 (42, new OpFetchGlobalShort);
+ interpreter.installSegment5 (43, new OpFetchGlobalLong);
+ interpreter.installSegment5 (44, new OpFetchGlobalFloat);
+ interpreter.installSegment5 (59, new OpStoreMemberShort);
+ interpreter.installSegment5 (60, new OpStoreMemberLong);
+ interpreter.installSegment5 (61, new OpStoreMemberFloat);
+ interpreter.installSegment5 (62, new OpFetchMemberShort);
+ interpreter.installSegment5 (63, new OpFetchMemberLong);
+ interpreter.installSegment5 (64, new OpFetchMemberFloat);
+
+ // math
+ interpreter.installSegment5 (9, new OpAddInt<Type_Integer>);
+ interpreter.installSegment5 (10, new OpAddInt<Type_Float>);
+ interpreter.installSegment5 (11, new OpSubInt<Type_Integer>);
+ interpreter.installSegment5 (12, new OpSubInt<Type_Float>);
+ interpreter.installSegment5 (13, new OpMulInt<Type_Integer>);
+ interpreter.installSegment5 (14, new OpMulInt<Type_Float>);
+ interpreter.installSegment5 (15, new OpDivInt<Type_Integer>);
+ interpreter.installSegment5 (16, new OpDivInt<Type_Float>);
+ interpreter.installSegment5 (19, new OpSquareRoot);
+ interpreter.installSegment5 (26,
+ new OpCompare<Type_Integer, std::equal_to<Type_Integer> >);
+ interpreter.installSegment5 (27,
+ new OpCompare<Type_Integer, std::not_equal_to<Type_Integer> >);
+ interpreter.installSegment5 (28,
+ new OpCompare<Type_Integer, std::less<Type_Integer> >);
+ interpreter.installSegment5 (29,
+ new OpCompare<Type_Integer, std::less_equal<Type_Integer> >);
+ interpreter.installSegment5 (30,
+ new OpCompare<Type_Integer, std::greater<Type_Integer> >);
+ interpreter.installSegment5 (31,
+ new OpCompare<Type_Integer, std::greater_equal<Type_Integer> >);
+
+ interpreter.installSegment5 (32,
+ new OpCompare<Type_Float, std::equal_to<Type_Float> >);
+ interpreter.installSegment5 (33,
+ new OpCompare<Type_Float, std::not_equal_to<Type_Float> >);
+ interpreter.installSegment5 (34,
+ new OpCompare<Type_Float, std::less<Type_Float> >);
+ interpreter.installSegment5 (35,
+ new OpCompare<Type_Float, std::less_equal<Type_Float> >);
+ interpreter.installSegment5 (36,
+ new OpCompare<Type_Float, std::greater<Type_Float> >);
+ interpreter.installSegment5 (37,
+ new OpCompare<Type_Float, std::greater_equal<Type_Float> >);
+
+ // control structures
+ interpreter.installSegment5 (20, new OpReturn);
+ interpreter.installSegment5 (24, new OpSkipZero);
+ interpreter.installSegment5 (25, new OpSkipNonZero);
+ interpreter.installSegment0 (1, new OpJumpForward);
+ interpreter.installSegment0 (2, new OpJumpBackward);
+
+ // misc
+ interpreter.installSegment3 (0, new OpMessageBox);
+ interpreter.installSegment5 (38, new OpMenuMode);
+ interpreter.installSegment5 (45, new OpRandom);
+ interpreter.installSegment5 (50, new OpGetSecondsPassed);
+ interpreter.installSegment5 (51, new OpEnable);
+ interpreter.installSegment5 (52, new OpDisable);
+ interpreter.installSegment5 (53, new OpGetDisabled);
+ interpreter.installSegment5 (54, new OpEnableExplicit);
+ interpreter.installSegment5 (55, new OpDisableExplicit);
+ interpreter.installSegment5 (56, new OpGetDisabledExplicit);
+ interpreter.installSegment5 (58, new OpReport);
+
+ // script control
+ interpreter.installSegment5 (46, new OpScriptRunning);
+ interpreter.installSegment5 (47, new OpStartScript);
+ interpreter.installSegment5 (48, new OpStopScript);
+
+ // spacial
+ interpreter.installSegment5 (49, new OpGetDistance);
+ interpreter.installSegment5 (57, new OpGetDistanceExplicit);
+ }
+}
diff --git a/components/interpreter/installopcodes.hpp b/components/interpreter/installopcodes.hpp
new file mode 100644
index 0000000000..8020490dd1
--- /dev/null
+++ b/components/interpreter/installopcodes.hpp
@@ -0,0 +1,11 @@
+#ifndef INTERPRETER_INSTALLOPCODES_H_INCLUDED
+#define INTERPRETER_INSTALLOPCODES_H_INCLUDED
+
+namespace Interpreter
+{
+ class Interpreter;
+
+ void installOpcodes (Interpreter& interpreter);
+}
+
+#endif
diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp
new file mode 100644
index 0000000000..10937e6bca
--- /dev/null
+++ b/components/interpreter/interpreter.cpp
@@ -0,0 +1,216 @@
+
+#include "interpreter.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+#include "opcodes.hpp"
+
+namespace Interpreter
+{
+ void Interpreter::execute (Type_Code code)
+ {
+ unsigned int segSpec = code>>30;
+
+ switch (segSpec)
+ {
+ case 0:
+ {
+ int opcode = code>>24;
+ unsigned int arg0 = code & 0xffffff;
+
+ std::map<int, Opcode1 *>::iterator iter = mSegment0.find (opcode);
+
+ if (iter==mSegment0.end())
+ abortUnknownCode (0, opcode);
+
+ iter->second->execute (mRuntime, arg0);
+
+ return;
+ }
+
+ case 1:
+ {
+ int opcode = (code>>24) & 0x3f;
+ unsigned int arg0 = (code>>16) & 0xfff;
+ unsigned int arg1 = code & 0xfff;
+
+ std::map<int, Opcode2 *>::iterator iter = mSegment1.find (opcode);
+
+ if (iter==mSegment1.end())
+ abortUnknownCode (1, opcode);
+
+ iter->second->execute (mRuntime, arg0, arg1);
+
+ return;
+ }
+
+ case 2:
+ {
+ int opcode = (code>>20) & 0x3ff;
+ unsigned int arg0 = code & 0xfffff;
+
+ std::map<int, Opcode1 *>::iterator iter = mSegment2.find (opcode);
+
+ if (iter==mSegment2.end())
+ abortUnknownCode (2, opcode);
+
+ iter->second->execute (mRuntime, arg0);
+
+ return;
+ }
+ }
+
+ segSpec = code>>26;
+
+ switch (segSpec)
+ {
+ case 0x30:
+ {
+ int opcode = (code>>8) & 0x3ffff;
+ unsigned int arg0 = code & 0xff;
+
+ std::map<int, Opcode1 *>::iterator iter = mSegment3.find (opcode);
+
+ if (iter==mSegment3.end())
+ abortUnknownCode (3, opcode);
+
+ iter->second->execute (mRuntime, arg0);
+
+ return;
+ }
+
+ case 0x31:
+ {
+ int opcode = (code>>16) & 0x3ff;
+ unsigned int arg0 = (code>>8) & 0xff;
+ unsigned int arg1 = code & 0xff;
+
+ std::map<int, Opcode2 *>::iterator iter = mSegment4.find (opcode);
+
+ if (iter==mSegment4.end())
+ abortUnknownCode (4, opcode);
+
+ iter->second->execute (mRuntime, arg0, arg1);
+
+ return;
+ }
+
+ case 0x32:
+ {
+ int opcode = code & 0x3ffffff;
+
+ std::map<int, Opcode0 *>::iterator iter = mSegment5.find (opcode);
+
+ if (iter==mSegment5.end())
+ abortUnknownCode (5, opcode);
+
+ iter->second->execute (mRuntime);
+
+ return;
+ }
+ }
+
+ abortUnknownSegment (code);
+ }
+
+ void Interpreter::abortUnknownCode (int segment, int opcode)
+ {
+ std::ostringstream error;
+
+ error << "unknown opcode " << opcode << " in segment " << segment;
+
+ throw std::runtime_error (error.str());
+ }
+
+ void Interpreter::abortUnknownSegment (Type_Code code)
+ {
+ std::ostringstream error;
+
+ error << "opcode outside of the allocated segment range: " << code;
+
+ throw std::runtime_error (error.str());
+ }
+
+ Interpreter::Interpreter()
+ {}
+
+ Interpreter::~Interpreter()
+ {
+ for (std::map<int, Opcode1 *>::iterator iter (mSegment0.begin());
+ iter!=mSegment0.end(); ++iter)
+ delete iter->second;
+
+ for (std::map<int, Opcode2 *>::iterator iter (mSegment1.begin());
+ iter!=mSegment1.end(); ++iter)
+ delete iter->second;
+
+ for (std::map<int, Opcode1 *>::iterator iter (mSegment2.begin());
+ iter!=mSegment2.end(); ++iter)
+ delete iter->second;
+
+ for (std::map<int, Opcode1 *>::iterator iter (mSegment3.begin());
+ iter!=mSegment3.end(); ++iter)
+ delete iter->second;
+
+ for (std::map<int, Opcode2 *>::iterator iter (mSegment4.begin());
+ iter!=mSegment4.end(); ++iter)
+ delete iter->second;
+
+ for (std::map<int, Opcode0 *>::iterator iter (mSegment5.begin());
+ iter!=mSegment5.end(); ++iter)
+ delete iter->second;
+ }
+
+ void Interpreter::installSegment0 (int code, Opcode1 *opcode)
+ {
+ mSegment0.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::installSegment1 (int code, Opcode2 *opcode)
+ {
+ mSegment1.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::installSegment2 (int code, Opcode1 *opcode)
+ {
+ mSegment2.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::installSegment3 (int code, Opcode1 *opcode)
+ {
+ mSegment3.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::installSegment4 (int code, Opcode2 *opcode)
+ {
+ mSegment4.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::installSegment5 (int code, Opcode0 *opcode)
+ {
+ mSegment5.insert (std::make_pair (code, opcode));
+ }
+
+ void Interpreter::run (const Type_Code *code, int codeSize, Context& context)
+ {
+ assert (codeSize>=4);
+
+ mRuntime.configure (code, codeSize, context);
+
+ int opcodes = static_cast<int> (code[0]);
+
+ const Type_Code *codeBlock = code + 4;
+
+ while (mRuntime.getPC()>=0 && mRuntime.getPC()<opcodes)
+ {
+ Type_Code code = codeBlock[mRuntime.getPC()];
+ mRuntime.setPC (mRuntime.getPC()+1);
+ execute (code);
+ }
+
+ mRuntime.clear();
+ }
+}
diff --git a/components/interpreter/interpreter.hpp b/components/interpreter/interpreter.hpp
new file mode 100644
index 0000000000..e1016235a0
--- /dev/null
+++ b/components/interpreter/interpreter.hpp
@@ -0,0 +1,63 @@
+#ifndef INTERPRETER_INTERPRETER_H_INCLUDED
+#define INTERPRETER_INTERPRETER_H_INCLUDED
+
+#include <map>
+
+#include "runtime.hpp"
+#include "types.hpp"
+
+namespace Interpreter
+{
+ class Opcode0;
+ class Opcode1;
+ class Opcode2;
+
+ class Interpreter
+ {
+ Runtime mRuntime;
+ std::map<int, Opcode1 *> mSegment0;
+ std::map<int, Opcode2 *> mSegment1;
+ std::map<int, Opcode1 *> mSegment2;
+ std::map<int, Opcode1 *> mSegment3;
+ std::map<int, Opcode2 *> mSegment4;
+ std::map<int, Opcode0 *> mSegment5;
+
+ // not implemented
+ Interpreter (const Interpreter&);
+ Interpreter& operator= (const Interpreter&);
+
+ void execute (Type_Code code);
+
+ void abortUnknownCode (int segment, int opcode);
+
+ void abortUnknownSegment (Type_Code code);
+
+ public:
+
+ Interpreter();
+
+ ~Interpreter();
+
+ void installSegment0 (int code, Opcode1 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void installSegment1 (int code, Opcode2 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void installSegment2 (int code, Opcode1 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void installSegment3 (int code, Opcode1 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void installSegment4 (int code, Opcode2 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void installSegment5 (int code, Opcode0 *opcode);
+ ///< ownership of \a opcode is transferred to *this.
+
+ void run (const Type_Code *code, int codeSize, Context& context);
+ };
+}
+
+#endif
diff --git a/components/interpreter/localopcodes.hpp b/components/interpreter/localopcodes.hpp
new file mode 100644
index 0000000000..731c16276d
--- /dev/null
+++ b/components/interpreter/localopcodes.hpp
@@ -0,0 +1,321 @@
+#ifndef INTERPRETER_LOCALOPCODES_H_INCLUDED
+#define INTERPRETER_LOCALOPCODES_H_INCLUDED
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+#include "context.hpp"
+
+namespace Interpreter
+{
+ class OpStoreLocalShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ int index = runtime[1].mInteger;
+
+ runtime.getContext().setLocalShort (index, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreLocalLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ int index = runtime[1].mInteger;
+
+ runtime.getContext().setLocalLong (index, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreLocalFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[0].mFloat;
+ int index = runtime[1].mInteger;
+
+ runtime.getContext().setLocalFloat (index, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpFetchIntLiteral : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer intValue = runtime.getIntegerLiteral (runtime[0].mInteger);
+ runtime[0].mInteger = intValue;
+ }
+ };
+
+ class OpFetchFloatLiteral : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float floatValue = runtime.getFloatLiteral (runtime[0].mInteger);
+ runtime[0].mFloat = floatValue;
+ }
+ };
+
+ class OpFetchLocalShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ int value = runtime.getContext().getLocalShort (index);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchLocalLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ int value = runtime.getContext().getLocalLong (index);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchLocalFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ float value = runtime.getContext().getLocalFloat (index);
+ runtime[0].mFloat = value;
+ }
+ };
+
+ class OpStoreGlobalShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ int index = runtime[1].mInteger;
+
+ std::string name = runtime.getStringLiteral (index);
+
+ runtime.getContext().setGlobalShort (name, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreGlobalLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ int index = runtime[1].mInteger;
+
+ std::string name = runtime.getStringLiteral (index);
+
+ runtime.getContext().setGlobalLong (name, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreGlobalFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[0].mFloat;
+ int index = runtime[1].mInteger;
+
+ std::string name = runtime.getStringLiteral (index);
+
+ runtime.getContext().setGlobalFloat (name, data);
+
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpFetchGlobalShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ std::string name = runtime.getStringLiteral (index);
+ Type_Integer value = runtime.getContext().getGlobalShort (name);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchGlobalLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ std::string name = runtime.getStringLiteral (index);
+ Type_Integer value = runtime.getContext().getGlobalLong (name);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchGlobalFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ std::string name = runtime.getStringLiteral (index);
+ Type_Float value = runtime.getContext().getGlobalFloat (name);
+ runtime[0].mFloat = value;
+ }
+ };
+
+ class OpStoreMemberShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ Type_Integer index = runtime[1].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[2].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+
+ runtime.getContext().setMemberShort (id, variable, data);
+
+ runtime.pop();
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreMemberLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer data = runtime[0].mInteger;
+ Type_Integer index = runtime[1].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[2].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+
+ runtime.getContext().setMemberLong (id, variable, data);
+
+ runtime.pop();
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpStoreMemberFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float data = runtime[0].mFloat;
+ Type_Integer index = runtime[1].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[2].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+
+ runtime.getContext().setMemberFloat (id, variable, data);
+
+ runtime.pop();
+ runtime.pop();
+ runtime.pop();
+ }
+ };
+
+ class OpFetchMemberShort : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer index = runtime[0].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[1].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+ runtime.pop();
+
+ int value = runtime.getContext().getMemberShort (id, variable);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchMemberLong : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer index = runtime[0].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[1].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+ runtime.pop();
+
+ int value = runtime.getContext().getMemberLong (id, variable);
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpFetchMemberFloat : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Integer index = runtime[0].mInteger;
+ std::string id = runtime.getStringLiteral (index);
+ index = runtime[1].mInteger;
+ std::string variable = runtime.getStringLiteral (index);
+ runtime.pop();
+
+ float value = runtime.getContext().getMemberFloat (id, variable);
+ runtime[0].mFloat = value;
+ }
+ };
+}
+
+#endif
diff --git a/components/interpreter/mathopcodes.hpp b/components/interpreter/mathopcodes.hpp
new file mode 100644
index 0000000000..14d5d98df8
--- /dev/null
+++ b/components/interpreter/mathopcodes.hpp
@@ -0,0 +1,112 @@
+#ifndef INTERPRETER_MATHOPCODES_H_INCLUDED
+#define INTERPRETER_MATHOPCODES_H_INCLUDED
+
+#include <stdexcept>
+#include <cmath>
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+
+namespace Interpreter
+{
+ template<typename T>
+ class OpAddInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ T result = getData<T> (runtime[1]) + getData<T> (runtime[0]);
+
+ runtime.pop();
+
+ getData<T> (runtime[0]) = result;
+ }
+ };
+
+ template<typename T>
+ class OpSubInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ T result = getData<T> (runtime[1]) - getData<T> (runtime[0]);
+
+ runtime.pop();
+
+ getData<T> (runtime[0]) = result;
+ }
+ };
+
+ template<typename T>
+ class OpMulInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ T result = getData<T> (runtime[1]) * getData<T> (runtime[0]);
+
+ runtime.pop();
+
+ getData<T> (runtime[0]) = result;
+ }
+ };
+
+ template<typename T>
+ class OpDivInt : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ T left = getData<T> (runtime[0]);
+
+ if (left==0)
+ throw std::runtime_error ("division by zero");
+
+ T result = getData<T> (runtime[1]) / left;
+
+ runtime.pop();
+
+ getData<T> (runtime[0]) = result;
+ }
+ };
+
+ class OpSquareRoot : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float value = runtime[0].mFloat;
+
+ if (value<0)
+ throw std::runtime_error (
+ "square root of negative number (we aren't that imaginary)");
+
+ value = std::sqrt (value);
+
+ runtime[0].mFloat = value;
+ }
+ };
+
+ template<typename T, typename C>
+ class OpCompare : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int result = C() (getData<T> (runtime[1]), getData<T> (runtime[0]));
+
+ runtime.pop();
+
+ runtime[0].mInteger = result;
+ }
+ };
+}
+
+#endif
+
diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp
new file mode 100644
index 0000000000..1b4c823a0e
--- /dev/null
+++ b/components/interpreter/miscopcodes.hpp
@@ -0,0 +1,242 @@
+#ifndef INTERPRETER_MISCOPCODES_H_INCLUDED
+#define INTERPRETER_MISCOPCODES_H_INCLUDED
+
+#include <cstdlib>
+#include <stdexcept>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <algorithm>
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+#include "defines.hpp"
+
+namespace Interpreter
+{
+ inline std::string formatMessage (const std::string& message, Runtime& runtime)
+ {
+ std::string formattedMessage;
+
+ for (std::size_t i=0; i<message.size(); ++i)
+ {
+ char c = message[i];
+
+ if (c!='%')
+ formattedMessage += c;
+ else
+ {
+ ++i;
+ if (i<message.size())
+ {
+ c = message[i];
+
+ if (c=='S' || c=='s')
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ formattedMessage += runtime.getStringLiteral (index);
+ }
+ else if (c=='g' || c=='G')
+ {
+ Type_Integer value = runtime[0].mInteger;
+ runtime.pop();
+
+ std::ostringstream out;
+ out << value;
+ formattedMessage += out.str();
+ }
+ else if (c=='f' || c=='F' || c=='.')
+ {
+ while (c!='f' && i<message.size())
+ {
+ ++i;
+ }
+
+ float value = runtime[0].mFloat;
+ runtime.pop();
+
+ std::ostringstream out;
+ out << value;
+ formattedMessage += out.str();
+ }
+ else if (c=='%')
+ formattedMessage += "%";
+ else
+ {
+ formattedMessage += "%";
+ formattedMessage += c;
+ }
+ }
+ }
+ }
+
+ formattedMessage = fixDefinesMsgBox(formattedMessage, runtime.getContext());
+ return formattedMessage;
+ }
+
+ class OpMessageBox : public Opcode1
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg0)
+ {
+ // message
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string message = runtime.getStringLiteral (index);
+
+ // buttons
+ std::vector<std::string> buttons;
+
+ for (std::size_t i=0; i<arg0; ++i)
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ buttons.push_back (runtime.getStringLiteral (index));
+ }
+
+ std::reverse (buttons.begin(), buttons.end());
+
+ // handle additional parameters
+ std::string formattedMessage = formatMessage (message, runtime);
+
+ runtime.getContext().messageBox (formattedMessage, buttons);
+ }
+ };
+
+ class OpReport : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ // message
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string message = runtime.getStringLiteral (index);
+
+ // handle additional parameters
+ std::string formattedMessage = formatMessage (message, runtime);
+
+ runtime.getContext().report (formattedMessage);
+ }
+ };
+
+ class OpMenuMode : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ runtime.push (runtime.getContext().menuMode());
+ }
+ };
+
+ class OpRandom : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ double r = static_cast<double> (std::rand()) / RAND_MAX; // [0, 1)
+
+ Type_Integer limit = runtime[0].mInteger;
+
+ if (limit<0)
+ throw std::runtime_error (
+ "random: argument out of range (Don't be so negative!)");
+
+ Type_Integer value = static_cast<Type_Integer> (r*limit); // [o, limit)
+
+ runtime[0].mInteger = value;
+ }
+ };
+
+ class OpGetSecondsPassed : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ Type_Float duration = runtime.getContext().getSecondsPassed();
+
+ runtime.push (duration);
+ }
+ };
+
+ class OpEnable : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ runtime.getContext().enable();
+ }
+ };
+
+ class OpDisable : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ runtime.getContext().disable();
+ }
+ };
+
+ class OpGetDisabled : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ runtime.push (runtime.getContext().isDisabled());
+ }
+ };
+
+ class OpEnableExplicit : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string id = runtime.getStringLiteral (index);
+
+ runtime.getContext().enable (id);
+ }
+ };
+
+ class OpDisableExplicit : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string id = runtime.getStringLiteral (index);
+
+ runtime.getContext().disable (id);
+ }
+ };
+
+ class OpGetDisabledExplicit : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string id = runtime.getStringLiteral (index);
+
+ runtime.push (runtime.getContext().isDisabled (id));
+ }
+ };
+
+}
+
+#endif
diff --git a/components/interpreter/opcodes.hpp b/components/interpreter/opcodes.hpp
new file mode 100644
index 0000000000..c447e1f106
--- /dev/null
+++ b/components/interpreter/opcodes.hpp
@@ -0,0 +1,40 @@
+#ifndef INTERPRETER_OPCODES_H_INCLUDED
+#define INTERPRETER_OPCODES_H_INCLUDED
+
+namespace Interpreter
+{
+ class Runtime;
+
+ /// opcode for 0 arguments
+ class Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime) = 0;
+
+ virtual ~Opcode0() {}
+ };
+
+ /// opcode for 1 argument
+ class Opcode1
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg0) = 0;
+
+ virtual ~Opcode1() {}
+ };
+
+ /// opcode for 2 arguments
+ class Opcode2
+ {
+ public:
+
+ virtual void execute (Runtime& runtime, unsigned int arg1, unsigned int arg2) = 0;
+
+ virtual ~Opcode2() {}
+ };
+
+}
+
+#endif
diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp
new file mode 100644
index 0000000000..bb0dffb87f
--- /dev/null
+++ b/components/interpreter/runtime.cpp
@@ -0,0 +1,116 @@
+
+#include "runtime.hpp"
+
+#include <stdexcept>
+#include <cassert>
+#include <cstring>
+
+namespace Interpreter
+{
+ Runtime::Runtime() : mContext (0), mCode (0), mPC (0), mCodeSize(0) {}
+
+ int Runtime::getPC() const
+ {
+ return mPC;
+ }
+
+ int Runtime::getIntegerLiteral (int index) const
+ {
+ assert (index>=0 && index<static_cast<int> (mCode[1]));
+
+ const Type_Code *literalBlock = mCode + 4 + mCode[0];
+
+ return *reinterpret_cast<const int *> (&literalBlock[index]);
+ }
+
+ float Runtime::getFloatLiteral (int index) const
+ {
+ assert (index>=0 && index<static_cast<int> (mCode[2]));
+
+ const Type_Code *literalBlock = mCode + 4 + mCode[0] + mCode[1];
+
+ return *reinterpret_cast<const float *> (&literalBlock[index]);
+ }
+
+ std::string Runtime::getStringLiteral (int index) const
+ {
+ assert (index>=0 && static_cast<int> (mCode[3])>0);
+
+ const char *literalBlock =
+ reinterpret_cast<const char *> (mCode + 4 + mCode[0] + mCode[1] + mCode[2]);
+
+ int offset = 0;
+
+ for (; index; --index)
+ {
+ offset += std::strlen (literalBlock+offset) + 1;
+ assert (offset/4<static_cast<int> (mCode[3]));
+ }
+
+ return literalBlock+offset;
+ }
+
+ void Runtime::configure (const Interpreter::Type_Code *code, int codeSize, Context& context)
+ {
+ clear();
+
+ mContext = &context;
+ mCode = code;
+ mCodeSize = codeSize;
+ mPC = 0;
+ }
+
+ void Runtime::clear()
+ {
+ mContext = 0;
+ mCode = 0;
+ mCodeSize = 0;
+ mStack.clear();
+ }
+
+ void Runtime::setPC (int PC)
+ {
+ mPC = PC;
+ }
+
+ void Runtime::push (const Data& data)
+ {
+ mStack.push_back (data);
+ }
+
+ void Runtime::push (Type_Integer value)
+ {
+ Data data;
+ data.mInteger = value;
+ push (data);
+ }
+
+ void Runtime::push (Type_Float value)
+ {
+ Data data;
+ data.mFloat = value;
+ push (data);
+ }
+
+ void Runtime::pop()
+ {
+ if (mStack.empty())
+ throw std::runtime_error ("stack underflow");
+
+ mStack.resize (mStack.size()-1);
+ }
+
+ Data& Runtime::operator[] (int Index)
+ {
+ if (Index<0 || Index>=static_cast<int> (mStack.size()))
+ throw std::runtime_error ("stack index out of range");
+
+ return mStack[mStack.size()-Index-1];
+ }
+
+ Context& Runtime::getContext()
+ {
+ assert (mContext);
+ return *mContext;
+ }
+}
diff --git a/components/interpreter/runtime.hpp b/components/interpreter/runtime.hpp
new file mode 100644
index 0000000000..2811ab0f0c
--- /dev/null
+++ b/components/interpreter/runtime.hpp
@@ -0,0 +1,64 @@
+#ifndef INTERPRETER_RUNTIME_H_INCLUDED
+#define INTERPRETER_RUNTIME_H_INCLUDED
+
+#include <vector>
+#include <string>
+
+#include "types.hpp"
+
+namespace Interpreter
+{
+ class Context;
+
+ /// Runtime data and engine interface
+
+ class Runtime
+ {
+ Context *mContext;
+ const Type_Code *mCode;
+ int mCodeSize;
+ int mPC;
+ std::vector<Data> mStack;
+
+ public:
+
+ Runtime ();
+
+ int getPC() const;
+ ///< return program counter.
+
+ int getIntegerLiteral (int index) const;
+
+ float getFloatLiteral (int index) const;
+
+ std::string getStringLiteral (int index) const;
+
+ void configure (const Type_Code *code, int codeSize, Context& context);
+ ///< \a context and \a code must exist as least until either configure, clear or
+ /// the destructor is called. \a codeSize is given in 32-bit words.
+
+ void clear();
+
+ void setPC (int PC);
+ ///< set program counter.
+
+ void push (const Data& data);
+ ///< push data on stack
+
+ void push (Type_Integer value);
+ ///< push integer data on stack.
+
+ void push (Type_Float value);
+ ///< push float data on stack.
+
+ void pop();
+ ///< pop stack
+
+ Data& operator[] (int Index);
+ ///< Access stack member, counted from the top.
+
+ Context& getContext();
+ };
+}
+
+#endif
diff --git a/components/interpreter/scriptopcodes.hpp b/components/interpreter/scriptopcodes.hpp
new file mode 100644
index 0000000000..56502d510a
--- /dev/null
+++ b/components/interpreter/scriptopcodes.hpp
@@ -0,0 +1,47 @@
+#ifndef INTERPRETER_SCRIPTOPCODES_H_INCLUDED
+#define INTERPRETER_SCRIPTOPCODES_H_INCLUDED
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+#include "context.hpp"
+
+namespace Interpreter
+{
+ class OpScriptRunning : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime[0].mInteger = runtime.getContext().isScriptRunning (name);
+ }
+ };
+
+ class OpStartScript : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ runtime.getContext().startScript (name);
+ }
+ };
+
+ class OpStopScript : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+ runtime.getContext().stopScript (name);
+ }
+ };
+}
+
+#endif
+
diff --git a/components/interpreter/spatialopcodes.hpp b/components/interpreter/spatialopcodes.hpp
new file mode 100644
index 0000000000..e37df81161
--- /dev/null
+++ b/components/interpreter/spatialopcodes.hpp
@@ -0,0 +1,43 @@
+#ifndef INTERPRETER_SPATIALOPCODES_H_INCLUDED
+#define INTERPRETER_SPATIALOPCODES_H_INCLUDED
+
+#include "opcodes.hpp"
+#include "runtime.hpp"
+
+namespace Interpreter
+{
+ class OpGetDistance : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+
+ Type_Float distance = runtime.getContext().getDistance (name);
+
+ runtime[0].mFloat = distance;
+ }
+ };
+
+ class OpGetDistanceExplicit : public Opcode0
+ {
+ public:
+
+ virtual void execute (Runtime& runtime)
+ {
+ int index = runtime[0].mInteger;
+ runtime.pop();
+ std::string id = runtime.getStringLiteral (index);
+
+ std::string name = runtime.getStringLiteral (runtime[0].mInteger);
+
+ Type_Float distance = runtime.getContext().getDistance (name, id);
+
+ runtime[0].mFloat = distance;
+ }
+ };
+}
+
+#endif
+
diff --git a/components/interpreter/types.hpp b/components/interpreter/types.hpp
new file mode 100644
index 0000000000..89529189f6
--- /dev/null
+++ b/components/interpreter/types.hpp
@@ -0,0 +1,44 @@
+#ifndef INTERPRETER_TYPES_H_INCLUDED
+#define INTERPRETER_TYPES_H_INCLUDED
+
+#include <stdexcept>
+
+namespace Interpreter
+{
+ typedef unsigned int Type_Code; // 32 bit
+
+ typedef unsigned int Type_Data; // 32 bit
+
+ typedef short Type_Short; // 16 bit
+
+ typedef int Type_Integer; // 32 bit
+
+ typedef float Type_Float; // 32 bit
+
+ union Data
+ {
+ Type_Integer mInteger;
+ Type_Float mFloat;
+ };
+
+ template<typename T>
+ T& getData (Data& data)
+ {
+ throw std::runtime_error ("unsupported data type");
+ }
+
+ template<>
+ inline Type_Integer& getData (Data& data)
+ {
+ return data.mInteger;
+ }
+
+ template<>
+ inline Type_Float& getData (Data& data)
+ {
+ return data.mFloat;
+ }
+}
+
+#endif
+
diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp
new file mode 100644
index 0000000000..483d524910
--- /dev/null
+++ b/components/loadinglistener/loadinglistener.hpp
@@ -0,0 +1,35 @@
+#ifndef COMPONENTS_LOADINGLISTENER_H
+#define COMPONENTS_LOADINGLISTENER_H
+
+namespace Loading
+{
+ class Listener
+ {
+ public:
+ virtual void setLabel (const std::string& label) = 0;
+
+ // Use ScopedLoad instead of using these directly
+ virtual void loadingOn() = 0;
+ virtual void loadingOff() = 0;
+
+ /// Indicate that some progress has been made, without specifying how much
+ virtual void indicateProgress () = 0;
+
+ virtual void setProgressRange (size_t range) = 0;
+ virtual void setProgress (size_t value) = 0;
+ virtual void increaseProgress (size_t increase) = 0;
+
+ /// Indicate the scene is now ready to be shown
+ virtual void removeWallpaper() = 0;
+ };
+
+ // Used for stopping a loading sequence when the object goes out of scope
+ struct ScopedLoad
+ {
+ ScopedLoad(Listener* l) : mListener(l) { mListener->loadingOn(); }
+ ~ScopedLoad() { mListener->loadingOff(); }
+ Listener* mListener;
+ };
+}
+
+#endif
diff --git a/components/misc/slice_array.hpp b/components/misc/slice_array.hpp
new file mode 100644
index 0000000000..cd58e7bd69
--- /dev/null
+++ b/components/misc/slice_array.hpp
@@ -0,0 +1,82 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (slice_array.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef MISC_SLICE_ARRAY_H
+#define MISC_SLICE_ARRAY_H
+
+// A simple array implementation containing a pointer and a
+// length. Used for holding slices into a data buffer.
+#include <string.h>
+#include <string>
+
+namespace Misc
+{
+
+template <class T>
+struct SliceArray
+{
+ const T* ptr;
+ size_t length;
+
+ /// Initialize to zero length
+ SliceArray() : ptr(0), length(0) {}
+
+ /// Initialize from pointer + length
+ SliceArray(const T* _ptr, size_t _length)
+ : ptr(_ptr), length(_length) {}
+
+ /// Initialize from null-terminated string
+ SliceArray(const char* str)
+ {
+ ptr = str;
+ length = strlen(str);
+ }
+
+ bool operator==(SliceArray &t)
+ {
+ return
+ length == t.length &&
+ (memcmp(ptr,t.ptr, length*sizeof(T)) == 0);
+ }
+
+ /// Only use this for stings
+ bool operator==(const char* str)
+ {
+ return
+ str[length] == 0 &&
+ (strncmp(ptr, str, length) == 0);
+ }
+
+ /** This allocates a copy of the data. Only use this for debugging
+ and error messages. */
+ std::string toString()
+ { return std::string(ptr,length); }
+};
+
+typedef SliceArray<char> SString;
+typedef SliceArray<int> IntArray;
+typedef SliceArray<float> FloatArray;
+
+}
+
+#endif
diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp
new file mode 100644
index 0000000000..0bc8e290a1
--- /dev/null
+++ b/components/misc/stringops.cpp
@@ -0,0 +1,70 @@
+#include "stringops.hpp"
+
+#include <cctype>
+#include <algorithm>
+#include <iterator>
+
+#include <string.h>
+#include <libs/platform/strings.h>
+
+
+
+namespace Misc
+{
+
+bool begins(const char* str1, const char* str2)
+{
+ while(*str2)
+ {
+ if(*str1 == 0 || *str1 != *str2) return false;
+
+ str1++;
+ str2++;
+ }
+ return true;
+}
+
+bool ends(const char* str1, const char* str2)
+{
+ int len1 = strlen(str1);
+ int len2 = strlen(str2);
+
+ if(len1 < len2) return false;
+
+ return strcmp(str2, str1+len1-len2) == 0;
+}
+
+// True if the given chars match, case insensitive
+static bool icmp(char a, char b)
+{
+ if(a >= 'A' && a <= 'Z')
+ a += 'a' - 'A';
+ if(b >= 'A' && b <= 'Z')
+ b += 'a' - 'A';
+
+ return a == b;
+}
+
+bool ibegins(const char* str1, const char* str2)
+{
+ while(*str2)
+ {
+ if(*str1 == 0 || !icmp(*str1,*str2)) return false;
+
+ str1++;
+ str2++;
+ }
+ return true;
+}
+
+bool iends(const char* str1, const char* str2)
+{
+ int len1 = strlen(str1);
+ int len2 = strlen(str2);
+
+ if(len1 < len2) return false;
+
+ return strcasecmp(str2, str1+len1-len2) == 0;
+}
+
+}
diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp
new file mode 100644
index 0000000000..d41463cfce
--- /dev/null
+++ b/components/misc/stringops.hpp
@@ -0,0 +1,92 @@
+#ifndef MISC_STRINGOPS_H
+#define MISC_STRINGOPS_H
+
+#include <cctype>
+#include <string>
+#include <algorithm>
+
+namespace Misc
+{
+class StringUtils
+{
+ struct ci
+ {
+ bool operator()(int x, int y) const {
+ return std::tolower(x) < std::tolower(y);
+ }
+ };
+
+public:
+ static bool ciLess(const std::string &x, const std::string &y) {
+ return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci());
+ }
+
+ static bool ciEqual(const std::string &x, const std::string &y) {
+ if (x.size() != y.size()) {
+ return false;
+ }
+ std::string::const_iterator xit = x.begin();
+ std::string::const_iterator yit = y.begin();
+ for (; xit != x.end(); ++xit, ++yit) {
+ if (std::tolower(*xit) != std::tolower(*yit)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static int ciCompareLen(const std::string &x, const std::string &y, size_t len)
+ {
+ std::string::const_iterator xit = x.begin();
+ std::string::const_iterator yit = y.begin();
+ for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len)
+ {
+ int res = *xit - *yit;
+ if(res != 0 && std::tolower(*xit) != std::tolower(*yit))
+ return (res > 0) ? 1 : -1;
+ }
+ if(len > 0)
+ {
+ if(xit != x.end())
+ return 1;
+ if(yit != y.end())
+ return -1;
+ }
+ return 0;
+ }
+
+ /// Transforms input string to lower case w/o copy
+ static std::string &toLower(std::string &inout) {
+ std::transform(
+ inout.begin(),
+ inout.end(),
+ inout.begin(),
+ (int (*)(int)) std::tolower
+ );
+ return inout;
+ }
+
+ /// Returns lower case copy of input string
+ static std::string lowerCase(const std::string &in)
+ {
+ std::string out = in;
+ return toLower(out);
+ }
+};
+
+
+/// Returns true if str1 begins with substring str2
+bool begins(const char* str1, const char* str2);
+
+/// Returns true if str1 ends with substring str2
+bool ends(const char* str1, const char* str2);
+
+/// Case insensitive, returns true if str1 begins with substring str2
+bool ibegins(const char* str1, const char* str2);
+
+/// Case insensitive, returns true if str1 ends with substring str2
+bool iends(const char* str1, const char* str2);
+
+}
+
+#endif
diff --git a/components/misc/tests/Makefile b/components/misc/tests/Makefile
new file mode 100644
index 0000000000..dc1ded5ff1
--- /dev/null
+++ b/components/misc/tests/Makefile
@@ -0,0 +1,12 @@
+GCC=g++
+
+all: strops_test slice_test
+
+slice_test: slice_test.cpp ../slice_array.hpp
+ $(GCC) $< -o $@
+
+strops_test: strops_test.cpp ../stringops.hpp ../stringops.cpp
+ $(GCC) $< -o $@ ../stringops.cpp
+
+clean:
+ rm *_test
diff --git a/components/misc/tests/output/slice_test.out b/components/misc/tests/output/slice_test.out
new file mode 100644
index 0000000000..7b054082bb
--- /dev/null
+++ b/components/misc/tests/output/slice_test.out
@@ -0,0 +1,6 @@
+hello, len=5
+001
+hell, len=4
+010
+01
+4 3
diff --git a/components/misc/tests/output/strops_test.out b/components/misc/tests/output/strops_test.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/components/misc/tests/output/strops_test.out
diff --git a/components/misc/tests/slice_test.cpp b/components/misc/tests/slice_test.cpp
new file mode 100644
index 0000000000..0d9d7b4abf
--- /dev/null
+++ b/components/misc/tests/slice_test.cpp
@@ -0,0 +1,28 @@
+#include <iostream>
+
+using namespace std;
+
+#include "../slice_array.hpp"
+
+int main()
+{
+ Misc::SString s, t;
+ s = Misc::SString("hello");
+ cout << s.toString() << ", len=" << s.length << endl;
+ cout << (s=="hel") << (s=="hell") << (s=="hello") << endl;
+ t = s;
+
+ s = Misc::SString("othello"+2, 4);
+ cout << s.toString() << ", len=" << s.length << endl;
+ cout << (s=="hel") << (s=="hell") << (s=="hello") << endl;
+
+ cout << (s==t) << (Misc::SString("hello")==t) << endl;
+
+ const int arr[4] = {1,2,3,4};
+
+ Misc::IntArray ia(arr,4);
+
+ cout << ia.length << " " << ia.ptr[2] << endl;
+
+ return 0;
+}
diff --git a/components/misc/tests/strops_test.cpp b/components/misc/tests/strops_test.cpp
new file mode 100644
index 0000000000..24ab8a298a
--- /dev/null
+++ b/components/misc/tests/strops_test.cpp
@@ -0,0 +1,48 @@
+#include <cassert>
+
+#include "../stringops.hpp"
+
+int main()
+{
+ assert(Misc::begins("abc", "a"));
+ assert(Misc::begins("abc", "ab"));
+ assert(Misc::begins("abc", "abc"));
+ assert(Misc::begins("abcd", "abc"));
+
+ assert(!Misc::begins("abc", "b"));
+ assert(!Misc::begins("abc", "bc"));
+ assert(!Misc::begins("abc", "bcd"));
+ assert(!Misc::begins("abc", "abcd"));
+
+ assert(Misc::ibegins("Abc", "a"));
+ assert(Misc::ibegins("aBc", "ab"));
+ assert(Misc::ibegins("abC", "abc"));
+ assert(Misc::ibegins("abcD", "abc"));
+
+ assert(!Misc::ibegins("abc", "b"));
+ assert(!Misc::ibegins("abc", "bc"));
+ assert(!Misc::ibegins("abc", "bcd"));
+ assert(!Misc::ibegins("abc", "abcd"));
+
+ assert(Misc::ends("abc", "c"));
+ assert(Misc::ends("abc", "bc"));
+ assert(Misc::ends("abc", "abc"));
+ assert(Misc::ends("abcd", "abcd"));
+
+ assert(!Misc::ends("abc", "b"));
+ assert(!Misc::ends("abc", "ab"));
+ assert(!Misc::ends("abc", "bcd"));
+ assert(!Misc::ends("abc", "abcd"));
+
+ assert(Misc::iends("Abc", "c"));
+ assert(Misc::iends("aBc", "bc"));
+ assert(Misc::iends("abC", "abc"));
+ assert(Misc::iends("abcD", "abcd"));
+
+ assert(!Misc::iends("abc", "b"));
+ assert(!Misc::iends("abc", "ab"));
+ assert(!Misc::iends("abc", "bcd"));
+ assert(!Misc::iends("abc", "abcd"));
+
+ return 0;
+}
diff --git a/components/misc/tests/test.sh b/components/misc/tests/test.sh
new file mode 100755
index 0000000000..2d07708adc
--- /dev/null
+++ b/components/misc/tests/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+make || exit
+
+mkdir -p output
+
+PROGS=*_test
+
+for a in $PROGS; do
+ if [ -f "output/$a.out" ]; then
+ echo "Running $a:"
+ ./$a | diff output/$a.out -
+ else
+ echo "Creating $a.out"
+ ./$a > "output/$a.out"
+ git add "output/$a.out"
+ fi
+done
diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp
new file mode 100644
index 0000000000..19a0688b26
--- /dev/null
+++ b/components/misc/utf8stream.hpp
@@ -0,0 +1,116 @@
+#ifndef MISC_UTF8ITER_HPP
+#define MISC_UTF8ITER_HPP
+
+#include <boost/tuple/tuple.hpp>
+
+class Utf8Stream
+{
+public:
+
+ typedef uint32_t UnicodeChar;
+ typedef unsigned char const * Point;
+
+ //static const unicode_char sBadChar = 0xFFFFFFFF; gcc can't handle this
+ static UnicodeChar sBadChar () { return UnicodeChar (0xFFFFFFFF); }
+
+ Utf8Stream (Point begin, Point end) :
+ cur (begin), nxt (begin), end (end)
+ {
+ }
+
+ Utf8Stream (std::pair <Point, Point> range) :
+ cur (range.first), nxt (range.first), end (range.second)
+ {
+ }
+
+ bool eof () const
+ {
+ return cur == end;
+ }
+
+ Point current () const
+ {
+ return cur;
+ }
+
+ UnicodeChar peek ()
+ {
+ if (cur == nxt)
+ next ();
+ return val;
+ }
+
+ UnicodeChar consume ()
+ {
+ if (cur == nxt)
+ next ();
+ cur = nxt;
+ return val;
+ }
+
+ static std::pair <UnicodeChar, Point> decode (Point cur, Point end)
+ {
+ if ((*cur & 0x80) == 0)
+ {
+ UnicodeChar chr = *cur++;
+
+ return std::make_pair (chr, cur);
+ }
+
+ int octets;
+ UnicodeChar chr;
+
+ boost::tie (octets, chr) = octet_count (*cur++);
+
+ if (octets > 5)
+ return std::make_pair (sBadChar(), cur);
+
+ Point eoc = cur + octets;
+
+ if (eoc > end)
+ return std::make_pair (sBadChar(), cur);
+
+ while (cur != eoc)
+ {
+ if ((*cur & 0xC0) != 0x80) // check continuation mark
+ return std::make_pair (sBadChar(), cur);;
+
+ chr = (chr << 6) | UnicodeChar ((*cur++) & 0x3F);
+ }
+
+ return std::make_pair (chr, cur);
+ }
+
+private:
+
+ static std::pair <int, UnicodeChar> octet_count (unsigned char octet)
+ {
+ int octets;
+
+ unsigned char mark = 0xC0;
+ unsigned char mask = 0xE0;
+
+ for (octets = 1; octets <= 5; ++octets)
+ {
+ if ((octet & mask) == mark)
+ break;
+
+ mark = (mark >> 1) | 0x80;
+ mask = (mask >> 1) | 0x80;
+ }
+
+ return std::make_pair (octets, octet & ~mask);
+ }
+
+ void next ()
+ {
+ boost::tie (val, nxt) = decode (nxt, end);
+ }
+
+ Point cur;
+ Point nxt;
+ Point end;
+ UnicodeChar val;
+};
+
+#endif
diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp
new file mode 100644
index 0000000000..6acb8ff201
--- /dev/null
+++ b/components/nif/controlled.hpp
@@ -0,0 +1,151 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (controlled.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
+#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
+
+#include "extra.hpp"
+#include "controller.hpp"
+
+namespace Nif
+{
+
+/// Anything that has a controller
+class Controlled : public Extra
+{
+public:
+ ControllerPtr controller;
+
+ void read(NIFStream *nif)
+ {
+ Extra::read(nif);
+ controller.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Extra::post(nif);
+ controller.post(nif);
+ }
+};
+
+/// Has name, extra-data and controller
+class Named : public Controlled
+{
+public:
+ std::string name;
+
+ void read(NIFStream *nif)
+ {
+ name = nif->getString();
+ Controlled::read(nif);
+ }
+};
+typedef Named NiSequenceStreamHelper;
+
+class NiParticleGrowFade : public Controlled
+{
+public:
+ float growTime;
+ float fadeTime;
+
+ void read(NIFStream *nif)
+ {
+ Controlled::read(nif);
+ growTime = nif->getFloat();
+ fadeTime = nif->getFloat();
+ }
+};
+
+class NiParticleColorModifier : public Controlled
+{
+public:
+ NiColorDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controlled::read(nif);
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controlled::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiGravity : public Controlled
+{
+public:
+ float mForce;
+ /* 0 - Wind (fixed direction)
+ * 1 - Point (fixed origin)
+ */
+ int mType;
+ Ogre::Vector3 mPosition;
+ Ogre::Vector3 mDirection;
+
+ void read(NIFStream *nif)
+ {
+ Controlled::read(nif);
+
+ /*unknown*/nif->getFloat();
+ mForce = nif->getFloat();
+ mType = nif->getUInt();
+ mPosition = nif->getVector3();
+ mDirection = nif->getVector3();
+ }
+};
+
+// NiPinaColada
+class NiPlanarCollider : public Controlled
+{
+public:
+ void read(NIFStream *nif)
+ {
+ Controlled::read(nif);
+
+ // (I think) 4 floats + 4 vectors
+ nif->skip(4*16);
+ }
+};
+
+class NiParticleRotation : public Controlled
+{
+public:
+ void read(NIFStream *nif)
+ {
+ Controlled::read(nif);
+
+ /*
+ byte (0 or 1)
+ float (1)
+ float*3
+ */
+ nif->skip(17);
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp
new file mode 100644
index 0000000000..011e0e4452
--- /dev/null
+++ b/components/nif/controller.hpp
@@ -0,0 +1,330 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (controller.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
+#define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP
+
+#include "record.hpp"
+#include "niffile.hpp"
+#include "recordptr.hpp"
+
+namespace Nif
+{
+
+class Controller : public Record
+{
+public:
+ ControllerPtr next;
+ int flags;
+ float frequency, phase;
+ float timeStart, timeStop;
+ ControlledPtr target;
+
+ void read(NIFStream *nif)
+ {
+ next.read(nif);
+
+ flags = nif->getUShort();
+
+ frequency = nif->getFloat();
+ phase = nif->getFloat();
+ timeStart = nif->getFloat();
+ timeStop = nif->getFloat();
+
+ target.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Record::post(nif);
+ next.post(nif);
+ target.post(nif);
+ }
+};
+
+class NiParticleSystemController : public Controller
+{
+public:
+ struct Particle {
+ Ogre::Vector3 velocity;
+ float lifetime;
+ float lifespan;
+ float timestamp;
+ int vertex;
+ };
+
+ float velocity;
+ float velocityRandom;
+
+ float verticalDir; // 0=up, pi/2=horizontal, pi=down
+ float verticalAngle;
+ float horizontalDir;
+ float horizontalAngle;
+
+ float size;
+ float startTime;
+ float stopTime;
+
+ float emitRate;
+ float lifetime;
+ float lifetimeRandom;
+
+ int emitFlags; // Bit 0: Emit Rate toggle bit (0 = auto adjust, 1 = use Emit Rate value)
+ Ogre::Vector3 offsetRandom;
+
+ NodePtr emitter;
+
+ int numParticles;
+ int activeCount;
+ std::vector<Particle> particles;
+
+ ExtraPtr extra;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+
+ velocity = nif->getFloat();
+ velocityRandom = nif->getFloat();
+ verticalDir = nif->getFloat();
+ verticalAngle = nif->getFloat();
+ horizontalDir = nif->getFloat();
+ horizontalAngle = nif->getFloat();
+ /*normal?*/ nif->getVector3();
+ /*color?*/ nif->getVector4();
+ size = nif->getFloat();
+ startTime = nif->getFloat();
+ stopTime = nif->getFloat();
+ nif->getChar();
+ emitRate = nif->getFloat();
+ lifetime = nif->getFloat();
+ lifetimeRandom = nif->getFloat();
+
+ emitFlags = nif->getUShort();
+ offsetRandom = nif->getVector3();
+
+ emitter.read(nif);
+
+ /* Unknown Short, 0?
+ * Unknown Float, 1.0?
+ * Unknown Int, 1?
+ * Unknown Int, 0?
+ * Unknown Short, 0?
+ */
+ nif->skip(16);
+
+ numParticles = nif->getUShort();
+ activeCount = nif->getUShort();
+
+ particles.resize(numParticles);
+ for(size_t i = 0;i < particles.size();i++)
+ {
+ particles[i].velocity = nif->getVector3();
+ nif->getVector3(); /* unknown */
+ particles[i].lifetime = nif->getFloat();
+ particles[i].lifespan = nif->getFloat();
+ particles[i].timestamp = nif->getFloat();
+ nif->getUShort(); /* unknown */
+ particles[i].vertex = nif->getUShort();
+ }
+
+ nif->getUInt(); /* -1? */
+ extra.read(nif);
+ nif->getUInt(); /* -1? */
+ nif->getChar();
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ emitter.post(nif);
+ extra.post(nif);
+ }
+};
+typedef NiParticleSystemController NiBSPArrayController;
+
+class NiMaterialColorController : public Controller
+{
+public:
+ NiPosDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiPathController : public Controller
+{
+public:
+ NiPosDataPtr posData;
+ NiFloatDataPtr floatData;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+
+ /*
+ int = 1
+ 2xfloat
+ short = 0 or 1
+ */
+ nif->skip(14);
+ posData.read(nif);
+ floatData.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+
+ posData.post(nif);
+ floatData.post(nif);
+ }
+};
+
+class NiUVController : public Controller
+{
+public:
+ NiUVDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+
+ nif->getUShort(); // always 0
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiKeyframeController : public Controller
+{
+public:
+ NiKeyframeDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiAlphaController : public Controller
+{
+public:
+ NiFloatDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiGeomMorpherController : public Controller
+{
+public:
+ NiMorphDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ data.read(nif);
+ nif->getChar(); // always 0
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiVisController : public Controller
+{
+public:
+ NiVisDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ data.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ data.post(nif);
+ }
+};
+
+class NiFlipController : public Controller
+{
+public:
+ int mTexSlot;
+ float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources
+ NiSourceTextureList mSources;
+
+ void read(NIFStream *nif)
+ {
+ Controller::read(nif);
+ mTexSlot = nif->getUInt();
+ /*unknown=*/nif->getUInt();/*0?*/
+ mDelta = nif->getFloat();
+ mSources.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Controller::post(nif);
+ mSources.post(nif);
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/data.hpp b/components/nif/data.hpp
new file mode 100644
index 0000000000..f1f34184ba
--- /dev/null
+++ b/components/nif/data.hpp
@@ -0,0 +1,428 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (data.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_DATA_HPP
+#define OPENMW_COMPONENTS_NIF_DATA_HPP
+
+#include "controlled.hpp"
+
+#include <OgreQuaternion.h>
+#include <OgreVector3.h>
+
+namespace Nif
+{
+
+class NiSourceTexture : public Named
+{
+public:
+ // Is this an external (references a separate texture file) or
+ // internal (data is inside the nif itself) texture?
+ bool external;
+
+ std::string filename; // In case of external textures
+ NiPixelDataPtr data; // In case of internal textures
+
+ /* Pixel layout
+ 0 - Palettised
+ 1 - High color 16
+ 2 - True color 32
+ 3 - Compressed
+ 4 - Bumpmap
+ 5 - Default */
+ int pixel;
+
+ /* Mipmap format
+ 0 - no
+ 1 - yes
+ 2 - default */
+ int mipmap;
+
+ /* Alpha
+ 0 - none
+ 1 - binary
+ 2 - smooth
+ 3 - default (use material alpha, or multiply material with texture if present)
+ */
+ int alpha;
+
+ void read(NIFStream *nif)
+ {
+ Named::read(nif);
+
+ external = !!nif->getChar();
+ if(external)
+ filename = nif->getString();
+ else
+ {
+ nif->getChar(); // always 1
+ data.read(nif);
+ }
+
+ pixel = nif->getInt();
+ mipmap = nif->getInt();
+ alpha = nif->getInt();
+
+ nif->getChar(); // always 1
+ }
+
+ void post(NIFFile *nif)
+ {
+ Named::post(nif);
+ data.post(nif);
+ }
+};
+
+// Common ancestor for several data classes
+class ShapeData : public Record
+{
+public:
+ std::vector<Ogre::Vector3> vertices, normals;
+ std::vector<Ogre::Vector4> colors;
+ std::vector< std::vector<Ogre::Vector2> > uvlist;
+ Ogre::Vector3 center;
+ float radius;
+
+ void read(NIFStream *nif)
+ {
+ int verts = nif->getUShort();
+
+ if(nif->getInt())
+ nif->getVector3s(vertices, verts);
+
+ if(nif->getInt())
+ nif->getVector3s(normals, verts);
+
+ center = nif->getVector3();
+ radius = nif->getFloat();
+
+ if(nif->getInt())
+ nif->getVector4s(colors, verts);
+
+ // Only the first 6 bits are used as a count. I think the rest are
+ // flags of some sort.
+ int uvs = nif->getUShort();
+ uvs &= 0x3f;
+
+ if(nif->getInt())
+ {
+ uvlist.resize(uvs);
+ for(int i = 0;i < uvs;i++)
+ nif->getVector2s(uvlist[i], verts);
+ }
+ }
+};
+
+class NiTriShapeData : public ShapeData
+{
+public:
+ // Triangles, three vertex indices per triangle
+ std::vector<short> triangles;
+
+ void read(NIFStream *nif)
+ {
+ ShapeData::read(nif);
+
+ /*int tris =*/ nif->getUShort();
+
+ // We have three times as many vertices as triangles, so this
+ // is always equal to tris*3.
+ int cnt = nif->getInt();
+ nif->getShorts(triangles, cnt);
+
+ // Read the match list, which lists the vertices that are equal to
+ // vertices. We don't actually need need this for anything, so
+ // just skip it.
+ int verts = nif->getUShort();
+ for(int i=0;i < verts;i++)
+ {
+ // Number of vertices matching vertex 'i'
+ int num = nif->getUShort();
+ nif->skip(num * sizeof(short));
+ }
+ }
+};
+
+class NiAutoNormalParticlesData : public ShapeData
+{
+public:
+ int numParticles;
+
+ float particleRadius;
+
+ int activeCount;
+
+ std::vector<float> sizes;
+
+ void read(NIFStream *nif)
+ {
+ ShapeData::read(nif);
+
+ // Should always match the number of vertices
+ numParticles = nif->getUShort();
+
+ particleRadius = nif->getFloat();
+ activeCount = nif->getUShort();
+
+ if(nif->getInt())
+ {
+ // Particle sizes
+ nif->getFloats(sizes, vertices.size());
+ }
+ }
+};
+
+class NiRotatingParticlesData : public NiAutoNormalParticlesData
+{
+public:
+ std::vector<Ogre::Quaternion> rotations;
+
+ void read(NIFStream *nif)
+ {
+ NiAutoNormalParticlesData::read(nif);
+
+ if(nif->getInt())
+ {
+ // Rotation quaternions.
+ nif->getQuaternions(rotations, vertices.size());
+ }
+ }
+};
+
+class NiPosData : public Record
+{
+public:
+ Vector3KeyList mKeyList;
+
+ void read(NIFStream *nif)
+ {
+ mKeyList.read(nif);
+ }
+};
+
+class NiUVData : public Record
+{
+public:
+ FloatKeyList mKeyList[4];
+
+ void read(NIFStream *nif)
+ {
+ for(int i = 0;i < 4;i++)
+ mKeyList[i].read(nif);
+ }
+};
+
+class NiFloatData : public Record
+{
+public:
+ FloatKeyList mKeyList;
+
+ void read(NIFStream *nif)
+ {
+ mKeyList.read(nif);
+ }
+};
+
+class NiPixelData : public Record
+{
+public:
+ unsigned int rmask, gmask, bmask, amask;
+ int bpp, mips;
+
+ void read(NIFStream *nif)
+ {
+ nif->getInt(); // always 0 or 1
+
+ rmask = nif->getInt(); // usually 0xff
+ gmask = nif->getInt(); // usually 0xff00
+ bmask = nif->getInt(); // usually 0xff0000
+ amask = nif->getInt(); // usually 0xff000000 or zero
+
+ bpp = nif->getInt();
+
+ // Unknown
+ nif->skip(12);
+
+ mips = nif->getInt();
+
+ // Bytes per pixel, should be bpp * 8
+ /*int bytes =*/ nif->getInt();
+
+ for(int i=0; i<mips; i++)
+ {
+ // Image size and offset in the following data field
+ /*int x =*/ nif->getInt();
+ /*int y =*/ nif->getInt();
+ /*int offset =*/ nif->getInt();
+ }
+
+ // Skip the data
+ unsigned int dataSize = nif->getInt();
+ nif->skip(dataSize);
+ }
+};
+
+class NiColorData : public Record
+{
+public:
+ Vector4KeyList mKeyList;
+
+ void read(NIFStream *nif)
+ {
+ mKeyList.read(nif);
+ }
+};
+
+class NiVisData : public Record
+{
+public:
+ struct VisData {
+ float time;
+ char isSet;
+ };
+ std::vector<VisData> mVis;
+
+ void read(NIFStream *nif)
+ {
+ int count = nif->getInt();
+ mVis.resize(count);
+ for(size_t i = 0;i < mVis.size();i++)
+ {
+ mVis[i].time = nif->getFloat();
+ mVis[i].isSet = nif->getChar();
+ }
+ }
+};
+
+class NiSkinInstance : public Record
+{
+public:
+ NiSkinDataPtr data;
+ NodePtr root;
+ NodeList bones;
+
+ void read(NIFStream *nif)
+ {
+ data.read(nif);
+ root.read(nif);
+ bones.read(nif);
+ }
+
+ void post(NIFFile *nif);
+};
+
+class NiSkinData : public Record
+{
+public:
+ struct BoneTrafo
+ {
+ Ogre::Matrix3 rotation; // Rotation offset from bone?
+ Ogre::Vector3 trans; // Translation
+ float scale; // Probably scale (always 1)
+ };
+
+ struct VertWeight
+ {
+ short vertex;
+ float weight;
+ };
+
+ struct BoneInfo
+ {
+ BoneTrafo trafo;
+ Ogre::Vector4 unknown;
+ std::vector<VertWeight> weights;
+ };
+
+ BoneTrafo trafo;
+ std::vector<BoneInfo> bones;
+
+ void read(NIFStream *nif)
+ {
+ trafo.rotation = nif->getMatrix3();
+ trafo.trans = nif->getVector3();
+ trafo.scale = nif->getFloat();
+
+ int boneNum = nif->getInt();
+ nif->getInt(); // -1
+
+ bones.resize(boneNum);
+ for(int i=0;i<boneNum;i++)
+ {
+ BoneInfo &bi = bones[i];
+
+ bi.trafo.rotation = nif->getMatrix3();
+ bi.trafo.trans = nif->getVector3();
+ bi.trafo.scale = nif->getFloat();
+ bi.unknown = nif->getVector4();
+
+ // Number of vertex weights
+ bi.weights.resize(nif->getUShort());
+ for(size_t j = 0;j < bi.weights.size();j++)
+ {
+ bi.weights[j].vertex = nif->getUShort();
+ bi.weights[j].weight = nif->getFloat();
+ }
+ }
+ }
+};
+
+struct NiMorphData : public Record
+{
+ struct MorphData {
+ FloatKeyList mData;
+ std::vector<Ogre::Vector3> mVertices;
+ };
+ std::vector<MorphData> mMorphs;
+
+ void read(NIFStream *nif)
+ {
+ int morphCount = nif->getInt();
+ int vertCount = nif->getInt();
+ /*relative targets?*/nif->getChar();
+
+ mMorphs.resize(morphCount);
+ for(int i = 0;i < morphCount;i++)
+ {
+ mMorphs[i].mData.read(nif, true);
+ nif->getVector3s(mMorphs[i].mVertices, vertCount);
+ }
+ }
+};
+
+
+struct NiKeyframeData : public Record
+{
+ QuaternionKeyList mRotations;
+ Vector3KeyList mTranslations;
+ FloatKeyList mScales;
+
+ void read(NIFStream *nif)
+ {
+ mRotations.read(nif);
+ mTranslations.read(nif);
+ mScales.read(nif);
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp
new file mode 100644
index 0000000000..cc1b0f41c1
--- /dev/null
+++ b/components/nif/effect.hpp
@@ -0,0 +1,105 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (effect.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_EFFECT_HPP
+#define OPENMW_COMPONENTS_NIF_EFFECT_HPP
+
+#include "node.hpp"
+
+namespace Nif
+{
+
+typedef Node Effect;
+
+// Used for NiAmbientLight and NiDirectionalLight. Might also work for
+// NiPointLight and NiSpotLight?
+struct NiLight : Effect
+{
+ struct SLight
+ {
+ float dimmer;
+ Ogre::Vector3 ambient;
+ Ogre::Vector3 diffuse;
+ Ogre::Vector3 specular;
+
+ void read(NIFStream *nif)
+ {
+ dimmer = nif->getFloat();
+ ambient = nif->getVector3();
+ diffuse = nif->getVector3();
+ specular = nif->getVector3();
+ }
+ };
+ SLight light;
+
+ void read(NIFStream *nif)
+ {
+ Effect::read(nif);
+
+ nif->getInt(); // 1
+ nif->getInt(); // 1?
+ light.read(nif);
+ }
+};
+
+struct NiTextureEffect : Effect
+{
+ NiSourceTexturePtr texture;
+
+ void read(NIFStream *nif)
+ {
+ Effect::read(nif);
+
+ int tmp = nif->getInt();
+ if(tmp) nif->getInt(); // always 1?
+
+ /*
+ 3 x Vector4 = [1,0,0,0]
+ int = 2
+ int = 0 or 3
+ int = 2
+ int = 2
+ */
+ nif->skip(16*4);
+
+ texture.read(nif);
+
+ /*
+ byte = 0
+ vector4 = [1,0,0,0]
+ short = 0
+ short = -75
+ short = 0
+ */
+ nif->skip(23);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Effect::post(nif);
+ texture.post(nif);
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp
new file mode 100644
index 0000000000..45c4fefc69
--- /dev/null
+++ b/components/nif/extra.hpp
@@ -0,0 +1,108 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (extra.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP
+#define OPENMW_COMPONENTS_NIF_EXTRA_HPP
+
+#include "record.hpp"
+#include "niffile.hpp"
+#include "recordptr.hpp"
+
+namespace Nif
+{
+
+/** A record that can have extra data. The extra data objects
+ themselves decend from the Extra class, and all the extra data
+ connected to an object form a linked list
+*/
+class Extra : public Record
+{
+public:
+ ExtraPtr extra;
+
+ void read(NIFStream *nif) { extra.read(nif); }
+ void post(NIFFile *nif) { extra.post(nif); }
+};
+
+class NiVertWeightsExtraData : public Extra
+{
+public:
+ void read(NIFStream *nif)
+ {
+ Extra::read(nif);
+
+ // We should have s*4+2 == i, for some reason. Might simply be the
+ // size of the rest of the record, unhelpful as that may be.
+ /*int i =*/ nif->getInt();
+ int s = nif->getUShort();
+
+ nif->skip(s * sizeof(float)); // vertex weights I guess
+ }
+};
+
+class NiTextKeyExtraData : public Extra
+{
+public:
+ struct TextKey
+ {
+ float time;
+ std::string text;
+ };
+ std::vector<TextKey> list;
+
+ void read(NIFStream *nif)
+ {
+ Extra::read(nif);
+
+ nif->getInt(); // 0
+
+ int keynum = nif->getInt();
+ list.resize(keynum);
+ for(int i=0; i<keynum; i++)
+ {
+ list[i].time = nif->getFloat();
+ list[i].text = nif->getString();
+ }
+ }
+};
+
+class NiStringExtraData : public Extra
+{
+public:
+ /* Two known meanings:
+ "MRK" - marker, only visible in the editor, not rendered in-game
+ "NCO" - no collision
+ */
+ std::string string;
+
+ void read(NIFStream *nif)
+ {
+ Extra::read(nif);
+
+ nif->getInt(); // size of string + 4. Really useful...
+ string = nif->getString();
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp
new file mode 100644
index 0000000000..cb7c2feb07
--- /dev/null
+++ b/components/nif/niffile.cpp
@@ -0,0 +1,440 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (nif_file.cpp) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#include "niffile.hpp"
+#include "record.hpp"
+#include "components/misc/stringops.hpp"
+
+#include "extra.hpp"
+#include "controlled.hpp"
+#include "node.hpp"
+#include "property.hpp"
+#include "data.hpp"
+#include "effect.hpp"
+#include "controller.hpp"
+
+#include <iostream>
+
+//TODO: when threading is needed, enable these
+//#include <boost/mutex.hpp>
+#include <boost/thread/locks.hpp>
+
+namespace Nif
+{
+
+class NIFFile::LoadedCache
+{
+ //TODO: enable this to make cache thread safe...
+ //typedef boost::mutex mutex;
+
+ struct mutex
+ {
+ void lock () {};
+ void unlock () {}
+ };
+
+ typedef boost::lock_guard <mutex> lock_guard;
+ typedef std::map < std::string, boost::weak_ptr <NIFFile> > loaded_map;
+ typedef std::vector < boost::shared_ptr <NIFFile> > locked_files;
+
+ static int sLockLevel;
+ static mutex sProtector;
+ static loaded_map sLoadedMap;
+ static locked_files sLockedFiles;
+
+public:
+
+ static ptr create (const std::string &name)
+ {
+ lock_guard _ (sProtector);
+
+ ptr result;
+
+ // lookup the resource
+ loaded_map::iterator i = sLoadedMap.find (name);
+
+ if (i == sLoadedMap.end ()) // it doesn't existing currently,
+ { // or hasn't in the very near past
+
+ // create it now, for smoother threading if needed, the
+ // loading should be performed outside of the sLoaderMap
+ // lock and an alternate mechanism should be used to
+ // synchronize threads competing to load the same resource
+ result = boost::make_shared <NIFFile> (name, psudo_private_modifier());
+
+ // if we are locking the cache add an extra reference
+ // to keep the file in memory
+ if (sLockLevel > 0)
+ sLockedFiles.push_back (result);
+
+ // stash a reference to the resource so that future
+ // calls can benefit
+ sLoadedMap [name] = boost::weak_ptr <NIFFile> (result);
+ }
+ else // it may (probably) still exists
+ {
+ // attempt to get the reference
+ result = i->second.lock ();
+
+ if (!result) // resource is in the process of being destroyed
+ {
+ // create a new instance, to replace the one that has
+ // begun the irreversible process of being destroyed
+ result = boost::make_shared <NIFFile> (name, psudo_private_modifier());
+
+ // respect the cache lock...
+ if (sLockLevel > 0)
+ sLockedFiles.push_back (result);
+
+ // we potentially overwrite an expired pointer here
+ // but the other thread performing the delete on
+ // the previous copy of this resource will detect it
+ // and make sure not to erase the new reference
+ sLoadedMap [name] = boost::weak_ptr <NIFFile> (result);
+ }
+ }
+
+ // we made it!
+ return result;
+ }
+
+ static void release (NIFFile * file)
+ {
+ lock_guard _ (sProtector);
+
+ loaded_map::iterator i = sLoadedMap.find (file->filename);
+
+ // its got to be in here, it just might not be us...
+ assert (i != sLoadedMap.end ());
+
+ // if weak_ptr is still expired, this resource hasn't been recreated
+ // between the initiation of the final release due to destruction
+ // of the last shared pointer and this thread acquiring the lock on
+ // the loader map
+ if (i->second.expired ())
+ sLoadedMap.erase (i);
+ }
+
+ static void lockCache ()
+ {
+ lock_guard _ (sProtector);
+
+ sLockLevel++;
+ }
+
+ static void unlockCache ()
+ {
+ locked_files resetList;
+
+ {
+ lock_guard _ (sProtector);
+
+ if (--sLockLevel)
+ sLockedFiles.swap(resetList);
+ }
+
+ // this not necessary, but makes it clear that the
+ // deletion of the locked cache entries is being done
+ // outside the protection of sProtector
+ resetList.clear ();
+ }
+};
+
+int NIFFile::LoadedCache::sLockLevel = 0;
+NIFFile::LoadedCache::mutex NIFFile::LoadedCache::sProtector;
+NIFFile::LoadedCache::loaded_map NIFFile::LoadedCache::sLoadedMap;
+NIFFile::LoadedCache::locked_files NIFFile::LoadedCache::sLockedFiles;
+
+// these three calls are forwarded to the cache implementation...
+void NIFFile::lockCache () { LoadedCache::lockCache (); }
+void NIFFile::unlockCache () { LoadedCache::unlockCache (); }
+NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::create (name); }
+
+/// Open a NIF stream. The name is used for error messages.
+NIFFile::NIFFile(const std::string &name, psudo_private_modifier)
+ : filename(name)
+{
+ parse();
+}
+
+NIFFile::~NIFFile()
+{
+ LoadedCache::release (this);
+
+ for(std::size_t i=0; i<records.size(); i++)
+ delete records[i];
+}
+
+template <typename NodeType> static Record* construct() { return new NodeType; }
+
+struct RecordFactoryEntry {
+
+ typedef Record* (*create_t) ();
+
+ char const * mName;
+ create_t mCreate;
+ RecordType mType;
+
+};
+
+/* These are all the record types we know how to read.
+
+ This can be heavily optimized later if needed. For example, a
+ hash table or a FSM-based parser could be used to look up
+ node names.
+*/
+
+static const RecordFactoryEntry recordFactories [] = {
+
+ { "NiNode", &construct <NiNode >, RC_NiNode },
+ { "AvoidNode", &construct <NiNode >, RC_AvoidNode },
+ { "NiBSParticleNode", &construct <NiNode >, RC_NiBSParticleNode },
+ { "NiBSAnimationNode", &construct <NiNode >, RC_NiBSAnimationNode },
+ { "NiBillboardNode", &construct <NiNode >, RC_NiNode },
+ { "NiTriShape", &construct <NiTriShape >, RC_NiTriShape },
+ { "NiRotatingParticles", &construct <NiRotatingParticles >, RC_NiRotatingParticles },
+ { "NiAutoNormalParticles", &construct <NiAutoNormalParticles >, RC_NiAutoNormalParticles },
+ { "NiCamera", &construct <NiCamera >, RC_NiCamera },
+ { "RootCollisionNode", &construct <NiNode >, RC_RootCollisionNode },
+ { "NiTexturingProperty", &construct <NiTexturingProperty >, RC_NiTexturingProperty },
+ { "NiMaterialProperty", &construct <NiMaterialProperty >, RC_NiMaterialProperty },
+ { "NiZBufferProperty", &construct <NiZBufferProperty >, RC_NiZBufferProperty },
+ { "NiAlphaProperty", &construct <NiAlphaProperty >, RC_NiAlphaProperty },
+ { "NiVertexColorProperty", &construct <NiVertexColorProperty >, RC_NiVertexColorProperty },
+ { "NiShadeProperty", &construct <NiShadeProperty >, RC_NiShadeProperty },
+ { "NiDitherProperty", &construct <NiDitherProperty >, RC_NiDitherProperty },
+ { "NiWireframeProperty", &construct <NiWireframeProperty >, RC_NiWireframeProperty },
+ { "NiSpecularProperty", &construct <NiSpecularProperty >, RC_NiSpecularProperty },
+ { "NiStencilProperty", &construct <NiStencilProperty >, RC_NiStencilProperty },
+ { "NiVisController", &construct <NiVisController >, RC_NiVisController },
+ { "NiGeomMorpherController", &construct <NiGeomMorpherController >, RC_NiGeomMorpherController },
+ { "NiKeyframeController", &construct <NiKeyframeController >, RC_NiKeyframeController },
+ { "NiAlphaController", &construct <NiAlphaController >, RC_NiAlphaController },
+ { "NiUVController", &construct <NiUVController >, RC_NiUVController },
+ { "NiPathController", &construct <NiPathController >, RC_NiPathController },
+ { "NiMaterialColorController", &construct <NiMaterialColorController >, RC_NiMaterialColorController },
+ { "NiBSPArrayController", &construct <NiBSPArrayController >, RC_NiBSPArrayController },
+ { "NiParticleSystemController", &construct <NiParticleSystemController >, RC_NiParticleSystemController },
+ { "NiFlipController", &construct <NiFlipController >, RC_NiFlipController },
+ { "NiAmbientLight", &construct <NiLight >, RC_NiLight },
+ { "NiDirectionalLight", &construct <NiLight >, RC_NiLight },
+ { "NiTextureEffect", &construct <NiTextureEffect >, RC_NiTextureEffect },
+ { "NiVertWeightsExtraData", &construct <NiVertWeightsExtraData >, RC_NiVertWeightsExtraData },
+ { "NiTextKeyExtraData", &construct <NiTextKeyExtraData >, RC_NiTextKeyExtraData },
+ { "NiStringExtraData", &construct <NiStringExtraData >, RC_NiStringExtraData },
+ { "NiGravity", &construct <NiGravity >, RC_NiGravity },
+ { "NiPlanarCollider", &construct <NiPlanarCollider >, RC_NiPlanarCollider },
+ { "NiParticleGrowFade", &construct <NiParticleGrowFade >, RC_NiParticleGrowFade },
+ { "NiParticleColorModifier", &construct <NiParticleColorModifier >, RC_NiParticleColorModifier },
+ { "NiParticleRotation", &construct <NiParticleRotation >, RC_NiParticleRotation },
+ { "NiFloatData", &construct <NiFloatData >, RC_NiFloatData },
+ { "NiTriShapeData", &construct <NiTriShapeData >, RC_NiTriShapeData },
+ { "NiVisData", &construct <NiVisData >, RC_NiVisData },
+ { "NiColorData", &construct <NiColorData >, RC_NiColorData },
+ { "NiPixelData", &construct <NiPixelData >, RC_NiPixelData },
+ { "NiMorphData", &construct <NiMorphData >, RC_NiMorphData },
+ { "NiKeyframeData", &construct <NiKeyframeData >, RC_NiKeyframeData },
+ { "NiSkinData", &construct <NiSkinData >, RC_NiSkinData },
+ { "NiUVData", &construct <NiUVData >, RC_NiUVData },
+ { "NiPosData", &construct <NiPosData >, RC_NiPosData },
+ { "NiRotatingParticlesData", &construct <NiRotatingParticlesData >, RC_NiRotatingParticlesData },
+ { "NiAutoNormalParticlesData", &construct <NiAutoNormalParticlesData >, RC_NiAutoNormalParticlesData },
+ { "NiSequenceStreamHelper", &construct <NiSequenceStreamHelper >, RC_NiSequenceStreamHelper },
+ { "NiSourceTexture", &construct <NiSourceTexture >, RC_NiSourceTexture },
+ { "NiSkinInstance", &construct <NiSkinInstance >, RC_NiSkinInstance },
+};
+
+static RecordFactoryEntry const * recordFactories_begin = &recordFactories [0];
+static RecordFactoryEntry const * recordFactories_end = &recordFactories [sizeof (recordFactories) / sizeof (recordFactories[0])];
+
+RecordFactoryEntry const * lookupRecordFactory (char const * name)
+{
+ RecordFactoryEntry const * i;
+
+ for (i = recordFactories_begin; i != recordFactories_end; ++i)
+ if (strcmp (name, i->mName) == 0)
+ break;
+
+ if (i == recordFactories_end)
+ return NULL;
+
+ return i;
+}
+
+/* This file implements functions from the NIFFile class. It is also
+ where we stash all the functions we couldn't add as inline
+ definitions in the record types.
+ */
+
+void NIFFile::parse()
+{
+ NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename));
+
+ // Check the header string
+ std::string head = nif.getString(40);
+ if(head.compare(0, 22, "NetImmerse File Format") != 0)
+ fail("Invalid NIF header");
+
+ // Get BCD version
+ ver = nif.getInt();
+ if(ver != VER_MW)
+ fail("Unsupported NIF version");
+
+ // Number of records
+ size_t recNum = nif.getInt();
+ records.resize(recNum);
+
+ /* The format for 10.0.1.0 seems to be a bit different. After the
+ header, it contains the number of records, r (int), just like
+ 4.0.0.2, but following that it contains a short x, followed by x
+ strings. Then again by r shorts, one for each record, giving
+ which of the above strings to use to identify the record. After
+ this follows two ints (zero?) and then the record data. However
+ we do not support or plan to support other versions yet.
+ */
+
+ for(size_t i = 0;i < recNum;i++)
+ {
+ Record *r = NULL;
+
+ std::string rec = nif.getString();
+
+ RecordFactoryEntry const * entry = lookupRecordFactory (rec.c_str ());
+
+ if (entry != NULL)
+ {
+ r = entry->mCreate ();
+ r->recType = entry->mType;
+ }
+ else
+ fail("Unknown record type " + rec);
+
+ assert(r != NULL);
+ assert(r->recType != RC_MISSING);
+ r->recName = rec;
+ r->recIndex = i;
+ records[i] = r;
+ r->read(&nif);
+
+ // Discard tranformations for the root node, otherwise some meshes
+ // occasionally get wrong orientation. Only for NiNode-s for now, but
+ // can be expanded if needed.
+ // This should be rewritten when the method is cleaned up.
+ if (0 == i && rec == "NiNode")
+ {
+ static_cast<Nif::Node*>(r)->trafo = Nif::Transformation::getIdentity();
+ }
+ }
+
+ size_t rootNum = nif.getUInt();
+ roots.resize(rootNum);
+
+ for(size_t i = 0;i < rootNum;i++)
+ {
+ intptr_t idx = nif.getInt();
+ roots[i] = ((idx >= 0) ? records.at(idx) : NULL);
+ }
+
+ // Once parsing is done, do post-processing.
+ for(size_t i=0; i<recNum; i++)
+ records[i]->post(this);
+}
+
+/// \todo move to the write cpp file
+
+void NiSkinInstance::post(NIFFile *nif)
+{
+ data.post(nif);
+ root.post(nif);
+ bones.post(nif);
+
+ if(data.empty() || root.empty())
+ nif->fail("NiSkinInstance missing root or data");
+
+ size_t bnum = bones.length();
+ if(bnum != data->bones.size())
+ nif->fail("Mismatch in NiSkinData bone count");
+
+ root->makeRootBone(&data->trafo);
+
+ for(size_t i=0; i<bnum; i++)
+ {
+ if(bones[i].empty())
+ nif->fail("Oops: Missing bone! Don't know how to handle this.");
+ bones[i]->makeBone(i, data->bones[i]);
+ }
+}
+
+
+void Node::getProperties(const Nif::NiTexturingProperty *&texprop,
+ const Nif::NiMaterialProperty *&matprop,
+ const Nif::NiAlphaProperty *&alphaprop,
+ const Nif::NiVertexColorProperty *&vertprop,
+ const Nif::NiZBufferProperty *&zprop,
+ const Nif::NiSpecularProperty *&specprop,
+ const Nif::NiWireframeProperty *&wireprop) const
+{
+ if(parent)
+ parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
+
+ for(size_t i = 0;i < props.length();i++)
+ {
+ // Entries may be empty
+ if(props[i].empty())
+ continue;
+
+ const Nif::Property *pr = props[i].getPtr();
+ if(pr->recType == Nif::RC_NiTexturingProperty)
+ texprop = static_cast<const Nif::NiTexturingProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiMaterialProperty)
+ matprop = static_cast<const Nif::NiMaterialProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiAlphaProperty)
+ alphaprop = static_cast<const Nif::NiAlphaProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiVertexColorProperty)
+ vertprop = static_cast<const Nif::NiVertexColorProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiZBufferProperty)
+ zprop = static_cast<const Nif::NiZBufferProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiSpecularProperty)
+ specprop = static_cast<const Nif::NiSpecularProperty*>(pr);
+ else if(pr->recType == Nif::RC_NiWireframeProperty)
+ wireprop = static_cast<const Nif::NiWireframeProperty*>(pr);
+ else
+ std::cerr<< "Unhandled property type: "<<pr->recName <<std::endl;
+ }
+}
+
+Ogre::Matrix4 Node::getLocalTransform() const
+{
+ Ogre::Matrix4 mat4(Ogre::Matrix4::IDENTITY);
+ mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation));
+ return mat4;
+}
+
+Ogre::Matrix4 Node::getWorldTransform() const
+{
+ if(parent != NULL)
+ return parent->getWorldTransform() * getLocalTransform();
+ return getLocalTransform();
+}
+
+}
diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp
new file mode 100644
index 0000000000..6e629772e6
--- /dev/null
+++ b/components/nif/niffile.hpp
@@ -0,0 +1,212 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (nif_file.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_NIFFILE_HPP
+#define OPENMW_COMPONENTS_NIF_NIFFILE_HPP
+
+#include <OgreResourceGroupManager.h>
+#include <OgreDataStream.h>
+#include <OgreVector2.h>
+#include <OgreVector3.h>
+#include <OgreVector4.h>
+#include <OgreMatrix3.h>
+#include <OgreQuaternion.h>
+#include <OgreStringConverter.h>
+
+#include <stdexcept>
+#include <vector>
+#include <cassert>
+
+#include <boost/weak_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/detail/endian.hpp>
+
+#include <libs/platform/stdint.h>
+
+#include "record.hpp"
+#include "niftypes.hpp"
+#include "nifstream.hpp"
+
+namespace Nif
+{
+
+class NIFFile
+{
+ enum NIFVersion {
+ VER_MW = 0x04000002 // Morrowind NIFs
+ };
+
+ /// Nif file version
+ int ver;
+
+ /// File name, used for error messages
+ std::string filename;
+
+ /// Record list
+ std::vector<Record*> records;
+
+ /// Root list
+ std::vector<Record*> roots;
+
+ /// Parse the file
+ void parse();
+
+ class LoadedCache;
+ friend class LoadedCache;
+
+ // attempt to protect NIFFile from misuse...
+ struct psudo_private_modifier {}; // this dirty little trick should optimize out
+ NIFFile (NIFFile const &);
+ void operator = (NIFFile const &);
+
+public:
+ /// Used for error handling
+ void fail(const std::string &msg)
+ {
+ std::string err = "NIFFile Error: " + msg;
+ err += "\nFile: " + filename;
+ throw std::runtime_error(err);
+ }
+
+ void warn(const std::string &msg)
+ {
+ std::cerr << "NIFFile Warning: " << msg <<std::endl
+ << "File: " << filename <<std::endl;
+ }
+
+ typedef boost::shared_ptr <NIFFile> ptr;
+
+ /// Open a NIF stream. The name is used for error messages.
+ NIFFile(const std::string &name, psudo_private_modifier);
+ ~NIFFile();
+
+ static ptr create (const std::string &name);
+ static void lockCache ();
+ static void unlockCache ();
+
+ struct CacheLock
+ {
+ CacheLock () { lockCache (); }
+ ~CacheLock () { unlockCache (); }
+ };
+
+ /// Get a given record
+ Record *getRecord(size_t index)
+ {
+ Record *res = records.at(index);
+ assert(res != NULL);
+ return res;
+ }
+ /// Number of records
+ size_t numRecords() { return records.size(); }
+
+ /// Get a given root
+ Record *getRoot(size_t index=0)
+ {
+ Record *res = roots.at(index);
+ assert(res != NULL);
+ return res;
+ }
+ /// Number of roots
+ size_t numRoots() { return roots.size(); }
+};
+
+
+template<typename T>
+struct KeyT {
+ float mTime;
+ T mValue;
+ T mForwardValue; // Only for Quadratic interpolation
+ T mBackwardValue; // Only for Quadratic interpolation
+ float mTension; // Only for TBC interpolation
+ float mBias; // Only for TBC interpolation
+ float mContinuity; // Only for TBC interpolation
+};
+typedef KeyT<float> FloatKey;
+typedef KeyT<Ogre::Vector3> Vector3Key;
+typedef KeyT<Ogre::Vector4> Vector4Key;
+typedef KeyT<Ogre::Quaternion> QuaternionKey;
+
+template<typename T, T (NIFStream::*getValue)()>
+struct KeyListT {
+ typedef std::vector< KeyT<T> > VecType;
+
+ static const int sLinearInterpolation = 1;
+ static const int sQuadraticInterpolation = 2;
+ static const int sTBCInterpolation = 3;
+
+ int mInterpolationType;
+ VecType mKeys;
+
+ void read(NIFStream *nif, bool force=false)
+ {
+ size_t count = nif->getInt();
+ if(count == 0 && !force)
+ return;
+
+ mInterpolationType = nif->getInt();
+ mKeys.resize(count);
+ if(mInterpolationType == sLinearInterpolation)
+ {
+ for(size_t i = 0;i < count;i++)
+ {
+ KeyT<T> &key = mKeys[i];
+ key.mTime = nif->getFloat();
+ key.mValue = (nif->*getValue)();
+ }
+ }
+ else if(mInterpolationType == sQuadraticInterpolation)
+ {
+ for(size_t i = 0;i < count;i++)
+ {
+ KeyT<T> &key = mKeys[i];
+ key.mTime = nif->getFloat();
+ key.mValue = (nif->*getValue)();
+ key.mForwardValue = (nif->*getValue)();
+ key.mBackwardValue = (nif->*getValue)();
+ }
+ }
+ else if(mInterpolationType == sTBCInterpolation)
+ {
+ for(size_t i = 0;i < count;i++)
+ {
+ KeyT<T> &key = mKeys[i];
+ key.mTime = nif->getFloat();
+ key.mValue = (nif->*getValue)();
+ key.mTension = nif->getFloat();
+ key.mBias = nif->getFloat();
+ key.mContinuity = nif->getFloat();
+ }
+ }
+ else
+ nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType));
+ }
+};
+typedef KeyListT<float,&NIFStream::getFloat> FloatKeyList;
+typedef KeyListT<Ogre::Vector3,&NIFStream::getVector3> Vector3KeyList;
+typedef KeyListT<Ogre::Vector4,&NIFStream::getVector4> Vector4KeyList;
+typedef KeyListT<Ogre::Quaternion,&NIFStream::getQuaternion> QuaternionKeyList;
+
+} // Namespace
+#endif
diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp
new file mode 100644
index 0000000000..a2595d17b8
--- /dev/null
+++ b/components/nif/nifstream.hpp
@@ -0,0 +1,181 @@
+#ifndef OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP
+#define OPENMW_COMPONENTS_NIF_NIFSTREAM_HPP
+
+namespace Nif
+{
+
+class NIFFile;
+
+class NIFStream {
+
+ /// Input stream
+ Ogre::DataStreamPtr inp;
+
+ uint8_t read_byte()
+ {
+ uint8_t byte;
+ if(inp->read(&byte, 1) != 1) return 0;
+ return byte;
+ }
+ uint16_t read_le16()
+ {
+ uint8_t buffer[2];
+ if(inp->read(buffer, 2) != 2) return 0;
+ return buffer[0] | (buffer[1]<<8);
+ }
+ uint32_t read_le32()
+ {
+ uint8_t buffer[4];
+ if(inp->read(buffer, 4) != 4) return 0;
+ return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24);
+ }
+ float read_le32f()
+ {
+ union {
+ uint32_t i;
+ float f;
+ } u = { read_le32() };
+ return u.f;
+ }
+
+public:
+
+ NIFFile * const file;
+
+ NIFStream (NIFFile * file, Ogre::DataStreamPtr inp): file (file), inp (inp) {}
+
+ /*************************************************
+ Parser functions
+ ****************************************************/
+
+ template <typename T>
+ struct GetHandler
+ {
+ typedef T (NIFStream::*fn_t)();
+
+ static const fn_t sValue; // this is specialized per supported type in the .cpp file
+
+ static T read (NIFStream* nif)
+ {
+ return (nif->*sValue) ();
+ }
+ };
+
+ template <typename T>
+ void read (NIFStream* nif, T & Value)
+ {
+ Value = GetHandler <T>::read (nif);
+ }
+
+ void skip(size_t size) { inp->skip(size); }
+ void read (void * data, size_t size) { inp->read (data, size); }
+
+ char getChar() { return read_byte(); }
+ short getShort() { return read_le16(); }
+ unsigned short getUShort() { return read_le16(); }
+ int getInt() { return read_le32(); }
+ int getUInt() { return read_le32(); }
+ float getFloat() { return read_le32f(); }
+ Ogre::Vector2 getVector2()
+ {
+ float a[2];
+ for(size_t i = 0;i < 2;i++)
+ a[i] = getFloat();
+ return Ogre::Vector2(a);
+ }
+ Ogre::Vector3 getVector3()
+ {
+ float a[3];
+ for(size_t i = 0;i < 3;i++)
+ a[i] = getFloat();
+ return Ogre::Vector3(a);
+ }
+ Ogre::Vector4 getVector4()
+ {
+ float a[4];
+ for(size_t i = 0;i < 4;i++)
+ a[i] = getFloat();
+ return Ogre::Vector4(a);
+ }
+ Ogre::Matrix3 getMatrix3()
+ {
+ Ogre::Real a[3][3];
+ for(size_t i = 0;i < 3;i++)
+ {
+ for(size_t j = 0;j < 3;j++)
+ a[i][j] = Ogre::Real(getFloat());
+ }
+ return Ogre::Matrix3(a);
+ }
+ Ogre::Quaternion getQuaternion()
+ {
+ float a[4];
+ for(size_t i = 0;i < 4;i++)
+ a[i] = getFloat();
+ return Ogre::Quaternion(a);
+ }
+ Transformation getTrafo()
+ {
+ Transformation t;
+ t.pos = getVector3();
+ t.rotation = getMatrix3();
+ t.scale = getFloat();
+ return t;
+ }
+
+ std::string getString(size_t length)
+ {
+ std::vector<char> str (length+1, 0);
+
+ if(inp->read(&str[0], length) != length)
+ throw std::runtime_error ("string length in NIF file does not match");
+
+ return &str[0];
+ }
+ std::string getString()
+ {
+ size_t size = read_le32();
+ return getString(size);
+ }
+
+ void getShorts(std::vector<short> &vec, size_t size)
+ {
+ vec.resize(size);
+ for(size_t i = 0;i < vec.size();i++)
+ vec[i] = getShort();
+ }
+ void getFloats(std::vector<float> &vec, size_t size)
+ {
+ vec.resize(size);
+ for(size_t i = 0;i < vec.size();i++)
+ vec[i] = getFloat();
+ }
+ void getVector2s(std::vector<Ogre::Vector2> &vec, size_t size)
+ {
+ vec.resize(size);
+ for(size_t i = 0;i < vec.size();i++)
+ vec[i] = getVector2();
+ }
+ void getVector3s(std::vector<Ogre::Vector3> &vec, size_t size)
+ {
+ vec.resize(size);
+ for(size_t i = 0;i < vec.size();i++)
+ vec[i] = getVector3();
+ }
+ void getVector4s(std::vector<Ogre::Vector4> &vec, size_t size)
+ {
+ vec.resize(size);
+ for(size_t i = 0;i < vec.size();i++)
+ vec[i] = getVector4();
+ }
+ void getQuaternions(std::vector<Ogre::Quaternion> &quat, size_t size)
+ {
+ quat.resize(size);
+ for(size_t i = 0;i < quat.size();i++)
+ quat[i] = getQuaternion();
+ }
+};
+
+}
+
+#endif
diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp
new file mode 100644
index 0000000000..786c48b65e
--- /dev/null
+++ b/components/nif/niftypes.hpp
@@ -0,0 +1,51 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (nif_types.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_NIFTYPES_HPP
+#define OPENMW_COMPONENTS_NIF_NIFTYPES_HPP
+
+#include <OgreVector3.h>
+#include <OgreMatrix3.h>
+
+// Common types used in NIF files
+
+namespace Nif
+{
+
+struct Transformation
+{
+ Ogre::Vector3 pos;
+ Ogre::Matrix3 rotation;
+ float scale;
+
+ static const Transformation& getIdentity()
+ {
+ static const Transformation identity = {
+ Ogre::Vector3::ZERO, Ogre::Matrix3::IDENTITY, 1.0f
+ };
+ return identity;
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/node.hpp b/components/nif/node.hpp
new file mode 100644
index 0000000000..917bc8add3
--- /dev/null
+++ b/components/nif/node.hpp
@@ -0,0 +1,272 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (node.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_NODE_HPP
+#define OPENMW_COMPONENTS_NIF_NODE_HPP
+
+#include <OgreMatrix4.h>
+
+#include "controlled.hpp"
+#include "data.hpp"
+#include "property.hpp"
+
+namespace Nif
+{
+
+class NiNode;
+
+/** A Node is an object that's part of the main NIF tree. It has
+ parent node (unless it's the root), and transformation (location
+ and rotation) relative to it's parent.
+ */
+class Node : public Named
+{
+public:
+ // Node flags. Interpretation depends somewhat on the type of node.
+ int flags;
+ Transformation trafo;
+ Ogre::Vector3 velocity; // Unused? Might be a run-time game state
+ PropertyList props;
+
+ // Bounding box info
+ bool hasBounds;
+ Ogre::Vector3 boundPos;
+ Ogre::Matrix3 boundRot;
+ Ogre::Vector3 boundXYZ; // Box size
+
+ void read(NIFStream *nif)
+ {
+ Named::read(nif);
+
+ flags = nif->getUShort();
+ trafo = nif->getTrafo();
+ velocity = nif->getVector3();
+ props.read(nif);
+
+ hasBounds = !!nif->getInt();
+ if(hasBounds)
+ {
+ nif->getInt(); // always 1
+ boundPos = nif->getVector3();
+ boundRot = nif->getMatrix3();
+ boundXYZ = nif->getVector3();
+ }
+
+ parent = NULL;
+
+ boneTrafo = NULL;
+ boneIndex = -1;
+ }
+
+ void post(NIFFile *nif)
+ {
+ Named::post(nif);
+ props.post(nif);
+ }
+
+ // Parent node, or NULL for the root node. As far as I'm aware, only
+ // NiNodes (or types derived from NiNodes) can be parents.
+ NiNode *parent;
+
+ // Bone transformation. If set, node is a part of a skeleton.
+ const NiSkinData::BoneTrafo *boneTrafo;
+
+ // Bone weight info, from NiSkinData
+ const NiSkinData::BoneInfo *boneInfo;
+
+ // Bone index. If -1, this node is either not a bone, or if
+ // boneTrafo is set it is the root bone in the skeleton.
+ short boneIndex;
+
+ void makeRootBone(const NiSkinData::BoneTrafo *tr)
+ {
+ boneTrafo = tr;
+ boneIndex = -1;
+ }
+
+ void makeBone(short ind, const NiSkinData::BoneInfo &bi)
+ {
+ boneInfo = &bi;
+ boneTrafo = &bi.trafo;
+ boneIndex = ind;
+ }
+
+ void getProperties(const Nif::NiTexturingProperty *&texprop,
+ const Nif::NiMaterialProperty *&matprop,
+ const Nif::NiAlphaProperty *&alphaprop,
+ const Nif::NiVertexColorProperty *&vertprop,
+ const Nif::NiZBufferProperty *&zprop,
+ const Nif::NiSpecularProperty *&specprop,
+ const Nif::NiWireframeProperty *&wireprop) const;
+
+ Ogre::Matrix4 getLocalTransform() const;
+ Ogre::Matrix4 getWorldTransform() const;
+};
+
+struct NiNode : Node
+{
+ NodeList children;
+ NodeList effects;
+
+ enum Flags {
+ Flag_Hidden = 0x0001,
+ Flag_MeshCollision = 0x0002,
+ Flag_BBoxCollision = 0x0004
+ };
+ enum BSAnimFlags {
+ AnimFlag_AutoPlay = 0x0020
+ };
+ enum BSParticleFlags {
+ ParticleFlag_AutoPlay = 0x0020
+ };
+
+ void read(NIFStream *nif)
+ {
+ Node::read(nif);
+ children.read(nif);
+ effects.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Node::post(nif);
+ children.post(nif);
+ effects.post(nif);
+
+ for(size_t i = 0;i < children.length();i++)
+ {
+ // Why would a unique list of children contain empty refs?
+ if(!children[i].empty())
+ children[i]->parent = this;
+ }
+ }
+};
+
+struct NiTriShape : Node
+{
+ /* Possible flags:
+ 0x40 - mesh has no vertex normals ?
+
+ Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have
+ been observed so far.
+ */
+
+ NiTriShapeDataPtr data;
+ NiSkinInstancePtr skin;
+
+ void read(NIFStream *nif)
+ {
+ Node::read(nif);
+ data.read(nif);
+ skin.read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ Node::post(nif);
+ data.post(nif);
+ skin.post(nif);
+ }
+};
+
+struct NiCamera : Node
+{
+ struct Camera
+ {
+ // Camera frustrum
+ float left, right, top, bottom, nearDist, farDist;
+
+ // Viewport
+ float vleft, vright, vtop, vbottom;
+
+ // Level of detail modifier
+ float LOD;
+
+ void read(NIFStream *nif)
+ {
+ left = nif->getFloat();
+ right = nif->getFloat();
+ top = nif->getFloat();
+ bottom = nif->getFloat();
+ nearDist = nif->getFloat();
+ farDist = nif->getFloat();
+
+ vleft = nif->getFloat();
+ vright = nif->getFloat();
+ vtop = nif->getFloat();
+ vbottom = nif->getFloat();
+
+ LOD = nif->getFloat();
+ }
+ };
+ Camera cam;
+
+ void read(NIFStream *nif)
+ {
+ Node::read(nif);
+
+ cam.read(nif);
+
+ nif->getInt(); // -1
+ nif->getInt(); // 0
+ }
+};
+
+struct NiAutoNormalParticles : Node
+{
+ NiAutoNormalParticlesDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Node::read(nif);
+ data.read(nif);
+ nif->getInt(); // -1
+ }
+
+ void post(NIFFile *nif)
+ {
+ Node::post(nif);
+ data.post(nif);
+ }
+};
+
+struct NiRotatingParticles : Node
+{
+ NiRotatingParticlesDataPtr data;
+
+ void read(NIFStream *nif)
+ {
+ Node::read(nif);
+ data.read(nif);
+ nif->getInt(); // -1
+ }
+
+ void post(NIFFile *nif)
+ {
+ Node::post(nif);
+ data.post(nif);
+ }
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/property.hpp b/components/nif/property.hpp
new file mode 100644
index 0000000000..06c8260ce5
--- /dev/null
+++ b/components/nif/property.hpp
@@ -0,0 +1,333 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (property.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP
+#define OPENMW_COMPONENTS_NIF_PROPERTY_HPP
+
+#include "controlled.hpp"
+
+namespace Nif
+{
+
+class Property : public Named
+{
+public:
+ // The meaning of these depends on the actual property type.
+ int flags;
+
+ void read(NIFStream *nif)
+ {
+ Named::read(nif);
+ flags = nif->getUShort();
+ }
+};
+
+class NiTexturingProperty : public Property
+{
+public:
+ // A sub-texture
+ struct Texture
+ {
+ /* Clamp mode
+ 0 - clampS clampT
+ 1 - clampS wrapT
+ 2 - wrapS clampT
+ 3 - wrapS wrapT
+ */
+
+ /* Filter:
+ 0 - nearest
+ 1 - bilinear
+ 2 - trilinear
+ 3, 4, 5 - who knows
+ */
+ bool inUse;
+ NiSourceTexturePtr texture;
+
+ int clamp, uvSet, filter;
+ short unknown2;
+
+ void read(NIFStream *nif)
+ {
+ inUse = !!nif->getInt();
+ if(!inUse) return;
+
+ texture.read(nif);
+ clamp = nif->getInt();
+ filter = nif->getInt();
+ uvSet = nif->getInt();
+
+ // I have no idea, but I think these are actually two
+ // PS2-specific shorts (ps2L and ps2K), followed by an unknown
+ // short.
+ nif->skip(6);
+ }
+
+ void post(NIFFile *nif)
+ {
+ texture.post(nif);
+ }
+ };
+
+ /* Apply mode:
+ 0 - replace
+ 1 - decal
+ 2 - modulate
+ 3 - hilight // These two are for PS2 only?
+ 4 - hilight2
+ */
+ int apply;
+
+ /*
+ * The textures in this list are as follows:
+ *
+ * 0 - Base texture
+ * 1 - Dark texture
+ * 2 - Detail texture
+ * 3 - Gloss texture (never used?)
+ * 4 - Glow texture
+ * 5 - Bump map texture
+ * 6 - Decal texture
+ */
+ enum TextureType
+ {
+ BaseTexture = 0,
+ DarkTexture = 1,
+ DetailTexture = 2,
+ GlossTexture = 3,
+ GlowTexture = 4,
+ BumpTexture = 5,
+ DecalTexture = 6
+ };
+
+ Texture textures[7];
+
+ void read(NIFStream *nif)
+ {
+ Property::read(nif);
+ apply = nif->getInt();
+
+ // Unknown, always 7. Probably the number of textures to read
+ // below
+ nif->getInt();
+
+ textures[0].read(nif); // Base
+ textures[1].read(nif); // Dark
+ textures[2].read(nif); // Detail
+ textures[3].read(nif); // Gloss (never present)
+ textures[4].read(nif); // Glow
+ textures[5].read(nif); // Bump map
+ if(textures[5].inUse)
+ {
+ // Ignore these at the moment
+ /*float lumaScale =*/ nif->getFloat();
+ /*float lumaOffset =*/ nif->getFloat();
+ /*const Vector4 *lumaMatrix =*/ nif->getVector4();
+ }
+ textures[6].read(nif); // Decal
+ }
+
+ void post(NIFFile *nif)
+ {
+ Property::post(nif);
+ for(int i = 0;i < 7;i++)
+ textures[i].post(nif);
+ }
+};
+
+// These contain no other data than the 'flags' field in Property
+class NiShadeProperty : public Property { };
+class NiDitherProperty : public Property { };
+class NiZBufferProperty : public Property { };
+class NiSpecularProperty : public Property { };
+class NiWireframeProperty : public Property { };
+
+// The rest are all struct-based
+template <typename T>
+struct StructPropT : Property
+{
+ T data;
+
+ void read(NIFStream *nif)
+ {
+ Property::read(nif);
+ data.read(nif);
+ }
+};
+
+struct S_MaterialProperty
+{
+ // The vector components are R,G,B
+ Ogre::Vector3 ambient, diffuse, specular, emissive;
+ float glossiness, alpha;
+
+ void read(NIFStream *nif)
+ {
+ ambient = nif->getVector3();
+ diffuse = nif->getVector3();
+ specular = nif->getVector3();
+ emissive = nif->getVector3();
+ glossiness = nif->getFloat();
+ alpha = nif->getFloat();
+ }
+};
+
+struct S_VertexColorProperty
+{
+ /* Vertex mode:
+ 0 - source ignore
+ 1 - source emmisive
+ 2 - source amb diff
+
+ Lighting mode
+ 0 - lighting emmisive
+ 1 - lighting emmisive ambient/diffuse
+ */
+ int vertmode, lightmode;
+
+ void read(NIFStream *nif)
+ {
+ vertmode = nif->getInt();
+ lightmode = nif->getInt();
+ }
+};
+
+struct S_AlphaProperty
+{
+ /*
+ In NiAlphaProperty, the flags have the following meaning:
+
+ Bit 0 : alpha blending enable
+ Bits 1-4 : source blend mode
+ Bits 5-8 : destination blend mode
+ Bit 9 : alpha test enable
+ Bit 10-12 : alpha test mode
+ Bit 13 : no sorter flag ( disables triangle sorting )
+
+ blend modes (glBlendFunc):
+ 0000 GL_ONE
+ 0001 GL_ZERO
+ 0010 GL_SRC_COLOR
+ 0011 GL_ONE_MINUS_SRC_COLOR
+ 0100 GL_DST_COLOR
+ 0101 GL_ONE_MINUS_DST_COLOR
+ 0110 GL_SRC_ALPHA
+ 0111 GL_ONE_MINUS_SRC_ALPHA
+ 1000 GL_DST_ALPHA
+ 1001 GL_ONE_MINUS_DST_ALPHA
+ 1010 GL_SRC_ALPHA_SATURATE
+
+ test modes (glAlphaFunc):
+ 000 GL_ALWAYS
+ 001 GL_LESS
+ 010 GL_EQUAL
+ 011 GL_LEQUAL
+ 100 GL_GREATER
+ 101 GL_NOTEQUAL
+ 110 GL_GEQUAL
+ 111 GL_NEVER
+
+ Taken from:
+ http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html
+
+ Right now we only use standard alpha blending (see the Ogre code
+ that sets it up) and it appears that this is the only blending
+ used in the original game. Bloodmoon (along with several mods) do
+ however use other settings, such as discarding pixel values with
+ alpha < 1.0. This is faster because we don't have to mess with the
+ depth stuff like we did for blending. And OGRE has settings for
+ this too.
+ */
+
+ // Tested against when certain flags are set (see above.)
+ unsigned char threshold;
+
+ void read(NIFStream *nif)
+ {
+ threshold = nif->getChar();
+ }
+};
+
+/*
+ Docs taken from:
+ http://niftools.sourceforge.net/doc/nif/NiStencilProperty.html
+ */
+struct S_StencilProperty
+{
+ // Is stencil test enabled?
+ unsigned char enabled;
+
+ /*
+ 0 TEST_NEVER
+ 1 TEST_LESS
+ 2 TEST_EQUAL
+ 3 TEST_LESS_EQUAL
+ 4 TEST_GREATER
+ 5 TEST_NOT_EQUAL
+ 6 TEST_GREATER_EQUAL
+ 7 TEST_ALWAYS
+ */
+ int compareFunc;
+ unsigned stencilRef;
+ unsigned stencilMask;
+ /*
+ Stencil test fail action, depth test fail action and depth test pass action:
+ 0 ACTION_KEEP
+ 1 ACTION_ZERO
+ 2 ACTION_REPLACE
+ 3 ACTION_INCREMENT
+ 4 ACTION_DECREMENT
+ 5 ACTION_INVERT
+ */
+ int failAction;
+ int zFailAction;
+ int zPassAction;
+ /*
+ Face draw mode:
+ 0 DRAW_CCW_OR_BOTH
+ 1 DRAW_CCW [default]
+ 2 DRAW_CW
+ 3 DRAW_BOTH
+ */
+ int drawMode;
+
+ void read(NIFStream *nif)
+ {
+ enabled = nif->getChar();
+ compareFunc = nif->getInt();
+ stencilRef = nif->getUInt();
+ stencilMask = nif->getUInt();
+ failAction = nif->getInt();
+ zFailAction = nif->getInt();
+ zPassAction = nif->getInt();
+ drawMode = nif->getInt();
+ }
+};
+
+class NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
+class NiMaterialProperty : public StructPropT<S_MaterialProperty> { };
+class NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
+class NiStencilProperty : public StructPropT<S_StencilProperty> { };
+
+} // Namespace
+#endif
diff --git a/components/nif/record.hpp b/components/nif/record.hpp
new file mode 100644
index 0000000000..87e342dca5
--- /dev/null
+++ b/components/nif/record.hpp
@@ -0,0 +1,120 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (record.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_RECORD_HPP
+#define OPENMW_COMPONENTS_NIF_RECORD_HPP
+
+#include <string>
+
+namespace Nif
+{
+
+class NIFFile;
+class NIFStream;
+
+enum RecordType
+{
+ RC_MISSING = 0,
+ RC_NiNode,
+ RC_AvoidNode,
+ RC_NiTriShape,
+ RC_NiRotatingParticles,
+ RC_NiAutoNormalParticles,
+ RC_NiBSParticleNode,
+ RC_NiCamera,
+ RC_NiTexturingProperty,
+ RC_NiMaterialProperty,
+ RC_NiZBufferProperty,
+ RC_NiAlphaProperty,
+ RC_NiVertexColorProperty,
+ RC_NiShadeProperty,
+ RC_NiDitherProperty,
+ RC_NiWireframeProperty,
+ RC_NiSpecularProperty,
+ RC_NiStencilProperty,
+ RC_NiVisController,
+ RC_NiGeomMorpherController,
+ RC_NiKeyframeController,
+ RC_NiAlphaController,
+ RC_NiUVController,
+ RC_NiPathController,
+ RC_NiMaterialColorController,
+ RC_NiBSPArrayController,
+ RC_NiParticleSystemController,
+ RC_NiFlipController,
+ RC_NiBSAnimationNode,
+ RC_NiLight,
+ RC_NiTextureEffect,
+ RC_NiVertWeightsExtraData,
+ RC_NiTextKeyExtraData,
+ RC_NiStringExtraData,
+ RC_NiGravity,
+ RC_NiPlanarCollider,
+ RC_NiParticleGrowFade,
+ RC_NiParticleColorModifier,
+ RC_NiParticleRotation,
+ RC_NiFloatData,
+ RC_NiTriShapeData,
+ RC_NiVisData,
+ RC_NiColorData,
+ RC_NiPixelData,
+ RC_NiMorphData,
+ RC_NiKeyframeData,
+ RC_NiSkinData,
+ RC_NiUVData,
+ RC_NiPosData,
+ RC_NiRotatingParticlesData,
+ RC_NiAutoNormalParticlesData,
+ RC_NiSequenceStreamHelper,
+ RC_NiSourceTexture,
+ RC_NiSkinInstance,
+ RC_RootCollisionNode
+};
+
+/// Base class for all records
+struct Record
+{
+ // Record type and type name
+ int recType;
+ std::string recName;
+ size_t recIndex;
+
+ Record() : recType(RC_MISSING), recIndex(~(size_t)0) {}
+
+ /// Parses the record from file
+ virtual void read(NIFStream *nif) = 0;
+
+ /// Does post-processing, after the entire tree is loaded
+ virtual void post(NIFFile *nif) {}
+
+ virtual ~Record() {}
+
+ /*
+ Use these later if you want custom allocation of all NIF objects
+ static void* operator new(size_t size);
+ static void operator delete(void *p);
+ */
+};
+
+} // Namespace
+#endif
diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp
new file mode 100644
index 0000000000..1a4fc235bc
--- /dev/null
+++ b/components/nif/recordptr.hpp
@@ -0,0 +1,181 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (record_ptr.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP
+#define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP
+
+#include "niffile.hpp"
+#include <vector>
+
+namespace Nif
+{
+
+/** A reference to another record. It is read as an index from the
+ NIF, and later looked up in the index table to get an actual
+ pointer.
+*/
+template <class X>
+class RecordPtrT
+{
+ union {
+ intptr_t index;
+ X* ptr;
+ };
+
+public:
+ RecordPtrT() : index(-2) {}
+
+ /// Read the index from the nif
+ void read(NIFStream *nif)
+ {
+ // Can only read the index once
+ assert(index == -2);
+
+ // Store the index for later
+ index = nif->getInt();
+ assert(index >= -1);
+ }
+
+ /// Resolve index to pointer
+ void post(NIFFile *nif)
+ {
+ if(index < 0)
+ ptr = NULL;
+ else
+ {
+ Record *r = nif->getRecord(index);
+ // And cast it
+ ptr = dynamic_cast<X*>(r);
+ assert(ptr != NULL);
+ }
+ }
+
+ /// Look up the actual object from the index
+ const X* getPtr() const
+ {
+ assert(ptr != NULL);
+ return ptr;
+ }
+ X* getPtr()
+ {
+ assert(ptr != NULL);
+ return ptr;
+ }
+
+ const X& get() const
+ { return *getPtr(); }
+ X& get()
+ { return *getPtr(); }
+
+ /// Syntactic sugar
+ const X* operator->() const
+ { return getPtr(); }
+ X* operator->()
+ { return getPtr(); }
+
+ /// Pointers are allowed to be empty
+ bool empty() const
+ { return ptr == NULL; }
+};
+
+/** A list of references to other records. These are read as a list,
+ and later converted to pointers as needed. Not an optimized
+ implementation.
+ */
+template <class X>
+class RecordListT
+{
+ typedef RecordPtrT<X> Ptr;
+ std::vector<Ptr> list;
+
+public:
+ void read(NIFStream *nif)
+ {
+ int len = nif->getInt();
+ list.resize(len);
+
+ for(size_t i=0;i < list.size();i++)
+ list[i].read(nif);
+ }
+
+ void post(NIFFile *nif)
+ {
+ for(size_t i=0;i < list.size();i++)
+ list[i].post(nif);
+ }
+
+ const Ptr& operator[](size_t index) const
+ { return list.at(index); }
+ Ptr& operator[](size_t index)
+ { return list.at(index); }
+
+ size_t length() const
+ { return list.size(); }
+};
+
+
+class Node;
+class Extra;
+class Property;
+class NiUVData;
+class NiPosData;
+class NiVisData;
+class Controller;
+class Controlled;
+class NiSkinData;
+class NiFloatData;
+class NiMorphData;
+class NiPixelData;
+class NiColorData;
+class NiKeyframeData;
+class NiTriShapeData;
+class NiSkinInstance;
+class NiSourceTexture;
+class NiRotatingParticlesData;
+class NiAutoNormalParticlesData;
+
+typedef RecordPtrT<Node> NodePtr;
+typedef RecordPtrT<Extra> ExtraPtr;
+typedef RecordPtrT<NiUVData> NiUVDataPtr;
+typedef RecordPtrT<NiPosData> NiPosDataPtr;
+typedef RecordPtrT<NiVisData> NiVisDataPtr;
+typedef RecordPtrT<Controller> ControllerPtr;
+typedef RecordPtrT<Controlled> ControlledPtr;
+typedef RecordPtrT<NiSkinData> NiSkinDataPtr;
+typedef RecordPtrT<NiMorphData> NiMorphDataPtr;
+typedef RecordPtrT<NiPixelData> NiPixelDataPtr;
+typedef RecordPtrT<NiFloatData> NiFloatDataPtr;
+typedef RecordPtrT<NiColorData> NiColorDataPtr;
+typedef RecordPtrT<NiKeyframeData> NiKeyframeDataPtr;
+typedef RecordPtrT<NiTriShapeData> NiTriShapeDataPtr;
+typedef RecordPtrT<NiSkinInstance> NiSkinInstancePtr;
+typedef RecordPtrT<NiSourceTexture> NiSourceTexturePtr;
+typedef RecordPtrT<NiRotatingParticlesData> NiRotatingParticlesDataPtr;
+typedef RecordPtrT<NiAutoNormalParticlesData> NiAutoNormalParticlesDataPtr;
+
+typedef RecordListT<Node> NodeList;
+typedef RecordListT<Property> PropertyList;
+typedef RecordListT<NiSourceTexture> NiSourceTextureList;
+
+} // Namespace
+#endif
diff --git a/components/nif/tests/Makefile b/components/nif/tests/Makefile
new file mode 100644
index 0000000000..0754bdfa61
--- /dev/null
+++ b/components/nif/tests/Makefile
@@ -0,0 +1,12 @@
+GCC=g++
+
+all: niftool nif_bsa_test
+
+niftool: niftool.cpp ../nif_file.hpp ../nif_file.cpp ../record.hpp
+ $(GCC) $< ../nif_file.cpp ../../tools/stringops.cpp -o $@
+
+nif_bsa_test: nif_bsa_test.cpp ../nif_file.cpp ../../bsa/bsa_file.cpp ../../tools/stringops.cpp
+ $(GCC) $^ -o $@
+
+clean:
+ rm niftool *_test
diff --git a/components/nif/tests/nif_bsa_test.cpp b/components/nif/tests/nif_bsa_test.cpp
new file mode 100644
index 0000000000..c22aad6802
--- /dev/null
+++ b/components/nif/tests/nif_bsa_test.cpp
@@ -0,0 +1,30 @@
+/*
+ Runs NIFFile through all the NIFs in Morrowind.bsa.
+ */
+
+#include "../nif_file.hpp"
+#include "../../bsa/bsa_file.hpp"
+#include "../../tools/stringops.hpp"
+#include <iostream>
+
+using namespace Mangle::Stream;
+using namespace std;
+using namespace Nif;
+
+int main(int argc, char **args)
+{
+ BSAFile bsa;
+ cout << "Reading Morrowind.bsa\n";
+ bsa.open("../../data/Morrowind.bsa");
+
+ const BSAFile::FileList &files = bsa.getList();
+
+ for(int i=0; i<files.size(); i++)
+ {
+ const char *n = files[i].name;
+ if(!ends(n, ".nif")) continue;
+
+ cout << "Decoding " << n << endl;
+ NIFFile nif(bsa.getFile(n), n);
+ }
+}
diff --git a/components/nif/tests/niftool.cpp b/components/nif/tests/niftool.cpp
new file mode 100644
index 0000000000..b34084e121
--- /dev/null
+++ b/components/nif/tests/niftool.cpp
@@ -0,0 +1,232 @@
+/*
+ Test of the NIFFile class
+ */
+
+#include "../nif_file.hpp"
+#include <iostream>
+#include <iomanip>
+#include "../../mangle/stream/servers/file_stream.hpp"
+#include "../node.hpp"
+#include "../controller.hpp"
+#include "../data.hpp"
+
+using namespace Mangle::Stream;
+using namespace std;
+using namespace Nif;
+
+// Display very verbose information
+bool verbose = false;
+
+void doVector(const Vector *vec)
+{
+ cout << "["
+ << vec->array[0] << ","
+ << vec->array[1] << ","
+ << vec->array[2] << "]\n";
+}
+
+void doVector4(const Vector4 *vec)
+{
+ cout << "["
+ << vec->array[0] << ","
+ << vec->array[1] << ","
+ << vec->array[2] << ","
+ << vec->array[3] << "]\n";
+}
+
+void doMatrix(const Matrix *mat)
+{
+ cout << "Matrix:\n";
+ for(int i=0; i<3; i++)
+ {
+ cout << " ";
+ doVector(&mat->v[i]);
+ }
+}
+
+void doTrafo(const Transformation* trafo)
+{
+ cout << "--- transformation:\n";
+ cout << "Pos: "; doVector(&trafo->pos);
+ cout << "Rot: "; doMatrix(&trafo->rotation);
+ cout << "Scale: " << trafo->scale << endl;
+ cout << "Vel: "; doVector(&trafo->velocity);
+ cout << "--- end transformation\n";
+}
+
+void doExtra(Extra *e)
+{
+ cout << "Extra: " << e->extra.getIndex() << endl;
+}
+
+void doControlled(Controlled *c)
+{
+ doExtra(c);
+ cout << "Controller: " << c->controller.getIndex() << endl;
+}
+
+void doNamed(Named *n)
+{
+ doControlled(n);
+ cout << "Name: " << n->name.toString() << endl;
+}
+
+void doNode(Node *n)
+{
+ doNamed(n);
+
+ cout << "Flags: 0x" << hex << n->flags << dec << endl;
+ doTrafo(n->trafo);
+
+ cout << "Properties:";
+ for(int i=0; i<n->props.length(); i++)
+ cout << " " << n->props.getIndex(i);
+ cout << endl;
+
+ if(n->hasBounds)
+ {
+ cout << "Bounding box:\n";
+ doVector(n->boundPos);
+ doMatrix(n->boundRot);
+ doVector(n->boundXYZ);
+ }
+
+ if(n->boneTrafo)
+ {
+ cout << "This is a bone: ";
+ if(n->boneIndex == -1)
+ cout << "root bone\n";
+ else
+ cout << "index " << n->boneIndex << endl;
+ }
+}
+
+void doNiTriShape(NiTriShape *n)
+{
+ doNode(n);
+
+ cout << "Shape data: " << n->data.getIndex() << endl;
+ cout << "Skin instance: " << n->skin.getIndex() << endl;
+}
+
+void doNiSkinData(NiSkinData *n)
+{
+ int c = n->bones.size();
+
+ cout << "Global transformation:\n";
+ doMatrix(&n->trafo->rotation);
+ doVector(&n->trafo->trans);
+ cout << "Scale: " << n->trafo->scale << endl;
+
+ cout << "Bone number: " << c << endl;
+ for(int i=0; i<c; i++)
+ {
+ NiSkinData::BoneInfo &bi = n->bones[i];
+
+ cout << "-- Bone " << i << ":\n";
+ doMatrix(&bi.trafo->rotation);
+ doVector(&bi.trafo->trans);
+ cout << "Scale: " << bi.trafo->scale << endl;
+ cout << "Unknown: "; doVector4(bi.unknown);
+ cout << "Weight number: " << bi.weights.length << endl;
+
+ if(verbose)
+ for(int j=0; j<bi.weights.length; j++)
+ {
+ const NiSkinData::VertWeight &w = bi.weights.ptr[j];
+ cout << " vert:" << w.vertex << " weight:" << w.weight << endl;
+ }
+ }
+}
+
+void doNiSkinInstance(NiSkinInstance *n)
+{
+ cout << "Data: " << n->data.getIndex() << endl;
+ cout << "Root: " << n->root.getIndex() << endl;
+ cout << "Bones:";
+ for(int i=0; i<n->bones.length(); i++)
+ cout << " " << n->bones.getIndex(i);
+ cout << endl;
+}
+
+void doNiNode(NiNode *n)
+{
+ doNode(n);
+
+ cout << "Children:";
+ for(int i=0; i<n->children.length(); i++)
+ cout << " " << n->children.getIndex(i);
+ cout << endl;
+
+ cout << "Effects:";
+ for(int i=0; i<n->effects.length(); i++)
+ cout << " " << n->effects.getIndex(i);
+ cout << endl;
+}
+
+void doNiStringExtraData(NiStringExtraData *s)
+{
+ doExtra(s);
+ cout << "String: " << s->string.toString() << endl;
+}
+
+void doNiTextKeyExtraData(NiTextKeyExtraData *t)
+{
+ doExtra(t);
+ for(int i=0; i<t->list.size(); i++)
+ {
+ cout << "@time " << t->list[i].time << ":\n\""
+ << t->list[i].text.toString() << "\"" << endl;
+ }
+}
+
+void doController(Controller *r)
+{
+ cout << "Next controller: " << r->next.getIndex() << endl;
+ cout << "Flags: " << hex << r->flags << dec << endl;
+ cout << "Frequency: " << r->frequency << endl;
+ cout << "Phase: " << r->phase << endl;
+ cout << "Time start: " << r->timeStart << endl;
+ cout << "Time stop: " << r->timeStop << endl;
+ cout << "Target: " << r->target.getIndex() << endl;
+}
+
+void doNiKeyframeController(NiKeyframeController *k)
+{
+ doController(k);
+ cout << "Data: " << k->data.getIndex() << endl;
+}
+
+int main(int argc, char **args)
+{
+ if(argc != 2)
+ {
+ cout << "Specify a NIF file on the command line\n";
+ return 1;
+ }
+
+ StreamPtr file(new FileStream(args[1]));
+ NIFFile nif(file, args[1]);
+
+ int num = nif.numRecords();
+
+ for(int i=0; i<num; i++)
+ {
+ Record *r = nif.getRecord(i);
+ cout << i << ": " << r->recName.toString() << endl;
+
+ switch(r->recType)
+ {
+ case RC_NiNode: doNiNode((NiNode*)r); break;
+ case RC_NiSkinData: doNiSkinData((NiSkinData*)r); break;
+ case RC_NiSkinInstance: doNiSkinInstance((NiSkinInstance*)r); break;
+ case RC_NiTriShape: doNiTriShape((NiTriShape*)r); break;
+ case RC_NiStringExtraData: doNiStringExtraData((NiStringExtraData*)r); break;
+ case RC_NiSequenceStreamHelper: doNamed((Named*)r); break;
+ case RC_NiTextKeyExtraData: doNiTextKeyExtraData((NiTextKeyExtraData*)r); break;
+ case RC_NiKeyframeController: doNiKeyframeController((NiKeyframeController*)r); break;
+ }
+
+ cout << endl;
+ }
+}
diff --git a/components/nif/tests/output/nif_bsa_test.out b/components/nif/tests/output/nif_bsa_test.out
new file mode 100644
index 0000000000..5499dafc79
--- /dev/null
+++ b/components/nif/tests/output/nif_bsa_test.out
@@ -0,0 +1,5799 @@
+Reading Morrowind.bsa
+Decoding meshes\m\probe_journeyman_01.nif
+Decoding meshes\b\b_n_redguard_f_skins.nif
+Decoding meshes\b\b_n_redguard_m_skins.nif
+Decoding meshes\b\b_n_redguard_f_wrist.nif
+Decoding meshes\b\b_n_redguard_m_foot.nif
+Decoding meshes\b\b_n_redguard_m_knee.nif
+Decoding meshes\b\b_n_redguard_f_knee.nif
+Decoding meshes\b\b_n_redguard_m_neck.nif
+Decoding meshes\b\b_n_redguard_f_neck.nif
+Decoding meshes\b\b_n_redguard_m_ankle.nif
+Decoding meshes\b\b_n_redguard_f_ankle.nif
+Decoding meshes\b\b_n_redguard_f_foot.nif
+Decoding meshes\b\b_n_redguard_m_wrist.nif
+Decoding meshes\b\b_n_redguard_f_groin.nif
+Decoding meshes\b\b_n_redguard_m_groin.nif
+Decoding meshes\b\b_n_redguard_m_head_02.nif
+Decoding meshes\b\b_n_redguard_m_head_06.nif
+Decoding meshes\b\b_n_redguard_m_head_04.nif
+Decoding meshes\b\b_n_redguard_f_head_02.nif
+Decoding meshes\b\b_n_redguard_f_head_06.nif
+Decoding meshes\b\b_n_redguard_f_head_04.nif
+Decoding meshes\b\b_n_redguard_m_hair_05.nif
+Decoding meshes\b\b_n_redguard_m_hair_03.nif
+Decoding meshes\b\b_n_redguard_m_hair_01.nif
+Decoding meshes\b\b_n_redguard_f_hair_05.nif
+Decoding meshes\b\b_n_redguard_f_hair_03.nif
+Decoding meshes\b\b_n_redguard_f_hair_01.nif
+Decoding meshes\b\b_n_redguard_m_forearm.nif
+Decoding meshes\b\b_n_redguard_m_head_03.nif
+Decoding meshes\b\b_n_redguard_m_head_01.nif
+Decoding meshes\b\b_n_redguard_m_head_05.nif
+Decoding meshes\b\b_n_redguard_f_head_03.nif
+Decoding meshes\b\b_n_redguard_f_head_01.nif
+Decoding meshes\b\b_n_redguard_f_head_05.nif
+Decoding meshes\b\b_n_redguard_m_hair_06.nif
+Decoding meshes\b\b_n_redguard_m_hair_04.nif
+Decoding meshes\b\b_n_redguard_m_hair_02.nif
+Decoding meshes\b\b_n_redguard_m_hair_00.nif
+Decoding meshes\b\b_n_redguard_f_hair_04.nif
+Decoding meshes\b\b_n_redguard_f_hair_02.nif
+Decoding meshes\b\b_n_redguard_f_forearm.nif
+Decoding meshes\b\b_n_redguard_f_upper leg.nif
+Decoding meshes\b\b_n_redguard_f_upper arm.nif
+Decoding meshes\b\b_n_redguard_m_upper leg.nif
+Decoding meshes\b\b_n_redguard_m_hands.1st.nif
+Decoding meshes\b\b_n_redguard_m_upper arm.nif
+Decoding meshes\b\b_n_redguard_f_hands.1st.nif
+Decoding meshes\w\w_longspear_daedric.nif
+Decoding meshes\w\w_longsword_crystal.nif
+Decoding meshes\w\w_longsword_daedric.nif
+Decoding meshes\l\light_com_lantern_01.nif
+Decoding meshes\l\light_com_candle_15.nif
+Decoding meshes\l\light_com_candle_05.nif
+Decoding meshes\l\light_com_candle_14.nif
+Decoding meshes\l\light_com_candle_04.nif
+Decoding meshes\l\light_com_candle_07.nif
+Decoding meshes\l\light_com_candle_16.nif
+Decoding meshes\l\light_com_candle_06.nif
+Decoding meshes\l\light_com_candle_11.nif
+Decoding meshes\l\light_com_candle_01.nif
+Decoding meshes\l\light_com_candle_10.nif
+Decoding meshes\l\light_com_candle_13.nif
+Decoding meshes\l\light_com_candle_03.nif
+Decoding meshes\l\light_com_candle_12.nif
+Decoding meshes\l\light_com_candle_02.nif
+Decoding meshes\l\light_com_candle_09.nif
+Decoding meshes\l\light_com_candle_08.nif
+Decoding meshes\l\light_com_sconce_02.nif
+Decoding meshes\l\light_com_sconce_01.nif
+Decoding meshes\l\light_com_lantern_02.nif
+Decoding meshes\l\light_com_chandelier_03.nif
+Decoding meshes\l\light_com_chandelier_01.nif
+Decoding meshes\l\light_com_chandelier_05.nif
+Decoding meshes\l\light_com_chandelier_02.nif
+Decoding meshes\l\light_com_chandelier_06.nif
+Decoding meshes\l\light_com_chandelier_04.nif
+Decoding meshes\f\flora_ash_grass_b_01.nif
+Decoding meshes\f\flora_ash_grass_r_01.nif
+Decoding meshes\f\flora_ash_grass_w_01.nif
+Decoding meshes\a\a_art_helm_bearclaw.nif
+Decoding meshes\l\light_com_chandelier_01_l.nif
+Decoding meshes\l\light_com_chandelier_05_l.nif
+Decoding meshes\l\light_com_chandelier_03_l.nif
+Decoding meshes\b\b_n_breton_f_head_04.nif
+Decoding meshes\b\b_n_breton_f_hair_04.nif
+Decoding meshes\b\b_n_breton_m_head_01.nif
+Decoding meshes\b\b_n_breton_f_head_01.nif
+Decoding meshes\b\b_n_breton_m_head_03.nif
+Decoding meshes\b\b_n_breton_m_hair_01.nif
+Decoding meshes\b\b_n_breton_m_head_04.nif
+Decoding meshes\b\b_n_breton_f_head_05.nif
+Decoding meshes\b\b_n_breton_m_head_07.nif
+Decoding meshes\b\b_n_breton_m_head_08.nif
+Decoding meshes\b\b_n_breton_m_head_06.nif
+Decoding meshes\b\b_n_breton_m_forearm.nif
+Decoding meshes\b\b_n_breton_m_head_05.nif
+Decoding meshes\b\b_n_breton_f_head_06.nif
+Decoding meshes\b\b_n_breton_f_hair_03.nif
+Decoding meshes\b\b_n_breton_m_hair_04.nif
+Decoding meshes\b\b_n_breton_m_hair_05.nif
+Decoding meshes\b\b_n_breton_m_hair_03.nif
+Decoding meshes\b\b_n_breton_f_head_03.nif
+Decoding meshes\b\b_n_breton_f_hair_02.nif
+Decoding meshes\b\b_n_breton_m_head_02.nif
+Decoding meshes\b\b_n_breton_f_forearm.nif
+Decoding meshes\b\b_n_breton_m_hair_02.nif
+Decoding meshes\b\b_n_breton_f_head_02.nif
+Decoding meshes\b\b_n_breton_m_hair_00.nif
+Decoding meshes\b\b_n_breton_f_hair_05.nif
+Decoding meshes\b\b_n_breton_f_hair_01.nif
+Decoding meshes\b\b_n_breton_m_upper leg.nif
+Decoding meshes\b\b_n_breton_f_upper leg.nif
+Decoding meshes\b\b_n_breton_f_upper arm.nif
+Decoding meshes\b\b_n_breton_m_upper arm.nif
+Decoding meshes\b\b_n_breton_m_hand.1st.nif
+Decoding meshes\b\b_n_breton_f_hands.1st.nif
+Decoding meshes\a\a_gondolier_m_helmet.nif
+Decoding meshes\m\light_com_candle_07.nif
+Decoding meshes\m\misc_dwrv_ark_cube00.nif
+Decoding meshes\m\misc_dwrv_artifact30.nif
+Decoding meshes\m\misc_dwrv_artifact20.nif
+Decoding meshes\m\misc_dwrv_artifact10.nif
+Decoding meshes\m\misc_dwrv_artifact00.nif
+Decoding meshes\m\misc_dwrv_artifact70.nif
+Decoding meshes\m\misc_dwrv_artifact60.nif
+Decoding meshes\m\misc_dwrv_artifact50.nif
+Decoding meshes\m\misc_dwrv_artifact40.nif
+Decoding meshes\m\misc_dwrv_artifact80.nif
+Decoding meshes\m\misc_dwrv_pitcher00.nif
+Decoding meshes\m\misc_dwrv_ark_key00.nif
+Decoding meshes\i\in_dae_hall_l_corner.nif
+Decoding meshes\i\in_dae_doorjamb_load.nif
+Decoding meshes\i\in_dae_connect_lcave.nif
+Decoding meshes\i\in_dae_platform_stairs.nif
+Decoding meshes\i\in_dae_pillar_tall.max.nif
+Decoding meshes\i\in_dae_platform_512_01.nif
+Decoding meshes\i\in_dae_room_l_roof_01.nif
+Decoding meshes\i\in_dae_room_l_floor_01.nif
+Decoding meshes\i\in_dae_room_l_roof_02.nif
+Decoding meshes\i\in_dae_room_l_side_01.nif
+Decoding meshes\i\in_dae_room_l_roof_03.nif
+Decoding meshes\i\in_dae_hall_ruin_l_02.nif
+Decoding meshes\i\in_dae_hall_l_entry_01.nif
+Decoding meshes\i\in_dae_hall_l_4way_02.nif
+Decoding meshes\i\in_dae_hall_ruin_l_01.nif
+Decoding meshes\i\in_dae_hall_l_3way_01.nif
+Decoding meshes\i\in_dae_hall_l_4way_01.nif
+Decoding meshes\i\in_dae_mezzanine_edge.nif
+Decoding meshes\i\in_dae_room_ruin_roof_03.nif
+Decoding meshes\i\in_dae_room_r_corner_02.nif
+Decoding meshes\i\in_dae_room_l_corner_02.nif
+Decoding meshes\i\in_dae_room_r_corner_01.nif
+Decoding meshes\i\in_dae_room_l_corner_01.nif
+Decoding meshes\i\in_dae_room_ruin_side_01.nif
+Decoding meshes\i\in_dae_hall_l_endcap_01.nif
+Decoding meshes\i\in_dae_hall_l_stairs_01.nif
+Decoding meshes\i\in_com_trapbottom_01.nif
+Decoding meshes\i\in_t_l_room_ceiling.nif
+Decoding meshes\i\in_v_l_int_center_02.nif
+Decoding meshes\i\in_v_l_int_corner_01.nif
+Decoding meshes\i\in_v_l_int_stairs_04.nif
+Decoding meshes\i\in_v_l_int_column_03.nif
+Decoding meshes\i\in_v_l_int_column_01.nif
+Decoding meshes\i\in_v_l_int_pillar_01.nif
+Decoding meshes\i\in_v_l_int_bridge_02.nif
+Decoding meshes\i\in_v_l_int_stairs_02.nif
+Decoding meshes\i\in_v_l_int_stairs_03.nif
+Decoding meshes\i\in_v_l_int_stairs_01.nif
+Decoding meshes\i\in_v_l_int_lwall_01.nif
+Decoding meshes\i\in_v_l_int_lwall_02.nif
+Decoding meshes\i\in_v_l_int_center_01.nif
+Decoding meshes\i\in_v_l_int_corner_03.nif
+Decoding meshes\i\in_v_l_int_column_02.nif
+Decoding meshes\i\in_v_l_int_bridge_01.nif
+Decoding meshes\i\in_v_l_int_corner_02.nif
+Decoding meshes\i\in_v_l_int_arches_01.nif
+Decoding meshes\i\in_r_l_int_center_02.nif
+Decoding meshes\i\in_r_l_int_corner_01.nif
+Decoding meshes\i\in_r_l_int_column_01.nif
+Decoding meshes\i\in_r_l_int_pillar_01.nif
+Decoding meshes\i\in_r_l_int_bridge_02.nif
+Decoding meshes\i\in_r_l_int_stairs_02.nif
+Decoding meshes\i\in_r_l_short_ramp_01.nif
+Decoding meshes\i\in_r_l_int_stairs_03.nif
+Decoding meshes\i\in_r_l_int_stairs_01.nif
+Decoding meshes\i\in_r_l_long_ramp_01.nif
+Decoding meshes\i\in_r_l_int_lwall_01.nif
+Decoding meshes\i\in_r_l_int_lwall_02.nif
+Decoding meshes\i\in_r_l_int_center_01.nif
+Decoding meshes\i\in_r_l_int_corner_03.nif
+Decoding meshes\i\in_r_l_int_pillar_02.nif
+Decoding meshes\i\in_r_l_int_bridge_01.nif
+Decoding meshes\i\in_r_l_int_corner_02.nif
+Decoding meshes\i\in_r_l_int_arches_01.nif
+Decoding meshes\i\in_v_l_int_lcenter_02.nif
+Decoding meshes\i\in_v_l_int_lcorner_01.nif
+Decoding meshes\i\in_v_l_int_lcolumn_03.nif
+Decoding meshes\i\in_v_l_int_lcolumn_01.nif
+Decoding meshes\i\in_v_l_int_ceiling_01.nif
+Decoding meshes\i\in_v_l_int_entrance_02.nif
+Decoding meshes\i\in_v_l_int_lcorner_03.nif
+Decoding meshes\i\in_v_l_int_lcenter_01.nif
+Decoding meshes\i\in_v_l_int_lcolumn_02.nif
+Decoding meshes\i\in_v_l_int_lcorner_02.nif
+Decoding meshes\i\in_v_l_int_entrance_01.nif
+Decoding meshes\i\in_r_l_int_lcenter_02.nif
+Decoding meshes\i\in_r_l_int_lcorner_01.nif
+Decoding meshes\i\in_r_l_int_lcolumn_01.nif
+Decoding meshes\i\in_r_l_int_entrance_02.nif
+Decoding meshes\i\in_r_l_int_lcorner_03.nif
+Decoding meshes\i\in_r_l_int_lcenter_01.nif
+Decoding meshes\i\in_r_l_int_lcenter_04.nif
+Decoding meshes\i\in_r_l_int_lcenter_03.nif
+Decoding meshes\i\in_r_l_int_balcony_01.nif
+Decoding meshes\i\in_r_l_int_railing_01.nif
+Decoding meshes\i\in_r_l_int_lcorner_02.nif
+Decoding meshes\i\in_r_l_int_entrance_01.nif
+Decoding meshes\i\in_r_l_int_entrance_03.nif
+Decoding meshes\i\in_t_l_room_highentry.nif
+Decoding meshes\m\misc_kwamaegg_gold_01.nif
+Decoding meshes\i\in_v_l_int_lentrance_02.nif
+Decoding meshes\i\in_v_l_int_partition_01.nif
+Decoding meshes\i\in_v_l_int_lentrance_01.nif
+Decoding meshes\i\in_r_l_int_lentrance_02.nif
+Decoding meshes\i\in_r_l_int_partition_01.nif
+Decoding meshes\i\in_r_l_int_lentrance_01.nif
+Decoding meshes\i\in_r_l_int_lpartition_01.nif
+Decoding meshes\i\cap01.nif
+Decoding meshes\i\box02.nif
+Decoding meshes\i\in_ar_s2.nif
+Decoding meshes\i\in_ar_s3.nif
+Decoding meshes\i\in_ar_s1.nif
+Decoding meshes\i\in_ar_s6.nif
+Decoding meshes\i\in_ar_s7.nif
+Decoding meshes\i\in_ar_s4.nif
+Decoding meshes\i\in_ar_s5.nif
+Decoding meshes\i\in_ar_02.nif
+Decoding meshes\i\in_ar_03.nif
+Decoding meshes\i\in_ar_01.nif
+Decoding meshes\i\in_ar_06.nif
+Decoding meshes\i\in_ar_07.nif
+Decoding meshes\i\in_ar_04.nif
+Decoding meshes\i\in_ar_05.nif
+Decoding meshes\i\in_ar_08.nif
+Decoding meshes\i\in_ar_09.nif
+Decoding meshes\i\in_ci_01.nif
+Decoding meshes\i\in_ar_10.nif
+Decoding meshes\i\in_imp_fireplace_grand.nif
+Decoding meshes\i\in_t_s_plain_hall_01.nif
+Decoding meshes\i\in_t_s_hallshaft_cap.nif
+Decoding meshes\i\in_t_s_hall_ramp_01.nif
+Decoding meshes\i\in_t_s_room_side_01.nif
+Decoding meshes\i\in_v_s_int_center_02.nif
+Decoding meshes\i\in_v_s_int_corner_01.nif
+Decoding meshes\i\in_v_s_int_column_01.nif
+Decoding meshes\i\in_v_s_int_lwall_01.nif
+Decoding meshes\i\in_v_s_int_lwall_03.nif
+Decoding meshes\i\in_v_s_int_lwall_02.nif
+Decoding meshes\i\in_v_s_int_lwall_04.nif
+Decoding meshes\i\in_v_s_int_center_01.nif
+Decoding meshes\i\in_v_s_int_corner_03.nif
+Decoding meshes\i\in_v_s_int_center_04.nif
+Decoding meshes\i\in_v_s_int_center_03.nif
+Decoding meshes\i\in_v_s_int_corner_02.nif
+Decoding meshes\i\in_v_s_int_lrail_01.nif
+Decoding meshes\i\in_r_s_int_center_02.nif
+Decoding meshes\i\in_r_s_int_center_06.nif
+Decoding meshes\i\in_r_s_int_corner_01.nif
+Decoding meshes\i\in_r_s_int_stairs_04.nif
+Decoding meshes\i\in_r_s_int_column_01.nif
+Decoding meshes\i\in_r_s_int_pillar_01.nif
+Decoding meshes\i\in_r_s_int_bridge_02.nif
+Decoding meshes\i\in_r_s_int_ledge_03.nif
+Decoding meshes\i\in_r_s_int_ledge_02.nif
+Decoding meshes\i\in_r_s_int_ledge_01.nif
+Decoding meshes\i\in_r_s_int_bridge_03.nif
+Decoding meshes\i\in_r_s_int_stairs_05.nif
+Decoding meshes\i\in_r_s_int_stairs_02.nif
+Decoding meshes\i\in_r_s_int_center_05.nif
+Decoding meshes\i\in_r_s_short_ramp_01.nif
+Decoding meshes\i\in_r_s_int_stairs_03.nif
+Decoding meshes\i\in_r_s_int_stairs_01.nif
+Decoding meshes\i\in_r_s_long_ramp_01.nif
+Decoding meshes\i\in_r_s_int_lwall_01.nif
+Decoding meshes\i\in_r_s_int_lwall_02.nif
+Decoding meshes\i\in_r_s_int_center_01.nif
+Decoding meshes\i\in_r_s_int_corner_03.nif
+Decoding meshes\i\in_r_s_int_center_04.nif
+Decoding meshes\i\in_r_s_int_pillar_02.nif
+Decoding meshes\i\in_r_s_int_center_03.nif
+Decoding meshes\i\in_r_s_int_bridge_01.nif
+Decoding meshes\i\in_r_s_int_corner_02.nif
+Decoding meshes\i\in_r_s_int_steps_01.nif
+Decoding meshes\i\in_r_s_int_steps_02.nif
+Decoding meshes\i\in_r_s_int_steps_03.nif
+Decoding meshes\i\in_r_s_int_stairs_06.nif
+Decoding meshes\i\in_t_s_hall_endcap_01.nif
+Decoding meshes\i\in_v_s_int_lcenter_02.nif
+Decoding meshes\i\in_v_s_int_lstairs_04.nif
+Decoding meshes\i\in_v_s_int_lcorner_01.nif
+Decoding meshes\i\in_v_s_int_lcolumn_03.nif
+Decoding meshes\i\in_v_s_int_lcolumn_01.nif
+Decoding meshes\i\in_v_s_int_lbridge_02.nif
+Decoding meshes\i\in_v_s_int_lpillar_01.nif
+Decoding meshes\i\in_v_s_int_entrance_02.nif
+Decoding meshes\i\in_v_s_int_lstairs_03.nif
+Decoding meshes\i\in_v_s_int_lstairs_02.nif
+Decoding meshes\i\in_v_s_int_lcorner_03.nif
+Decoding meshes\i\in_v_s_int_lcenter_01.nif
+Decoding meshes\i\in_v_s_int_lcenter_04.nif
+Decoding meshes\i\in_v_s_int_lstairs_01.nif
+Decoding meshes\i\in_v_s_int_lcolumn_02.nif
+Decoding meshes\i\in_v_s_int_larches_01.nif
+Decoding meshes\i\in_v_s_int_lcenter_03.nif
+Decoding meshes\i\in_v_s_int_lcorner_02.nif
+Decoding meshes\i\in_v_s_int_lbridge_01.nif
+Decoding meshes\i\in_v_s_int_entrance_01.nif
+Decoding meshes\i\in_v_s_lint_center_01.nif
+Decoding meshes\i\in_r_s_int_lcenter_02.nif
+Decoding meshes\i\in_r_s_int_lcenter_06.nif
+Decoding meshes\i\in_r_s_int_lcorner_01.nif
+Decoding meshes\i\in_r_s_int_lcolumn_01.nif
+Decoding meshes\i\in_r_s_int_entrance_02.nif
+Decoding meshes\i\in_r_s_int_lcenter_05.nif
+Decoding meshes\i\in_r_s_int_lcorner_03.nif
+Decoding meshes\i\in_r_s_int_lcenter_01.nif
+Decoding meshes\i\in_r_s_int_lcenter_04.nif
+Decoding meshes\i\in_r_s_int_larches_01.nif
+Decoding meshes\i\in_r_s_int_lcenter_03.nif
+Decoding meshes\i\in_r_s_int_balcony_01.nif
+Decoding meshes\i\in_r_s_int_railing_01.nif
+Decoding meshes\i\in_r_s_int_lcorner_02.nif
+Decoding meshes\i\in_r_s_int_entrance_01.nif
+Decoding meshes\i\in_t_s_pillar_large_01.nif
+Decoding meshes\i\in_t_s_plain_hall_ramp.nif
+Decoding meshes\i\in_t_s_plain_turret_02.nif
+Decoding meshes\i\in_t_s_plain_shaft_cap.nif
+Decoding meshes\i\in_t_s_plain_hall_4way.nif
+Decoding meshes\i\in_t_s_plain_hall_3way.nif
+Decoding meshes\i\in_t_s_plain_hall_plug.nif
+Decoding meshes\i\in_t_s_shaft_elbow_01.nif
+Decoding meshes\i\in_t_s_shaft_vconnect.nif
+Decoding meshes\i\in_v_s_wall_column_01.nif
+Decoding meshes\i\in_v_s_wall_column_02.nif
+Decoding meshes\i\in_t_s_hall_small_corner.nif
+Decoding meshes\i\in_t_s_plain_hall_corner.nif
+Decoding meshes\i\in_t_s_plain_hall_endcap.nif
+Decoding meshes\i\in_t_s_plain_room_center.nif
+Decoding meshes\i\in_v_s_int_lentrance_02.nif
+Decoding meshes\i\in_v_s_int_lentrance_04.nif
+Decoding meshes\i\in_v_s_int_lentrance_06.nif
+Decoding meshes\i\in_v_s_int_lentrance_01.nif
+Decoding meshes\i\in_v_s_int_lentrance_03.nif
+Decoding meshes\i\in_v_s_int_lentrance_05.nif
+Decoding meshes\i\in_v_s_int_lpartition_01.nif
+Decoding meshes\i\in_r_s_int_lentrance_02.nif
+Decoding meshes\i\in_r_s_int_lentrance_04.nif
+Decoding meshes\i\in_r_s_int_lentrance_06.nif
+Decoding meshes\i\in_r_s_int_partition_01.nif
+Decoding meshes\i\in_r_s_int_lentrance_01.nif
+Decoding meshes\i\in_r_s_int_lentrance_03.nif
+Decoding meshes\i\in_r_s_int_lentrance_05.nif
+Decoding meshes\i\in_r_s_int_lplatform_01.nif
+Decoding meshes\i\in_r_s_int_lpartition_01.nif
+Decoding meshes\m\apparatus_a_spipe_01.nif
+Decoding meshes\m\apparatus_s_alembic_01.nif
+Decoding meshes\m\apparatus_g_alembic_01.nif
+Decoding meshes\m\apparatus_a_alembic_01.nif
+Decoding meshes\m\apparatus_m_alembic_01.nif
+Decoding meshes\m\apparatus_j_alembic_01.nif
+Decoding meshes\m\apparatus_s_retort_01.nif
+Decoding meshes\m\apparatus_m_retort_01.nif
+Decoding meshes\m\apparatus_j_retort_01.nif
+Decoding meshes\m\apparatus_g_retort_01.nif
+Decoding meshes\m\apparatus_a_retort_01.nif
+Decoding meshes\i\in_dae_pillar_verytall.max.nif
+Decoding meshes\i\in_dae_hall_l_staircurve2.nif
+Decoding meshes\i\in_dae_hall_l_staircurve1.nif
+Decoding meshes\i\in_dae_room_l_cornerout_01.nif
+Decoding meshes\i\in_dae_room_ruin_cornerout.nif
+Decoding meshes\i\scene root.nif
+Decoding meshes\i\in_t_s_plain_hallshaft_cap.nif
+Decoding meshes\m\apparatus_a_calcinator_01.nif
+Decoding meshes\a\a_dragonscale_cuirass.nif
+Decoding meshes\a\a_dragonscale_cuir_gnd.nif
+Decoding meshes\l\light_ashl_lantern_01.nif
+Decoding meshes\l\light_ashl_lantern_04.nif
+Decoding meshes\l\light_ashl_lantern_05.nif
+Decoding meshes\l\light_ashl_lantern_07.nif
+Decoding meshes\l\light_ashl_lantern_03.nif
+Decoding meshes\l\light_ashl_lantern_02.nif
+Decoding meshes\l\light_ashl_lantern_06.nif
+Decoding meshes\d\door_cavern_doors20.nif
+Decoding meshes\d\door_cavern_doors10.nif
+Decoding meshes\d\door_cavern_doors00.nif
+Decoding meshes\m\misc_6th_ash_statue_01.nif
+Decoding meshes\b\b_n_argonian_m_wrist.nif
+Decoding meshes\b\b_n_argonian_m_skins.nif
+Decoding meshes\b\b_n_argonian_f_skins.nif
+Decoding meshes\b\b_n_argonian_f_neck.nif
+Decoding meshes\b\b_n_argonian_m_neck.nif
+Decoding meshes\b\b_n_argonian_f_wrist.nif
+Decoding meshes\b\b_n_argonian_f_knee.nif
+Decoding meshes\b\b_n_argonian_m_groin.nif
+Decoding meshes\b\b_n_argonian_m_knee.nif
+Decoding meshes\b\b_n_argonian_f_groin.nif
+Decoding meshes\b\b_n_argonian_f_ankle.nif
+Decoding meshes\b\b_n_argonian_m_ankle.nif
+Decoding meshes\b\b_n_argonian_m_hair02.nif
+Decoding meshes\b\b_n_argonian_f_hair02.nif
+Decoding meshes\b\b_n_argonian_m_hair06.nif
+Decoding meshes\b\b_n_argonian_m_head_02.nif
+Decoding meshes\b\b_n_argonian_f_head_02.nif
+Decoding meshes\b\b_n_argonian_f_hair03.nif
+Decoding meshes\b\b_n_argonian_m_hair03.nif
+Decoding meshes\b\b_n_argonian_m_forearm.nif
+Decoding meshes\b\b_n_argonian_m_hair04.nif
+Decoding meshes\b\b_n_argonian_f_hair04.nif
+Decoding meshes\b\b_n_argonian_f_hair01.nif
+Decoding meshes\b\b_n_argonian_m_hair01.nif
+Decoding meshes\b\b_n_argonian_m_head_03.nif
+Decoding meshes\b\b_n_argonian_m_head_01.nif
+Decoding meshes\b\b_n_argonian_f_head_03.nif
+Decoding meshes\b\b_n_argonian_f_head_01.nif
+Decoding meshes\b\b_n_argonian_m_hair05.nif
+Decoding meshes\b\b_n_argonian_f_hair05.nif
+Decoding meshes\b\b_n_argonian_f_upper leg.nif
+Decoding meshes\b\b_n_argonian_m_upper leg.nif
+Decoding meshes\b\b_n_argonian_m_hands.1st.nif
+Decoding meshes\b\b_n_argonian_f_hands.1st.nif
+Decoding meshes\b\b_n_argonian_f_upper arm.nif
+Decoding meshes\b\b_n_argonian_m_upper arm.nif
+Decoding meshes\c\c_f_pants_g_common00.nif
+Decoding meshes\c\c_f_pants_a_common00.nif
+Decoding meshes\c\c_f_pants_k_common00.nif
+Decoding meshes\c\c_f_pants_k_common02.nif
+Decoding meshes\c\c_f_pants_a_common02.nif
+Decoding meshes\c\c_f_pants_g_common02.nif
+Decoding meshes\c\c_f_pants_k_common01.nif
+Decoding meshes\c\c_f_pants_g_common01.nif
+Decoding meshes\c\c_f_pants_a_common01.nif
+Decoding meshes\c\c_f_pants_ul_common00.nif
+Decoding meshes\c\c_f_pants_ul_common02.nif
+Decoding meshes\c\c_f_pants_ul_common01.nif
+Decoding meshes\c\c_f_pants_common_4_b_a.nif
+Decoding meshes\c\c_f_pants_common_4_b_g.nif
+Decoding meshes\c\c_f_pants_common_4_b_k.nif
+Decoding meshes\c\c_f_pants_common_4_b_ul.nif
+Decoding meshes\b\b_n_high elf_f_skins.nif
+Decoding meshes\b\b_n_high elf_m_skins.nif
+Decoding meshes\b\b_n_high elf_m_foot.nif
+Decoding meshes\b\b_n_high elf_m_wrist.nif
+Decoding meshes\b\b_n_high elf_m_knee.nif
+Decoding meshes\b\b_n_high elf_f_knee.nif
+Decoding meshes\b\b_n_high elf_f_groin.nif
+Decoding meshes\b\b_n_high elf_m_groin.nif
+Decoding meshes\b\b_n_high elf_m_neck.nif
+Decoding meshes\b\b_n_high elf_f_neck.nif
+Decoding meshes\b\b_n_high elf_f_wrist.nif
+Decoding meshes\b\b_n_high elf_f_foot.nif
+Decoding meshes\b\b_n_high elf_m_ankle.nif
+Decoding meshes\b\b_n_high elf_f_ankle.nif
+Decoding meshes\b\b_n_high elf_f_head_02.nif
+Decoding meshes\b\b_n_high elf_f_head_06.nif
+Decoding meshes\b\b_n_high elf_f_head_04.nif
+Decoding meshes\b\b_n_high elf_m_head_02.nif
+Decoding meshes\b\b_n_high elf_m_head_06.nif
+Decoding meshes\b\b_n_high elf_m_head_04.nif
+Decoding meshes\b\b_n_high elf_f_hair_01.nif
+Decoding meshes\b\b_n_high elf_f_hair_03.nif
+Decoding meshes\b\b_n_high elf_m_hair_05.nif
+Decoding meshes\b\b_n_high elf_m_hair_01.nif
+Decoding meshes\b\b_n_high elf_m_hair_03.nif
+Decoding meshes\b\b_n_high elf_m_forearm.nif
+Decoding meshes\b\b_n_high elf_f_head_03.nif
+Decoding meshes\b\b_n_high elf_f_head_01.nif
+Decoding meshes\b\b_n_high elf_f_head_05.nif
+Decoding meshes\b\b_n_high elf_m_head_03.nif
+Decoding meshes\b\b_n_high elf_m_head_01.nif
+Decoding meshes\b\b_n_high elf_m_head_05.nif
+Decoding meshes\b\b_n_high elf_f_hair_04.nif
+Decoding meshes\b\b_n_high elf_f_hair_02.nif
+Decoding meshes\b\b_n_high elf_m_hair_04.nif
+Decoding meshes\b\b_n_high elf_m_hair_02.nif
+Decoding meshes\b\b_n_high elf_f_forearm.nif
+Decoding meshes\b\b_n_high elf_m_upper arm.nif
+Decoding meshes\b\b_n_high elf_f_upper leg.nif
+Decoding meshes\b\b_n_high elf_m_upper leg.nif
+Decoding meshes\b\b_n_high elf_f_hands.1st.nif
+Decoding meshes\b\b_n_high elf_m_hands.1st.nif
+Decoding meshes\b\b_n_high elf_f_upper arm.nif
+Decoding meshes\f\flora_bc_mushroom_08.nif
+Decoding meshes\f\flora_bc_lilypad_02.nif
+Decoding meshes\f\flora_bc_lilypad_03.nif
+Decoding meshes\f\flora_bc_lilypad_01.nif
+Decoding meshes\f\flora_bc_mushroom_05.nif
+Decoding meshes\f\flora_bc_mushroom_01.nif
+Decoding meshes\f\flora_bc_mushroom_02.nif
+Decoding meshes\f\flora_bc_mushroom_03.nif
+Decoding meshes\f\flora_bc_podplant_03.nif
+Decoding meshes\f\flora_bc_podplant_02.nif
+Decoding meshes\f\flora_bc_mushroom_07.nif
+Decoding meshes\f\flora_bc_mushroom_06.nif
+Decoding meshes\f\flora_bc_podplant_01.nif
+Decoding meshes\f\flora_bc_mushroom_04.nif
+Decoding meshes\f\flora_bc_shelffungus_02.nif
+Decoding meshes\f\flora_bc_shelffungus_04.nif
+Decoding meshes\f\flora_bc_shelffungus_03.nif
+Decoding meshes\f\flora_bc_shelffungus_01.nif
+Decoding meshes\m\misc_muck_shovel_01.nif
+Decoding meshes\m\pick_secretmaster_01.nif
+Decoding meshes\c\c_f_shirt_c_common01.nif
+Decoding meshes\c\c_f_shirt_common_4_a_c.nif
+Decoding meshes\c\c_f_shirt_common_4_c_c.nif
+Decoding meshes\c\c_f_shirt_common_4_b_c.nif
+Decoding meshes\c\c_f_shirt_c_commonl04.nif
+Decoding meshes\c\c_f_shirt_c_commonl02.nif
+Decoding meshes\c\c_f_skirt_g_common01.nif
+Decoding meshes\c\c_f_skirt_g_common_4_c.nif
+Decoding meshes\f\flora_emp_parasol_01.nif
+Decoding meshes\f\flora_emp_parasol_02.nif
+Decoding meshes\f\flora_emp_parasol_03.nif
+Decoding meshes\c\c_m_robe_common_02r.nif
+Decoding meshes\c\c_m_robe_common_02t.nif
+Decoding meshes\c\c_m_robe_common_03a.nif
+Decoding meshes\c\c_m_robe_common_05a.nif
+Decoding meshes\c\c_m_robe_common_05c.nif
+Decoding meshes\c\c_m_robe_common_03b.nif
+Decoding meshes\c\c_m_robe_common_05b.nif
+Decoding meshes\c\c_m_robe_common_02h.nif
+Decoding meshes\c\c_m_robe_common_02tt.nif
+Decoding meshes\c\c_m_robe_expensive_1.nif
+Decoding meshes\c\c_m_robe_exquisite_1.nif
+Decoding meshes\c\c_m_robe_common_02rr.nif
+Decoding meshes\c\c_m_robe_extrav_1_c.nif
+Decoding meshes\c\c_m_robe_common_02hh.nif
+Decoding meshes\c\c_m_robe_expensive_2.nif
+Decoding meshes\c\c_m_robe_common_02_gnd.nif
+Decoding meshes\c\c_m_robe_common_01_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_2_gnd.nif
+Decoding meshes\c\c_m_robe_expens_3.1st.nif
+Decoding meshes\c\c_m_robe_common_4.1st.nif
+Decoding meshes\c\c_m_robe_common_4_gnd.nif
+Decoding meshes\c\c_m_robe_common_3_gnd.nif
+Decoding meshes\c\c_m_robe_common_5.1st.nif
+Decoding meshes\c\c_m_robe_common_02.1st.nif
+Decoding meshes\c\c_m_robe_common_01.1st.nif
+Decoding meshes\c\c_m_robe_expens_3_gnd.nif
+Decoding meshes\c\c_m_robe_common_3.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_2.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1r_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1t_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1h_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1a_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1b_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1c_gnd.nif
+Decoding meshes\c\c_m_robe_common_5_gnd.nif
+Decoding meshes\c\c_m_robe_extrav_1t.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1r.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1a.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1b.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1c.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1h.1st.nif
+Decoding meshes\c\c_m_robe_expensive_2a.nif
+Decoding meshes\c\c_m_robe_extrav_1.1st.nif
+Decoding meshes\c\c_m_robe_exquisite_1.1st.nif
+Decoding meshes\c\c_m_robe_common_02hh_gnd.nif
+Decoding meshes\c\c_m_robe_common_02tt.1st.nif
+Decoding meshes\c\c_m_robe_common_02rr.1st.nif
+Decoding meshes\c\c_m_robe_common_02hh.1st.nif
+Decoding meshes\c\c_m_robe_exquisite_1_gnd.nif
+Decoding meshes\c\c_m_robe_common_05a.1st.nif
+Decoding meshes\c\c_m_robe_common_05b.1st.nif
+Decoding meshes\c\c_m_robe_common_05c.1st.nif
+Decoding meshes\c\c_m_robe_extrav_1_c.1st.nif
+Decoding meshes\c\c_m_robe_common_03b_gnd.nif
+Decoding meshes\c\c_m_robe_common_03a_gnd.nif
+Decoding meshes\c\c_m_robe_expensive_1_gnd.nif
+Decoding meshes\c\c_m_robe_common_05a_gnd.nif
+Decoding meshes\c\c_m_robe_common_05b_gnd.nif
+Decoding meshes\c\c_m_robe_common_05c_gnd.nif
+Decoding meshes\c\c_m_robe_common_02tt_gnd.nif
+Decoding meshes\c\c_m_robe_common_03b.1st.nif
+Decoding meshes\c\c_m_robe_common_03a.1st.nif
+Decoding meshes\c\c_m_robe_expensive_1.1st.nif
+Decoding meshes\c\c_m_robe_expensive_2.1st.nif
+Decoding meshes\c\c_m_robe_expensive_2_gnd.nif
+Decoding meshes\c\c_m_robe_common_02h.1st.nif
+Decoding meshes\c\c_m_robe_common_02t.1st.nif
+Decoding meshes\c\c_m_robe_common_02r.1st.nif
+Decoding meshes\c\c_m_robe_common_02rr_gnd.nif
+Decoding meshes\c\c_m_robe_common_02h_gnd.nif
+Decoding meshes\c\c_m_robe_common_02r_gnd.nif
+Decoding meshes\c\c_m_robe_common_02t_gnd.nif
+Decoding meshes\m\misc_argonianhead_01.nif
+Decoding meshes\lavasteam.nif
+Decoding meshes\left_arrow.nif
+Decoding meshes\f\furn_ex_ashl_guarskin.nif
+Decoding meshes\l\light_fire.nif
+Decoding meshes\lower_arrow.nif
+Decoding meshes\lava_sparks.nif
+Decoding meshes\c\c_m_robe_expensive_2a_gnd.nif
+Decoding meshes\c\c_m_robe_expensive_2a.1st.nif
+Decoding meshes\b\b_n_khajiit_m_ankle.nif
+Decoding meshes\b\b_n_khajiit_f_ankle.nif
+Decoding meshes\b\b_n_khajiit_m_hair02.nif
+Decoding meshes\b\b_n_khajiit_f_hair02.nif
+Decoding meshes\b\b_n_khajiit_m_hair04.nif
+Decoding meshes\b\b_n_khajiit_f_hair04.nif
+Decoding meshes\b\b_n_khajiit_f_groin.nif
+Decoding meshes\b\b_n_khajiit_m_groin.nif
+Decoding meshes\b\b_n_khajiit_m_wrist.nif
+Decoding meshes\b\b_n_khajiit_f_wrist.nif
+Decoding meshes\b\b_n_khajiit_f_skins.nif
+Decoding meshes\b\b_n_khajiit_m_skins.nif
+Decoding meshes\b\b_n_khajiit_f_hair03.nif
+Decoding meshes\b\b_n_khajiit_m_hair03.nif
+Decoding meshes\b\b_n_khajiit_m_hair05.nif
+Decoding meshes\b\b_n_khajiit_f_hair05.nif
+Decoding meshes\b\b_n_khajiit_f_hair01.nif
+Decoding meshes\b\b_n_khajiit_m_hair01.nif
+Decoding meshes\b\b_n_khajiit_f_forearm.nif
+Decoding meshes\b\b_n_khajiit_f_head_04.nif
+Decoding meshes\b\b_n_khajiit_m_head_01.nif
+Decoding meshes\b\b_n_khajiit_m_head_03.nif
+Decoding meshes\b\b_n_khajiit_m_forearm.nif
+Decoding meshes\b\b_n_khajiit_m_head_04.nif
+Decoding meshes\b\b_n_khajiit_f_head_03.nif
+Decoding meshes\b\b_n_khajiit_f_head_02.nif
+Decoding meshes\b\b_n_khajiit_m_head_02.nif
+Decoding meshes\b\b_n_khajiit_f_head_01.nif
+Decoding meshes\b\b_n_khajiit_f_upper arm.nif
+Decoding meshes\b\b_n_khajiit_m_upper arm.nif
+Decoding meshes\b\b_n_khajiit_m_upper leg.nif
+Decoding meshes\b\b_n_khajiit_f_upper leg.nif
+Decoding meshes\b\b_n_khajiit_f_hands.1st.nif
+Decoding meshes\b\b_n_khajiit_m_hands.1st.nif
+Decoding meshes\o\flora_marshmerrow_02.nif
+Decoding meshes\o\flora_marshmerrow_03.nif
+Decoding meshes\o\flora_marshmerrow_01.nif
+Decoding meshes\f\furn_pycave_spout00.nif
+Decoding meshes\m\gold_100.nif
+Decoding meshes\m\gold_025.nif
+Decoding meshes\m\gold_010.nif
+Decoding meshes\m\gold_001.nif
+Decoding meshes\m\gold_005.nif
+Decoding meshes\menu_help.nif
+Decoding meshes\menu_main.nif
+Decoding meshes\menu_book.nif
+Decoding meshes\marker_light.nif
+Decoding meshes\marker_north.nif
+Decoding meshes\marker_error.nif
+Decoding meshes\marker_arrow.nif
+Decoding meshes\m\misc_quill.nif
+Decoding meshes\menu_scroll.nif
+Decoding meshes\menu_target.nif
+Decoding meshes\a\a_bonemold_cuirass_c.nif
+Decoding meshes\a\a_bonemold_bracer_w.nif
+Decoding meshes\a\a_bonemold_boot_gnd.nif
+Decoding meshes\a\a_bonemold_greaves_g.nif
+Decoding meshes\a\a_bonemold_greaves_k.nif
+Decoding meshes\a\a_bonemold_pauldron_fa.nif
+Decoding meshes\a\a_bonemold_pauldron_ua.nif
+Decoding meshes\a\a_bonemold_cuirass_gnd.nif
+Decoding meshes\a\a_bonemold_gah_julan_c.nif
+Decoding meshes\a\a_bonemold_armun_an_ua.nif
+Decoding meshes\a\a_bonemold_greaves_gnd.nif
+Decoding meshes\a\a_bonemold_bracer_gnd.nif
+Decoding meshes\a\a_bonemold_armun_an_cl.nif
+Decoding meshes\a\a_bonemold_greaves_ul.nif
+Decoding meshes\a\a_bonemold_gah_julan_h.nif
+Decoding meshes\a\a_bonemold_gah_j_ua_gnd.nif
+Decoding meshes\a\a_bonemold_pauldron_gnd.nif
+Decoding meshes\a\a_bonemold_armun_an_helm.nif
+Decoding meshes\a\a_bonemold_gah_julan_cl.nif
+Decoding meshes\a\a_bonemold_gah_julan_ua.nif
+Decoding meshes\a\a_bonemold_armun_ua_gnd.nif
+Decoding meshes\a\a_bonemold_chuzei_helmet.nif
+Decoding meshes\d\door_redoran_tower_01.nif
+Decoding meshes\a\a_bonemold_gah_julan_cgnd.nif
+Decoding meshes\c\c_m_bracer_w_leather01.nif
+Decoding meshes\b\b_n_orc_m_upper leg.nif
+Decoding meshes\b\b_n_orc_f_upper leg.nif
+Decoding meshes\b\b_n_orc_m_hands.1st.nif
+Decoding meshes\b\b_n_orc_f_hands.1st.nif
+Decoding meshes\b\b_n_orc_f_upper arm.nif
+Decoding meshes\b\b_n_orc_m_upper arm.nif
+Decoding meshes\c\c_m_bracer_w_clothwrap02.nif
+Decoding meshes\l\light_de_lantern_08.nif
+Decoding meshes\l\light_de_lantern_09.nif
+Decoding meshes\l\light_de_lantern_04.nif
+Decoding meshes\l\light_de_lantern_14.nif
+Decoding meshes\l\light_de_lantern_05.nif
+Decoding meshes\l\light_de_lantern_06.nif
+Decoding meshes\l\light_de_lantern_07.nif
+Decoding meshes\l\light_de_lantern_10.nif
+Decoding meshes\l\light_de_lantern_01.nif
+Decoding meshes\l\light_de_lantern_11.nif
+Decoding meshes\l\light_de_lantern_02.nif
+Decoding meshes\l\light_de_lantern_12.nif
+Decoding meshes\l\light_de_lantern_03.nif
+Decoding meshes\l\light_de_lantern_13.nif
+Decoding meshes\l\light_dae_brazier00.nif
+Decoding meshes\l\light_dwrv_neonbroke00.nif
+Decoding meshes\l\light_de_candle_red_01.nif
+Decoding meshes\l\light_de_candle_red_02.nif
+Decoding meshes\l\light_de_candle_green_01.nif
+Decoding meshes\l\light_de_streetlight_01.nif
+Decoding meshes\l\light_de_candle_blue_01.nif
+Decoding meshes\l\light_de_candle_ivory_01.nif
+Decoding meshes\l\light_de_candle_blue_02.nif
+Decoding meshes\o\contain_de_closet_02.nif
+Decoding meshes\o\contain_com_chest_01.nif
+Decoding meshes\o\contain_egg_kwama00.nif
+Decoding meshes\o\contain_tramaroot_05.nif
+Decoding meshes\o\contain_tramaroot_02.nif
+Decoding meshes\o\contain_pot_blue_02.nif
+Decoding meshes\o\contain_pot_blue_01.nif
+Decoding meshes\o\contain_tramaroot_03.nif
+Decoding meshes\o\contain_com_chest_02.nif
+Decoding meshes\o\contain_ropecage_01.nif
+Decoding meshes\o\contain_com_hutch_01.nif
+Decoding meshes\o\contain_dwrv_desk00.nif
+Decoding meshes\o\contain_de_closet_01.nif
+Decoding meshes\o\contain_de_table_02.nif
+Decoding meshes\o\contain_de_table_01.nif
+Decoding meshes\o\contain_tramaroot_06.nif
+Decoding meshes\o\contain_com_sack_02.nif
+Decoding meshes\o\contain_com_sack_03.nif
+Decoding meshes\o\contain_com_sack_01.nif
+Decoding meshes\o\contain_tramaroot_01.nif
+Decoding meshes\o\contain_dwrv_table00.nif
+Decoding meshes\o\contain_de_chest_02.nif
+Decoding meshes\o\contain_de_chest_01.nif
+Decoding meshes\o\contain_tramaroot_04.nif
+Decoding meshes\o\contain_dwrv_chest10.nif
+Decoding meshes\o\contain_dwrv_chest00.nif
+Decoding meshes\o\contain_dwrv_drawers00.nif
+Decoding meshes\o\contain_de_drawers_01.nif
+Decoding meshes\o\contain_rock_ebony_03.nif
+Decoding meshes\o\contain_cavern_spore00.nif
+Decoding meshes\o\contain_pot_redware_01.nif
+Decoding meshes\o\contain_rock_glass_03.nif
+Decoding meshes\o\contain_rock_glass_04.nif
+Decoding meshes\o\contain_ropesphere_01.nif
+Decoding meshes\o\contain_trama_shrub_06.nif
+Decoding meshes\o\contain_trama_shrub_04.nif
+Decoding meshes\o\contain_trama_shrub_02.nif
+Decoding meshes\o\contain_rock_glass_01.nif
+Decoding meshes\o\contain_rock_ebony_04.nif
+Decoding meshes\o\contain_rock_ebony_07.nif
+Decoding meshes\o\contain_rock_ebony_06.nif
+Decoding meshes\o\contain_com_basket_01.nif
+Decoding meshes\o\contain_com_closet_01.nif
+Decoding meshes\o\contain_de_crate_logo.nif
+Decoding meshes\o\contain_rock_glass_05.nif
+Decoding meshes\o\contain_rock_ebony_05.nif
+Decoding meshes\o\contain_pot_mottled_01.nif
+Decoding meshes\o\contain_chest_small_02.nif
+Decoding meshes\o\contain_rock_ebony_02.nif
+Decoding meshes\o\contain_rock_glass_07.nif
+Decoding meshes\o\contain_rock_ebony_01.nif
+Decoding meshes\o\contain_dwrv_closet00.nif
+Decoding meshes\o\contain_trama_shrub_05.nif
+Decoding meshes\o\contain_trama_shrub_03.nif
+Decoding meshes\o\contain_trama_shrub_01.nif
+Decoding meshes\o\contain_chest_large_01.nif
+Decoding meshes\o\contain_com_drawers_01.nif
+Decoding meshes\o\contain_de_drawers_02.nif
+Decoding meshes\o\contain_dwrv_barrel10.nif
+Decoding meshes\o\contain_dwrv_barrel00.nif
+Decoding meshes\o\contain_rock_glass_06.nif
+Decoding meshes\o\contain_ropesphere_02.nif
+Decoding meshes\o\contain_rock_glass_02.nif
+Decoding meshes\o\contain_chest_small_01.nif
+Decoding meshes\o\contain_rock_diamond_01.nif
+Decoding meshes\o\contain_rock_diamond_03.nif
+Decoding meshes\o\contain_rock_diamond_05.nif
+Decoding meshes\o\contain_rock_diamond_07.nif
+Decoding meshes\o\contain_rock_diamond_02.nif
+Decoding meshes\o\contain_rock_diamond_04.nif
+Decoding meshes\o\contain_rock_diamond_06.nif
+Decoding meshes\o\contain_com_cupboard_01.nif
+Decoding meshes\o\lootbag.nif
+Decoding meshes\x\furn_de_lightpost_01.nif
+Decoding meshes\c\c_m_pants_extrav_2_g.nif
+Decoding meshes\c\c_m_pants_extrav_1_g.nif
+Decoding meshes\c\c_m_pants_extrav_1_k.nif
+Decoding meshes\c\c_m_pants_common_3_k.nif
+Decoding meshes\c\c_m_pants_common_5_k.nif
+Decoding meshes\c\c_m_pants_g_common00.nif
+Decoding meshes\c\c_m_pants_common_5_g.nif
+Decoding meshes\c\c_m_pants_common_3_g.nif
+Decoding meshes\c\c_m_pants_a_common00.nif
+Decoding meshes\c\c_m_pants_k_common00.nif
+Decoding meshes\c\c_m_pants_k_common02.nif
+Decoding meshes\c\c_m_pants_a_common02.nif
+Decoding meshes\c\c_m_pants_g_common02.nif
+Decoding meshes\c\c_m_pants_k_common01.nif
+Decoding meshes\c\c_m_pants_g_common01.nif
+Decoding meshes\c\c_m_pants_a_common01.nif
+Decoding meshes\c\c_m_pants_extrav_2_k.nif
+Decoding meshes\c\c_m_pants_extrav_1_a.nif
+Decoding meshes\c\c_m_pants_extrav_2_a.nif
+Decoding meshes\c\c_m_pants_common_5_a.nif
+Decoding meshes\c\c_m_pants_common_3_a.nif
+Decoding meshes\c\c_m_pants_expens_3_a.nif
+Decoding meshes\c\c_m_pants_common_3c_k.nif
+Decoding meshes\c\c_m_pants_extrav_1_ul.nif
+Decoding meshes\c\c_m_pants_extrav_2_ul.nif
+Decoding meshes\c\c_m_pants_ul_common00.nif
+Decoding meshes\c\c_m_pants_common_3_ul.nif
+Decoding meshes\c\c_m_pants_common_5_ul.nif
+Decoding meshes\c\c_m_pants_common_3c_g.nif
+Decoding meshes\c\c_m_pants_common_3b_g.nif
+Decoding meshes\c\c_m_pants_ul_common02.nif
+Decoding meshes\c\c_m_pants_gnd_common02.nif
+Decoding meshes\c\c_m_pants_gnd_common00.nif
+Decoding meshes\c\c_m_pants_ul_common01.nif
+Decoding meshes\c\c_m_pants_common_3b_a.nif
+Decoding meshes\c\c_m_pants_common_3c_a.nif
+Decoding meshes\c\c_m_pants_expens_1_e_g.nif
+Decoding meshes\c\c_m_pants_expens_1_e_a.nif
+Decoding meshes\c\c_m_pants_expens_1_e_k.nif
+Decoding meshes\c\c_m_pants_expens_1_a_g.nif
+Decoding meshes\c\c_m_pants_expens_1_a_a.nif
+Decoding meshes\c\c_m_pants_expens_1_a_k.nif
+Decoding meshes\c\c_m_pants_expens_1_z_g.nif
+Decoding meshes\c\c_m_pants_expens_1_z_a.nif
+Decoding meshes\c\c_m_pants_expens_1_z_k.nif
+Decoding meshes\c\c_m_pants_common_1_z_k.nif
+Decoding meshes\c\c_m_pants_common_1_z_a.nif
+Decoding meshes\c\c_m_pants_common_1_z_g.nif
+Decoding meshes\c\c_m_pants_common_1_u_k.nif
+Decoding meshes\c\c_m_pants_common_1_u_a.nif
+Decoding meshes\c\c_m_pants_common_1_u_g.nif
+Decoding meshes\c\c_m_pants_common_1_e_k.nif
+Decoding meshes\c\c_m_pants_common_1_e_a.nif
+Decoding meshes\c\c_m_pants_common_1_e_g.nif
+Decoding meshes\c\c_m_pants_common_1_a_k.nif
+Decoding meshes\c\c_m_pants_common_1_a_a.nif
+Decoding meshes\c\c_m_pants_common_1_a_g.nif
+Decoding meshes\c\c_m_pants_common_4_b_a.nif
+Decoding meshes\c\c_m_pants_common_4_b_g.nif
+Decoding meshes\c\c_m_pants_common_4_b_k.nif
+Decoding meshes\c\c_m_pants_common_3b_k.nif
+Decoding meshes\c\c_m_pants_common_5_gnd.nif
+Decoding meshes\c\c_m_pants_common_3_gnd.nif
+Decoding meshes\c\c_m_pants_extrav_1_gnd.nif
+Decoding meshes\c\c_m_pants_extrav_2_gnd.nif
+Decoding meshes\c\c_m_pants_common_3c_ul.nif
+Decoding meshes\c\c_m_pants_common_3b_ul.nif
+Decoding meshes\c\c_m_pants_gnd_common01.nif
+Decoding meshes\c\c_m_pants_exquisite_1g.nif
+Decoding meshes\c\c_m_pants_expens_3_gnd.nif
+Decoding meshes\c\c_m_pants_common_4_b_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_a_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_e_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_u_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_z_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_z_gnd.nif
+Decoding meshes\c\c_m_pants_expensive_1_k.nif
+Decoding meshes\c\c_m_pants_expensive_1_g.nif
+Decoding meshes\c\c_m_pants_expensive_1_a.nif
+Decoding meshes\c\c_m_pants_expensive_2_k.nif
+Decoding meshes\c\c_m_pants_expensive_2_g.nif
+Decoding meshes\c\c_m_pants_expensive_2_a.nif
+Decoding meshes\c\c_m_pants_exquisite_1_ul.nif
+Decoding meshes\c\c_m_pants_common_1_u_gnd.nif
+Decoding meshes\c\c_m_pants_common_1_a_ul.nif
+Decoding meshes\c\c_m_pants_common_1_e_ul.nif
+Decoding meshes\c\c_m_pants_common_1_z_ul.nif
+Decoding meshes\c\c_m_pants_common_1_u_ul.nif
+Decoding meshes\c\c_m_pants_common_1_z_gnd.nif
+Decoding meshes\c\c_m_pants_exquisite_1_k.nif
+Decoding meshes\c\c_m_pants_exquisite_1_g.nif
+Decoding meshes\c\c_m_pants_exquisite_1_a.nif
+Decoding meshes\c\c_m_pants_expens_1_u_gnd.nif
+Decoding meshes\c\c_m_pants_common_4_b_gnd.nif
+Decoding meshes\c\c_m_pants_common_3c_gnd.nif
+Decoding meshes\c\c_m_pants_common_3b_gnd.nif
+Decoding meshes\c\c_m_pants_common_1_a_gnd.nif
+Decoding meshes\c\c_m_pants_common_1_e_gnd.nif
+Decoding meshes\c\c_m_pants_exqisite_1_gnd.nif
+Decoding meshes\c\c_m_pants_expens_1_a_gnd.nif
+Decoding meshes\c\c_m_pants_expensive_1_ul.nif
+Decoding meshes\c\c_m_pants_expensive_2_ul.nif
+Decoding meshes\c\c_m_pants_expens_1_e_gnd.nif
+Decoding meshes\a\a_dwemer_cl_pauldron.nif
+Decoding meshes\a\a_dwemer_pauldron_ua.nif
+Decoding meshes\a\a_dwemer_pauldron_fa.nif
+Decoding meshes\a\a_dwemer_greaves_gnd.nif
+Decoding meshes\a\a_dwemer_greaves_ul.nif
+Decoding meshes\a\a_dwemer_pauldron_gnd.nif
+Decoding meshes\a\a_dwemer_bracer_w_gnd.nif
+Decoding meshes\i\xin_dagoth_bridge00.nif
+Decoding meshes\m\text_quarto_open_04.nif
+Decoding meshes\m\text_quarto_open_01.nif
+Decoding meshes\m\text_quarto_open_03.nif
+Decoding meshes\m\text_quarto_open_02.nif
+Decoding meshes\c\c_m_pants_expensive_1_u_ul.nif
+Decoding meshes\c\c_m_pants_exquisite_1_gnd.nif
+Decoding meshes\c\c_m_pants_expensive_1_u_k.nif
+Decoding meshes\c\c_m_pants_expensive_1_u_a.nif
+Decoding meshes\c\c_m_pants_expensive_1_u_g.nif
+Decoding meshes\c\c_m_pants_expensive_1_gnd.nif
+Decoding meshes\c\c_m_pants_expensive_2_gnd.nif
+Decoding meshes\f\furn_6th_bellhammer.nif
+Decoding meshes\f\furn_6th_tallbanner.nif
+Decoding meshes\f\furn_6th_dagothsymbol.nif
+Decoding meshes\f\furn_6th_corpus_plate_01.nif
+Decoding meshes\w\w_silver_shortsword.nif
+Decoding meshes\c\c_glove_common1.1st.nif
+Decoding meshes\c\c_glove_balmolagmer.nif
+Decoding meshes\c\c_glove_extravagant1.nif
+Decoding meshes\c\c_glove_common1_gnd.nif
+Decoding meshes\c\c_glove_expensive1.1st.nif
+Decoding meshes\c\c_glove_moragtong.1st.nif
+Decoding meshes\c\c_glove_moragtong_gnd.nif
+Decoding meshes\c\c_glove_expensive1_gnd.nif
+Decoding meshes\c\c_glove_balmolagmer_gnd.nif
+Decoding meshes\c\c_glove_balmolagmer.1st.nif
+Decoding meshes\c\c_glove_extravagant1_gnd.nif
+Decoding meshes\c\c_glove_extravagant1.1st.nif
+Decoding meshes\e\magic_cast_levitate.nif
+Decoding meshes\f\xfurn_redoran_flag_01.nif
+Decoding meshes\c\c_m_shoe_f_leather00.nif
+Decoding meshes\c\c_m_shoe_f_leather01.nif
+Decoding meshes\c\c_m_shoe_common01_gnd.nif
+Decoding meshes\c\c_m_shoe_expensive_1_f.nif
+Decoding meshes\c\c_m_shoe_common02_gnd.nif
+Decoding meshes\c\c_m_shoe_expens_1_gnd.nif
+Decoding meshes\w\w_warhammer_daedric.nif
+Decoding meshes\a\a_art_apostle_boots_r.nif
+Decoding meshes\ashcloud.nif
+Decoding meshes\w\w_art_warhammer_crusher.nif
+Decoding meshes\a\a_art_apostle_boots_gnd.nif
+Decoding meshes\f\furn_mudcave_pool00.nif
+Decoding meshes\f\furn_mudcave_spout00.nif
+Decoding meshes\f\furn_cushion_round_07.nif
+Decoding meshes\f\furn_cushion_round_03.nif
+Decoding meshes\f\furn_cushion_round_04.nif
+Decoding meshes\f\furn_cushion_square_03.nif
+Decoding meshes\f\furn_cushion_square_01.nif
+Decoding meshes\f\furn_cushion_square_07.nif
+Decoding meshes\f\furn_cushion_square_05.nif
+Decoding meshes\f\furn_cushion_square_09.nif
+Decoding meshes\f\furn_cushion_round_02.nif
+Decoding meshes\f\furn_cushion_round_01.nif
+Decoding meshes\f\furn_cushion_round_06.nif
+Decoding meshes\f\furn_cushion_square_02.nif
+Decoding meshes\f\furn_cushion_square_06.nif
+Decoding meshes\f\furn_cushion_square_04.nif
+Decoding meshes\f\furn_cushion_square_08.nif
+Decoding meshes\f\furn_cushion_round_05.nif
+Decoding meshes\f\furn_rug_big_04_collision.nif
+Decoding meshes\a\a_dreugh_cuirass_gnd.nif
+Decoding meshes\o\flora_bittergreen_07.nif
+Decoding meshes\o\flora_bittergreen_02.nif
+Decoding meshes\o\flora_bc_podplant_04.nif
+Decoding meshes\o\flora_bittergreen_03.nif
+Decoding meshes\o\flora_bittergreen_08.nif
+Decoding meshes\o\flora_bittergreen_09.nif
+Decoding meshes\o\flora_bittergreen_04.nif
+Decoding meshes\o\flora_bittergreen_06.nif
+Decoding meshes\o\flora_bittergreen_01.nif
+Decoding meshes\o\flora_bittergreen_10.nif
+Decoding meshes\o\flora_bittergreen_05.nif
+Decoding meshes\o\flora_black_lichen_02.nif
+Decoding meshes\o\flora_black_anther_01.nif
+Decoding meshes\o\flora_black_lichen_03.nif
+Decoding meshes\o\flora_black_anther_02.nif
+Decoding meshes\o\flora_black_lichen_01.nif
+Decoding meshes\o\flora_bittergreen_pod_01.nif
+Decoding meshes\button.nif
+Decoding meshes\base_anim.nif
+Decoding meshes\bloodsplat.nif
+Decoding meshes\bloodsplat2.nif
+Decoding meshes\bloodsplat3.nif
+Decoding meshes\blightcloud.nif
+Decoding meshes\base_animkna.nif
+Decoding meshes\m\probe_apprentice_01.nif
+Decoding meshes\c\c_shoes_extrav_2_gnd.nif
+Decoding meshes\c\c_shoes_common_4_gnd.nif
+Decoding meshes\c\c_shoes_common_3_gnd.nif
+Decoding meshes\c\c_shoes_expensive_2.nif
+Decoding meshes\c\c_shoes_extrav_1_gnd.nif
+Decoding meshes\c\c_shoes_common_5_gnd.nif
+Decoding meshes\c\c_shoes_expensive_03.nif
+Decoding meshes\c\c_shoes_exquisite_1_f.nif
+Decoding meshes\c\c_shoes_expensive_2_gnd.nif
+Decoding meshes\c\c_shoes_expensive_3_gnd.nif
+Decoding meshes\c\c_shoes_exquisite_1_gnd.nif
+Decoding meshes\l\light_hangingl_blue_01.nif
+Decoding meshes\l\light_hangings_blue_01.nif
+Decoding meshes\x\flora_t_mushroom_02.nif
+Decoding meshes\x\flora_t_mushroom_01.nif
+Decoding meshes\x\flora_t_shelffungus_01.nif
+Decoding meshes\b\b_n_wood elf_m_neck.nif
+Decoding meshes\b\b_n_wood elf_f_neck.nif
+Decoding meshes\b\b_n_wood elf_f_skins.nif
+Decoding meshes\b\b_n_wood elf_m_skins.nif
+Decoding meshes\b\b_n_wood elf_m_foot.nif
+Decoding meshes\b\b_n_wood elf_m_wrist.nif
+Decoding meshes\b\b_n_wood elf_m_knee.nif
+Decoding meshes\b\b_n_wood elf_f_knee.nif
+Decoding meshes\b\b_n_wood elf_f_groin.nif
+Decoding meshes\b\b_n_wood elf_m_groin.nif
+Decoding meshes\b\b_n_wood elf_f_wrist.nif
+Decoding meshes\b\b_n_wood elf_f_foot.nif
+Decoding meshes\b\b_n_wood elf_m_ankle.nif
+Decoding meshes\b\b_n_wood elf_f_ankle.nif
+Decoding meshes\b\b_n_wood elf_f_head_02.nif
+Decoding meshes\b\b_n_wood elf_f_head_06.nif
+Decoding meshes\b\b_n_wood elf_f_head_04.nif
+Decoding meshes\b\b_n_wood elf_m_head_02.nif
+Decoding meshes\b\b_n_wood elf_m_head_06.nif
+Decoding meshes\b\b_n_wood elf_m_head_04.nif
+Decoding meshes\b\b_n_wood elf_m_head_08.nif
+Decoding meshes\b\b_n_wood elf_f_hair_05.nif
+Decoding meshes\b\b_n_wood elf_f_hair_01.nif
+Decoding meshes\b\b_n_wood elf_f_hair_03.nif
+Decoding meshes\b\b_n_wood elf_m_hair_05.nif
+Decoding meshes\b\b_n_wood elf_m_hair_01.nif
+Decoding meshes\b\b_n_wood elf_m_hair_03.nif
+Decoding meshes\b\b_n_wood elf_m_forearm.nif
+Decoding meshes\b\b_n_wood elf_f_head_03.nif
+Decoding meshes\b\b_n_wood elf_f_head_01.nif
+Decoding meshes\b\b_n_wood elf_f_head_05.nif
+Decoding meshes\b\b_n_wood elf_m_head_03.nif
+Decoding meshes\b\b_n_wood elf_m_head_01.nif
+Decoding meshes\b\b_n_wood elf_m_head_07.nif
+Decoding meshes\b\b_n_wood elf_m_head_05.nif
+Decoding meshes\b\b_n_wood elf_f_hair_04.nif
+Decoding meshes\b\b_n_wood elf_f_hair_02.nif
+Decoding meshes\b\b_n_wood elf_m_hair_04.nif
+Decoding meshes\b\b_n_wood elf_m_hair_06.nif
+Decoding meshes\b\b_n_wood elf_m_hair_02.nif
+Decoding meshes\b\b_n_wood elf_f_forearm.nif
+Decoding meshes\b\b_n_wood elf_m_upper arm.nif
+Decoding meshes\b\b_n_wood elf_f_upper leg.nif
+Decoding meshes\b\b_n_wood elf_m_upper leg.nif
+Decoding meshes\b\b_n_wood elf_f_hands.1st.nif
+Decoding meshes\b\b_n_wood elf_m_hands.1st.nif
+Decoding meshes\b\b_n_wood elf_f_upper arm.nif
+Decoding meshes\x\terraiin_rock_ma_07.nif
+Decoding meshes\x\terraiin_rock_rm_07.nif
+Decoding meshes\a\a_art_cuirass_lords_c.nif
+Decoding meshes\a\a_art_cuirass_savior_c.nif
+Decoding meshes\cursor.nif
+Decoding meshes\cursormove.nif
+Decoding meshes\a\a_art_cuirass_lords_gnd.nif
+Decoding meshes\a\a_art_cuirass_savior_gnd.nif
+Decoding meshes\f\furn_dwrv_fitting50.nif
+Decoding meshes\f\furn_dwrv_fitting40.nif
+Decoding meshes\f\furn_dwrv_fitting10.nif
+Decoding meshes\f\furn_dwrv_fitting00.nif
+Decoding meshes\f\furn_dwrv_fitting30.nif
+Decoding meshes\f\furn_dwrv_fitting20.nif
+Decoding meshes\f\furn_dwrv_cabinet00.nif
+Decoding meshes\f\furn_dwrv_beltdrive00.nif
+Decoding meshes\f\furn_dwrv_bookshelf00.nif
+Decoding meshes\cursor_drop.nif
+Decoding meshes\c\c_skirt_exquisite_1.nif
+Decoding meshes\c\c_skirt_common_3_gnd.nif
+Decoding meshes\c\c_skirt_common_2_gnd.nif
+Decoding meshes\c\c_skirt_expensive_1.nif
+Decoding meshes\c\c_skirt_expensive_3.nif
+Decoding meshes\c\c_skirt_expensive_2.nif
+Decoding meshes\c\c_skirt_common_5_gnd.nif
+Decoding meshes\c\c_skirt_extravagant_1.nif
+Decoding meshes\c\c_skirt_extravagant_2.nif
+Decoding meshes\c\c_skirt_extravagant_1gnd.nif
+Decoding meshes\c\c_skirt_extravagant_2gnd.nif
+Decoding meshes\c\c_skirt_expensive_2_gnd.nif
+Decoding meshes\c\c_skirt_expensive_3_gnd.nif
+Decoding meshes\c\c_skirt_expensive_1_gnd.nif
+Decoding meshes\c\c_skirt_exquisite_1_gnd.nif
+Decoding meshes\w\magic_target_poison.nif
+Decoding meshes\w\magic_target_conjure.nif
+Decoding meshes\f\flora_muckspunge_06.nif
+Decoding meshes\f\flora_muckspunge_07.nif
+Decoding meshes\f\flora_muckspunge_04.nif
+Decoding meshes\f\flora_muckspunge_05.nif
+Decoding meshes\f\flora_muckspunge_02.nif
+Decoding meshes\f\flora_muckspunge_03.nif
+Decoding meshes\f\flora_muckspunge_01.nif
+Decoding meshes\b\b_v_imperial_m_head_01.nif
+Decoding meshes\b\b_v_imperial_f_head_01.nif
+Decoding meshes\x\terrain_rock_ma_550.nif
+Decoding meshes\x\terrain_lava_ventlg.nif
+Decoding meshes\x\terrain_rock_ma_arch.nif
+Decoding meshes\x\terrain_lava_ventlg01.nif
+Decoding meshes\x\terrain_ashland_rock_16.nif
+Decoding meshes\x\terrain_ashland_rock_14.nif
+Decoding meshes\x\terrain_ashland_rock_12.nif
+Decoding meshes\x\terrain_ashland_rock_10.nif
+Decoding meshes\x\terrain_ashland_rock_08.nif
+Decoding meshes\x\terrain_ashland_rock_04.nif
+Decoding meshes\x\terrain_ashland_rock_06.nif
+Decoding meshes\x\terrain_ashland_rock_02.nif
+Decoding meshes\x\terrain_ashland_rock_15.nif
+Decoding meshes\x\terrain_ashland_rock_13.nif
+Decoding meshes\x\terrain_ashland_rock_11.nif
+Decoding meshes\x\terrain_ashland_rock_09.nif
+Decoding meshes\x\terrain_ashland_rock_05.nif
+Decoding meshes\x\terrain_ashland_rock_07.nif
+Decoding meshes\x\terrain_ashland_rock_01.nif
+Decoding meshes\x\terrain_ashland_rock_03.nif
+Decoding meshes\f\furn_spinningwheel_01.nif
+Decoding meshes\f\xact_banner_hla_oad.nif
+Decoding meshes\f\xact_banner_tel_fyr.nif
+Decoding meshes\f\xact_banner_tel_mora.nif
+Decoding meshes\f\xact_banner_tel_vos.nif
+Decoding meshes\f\xact_banner_tel_aruhn.nif
+Decoding meshes\f\xact_banner_gnaar_mok.nif
+Decoding meshes\f\xact_banner_sadrith_mora.nif
+Decoding meshes\f\xact_banner_ald_velothi.nif
+Decoding meshes\f\xact_banner_tel_branora.nif
+Decoding meshes\a\a_art_dragon_cuirass_c.nif
+Decoding meshes\d\ex_dae_door_load_oval.nif
+Decoding meshes\d\scene root.nif
+Decoding meshes\c\c_skirt_extravagant_2_gnd.nif
+Decoding meshes\c\c_skirt_extravagant_1_gnd.nif
+Decoding meshes\m\probe_grandmaster_01.nif
+Decoding meshes\a\a_m_chitin_hands.1st.nif
+Decoding meshes\a\a_m_chitin_g_greaves.nif
+Decoding meshes\a\a_m_chitin_boot_gnd.nif
+Decoding meshes\a\a_m_chitin_ua_pauldron.nif
+Decoding meshes\a\a_m_chitin_greaves_gnd.nif
+Decoding meshes\a\a_m_chitin_ul_greaves.nif
+Decoding meshes\a\a_m_chitin_cuirass_gnd.nif
+Decoding meshes\a\a_m_chitin_pauldron_cl.nif
+Decoding meshes\a\a_m_chitin_gauntlet_gnd.nif
+Decoding meshes\a\a_art_ebon_cuirass_c.nif
+Decoding meshes\a\a_art_ebon_cuirass_gnd.nif
+Decoding meshes\w\w_art_staff_hasedoki.nif
+Decoding meshes\e\blight.nif
+Decoding meshes\e\hand01.nif
+Decoding meshes\e\absorb.nif
+Decoding meshes\e\cure_hit.nif
+Decoding meshes\e\corprus.nif
+Decoding meshes\w\shadowshortbladeonehand.nif
+Decoding meshes\e\magic_hit.nif
+Decoding meshes\e\magic_cast.nif
+Decoding meshes\e\magic_area.nif
+Decoding meshes\editormarker.nif
+Decoding meshes\e\frost_hit.nif
+Decoding meshes\b\b_v_dark elf_f_head_01.nif
+Decoding meshes\b\b_v_dark elf_m_head_01.nif
+Decoding meshes\a\a_nordicfur_skinned.nif
+Decoding meshes\a\a_nordicfur_bracer_w.nif
+Decoding meshes\a\a_nordicfur_boot_gnd.nif
+Decoding meshes\a\a_nordicfur_greave_g.nif
+Decoding meshes\a\a_nordicfur_greave_gnd.nif
+Decoding meshes\a\a_nordicfur_hands.1st.nif
+Decoding meshes\a\a_nordicfur_greave_ul.nif
+Decoding meshes\a\a_nordicfur_pauldron_gnd.nif
+Decoding meshes\a\a_nordicfur_gauntlet_gnd.nif
+Decoding meshes\a\a_nordicfur_pauldron_cl.nif
+Decoding meshes\a\a_nordicfur_pauldron_ua.nif
+Decoding meshes\f\furn_practice_dummy.nif
+Decoding meshes\a\a_art_fists_gauntlets.nif
+Decoding meshes\fire_small.nif
+Decoding meshes\f\ex_turf00.nif
+Decoding meshes\f\furn_web00.nif
+Decoding meshes\f\furn_web10.nif
+Decoding meshes\f\furn_cot00.nif
+Decoding meshes\a\a_art_fists_gauntlets.1st.nif
+Decoding meshes\a\a_nordicfur_m_cuirass_gnd.nif
+Decoding meshes\a\a_netch_m_greave_ul.nif
+Decoding meshes\a\a_netch_m_greave_gnd.nif
+Decoding meshes\a\a_netch_m_hands.1st.nif
+Decoding meshes\a\a_netch_boiled_helm.nif
+Decoding meshes\a\a_netch_m_pauldron_ua.nif
+Decoding meshes\a\a_netch_m_pauldron_gnd.nif
+Decoding meshes\a\a_netch_m_currais2_gnd.nif
+Decoding meshes\a\a_netch_m_cuirass_gnd.nif
+Decoding meshes\a\a_netch_m_pauldron_cl.nif
+Decoding meshes\a\a_netch_m_gauntlet_gnd.nif
+Decoding meshes\c\c_m_shirt_common_3_w.nif
+Decoding meshes\c\c_m_shirt_common_5_w.nif
+Decoding meshes\c\c_m_shirt_common_5_c.nif
+Decoding meshes\c\c_m_shirt_common_3_c.nif
+Decoding meshes\c\c_m_shirt_expens_3_w.nif
+Decoding meshes\c\c_m_shirt_extrav_1_c.nif
+Decoding meshes\c\c_m_shirt_extrav_2_c.nif
+Decoding meshes\c\c_m_shirt_c_common01.nif
+Decoding meshes\c\c_m_shirt_common_5_f.nif
+Decoding meshes\c\c_m_shirt_extrav_1_w.nif
+Decoding meshes\c\c_m_shirt_extrav_2_w.nif
+Decoding meshes\c\c_m_shirt_c_common03.nif
+Decoding meshes\c\c_m_shirt_w_common03.nif
+Decoding meshes\c\c_m_shirt_expens_3_c.nif
+Decoding meshes\c\c_m_shirt_extrav_1_tfa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_rfa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_hfa.nif
+Decoding meshes\c\c_m_shirt_expens_1_z_f.nif
+Decoding meshes\c\c_m_shirt_common_1_z_c.nif
+Decoding meshes\c\c_m_shirt_common_1_u_w.nif
+Decoding meshes\c\c_m_shirt_common_1_u_c.nif
+Decoding meshes\c\c_m_shirt_common_2tt_w.nif
+Decoding meshes\c\c_m_shirt_common_2tt_c.nif
+Decoding meshes\c\c_m_shirt_common_2_t_w.nif
+Decoding meshes\c\c_m_shirt_common_2_t_c.nif
+Decoding meshes\c\c_m_shirt_common_2rr_w.nif
+Decoding meshes\c\c_m_shirt_common_2rr_c.nif
+Decoding meshes\c\c_m_shirt_common_2_r_w.nif
+Decoding meshes\c\c_m_shirt_common_2_r_c.nif
+Decoding meshes\c\c_m_shirt_common_2hh_w.nif
+Decoding meshes\c\c_m_shirt_common_2hh_c.nif
+Decoding meshes\c\c_m_shirt_common_2_h_w.nif
+Decoding meshes\c\c_m_shirt_common_2_h_c.nif
+Decoding meshes\c\c_m_shirt_common_1_e_w.nif
+Decoding meshes\c\c_m_shirt_common_1_e_c.nif
+Decoding meshes\c\c_m_shirt_common_4_a_w.nif
+Decoding meshes\c\c_m_shirt_common_1_a_w.nif
+Decoding meshes\c\c_m_shirt_common_4_a_c.nif
+Decoding meshes\c\c_m_shirt_common_1_a_c.nif
+Decoding meshes\c\c_m_shirt_common_4_c_w.nif
+Decoding meshes\c\c_m_shirt_common_4_c_c.nif
+Decoding meshes\c\c_m_shirt_common_4_b_w.nif
+Decoding meshes\c\c_m_shirt_common_4_b_c.nif
+Decoding meshes\c\c_m_shirt_gondalier_c.nif
+Decoding meshes\c\c_m_shirt_common_3c_w.nif
+Decoding meshes\c\c_m_shirt_extrav_2_ua.nif
+Decoding meshes\c\c_m_shirt_extrav_1_ua.nif
+Decoding meshes\c\c_m_shirt_extrav_2_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_tw.nif
+Decoding meshes\c\c_m_shirt_fa_commonl02.nif
+Decoding meshes\c\c_m_shirt_fa_commonl04.nif
+Decoding meshes\c\c_m_shirt_ua_commonl02.nif
+Decoding meshes\c\c_m_shirt_ua_commonl04.nif
+Decoding meshes\c\c_m_shirt_common_3c_c.nif
+Decoding meshes\c\c_m_shirt_common_3b_c.nif
+Decoding meshes\c\c_m_shirt_common_3c_ua.nif
+Decoding meshes\c\c_m_shirt_common_3b_ua.nif
+Decoding meshes\c\c_m_shirt_extrav_1_rw.nif
+Decoding meshes\c\c_m_shirt_ua_common01.nif
+Decoding meshes\c\c_m_shirt_common_3c_fa.nif
+Decoding meshes\c\c_m_shirt_expens_1_e_w.nif
+Decoding meshes\c\c_m_shirt_expens_1_e_c.nif
+Decoding meshes\c\c_m_shirt_expens_1_a_w.nif
+Decoding meshes\c\c_m_shirt_expens_1_z_w.nif
+Decoding meshes\c\c_m_shirt_expens_1_z_c.nif
+Decoding meshes\c\c_m_shirt_common_1_u_f.nif
+Decoding meshes\c\c_m_shirt_common_1_a_f.nif
+Decoding meshes\c\c_m_shirt_common_5_gnd.nif
+Decoding meshes\c\c_m_shirt_common_3_gnd.nif
+Decoding meshes\c\c_m_shirt_extrav_1_hw.nif
+Decoding meshes\c\c_m_shirt_common_3_ua.nif
+Decoding meshes\c\c_m_shirt_common_5_ua.nif
+Decoding meshes\c\c_m_shirt_common_3_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_hua.nif
+Decoding meshes\c\c_m_shirt_expens_3_fa.nif
+Decoding meshes\c\c_m_shirt_expens_3_ua.nif
+Decoding meshes\c\c_m_shirt_extrav_1_rua.nif
+Decoding meshes\c\c_m_shirt_w_commonl04.nif
+Decoding meshes\c\c_m_shirt_c_commonl04.nif
+Decoding meshes\c\c_m_shirt_extrav_1_gnd.nif
+Decoding meshes\c\c_m_shirt_extrav_2_gnd.nif
+Decoding meshes\c\c_m_shirt_extrav_1_tua.nif
+Decoding meshes\c\c_m_shirt_ua_common03.nif
+Decoding meshes\c\c_m_shirt_fa_common03.nif
+Decoding meshes\c\c_m_shirt_gnd_common03.nif
+Decoding meshes\c\c_m_shirt_gnd_common01.nif
+Decoding meshes\c\c_m_shirt_extrav_1_hc.nif
+Decoding meshes\c\c_m_shirt_extrav_1_tc.nif
+Decoding meshes\c\c_m_shirt_extrav_1_rc.nif
+Decoding meshes\c\c_m_shirt_expens_3_gnd.nif
+Decoding meshes\c\c_m_shirt_c_commonl02.nif
+Decoding meshes\c\c_m_shirt_expens_1_z_gnd.nif
+Decoding meshes\c\c_m_shirt_expensive_1_c.nif
+Decoding meshes\c\c_m_shirt_expensive_1_w.nif
+Decoding meshes\c\c_m_shirt_expensive_2_c.nif
+Decoding meshes\c\c_m_shirt_expensive_2_w.nif
+Decoding meshes\c\c_m_shirt_exquisite_1_ua.nif
+Decoding meshes\c\c_m_shirt_common_1_u_gnd.nif
+Decoding meshes\c\c_m_shirt_common_1_z_gnd.nif
+Decoding meshes\c\c_m_shirt_common_1_e_fa.nif
+Decoding meshes\c\c_m_shirt_exquisite_1_w.nif
+Decoding meshes\c\c_m_shirt_exquisite_1_c.nif
+Decoding meshes\c\c_m_shirt_exquisite_1_fa.nif
+Decoding meshes\c\c_m_shirt_common_2hh_gnd.nif
+Decoding meshes\c\c_m_shirt_common_2_h_gnd.nif
+Decoding meshes\c\c_m_shirt_expens_1_u_gnd.nif
+Decoding meshes\c\c_m_shirt_gondolier_gnd.nif
+Decoding meshes\c\c_m_shirt_common_4_c_gnd.nif
+Decoding meshes\c\c_m_shirt_common_4_a_ua.nif
+Decoding meshes\c\c_m_shirt_common_4_c_ua.nif
+Decoding meshes\c\c_m_shirt_common_4_b_ua.nif
+Decoding meshes\c\c_m_shirt_common_2tt_gnd.nif
+Decoding meshes\c\c_m_shirt_common_2_t_gnd.nif
+Decoding meshes\c\c_m_shirt_common_4_b_gnd.nif
+Decoding meshes\c\c_m_shirt_expens_1_u_fa.nif
+Decoding meshes\c\c_m_shirt_expens_1_e_fa.nif
+Decoding meshes\c\c_m_shirt_expens_1_a_fa.nif
+Decoding meshes\c\c_m_shirt_expens_1_a_ua.nif
+Decoding meshes\c\c_m_shirt_expens_1_e_ua.nif
+Decoding meshes\c\c_m_shirt_expens_1_u_ua.nif
+Decoding meshes\c\c_m_shirt_expens_1_z_ua.nif
+Decoding meshes\c\c_m_shirt_common_4_a_gnd.nif
+Decoding meshes\c\c_m_shirt_common_3c_gnd.nif
+Decoding meshes\c\c_m_shirt_common_3b_gnd.nif
+Decoding meshes\c\c_m_shirt_common_1_a_gnd.nif
+Decoding meshes\c\c_m_shirt_expensive_1_fa.nif
+Decoding meshes\c\c_m_shirt_expensive_2_fa.nif
+Decoding meshes\c\c_m_shirt_common_2_h_fa.nif
+Decoding meshes\c\c_m_shirt_common_2_r_fa.nif
+Decoding meshes\c\c_m_shirt_common_2_t_fa.nif
+Decoding meshes\c\c_m_shirt_common_2rr_gnd.nif
+Decoding meshes\c\c_m_shirt_common_2_r_gnd.nif
+Decoding meshes\c\c_m_shirt_common_1_e_gnd.nif
+Decoding meshes\c\c_m_shirt_gnd_commonl02.nif
+Decoding meshes\c\c_m_shirt_gnd_commonl04.nif
+Decoding meshes\c\c_m_shirt_common_1_a_ua.nif
+Decoding meshes\c\c_m_shirt_common_1_e_ua.nif
+Decoding meshes\c\c_m_shirt_common_1_z_ua.nif
+Decoding meshes\c\c_m_shirt_common_1_u_ua.nif
+Decoding meshes\c\c_m_shirt_common_2hh_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_hgnd.nif
+Decoding meshes\c\c_m_shirt_common_2_t_ua.nif
+Decoding meshes\c\c_m_shirt_common_2tt_ua.nif
+Decoding meshes\c\c_m_shirt_expens_1_a_gnd.nif
+Decoding meshes\c\c_m_shirt_common_2_r_ua.nif
+Decoding meshes\c\c_m_shirt_common_2rr_ua.nif
+Decoding meshes\c\c_m_shirt_common_2_h_ua.nif
+Decoding meshes\c\c_m_shirt_common_2hh_ua.nif
+Decoding meshes\c\c_m_shirt_common_4_a_fa.nif
+Decoding meshes\c\c_m_shirt_common_4_b_fa.nif
+Decoding meshes\c\c_m_shirt_common_4_c_fa.nif
+Decoding meshes\c\c_m_shirt_expensive_1_ua.nif
+Decoding meshes\c\c_m_shirt_expensive_2_ua.nif
+Decoding meshes\c\c_m_shirt_expens_1_e_gnd.nif
+Decoding meshes\c\c_m_shirt_common_2rr_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_tgnd.nif
+Decoding meshes\c\c_m_shirt_common_2tt_fa.nif
+Decoding meshes\c\c_m_shirt_extrav_1_rgnd.nif
+Decoding meshes\c\c_m_skirt_imperial_gnd.nif
+Decoding meshes\c\c_m_skirt_templar_gnd.nif
+Decoding meshes\c\c_m_skirt_common_01_gnd.nif
+Decoding meshes\o\flora_gold_kanet_02.nif
+Decoding meshes\o\flora_gold_kanet_01.nif
+Decoding meshes\o\flora_green_lichen_02.nif
+Decoding meshes\o\flora_green_lichen_03.nif
+Decoding meshes\o\flora_green_lichen_01.nif
+Decoding meshes\a\a_art_gauntlet_fist_gnd.nif
+Decoding meshes\x\furn_imp_rubble_ring.nif
+Decoding meshes\f\furn_ashl_bugbowl_01.nif
+Decoding meshes\f\furn_ashl_bugbowl_02.nif
+Decoding meshes\f\furn_ashl_bugbowl_03.nif
+Decoding meshes\f\furn_ashl_chimes_01.nif
+Decoding meshes\c\c_m_skirt_common_04_c_gnd.nif
+Decoding meshes\c\c_m_shirt_expensive_1_a_c.nif
+Decoding meshes\c\c_m_shirt_expensive_1_u_fa.nif
+Decoding meshes\c\c_m_shirt_expensive_1_u_ua.nif
+Decoding meshes\c\c_m_shirt_exquisite_1_gnd.nif
+Decoding meshes\c\c_m_shirt_expensive_1_u_c.nif
+Decoding meshes\c\c_m_shirt_expensive_1_u_w.nif
+Decoding meshes\c\c_m_shirt_expensive_1_gnd.nif
+Decoding meshes\c\c_m_shirt_expensive_2_gnd.nif
+Decoding meshes\l\light_spear_skull00.nif
+Decoding meshes\c\c_indoril_m_ul_pants.nif
+Decoding meshes\c\c_indoril_m_g_pants.nif
+Decoding meshes\c\c_indoril_m_k_pants.nif
+Decoding meshes\x\ex_imp_plaza_stairs.nif
+Decoding meshes\x\ex_imp_guardtower_02.nif
+Decoding meshes\x\ex_imp_foundation_01.nif
+Decoding meshes\x\ex_imp_towerb_med_01.nif
+Decoding meshes\x\ex_imp_towers_med_01.nif
+Decoding meshes\x\ex_imp_wall_arch_01.nif
+Decoding meshes\x\ex_imp_arrowslit_01.nif
+Decoding meshes\x\ex_imp_govman_stair.nif
+Decoding meshes\x\ex_imp_kdoorframe_01.nif
+Decoding meshes\x\ex_imp_wall_tower_01.nif
+Decoding meshes\x\ex_imp_towerb_top_01.nif
+Decoding meshes\x\ex_imp_towers_top_01.nif
+Decoding meshes\x\ex_imp_gov_arrowlit.nif
+Decoding meshes\x\ex_imp_guardtower_01.nif
+Decoding meshes\x\ex_imp_foundation_02.nif
+Decoding meshes\x\ex_imp_dragonstatue.nif
+Decoding meshes\x\ex_imp_towers_light_01.nif
+Decoding meshes\x\ex_imp_towerb_cons_02.nif
+Decoding meshes\x\ex_imp_towers_base_01.nif
+Decoding meshes\x\ex_imp_towerb_dark_01.nif
+Decoding meshes\x\ex_imp_towerb_base_01.nif
+Decoding meshes\x\ex_imp_towerb_cons_01.nif
+Decoding meshes\x\ex_imp_towers_dark_01.nif
+Decoding meshes\x\ex_imp_towerb_light_01.nif
+Decoding meshes\x\ex_imp_wall_stairs_02.nif
+Decoding meshes\x\ex_imp_wall_stairs_03.nif
+Decoding meshes\x\ex_imp_wall_corner_02.nif
+Decoding meshes\x\ex_imp_wall_stairs_04.nif
+Decoding meshes\x\ex_imp_wall_corner_01.nif
+Decoding meshes\x\ex_imp_wall_stairs_01.nif
+Decoding meshes\x\ex_imp_govmansion_gate.nif
+Decoding meshes\x\ex_imp_govmansion_wing.nif
+Decoding meshes\x\ex_imp_towerb_roof_pitch.nif
+Decoding meshes\x\ex_imp_govmansion_donjon.nif
+Decoding meshes\x\contain_tramaroot_06.nif
+Decoding meshes\xyz_pole2.nif
+Decoding meshes\x\ex_skiff.nif
+Decoding meshes\x\ex_ar_01.nif
+Decoding meshes\x\ex_gg_02.nif
+Decoding meshes\x\ex_gg_03.nif
+Decoding meshes\x\ex_gg_01.nif
+Decoding meshes\xbase_anim.nif
+Decoding meshes\x\ex_dae_ruin_02_skew.nif
+Decoding meshes\x\ex_dae_mehrunesdagon.nif
+Decoding meshes\x\ex_dae_b_bo_slpiece.nif
+Decoding meshes\x\ex_dae_b_bo_bskirt1.nif
+Decoding meshes\x\ex_dae_b_bo_bskirt2.nif
+Decoding meshes\x\ex_dae_pillar_02_ruin.nif
+Decoding meshes\x\ex_dae_ruin_entry.max.nif
+Decoding meshes\x\ex_dae_ruin_04_skew_01.nif
+Decoding meshes\x\ex_dae_ruin_04_skew_02.nif
+Decoding meshes\x\ex_dae_ruin_02_skew_02.nif
+Decoding meshes\x\ex_dae_b_bo_frontskirt.nif
+Decoding meshes\x\ex_dae_boethiah_small.nif
+Decoding meshes\x\ex_dae_malacath_small.nif
+Decoding meshes\x\ex_dae_molagbal_small.nif
+Decoding meshes\x\ex_dae_malacath_attack.nif
+Decoding meshes\x\ex_dae_malacath_stand.nif
+Decoding meshes\x\ex_dae_ruin_platform_01.nif
+Decoding meshes\x\ex_dae_buttress_ruin_01.nif
+Decoding meshes\x\ex_dae_sheogorath_small.nif
+Decoding meshes\x\ex_dae_pillar_02_ruin_02.nif
+Decoding meshes\x\ex_com_dframe_lthus.nif
+Decoding meshes\x\ex_imp_govmansion_barbican.nif
+Decoding meshes\x\ex_de_ship.nif
+Decoding meshes\x\ex_de_oar.nif
+Decoding meshes\x\ex_trellis.nif
+Decoding meshes\x\ex_ruin_10.nif
+Decoding meshes\x\ex_ruin_00.nif
+Decoding meshes\x\ex_ruin_30.nif
+Decoding meshes\x\ex_ruin_20.nif
+Decoding meshes\x\ex_ruin_40.nif
+Decoding meshes\x\ex_t_hook.nif
+Decoding meshes\x\ex_dae_mehrunesdagon_small.nif
+Decoding meshes\x\ex_dae_ruin_stair01_short.nif
+Decoding meshes\w\w_dai-katana_daedric.nif
+Decoding meshes\a\a_templar_m_w_bracer.nif
+Decoding meshes\a\a_templar_greaves_k.nif
+Decoding meshes\a\a_templar_greaves_g.nif
+Decoding meshes\a\a_templar_m_boot_gnd.nif
+Decoding meshes\a\a_templar_greaves_ul.nif
+Decoding meshes\a\a_templar_pauldron_ua.nif
+Decoding meshes\a\a_templar_pauldron_fa.nif
+Decoding meshes\a\a_templar_greaves_gnd.nif
+Decoding meshes\a\a_templar_m_cl_pauldron.nif
+Decoding meshes\a\a_templar_m_cuirass_gnd.nif
+Decoding meshes\a\a_masque_clavicus_vile.nif
+Decoding meshes\w\w_battleaxe_daedric.nif
+Decoding meshes\a\a_trollbone_cuirass_gnd.nif
+Decoding meshes\f\furn_imp_rubble_ring.nif
+Decoding meshes\f\furn_imp_stoneblock_01.nif
+Decoding meshes\w\w_wakazashi_daedric.nif
+Decoding meshes\a\a_imperial_greaves_g.nif
+Decoding meshes\a\a_imperial_hands.1st.nif
+Decoding meshes\a\a_imperial_greaves_k.nif
+Decoding meshes\a\a_imperial_m_helmet.nif
+Decoding meshes\a\a_imperial_a_boot01.nif
+Decoding meshes\a\a_imperial_cl_pauldron.nif
+Decoding meshes\a\a_imperial_ua_pauldron.nif
+Decoding meshes\a\a_imperial_greaves_gnd.nif
+Decoding meshes\a\a_imperial_greaves_ul.nif
+Decoding meshes\a\a_imperial_a_boot_gnd.nif
+Decoding meshes\a\a_imperial_m_cuirass_gnd.nif
+Decoding meshes\a\a_imperial_pauldron_gnd.nif
+Decoding meshes\a\a_indoril_m_boot_gnd.nif
+Decoding meshes\a\a_indoril_m_fa_mail.nif
+Decoding meshes\a\a_indoril_m_hands.1st.nif
+Decoding meshes\a\a_indoril_m_cl_pauldron.nif
+Decoding meshes\a\a_indoril_m_cuirass_gnd.nif
+Decoding meshes\a\a_indoril_m_ua_pauldron.nif
+Decoding meshes\a\a_indoril_m_pauldron_gnd.nif
+Decoding meshes\a\a_indoril_m_gauntlet_gnd.nif
+Decoding meshes\f\terrain_rocks_gl_03.nif
+Decoding meshes\f\terrain_rocks_gl_02.nif
+Decoding meshes\f\terrain_rocks_gl_01.nif
+Decoding meshes\f\terrain_rocks_gl_04.nif
+Decoding meshes\f\terrain_cairn_al_01.nif
+Decoding meshes\f\terrain_cairn_al_02.nif
+Decoding meshes\f\terrain_cairn_al_03.nif
+Decoding meshes\f\terrain_rocks_wg_01.nif
+Decoding meshes\f\terrain_rocks_wg_03.nif
+Decoding meshes\f\terrain_rocks_wg_02.nif
+Decoding meshes\f\terrain_rocks_wg_04.nif
+Decoding meshes\f\terrain_cairn_ma_01.nif
+Decoding meshes\f\terrain_cairn_ma_03.nif
+Decoding meshes\f\terrain_cairn_ma_02.nif
+Decoding meshes\f\terrain_rocks_ai_01.nif
+Decoding meshes\f\terrain_rocks_ai_02.nif
+Decoding meshes\f\terrain_rocks_ai_03.nif
+Decoding meshes\f\terrain_rocks_ai_04.nif
+Decoding meshes\w\w_art_longbow_shade.nif
+Decoding meshes\f\furn_uni_weaponrack_01.nif
+Decoding meshes\f\furn_uni_spearholder_01.nif
+Decoding meshes\w\shadowlongbladeonehand.nif
+Decoding meshes\w\shadowlongbladetwoclose.nif
+Decoding meshes\f\furn_kneeling_stool_01.nif
+Decoding meshes\m\artifact_devourer_01.nif
+Decoding meshes\m\artifact_bittercup_01.nif
+Decoding meshes\f\terrain_natural_bridge_01.nif
+Decoding meshes\a\a_imperial_a_gauntlet_gnd.nif
+Decoding meshes\c\c_templar_m_g_skirt.nif
+Decoding meshes\l\light_paper_lantern_02.nif
+Decoding meshes\l\light_paper_lantern_01.nif
+Decoding meshes\l\light_paper_lantern_off.nif
+Decoding meshes\f\flora_rm_scathecraw_02.nif
+Decoding meshes\f\flora_rm_scathecraw_01.nif
+Decoding meshes\b\b_v_wood elf_f_head_01.nif
+Decoding meshes\b\b_v_wood elf_m_head_01.nif
+Decoding meshes\m\misc_de_fishing_pole.nif
+Decoding meshes\f\furn_pole_support_01.nif
+Decoding meshes\w\w_art_molagbal_mace.nif
+Decoding meshes\w\w_art_mehrunesrazor.nif
+Decoding meshes\w\shadowmarksmancrossbow.nif
+Decoding meshes\m\misc_redware_platter.nif
+Decoding meshes\m\misc_redware_bowl_01.nif
+Decoding meshes\m\misc_redware_pitcher.nif
+Decoding meshes\f\furn_moldcave_pool00.nif
+Decoding meshes\f\furn_moldcave_spout00.nif
+Decoding meshes\f\furn_bonecave_pool00.nif
+Decoding meshes\f\furn_bonecave_spout00.nif
+Decoding meshes\f\furn_com_tapestry_02.nif
+Decoding meshes\f\furn_com_cauldron_02.nif
+Decoding meshes\f\furn_com_tapestry_03.nif
+Decoding meshes\f\furn_com_tapestry_01.nif
+Decoding meshes\f\furn_com_wincover_02.nif
+Decoding meshes\f\furn_com_wincover_03.nif
+Decoding meshes\f\furn_com_wincover_05.nif
+Decoding meshes\f\furn_com_wincover_04.nif
+Decoding meshes\f\furn_com_tapestry_05.nif
+Decoding meshes\f\furn_com_cauldron_01.nif
+Decoding meshes\f\furn_com_tapestry_04.nif
+Decoding meshes\f\furn_com_bookshelf_01.nif
+Decoding meshes\f\furn_com_coatofarms_01.nif
+Decoding meshes\f\furn_com_torch_ring_02.nif
+Decoding meshes\f\furn_com_coatofarms_02.nif
+Decoding meshes\f\furn_com_torch_ring_01.nif
+Decoding meshes\f\furn_com_bookshelf_02.nif
+Decoding meshes\f\furn_com_lantern_hook.nif
+Decoding meshes\f\furn_com_lantern_hook_02.nif
+Decoding meshes\l\furn_de_firepit_f_01.nif
+Decoding meshes\a\a_newtscale_cuirass.nif
+Decoding meshes\b\b_n_imperial_m_knee.nif
+Decoding meshes\b\b_n_imperial_f_knee.nif
+Decoding meshes\b\b_n_imperial_f_wrist.nif
+Decoding meshes\b\b_n_imperial_f_skins.nif
+Decoding meshes\b\b_n_imperial_m_skins.nif
+Decoding meshes\b\b_n_imperial_m_foot.nif
+Decoding meshes\b\b_n_imperial_f_groin.nif
+Decoding meshes\b\b_n_imperial_m_groin.nif
+Decoding meshes\b\b_n_imperial_m_neck.nif
+Decoding meshes\b\b_n_imperial_f_neck.nif
+Decoding meshes\b\b_n_imperial_m_wrist.nif
+Decoding meshes\b\b_n_imperial_m_ankle.nif
+Decoding meshes\b\b_n_imperial_f_ankle.nif
+Decoding meshes\b\b_n_imperial_f_foot.nif
+Decoding meshes\b\b_n_imperial_m_hair_07.nif
+Decoding meshes\b\b_n_imperial_m_hair_05.nif
+Decoding meshes\b\b_n_imperial_m_hair_03.nif
+Decoding meshes\b\b_n_imperial_m_hair_01.nif
+Decoding meshes\b\b_n_imperial_m_hair_09.nif
+Decoding meshes\b\b_n_imperial_f_hair_07.nif
+Decoding meshes\b\b_n_imperial_f_hair_05.nif
+Decoding meshes\b\b_n_imperial_f_hair_03.nif
+Decoding meshes\b\b_n_imperial_f_hair_01.nif
+Decoding meshes\b\b_n_imperial_m_head_02.nif
+Decoding meshes\b\b_n_imperial_m_head_06.nif
+Decoding meshes\b\b_n_imperial_m_head_04.nif
+Decoding meshes\b\b_n_imperial_f_head_02.nif
+Decoding meshes\b\b_n_imperial_f_head_06.nif
+Decoding meshes\b\b_n_imperial_f_head_04.nif
+Decoding meshes\b\b_n_imperial_m_forearm.nif
+Decoding meshes\b\b_n_imperial_m_hair_06.nif
+Decoding meshes\b\b_n_imperial_m_hair_04.nif
+Decoding meshes\b\b_n_imperial_m_hair_02.nif
+Decoding meshes\b\b_n_imperial_m_hair_00.nif
+Decoding meshes\b\b_n_imperial_m_hair_08.nif
+Decoding meshes\b\b_n_imperial_f_hair_06.nif
+Decoding meshes\b\b_n_imperial_f_hair_04.nif
+Decoding meshes\b\b_n_imperial_f_hair_02.nif
+Decoding meshes\b\b_n_imperial_f_forearm.nif
+Decoding meshes\b\b_n_imperial_m_head_03.nif
+Decoding meshes\b\b_n_imperial_m_head_01.nif
+Decoding meshes\b\b_n_imperial_m_head_07.nif
+Decoding meshes\b\b_n_imperial_m_head_05.nif
+Decoding meshes\b\b_n_imperial_f_head_03.nif
+Decoding meshes\b\b_n_imperial_f_head_01.nif
+Decoding meshes\b\b_n_imperial_f_head_07.nif
+Decoding meshes\b\b_n_imperial_f_head_05.nif
+Decoding meshes\a\a_m_imperialchain_gr_g.nif
+Decoding meshes\a\a_m_imperialchain_pa_gnd.nif
+Decoding meshes\a\a_m_imperialchain_c_gnd.nif
+Decoding meshes\a\a_m_imperialchain_gr_ul.nif
+Decoding meshes\a\a_m_imperialchain_helmet.nif
+Decoding meshes\a\a_m_imperialchain_gr_gnd.nif
+Decoding meshes\a\a_m_imperialchain_pa_ua.nif
+Decoding meshes\b\b_n_imperial_f_upper leg.nif
+Decoding meshes\b\b_n_imperial_m_upper leg.nif
+Decoding meshes\b\b_n_imperial_m_hands.1st.nif
+Decoding meshes\b\b_n_imperial_f_upper arm.nif
+Decoding meshes\b\b_n_imperial_f_hands.1st.nif
+Decoding meshes\b\b_n_imperial_m_upper arm.nif
+Decoding meshes\m\pick_grandmaster_01.nif
+Decoding meshes\d\door_dwrv_loaddown00.nif
+Decoding meshes\f\furn_shrine_rilms_01.nif
+Decoding meshes\f\furn_shrine_relms_01.nif
+Decoding meshes\f\furn_shrine_felms_01.nif
+Decoding meshes\f\furn_shrine_seryn_01.nif
+Decoding meshes\f\furn_shrine_delyn_01.nif
+Decoding meshes\f\furn_shrine_meris_01.nif
+Decoding meshes\f\furn_shrine_roris_01.nif
+Decoding meshes\f\furn_shrine_olms_01.nif
+Decoding meshes\f\furn_shrine_vivec_01.nif
+Decoding meshes\f\furn_shrine_llothis_01.nif
+Decoding meshes\f\furn_shrine_aralor_01.nif
+Decoding meshes\f\furn_shrine_nerevar_01.nif
+Decoding meshes\f\furn_shrine_veloth_01.nif
+Decoding meshes\f\furn_shrine_tribunal_01.nif
+Decoding meshes\f\act_banner_tel_aruhn.nif
+Decoding meshes\f\act_banner_gnaar_mok.nif
+Decoding meshes\f\act_banner_tel_mora.nif
+Decoding meshes\f\act_banner_ald_velothi.nif
+Decoding meshes\f\act_banner_tel_branora.nif
+Decoding meshes\f\act_banner_sadrith_mora.nif
+Decoding meshes\a\a_m_imperialchain_cuirass.nif
+Decoding meshes\l\light_velothismall_01.nif
+Decoding meshes\l\light_velothi_brazier.nif
+Decoding meshes\a\a_orcish_pauldron_ua.nif
+Decoding meshes\a\a_orcish_pauldron_fa.nif
+Decoding meshes\a\a_orcish_greaves_gnd.nif
+Decoding meshes\a\a_orcish_greaves_ul.nif
+Decoding meshes\a\a_orcish_cuirass_gnd.nif
+Decoding meshes\a\a_orcish_bracer_gnd.nif
+Decoding meshes\a\a_orcish_cl_pauldron.nif
+Decoding meshes\a\a_orcish_pauldron_gnd.nif
+Decoding meshes\f\flora_trama_shrub_05.nif
+Decoding meshes\f\flora_trama_shrub_01.nif
+Decoding meshes\f\flora_trama_shrub_06.nif
+Decoding meshes\f\flora_trama_shrub_02.nif
+Decoding meshes\f\flora_trama_shrub_03.nif
+Decoding meshes\f\flora_trama_shrub_04.nif
+Decoding meshes\f\flora_treestump_wg_01.nif
+Decoding meshes\f\flora_treestump_wg_02.nif
+Decoding meshes\a\a_ebony_pauldron_gnd.nif
+Decoding meshes\a\a_ebony_cuirass_gnd.nif
+Decoding meshes\a\a_ebony_pauldron_fa.nif
+Decoding meshes\a\a_ebony_pauldron_ua.nif
+Decoding meshes\a\a_ebony_pauldron_cl.nif
+Decoding meshes\a\a_ebony_cl_pauldron.nif
+Decoding meshes\a\a_ebony_greaves_gnd.nif
+Decoding meshes\w\w_art_katana_goldbrand.nif
+Decoding meshes\m\app_s_calcinator_01.nif
+Decoding meshes\m\app_g_calcinator_01.nif
+Decoding meshes\m\app_m_calcinator_01.nif
+Decoding meshes\m\app_j_calcinator_01.nif
+Decoding meshes\b\b_n_dark elf_f_skins.nif
+Decoding meshes\b\b_n_dark elf_m_skins.nif
+Decoding meshes\b\b_n_dark elf_m_foot.nif
+Decoding meshes\b\b_n_dark elf_f_wrist.nif
+Decoding meshes\b\b_n_dark elf_m_neck.nif
+Decoding meshes\b\b_n_dark elf_f_neck.nif
+Decoding meshes\b\b_n_dark elf_m_knee.nif
+Decoding meshes\b\b_n_dark elf_f_knee.nif
+Decoding meshes\b\b_n_dark elf_f_groin.nif
+Decoding meshes\b\b_n_dark elf_m_groin.nif
+Decoding meshes\b\b_n_dark elf_m_wrist.nif
+Decoding meshes\b\b_n_dark elf_m_ankle.nif
+Decoding meshes\b\b_n_dark elf_f_ankle.nif
+Decoding meshes\b\b_n_dark elf_f_foot.nif
+Decoding meshes\b\b_n_dark elf_m_hair_23.nif
+Decoding meshes\b\b_n_dark elf_m_hair_21.nif
+Decoding meshes\b\b_n_dark elf_m_hair_25.nif
+Decoding meshes\b\b_n_dark elf_f_hair_23.nif
+Decoding meshes\b\b_n_dark elf_f_hair_21.nif
+Decoding meshes\b\b_n_dark elf_f_head_02.nif
+Decoding meshes\b\b_n_dark elf_f_head_06.nif
+Decoding meshes\b\b_n_dark elf_f_head_04.nif
+Decoding meshes\b\b_n_dark elf_f_head_08.nif
+Decoding meshes\b\b_n_dark elf_m_head_02.nif
+Decoding meshes\b\b_n_dark elf_m_head_06.nif
+Decoding meshes\b\b_n_dark elf_m_head_04.nif
+Decoding meshes\b\b_n_dark elf_m_head_08.nif
+Decoding meshes\b\b_n_dark elf_m_head_11.nif
+Decoding meshes\b\b_n_dark elf_m_head_13.nif
+Decoding meshes\b\b_n_dark elf_m_head_15.nif
+Decoding meshes\b\b_n_dark elf_m_head_17.nif
+Decoding meshes\b\b_n_dark elf_f_hair_09.nif
+Decoding meshes\b\b_n_dark elf_f_hair_05.nif
+Decoding meshes\b\b_n_dark elf_f_hair_07.nif
+Decoding meshes\b\b_n_dark elf_f_hair_01.nif
+Decoding meshes\b\b_n_dark elf_f_hair_03.nif
+Decoding meshes\b\b_n_dark elf_m_hair_09.nif
+Decoding meshes\b\b_n_dark elf_m_hair_05.nif
+Decoding meshes\b\b_n_dark elf_m_hair_07.nif
+Decoding meshes\b\b_n_dark elf_m_hair_01.nif
+Decoding meshes\b\b_n_dark elf_m_hair_03.nif
+Decoding meshes\b\b_n_dark elf_f_hair_16.nif
+Decoding meshes\b\b_n_dark elf_f_hair_14.nif
+Decoding meshes\b\b_n_dark elf_f_hair_12.nif
+Decoding meshes\b\b_n_dark elf_f_hair_10.nif
+Decoding meshes\b\b_n_dark elf_f_hair_18.nif
+Decoding meshes\b\b_n_dark elf_m_hair_16.nif
+Decoding meshes\b\b_n_dark elf_m_hair_14.nif
+Decoding meshes\b\b_n_dark elf_m_hair_12.nif
+Decoding meshes\b\b_n_dark elf_m_hair_10.nif
+Decoding meshes\b\b_n_dark elf_m_hair_18.nif
+Decoding meshes\b\b_n_dark elf_m_forearm.nif
+Decoding meshes\b\b_n_dark elf_m_hair_22.nif
+Decoding meshes\b\b_n_dark elf_m_hair_20.nif
+Decoding meshes\b\b_n_dark elf_m_hair_26.nif
+Decoding meshes\b\b_n_dark elf_m_hair_24.nif
+Decoding meshes\b\b_n_dark elf_f_hair_22.nif
+Decoding meshes\b\b_n_dark elf_f_hair_20.nif
+Decoding meshes\b\b_n_dark elf_f_hair_24.nif
+Decoding meshes\b\b_n_dark elf_f_head_03.nif
+Decoding meshes\b\b_n_dark elf_f_head_01.nif
+Decoding meshes\b\b_n_dark elf_f_head_07.nif
+Decoding meshes\b\b_n_dark elf_f_head_05.nif
+Decoding meshes\b\b_n_dark elf_f_head_09.nif
+Decoding meshes\b\b_n_dark elf_m_head_03.nif
+Decoding meshes\b\b_n_dark elf_m_head_01.nif
+Decoding meshes\b\b_n_dark elf_m_head_07.nif
+Decoding meshes\b\b_n_dark elf_m_head_05.nif
+Decoding meshes\b\b_n_dark elf_m_head_09.nif
+Decoding meshes\b\b_n_dark elf_f_head_10.nif
+Decoding meshes\b\b_n_dark elf_m_head_10.nif
+Decoding meshes\b\b_n_dark elf_m_head_12.nif
+Decoding meshes\b\b_n_dark elf_m_head_14.nif
+Decoding meshes\b\b_n_dark elf_m_head_16.nif
+Decoding meshes\b\b_n_dark elf_f_hair_08.nif
+Decoding meshes\b\b_n_dark elf_f_hair_04.nif
+Decoding meshes\b\b_n_dark elf_f_hair_06.nif
+Decoding meshes\b\b_n_dark elf_f_hair_02.nif
+Decoding meshes\b\b_n_dark elf_m_hair_08.nif
+Decoding meshes\b\b_n_dark elf_m_hair_04.nif
+Decoding meshes\b\b_n_dark elf_m_hair_06.nif
+Decoding meshes\b\b_n_dark elf_m_hair_02.nif
+Decoding meshes\b\b_n_dark elf_f_forearm.nif
+Decoding meshes\b\b_n_dark elf_f_hair_17.nif
+Decoding meshes\b\b_n_dark elf_f_hair_15.nif
+Decoding meshes\b\b_n_dark elf_f_hair_13.nif
+Decoding meshes\b\b_n_dark elf_f_hair_11.nif
+Decoding meshes\b\b_n_dark elf_f_hair_19.nif
+Decoding meshes\b\b_n_dark elf_m_hair_17.nif
+Decoding meshes\b\b_n_dark elf_m_hair_15.nif
+Decoding meshes\b\b_n_dark elf_m_hair_13.nif
+Decoding meshes\b\b_n_dark elf_m_hair_11.nif
+Decoding meshes\b\b_n_dark elf_m_hair_19.nif
+Decoding meshes\b\b_n_dark elf_m_upper arm.nif
+Decoding meshes\b\b_n_dark elf_f_upper leg.nif
+Decoding meshes\b\b_n_dark elf_m_upper leg.nif
+Decoding meshes\b\b_n_dark elf_f_hands.1st.nif
+Decoding meshes\b\b_n_dark elf_m_hands.1st.nif
+Decoding meshes\b\b_n_dark elf_f_upper arm.nif
+Decoding meshes\a\a_steel_pauldron_gnd.nif
+Decoding meshes\a\a_steel_cuirass_gnd.nif
+Decoding meshes\a\a_steel_pauldron_fa.nif
+Decoding meshes\a\a_steel_pauldron_ua.nif
+Decoding meshes\a\a_steel_pauldron_cl.nif
+Decoding meshes\a\a_steel_gauntlet_gnd.nif
+Decoding meshes\a\a_steel_greaves_gnd.nif
+Decoding meshes\a\a_ringmail_cuirass_gnd.nif
+Decoding meshes\b\b_n_nord_m_hands.1st.nif
+Decoding meshes\b\b_n_nord_f_upper leg.nif
+Decoding meshes\b\b_n_nord_f_hands.1st.nif
+Decoding meshes\b\b_n_nord_m_upper leg.nif
+Decoding meshes\b\b_n_nord_m_upper arm.nif
+Decoding meshes\b\b_n_nord_f_upper arm.nif
+Decoding meshes\a\towershield_telvanni.nif
+Decoding meshes\a\towershield_bonemold.nif
+Decoding meshes\a\towershield_daedric.nif
+Decoding meshes\a\towershield_redoranm.nif
+Decoding meshes\a\towershield_trollbone.nif
+Decoding meshes\a\towershield_dragonscale.nif
+Decoding meshes\a\towershield_netch_leather.nif
+Decoding meshes\l\light_torch_small_01.nif
+Decoding meshes\m\misc_candle_blue_01.nif
+Decoding meshes\m\misc_candle_ivory_01.nif
+Decoding meshes\m\misc_candle_green_01.nif
+Decoding meshes\m\misc_paper_plain_01.nif
+Decoding meshes\w\w_shortsword_chitin.nif
+Decoding meshes\w\w_shortsword_daedric.nif
+Decoding meshes\w\w_shortsword_imperial.nif
+Decoding meshes\a\a_boots_heavy_leather.nif
+Decoding meshes\w\w_nordic_broadsword.nif
+Decoding meshes\b\b_v_redguard_m_head_01.nif
+Decoding meshes\b\b_v_redguard_f_head_01.nif
+Decoding meshes\c\c_art_ring_vampiric.nif
+Decoding meshes\c\c_art_ring_phynaster.nif
+Decoding meshes\c\c_art_ring_denstagmer.nif
+Decoding meshes\c\c_art_ring_surrounding.nif
+Decoding meshes\m\furn_com_coatofarms_01.nif
+Decoding meshes\m\probe_secretmaster_01.nif
+Decoding meshes\b\b_v_breton_m_head_01.nif
+Decoding meshes\b\b_v_breton_f_head_01.nif
+Decoding meshes\w\w_broadsword_leafblade.nif
+Decoding meshes\w\w_broadsword_imperial.nif
+Decoding meshes\f\xfurn_banner_tavern_01.nif
+Decoding meshes\f\xfurn_banner_temple_03.nif
+Decoding meshes\f\xfurn_banner_temple_01.nif
+Decoding meshes\f\xfurn_bannerd_goods_01.nif
+Decoding meshes\f\xfurn_banner_hlaalu_01.nif
+Decoding meshes\f\xfurn_banner_dagoth_01.nif
+Decoding meshes\f\xfurn_banner_temple_02.nif
+Decoding meshes\f\xfurn_bannerd_welcome_01.nif
+Decoding meshes\f\xfurn_bannerd_wa_shop_01.nif
+Decoding meshes\f\xfurn_bannerd_alchemy_01.nif
+Decoding meshes\f\xfurn_bannerd_danger_01.nif
+Decoding meshes\m\misc_bowl_redware_01.nif
+Decoding meshes\m\misc_bowl_redware_03.nif
+Decoding meshes\m\misc_bowl_redware_02.nif
+Decoding meshes\m\misc_bowl_bugdesign_01.nif
+Decoding meshes\m\misc_bowl_glass_peach_01.nif
+Decoding meshes\m\misc_com_tankard_01.nif
+Decoding meshes\m\misc_com_wood_cup_02.nif
+Decoding meshes\m\misc_com_wood_cup_03.nif
+Decoding meshes\m\misc_com_wood_knife.nif
+Decoding meshes\m\misc_com_wood_cup_01.nif
+Decoding meshes\m\misc_com_iron_ladle.nif
+Decoding meshes\m\misc_com_wood_cup_04.nif
+Decoding meshes\m\misc_com_wood_spoon_01.nif
+Decoding meshes\m\misc_com_wood_bowl_03.nif
+Decoding meshes\m\misc_com_wood_bowl_01.nif
+Decoding meshes\m\misc_com_bucket_metal.nif
+Decoding meshes\m\misc_com_wood_spoon_02.nif
+Decoding meshes\m\misc_com_wood_bowl_02.nif
+Decoding meshes\m\misc_com_wood_bowl_05.nif
+Decoding meshes\m\misc_com_wood_bowl_04.nif
+Decoding meshes\m\misc_com_metal_plate_04.nif
+Decoding meshes\m\misc_com_silverware_fork.nif
+Decoding meshes\m\misc_com_metal_goblet_02.nif
+Decoding meshes\m\misc_com_metal_goblet_01.nif
+Decoding meshes\m\misc_com_metal_plate_03.nif
+Decoding meshes\m\misc_com_metal_plate_07.nif
+Decoding meshes\m\misc_com_metal_plate_05.nif
+Decoding meshes\f\furn_redoran_flag_in.nif
+Decoding meshes\f\furn_redoran_flag_01.nif
+Decoding meshes\f\furn_redoran_hearth_02.nif
+Decoding meshes\f\furn_redoran_shelf_03.nif
+Decoding meshes\f\furn_redoran_hearth_01.nif
+Decoding meshes\f\furn_redoran_shelf2_01.nif
+Decoding meshes\f\furn_redoran_shelf1_01.nif
+Decoding meshes\m\misc_mortarpestle_01.nif
+Decoding meshes\m\misc_mortarpestle_s_01.nif
+Decoding meshes\m\misc_mortarpestle_g_01.nif
+Decoding meshes\m\misc_mortarpestle_a_01.nif
+Decoding meshes\m\misc_mortarpestle_m_01.nif
+Decoding meshes\f\furn_velothi_altar_01.nif
+Decoding meshes\m\misc_soulgem_lesser.nif
+Decoding meshes\m\misc_soulgem_common.nif
+Decoding meshes\m\misc_soulgem_greater.nif
+Decoding meshes\m\misc_potion_fresh_01.nif
+Decoding meshes\m\misc_potion_cheap_01.nif
+Decoding meshes\m\misc_pot_mottled_01.nif
+Decoding meshes\m\misc_pot_redware_02.nif
+Decoding meshes\m\misc_pot_redware_03.nif
+Decoding meshes\m\misc_pot_redware_01.nif
+Decoding meshes\m\misc_pot_redware_04.nif
+Decoding meshes\m\misc_potion_bargain_01.nif
+Decoding meshes\m\misc_potion_quality_01.nif
+Decoding meshes\m\misc_potion_exclusive_01.nif
+Decoding meshes\m\misc_potion_standard_01.nif
+Decoding meshes\m\misc_pot_glass_peach_01.nif
+Decoding meshes\m\misc_pot_glass_peach_02.nif
+Decoding meshes\f\furn_de_winerack_01.nif
+Decoding meshes\f\furn_de_bookshelf_01.nif
+Decoding meshes\f\furn_de_practice_mat.nif
+Decoding meshes\f\furn_de_signpost_04.nif
+Decoding meshes\f\furn_de_signpost_01.nif
+Decoding meshes\f\furn_de_signpost_03.nif
+Decoding meshes\f\furn_de_signpost_02.nif
+Decoding meshes\f\furn_de_tapestry_10.nif
+Decoding meshes\f\furn_de_tapestry_01.nif
+Decoding meshes\f\furn_de_tapestry_11.nif
+Decoding meshes\f\furn_de_tapestry_02.nif
+Decoding meshes\f\furn_de_tapestry_12.nif
+Decoding meshes\f\furn_de_tapestry_03.nif
+Decoding meshes\f\furn_de_tapestry_13.nif
+Decoding meshes\f\furn_de_tapestry_04.nif
+Decoding meshes\f\furn_de_tapestry_05.nif
+Decoding meshes\f\furn_de_tapestry_06.nif
+Decoding meshes\f\furn_de_tapestry_07.nif
+Decoding meshes\f\furn_de_tapestry_08.nif
+Decoding meshes\f\furn_de_tapestry_09.nif
+Decoding meshes\f\furn_de_ex_table_02.nif
+Decoding meshes\f\furn_de_ex_table_03.nif
+Decoding meshes\f\furn_de_bookshelf_02.nif
+Decoding meshes\f\furn_de_firepit_f_01.nif
+Decoding meshes\f\furn_de_ex_bench_01.nif
+Decoding meshes\f\furn_de_ex_stool_02.nif
+Decoding meshes\f\furn_de_tapestry_m_01.nif
+Decoding meshes\f\furn_de_banner_book_01.nif
+Decoding meshes\f\furn_de_banner_book_in.nif
+Decoding meshes\f\furn_de_banner_pawn_01.nif
+Decoding meshes\f\furn_de_shack_basket_01.nif
+Decoding meshes\f\furn_de_shack_basket_02.nif
+Decoding meshes\m\misc_com_pitcher_metal_01.nif
+Decoding meshes\m\misc_com_silverware_spoon.nif
+Decoding meshes\m\misc_com_silverware_knife.nif
+Decoding meshes\f\furn_de_banner_telvani_in.nif
+Decoding meshes\f\furn_de_banner_telvani_01.nif
+Decoding meshes\f\xfurn_bannerd_clothing_01.nif
+Decoding meshes\m\misc_bowl_orange_green_01.nif
+Decoding meshes\m\misc_bowl_glass_yellow_01.nif
+Decoding meshes\o\flora_red_lichen_01.nif
+Decoding meshes\o\flora_red_lichen_02.nif
+Decoding meshes\o\flora_red_lichen_03.nif
+Decoding meshes\m\misc_glass_yellow_01.nif
+Decoding meshes\m\misc_glass_green_01.nif
+Decoding meshes\r\guar.nif
+Decoding meshes\r\xazura.nif
+Decoding meshes\r\shalk.nif
+Decoding meshes\r\azura.nif
+Decoding meshes\r\dreugh.nif
+Decoding meshes\r\hunger.nif
+Decoding meshes\r\xguar.nif
+Decoding meshes\r\xshalk.nif
+Decoding meshes\raindrop.nif
+Decoding meshes\r\rust rat.nif
+Decoding meshes\r\skeleton.nif
+Decoding meshes\r\xdagothr.nif
+Decoding meshes\r\xdreugh.nif
+Decoding meshes\r\xbyagram.nif
+Decoding meshes\r\xdremora.nif
+Decoding meshes\r\xhunger.nif
+Decoding meshes\r\daedroth.nif
+Decoding meshes\r\dremora.nif
+Decoding meshes\r\dagothr.nif
+Decoding meshes\r\bonelord.nif
+Decoding meshes\r\byagram.nif
+Decoding meshes\r\ashghoul.nif
+Decoding meshes\r\ashslave.nif
+Decoding meshes\r\nixhound.nif
+Decoding meshes\rainsplash.nif
+Decoding meshes\r\bonewalker.nif
+Decoding meshes\r\lordvivec.nif
+Decoding meshes\r\xnixhound.nif
+Decoding meshes\r\xminescrib.nif
+Decoding meshes\r\xlordvivec.nif
+Decoding meshes\r\cliffracer.nif
+Decoding meshes\r\clannfear.nif
+Decoding meshes\r\minescrib.nif
+Decoding meshes\right_arrow.nif
+Decoding meshes\r\netch_bull.nif
+Decoding meshes\r\xdaedroth.nif
+Decoding meshes\r\xduskyalit.nif
+Decoding meshes\r\xclannfear.nif
+Decoding meshes\r\xbabelfish.nif
+Decoding meshes\r\xbonelord.nif
+Decoding meshes\r\xashzombie.nif
+Decoding meshes\r\xashghoul.nif
+Decoding meshes\r\xashslave.nif
+Decoding meshes\r\babelfish.nif
+Decoding meshes\r\guar_white.nif
+Decoding meshes\r\duskyalit.nif
+Decoding meshes\r\xskeleton.nif
+Decoding meshes\r\ashvampire.nif
+Decoding meshes\r\ashzombie.nif
+Decoding meshes\r\xrust rat.nif
+Decoding meshes\b\b_v_argonian_m_head_01.nif
+Decoding meshes\b\b_v_argonian_f_head_01.nif
+Decoding meshes\b\b_v_high elf_f_head_01.nif
+Decoding meshes\b\b_v_high elf_m_head_01.nif
+Decoding meshes\o\flora_stoneflower_02.nif
+Decoding meshes\o\flora_stoneflower_01.nif
+Decoding meshes\a\a_art_shield_breaker.nif
+Decoding meshes\slider_bar.nif
+Decoding meshes\smoke_green.nif
+Decoding meshes\sky_night_01.nif
+Decoding meshes\w\w_art_blade_crescent.nif
+Decoding meshes\torchfire.nif
+Decoding meshes\w\shadowblunttwoclose.nif
+Decoding meshes\c\artifact_bloodring_01.nif
+Decoding meshes\c\artifact_belt_hfire_01.nif
+Decoding meshes\c\artifact_ring_soul_01.nif
+Decoding meshes\c\artifact_amulet_hring_01.nif
+Decoding meshes\c\artifact_amulet_hheal_01.nif
+Decoding meshes\c\artifact_amulet_hfire_01.nif
+Decoding meshes\a\a_art_towershield_eleidon.nif
+Decoding meshes\c\artifact_amulet_hthrum_01.nif
+Decoding meshes\c\artifact_amulet_htrime_01.nif
+Decoding meshes\a\a_glass_pauldron_gnd.nif
+Decoding meshes\a\a_glass_cuirass_gnd.nif
+Decoding meshes\a\a_glass_pauldron_fa.nif
+Decoding meshes\a\a_glass_pauldron_ua.nif
+Decoding meshes\a\a_glass_cl_pauldron.nif
+Decoding meshes\a\a_glass_greaves_gnd.nif
+Decoding meshes\a\a_glass_cl_pauldron_gnd.nif
+Decoding meshes\b\b_v_khajiit_m_head_01.nif
+Decoding meshes\b\b_v_khajiit_f_head_01.nif
+Decoding meshes\f\furn_rail_straight_00.nif
+Decoding meshes\w\w_art_claymore_umbra.nif
+Decoding meshes\w\w_art_cleaverstfelms.nif
+Decoding meshes\w\w_art_crosierstlloth.nif
+Decoding meshes\w\w_art_claymore_chrys.nif
+Decoding meshes\w\w_art_claymore_iceblade.nif
+Decoding meshes\f\furn_lavacave_pool00.nif
+Decoding meshes\f\furn_lavacave_spout00.nif
+Decoding meshes\f\furn_banner_hlaalu_01.nif
+Decoding meshes\f\furn_banner_tavern_in.nif
+Decoding meshes\f\furn_banner_temple_01.nif
+Decoding meshes\f\furn_bannerd_danger_in.nif
+Decoding meshes\f\furn_banner_tavern_01.nif
+Decoding meshes\f\furn_banner_temple_04.nif
+Decoding meshes\f\furn_bannerd_goods_in.nif
+Decoding meshes\f\furn_bannerd_danger_01.nif
+Decoding meshes\f\furn_banner_temple_03.nif
+Decoding meshes\f\furn_bannerd_goods_01.nif
+Decoding meshes\f\furn_banner_dagoth_01.nif
+Decoding meshes\f\furn_banner_temple_02.nif
+Decoding meshes\f\furn_bannerd_alchemy_01.nif
+Decoding meshes\f\furn_bannerd_wa_shop_01.nif
+Decoding meshes\f\furn_bannerd_welcome_01.nif
+Decoding meshes\f\furn_bannerd_clothing_01.nif
+Decoding meshes\f\furn_bannerd_wa_shop_in.nif
+Decoding meshes\f\furn_bannerd_alchemy_in.nif
+Decoding meshes\f\furn_bannerd_clothing_in.nif
+Decoding meshes\f\furn_banner_temple_01_in.nif
+Decoding meshes\f\furn_banner_temple_02_in.nif
+Decoding meshes\f\furn_banner_temple_03_in.nif
+Decoding meshes\f\furn_dae_rubble_01c.nif
+Decoding meshes\f\furn_dae_rubble_01b.nif
+Decoding meshes\f\furn_dae_rubble_03b.nif
+Decoding meshes\f\furn_dae_rubble_01a.nif
+Decoding meshes\f\furn_dae_rubble_03a.nif
+Decoding meshes\f\furn_dae_rubble_04a.nif
+Decoding meshes\f\furn_dae_rubble_pointy.nif
+Decoding meshes\upper_arrow.nif
+Decoding meshes\a\a_studdedleather_c_gnd.nif
+Decoding meshes\a\a_studdedleather_cuirass.nif
+Decoding meshes\w\w_dwemer_shortsword.nif
+Decoding meshes\m\misc_chest_small_02.nif
+Decoding meshes\m\misc_chest_small_01.nif
+Decoding meshes\m\text_octavo_open_08.nif
+Decoding meshes\m\text_octavo_open_04.nif
+Decoding meshes\m\text_octavo_open_06.nif
+Decoding meshes\m\text_octavo_open_07.nif
+Decoding meshes\m\text_octavo_open_01.nif
+Decoding meshes\m\text_octavo_open_02.nif
+Decoding meshes\m\text_octavo_open_03.nif
+Decoding meshes\f\xex_ashl_e_banner_r.nif
+Decoding meshes\f\xex_ashl_a_banner_r.nif
+Decoding meshes\f\xex_ashl_z_banner_r.nif
+Decoding meshes\f\xex_ashl_u_banner_r.nif
+Decoding meshes\m\text_scroll_open_01.nif
+Decoding meshes\m\text_scroll_open_02.nif
+Decoding meshes\m\text_scroll_open_03.nif
+Decoding meshes\a\a_silver_cuirass_duke.nif
+Decoding meshes\a\a_daedric_greaves_g.nif
+Decoding meshes\a\a_daedric_greaves_ul.nif
+Decoding meshes\a\a_daedric_hands.1st.nif
+Decoding meshes\a\a_daedric_fountain_h.nif
+Decoding meshes\a\a_daedric_boots_gnd.nif
+Decoding meshes\a\a_daedric_pauldron_gnd.nif
+Decoding meshes\a\a_daedric_pauldron_ua.nif
+Decoding meshes\a\a_daedric_greaves_gnd.nif
+Decoding meshes\a\a_daedric_pauldron_cl.nif
+Decoding meshes\a\a_daedric_cuirass_gnd.nif
+Decoding meshes\a\a_daedric_terrifying_h.nif
+Decoding meshes\a\a_daedric_gauntlet_gnd.nif
+Decoding meshes\f\xfurn_de_banner_book_01.nif
+Decoding meshes\f\xfurn_de_banner_pawn_01.nif
+Decoding meshes\o\flora_willow_flower_02.nif
+Decoding meshes\o\flora_willow_flower_01.nif
+Decoding meshes\f\furn_screen_guar_01.nif
+Decoding meshes\a\a_art_wraithguard.1st.nif
+Decoding meshes\a\a_art_wraithguard_gnd.nif
+Decoding meshes\w\w_mace.nif
+Decoding meshes\w\w_bolt01.nif
+Decoding meshes\w\w_club00.nif
+Decoding meshes\w\w_spear.nif
+Decoding meshes\w\w_tanto.nif
+Decoding meshes\w\w_saber.nif
+Decoding meshes\m\misc_vivec_ashmask_01.nif
+Decoding meshes\m\misc_silverware_bowl.nif
+Decoding meshes\m\misc_silverware_cup.nif
+Decoding meshes\m\misc_silverware_cup_01.nif
+Decoding meshes\m\misc_silverware_pitcher.nif
+Decoding meshes\m\misc_silverware_plate_01.nif
+Decoding meshes\m\misc_silverware_plate_03.nif
+Decoding meshes\m\misc_silverware_plate_02.nif
+Decoding meshes\w\w_staff00.nif
+Decoding meshes\w\w_crossbow.nif
+Decoding meshes\w\w_longbow.nif
+Decoding meshes\w\w_claymore.nif
+Decoding meshes\w\w_n_katana.nif
+Decoding meshes\w\w_de_fork.nif
+Decoding meshes\w\w_arrow01.nif
+Decoding meshes\f\xfurn_de_banner_telvani_01.nif
+Decoding meshes\d\ex_de_ship_trapdoor.nif
+Decoding meshes\d\in_de_shipdoor_toplevel.nif
+Decoding meshes\x\ex_t_tower_seedling.nif
+Decoding meshes\x\ex_t_tower_strght_lrg.nif
+Decoding meshes\x\ex_stronghold_fort00.nif
+Decoding meshes\x\ex_strongruin_fort01.nif
+Decoding meshes\x\ex_stronghold_door10.nif
+Decoding meshes\x\ex_stronghold_wall00.nif
+Decoding meshes\x\ex_stronghold_wall02.nif
+Decoding meshes\x\ex_stronghold_fort02.nif
+Decoding meshes\x\ex_strongruin_fort05.nif
+Decoding meshes\x\ex_stronghold_dome00.nif
+Decoding meshes\x\ex_strongruin_fort02.nif
+Decoding meshes\x\ex_strongruin_fort00.nif
+Decoding meshes\x\ex_stronghold_wall03.nif
+Decoding meshes\x\ex_stronghold_fort03.nif
+Decoding meshes\x\ex_stronghold_fort05.nif
+Decoding meshes\x\ex_stronghold_fort01.nif
+Decoding meshes\x\ex_stronghold_wall01.nif
+Decoding meshes\x\ex_strongruin_dome00.nif
+Decoding meshes\x\ex_strongruin_fort03.nif
+Decoding meshes\x\ex_stronghold_pylon01.nif
+Decoding meshes\x\ex_stronghold_enter00.nif
+Decoding meshes\x\ex_stronghold_window00.nif
+Decoding meshes\x\ex_stronghold_pylon02.nif
+Decoding meshes\x\ex_strong_roofstack00.nif
+Decoding meshes\x\ex_stronghold_pylon00.nif
+Decoding meshes\x\ex_strongruin_enter00.nif
+Decoding meshes\x\ex_t_root_bridge_01.nif
+Decoding meshes\x\ex_t_root_spikes_01.nif
+Decoding meshes\x\ex_t_root_spikes_02.nif
+Decoding meshes\x\ex_t_rock_coastal_03.nif
+Decoding meshes\x\ex_t_rock_coastal_01.nif
+Decoding meshes\x\ex_t_rock_coastal_02.nif
+Decoding meshes\x\ex_t_root_lendsplit.nif
+Decoding meshes\x\ex_strongholdruin_wall00.nif
+Decoding meshes\x\ex_strongholdruin_wall01.nif
+Decoding meshes\x\ex_strongholdruin_wall02.nif
+Decoding meshes\x\ex_strongholdruin_wall03.nif
+Decoding meshes\x\ex_strongruin_smdwell00.nif
+Decoding meshes\x\ex_stronghold_smdwell00.nif
+Decoding meshes\x\ex_stronghold_sandpit00.nif
+Decoding meshes\x\in_t_housepod_2flr_stair.nif
+Decoding meshes\d\in_impsmall_door_01.nif
+Decoding meshes\d\in_impsmall_d_cave_01.nif
+Decoding meshes\d\in_impsmall_d_hidden_01.nif
+Decoding meshes\d\in_impsmall_loaddoor_01.nif
+Decoding meshes\d\in_impsmall_door_jail_01.nif
+Decoding meshes\d\in_impsmall_door_jail_02.nif
+Decoding meshes\x\ex_redoran_tower_01.nif
+Decoding meshes\x\ex_redoran_window_01.nif
+Decoding meshes\x\ex_redoran_window_02.nif
+Decoding meshes\x\ex_redoran_tavern_01.nif
+Decoding meshes\x\ex_redoran_steps_01.nif
+Decoding meshes\x\ex_redoran_steps_02.nif
+Decoding meshes\x\ex_redoran_awning_01.nif
+Decoding meshes\x\ex_redoran_barracks_01.nif
+Decoding meshes\x\ex_redoran_building_02.nif
+Decoding meshes\x\ex_redoran_building_03.nif
+Decoding meshes\x\ex_redoran_building_01.nif
+Decoding meshes\x\ex_t_doorway_sphere_01.nif
+Decoding meshes\x\ex_redoran_building_01a.nif
+Decoding meshes\x\ex_velothi_window_01.nif
+Decoding meshes\x\ex_velothi_temple_02.nif
+Decoding meshes\x\ex_velothi_temple_01.nif
+Decoding meshes\x\ex_velothi_tower_01.nif
+Decoding meshes\x\ex_velothi_triwin_01.nif
+Decoding meshes\x\ex_velothi_hilltent_01.nif
+Decoding meshes\x\ex_velothi_entrance_03.nif
+Decoding meshes\x\ex_velothi_entrance_01.nif
+Decoding meshes\x\ex_velothi_entrance_02.nif
+Decoding meshes\x\ex_velothi_tower_01_a.nif
+Decoding meshes\x\ex_velothi_striderport_01.nif
+Decoding meshes\x\ex_strongruin_fort05_half.nif
+Decoding meshes\x\ex_redoran_striderport_01.nif
+Decoding meshes\c\amulet_extravagant_1.nif
+Decoding meshes\c\amulet_thongofzainab.nif
+Decoding meshes\c\amulet_extravagant_2.nif
+Decoding meshes\c\amulet_teeth_urshilaku.nif
+Decoding meshes\x\ex_common_skywalk_01.nif
+Decoding meshes\x\ex_common_window_01.nif
+Decoding meshes\x\ex_common_window_02.nif
+Decoding meshes\x\ex_common_window_03.nif
+Decoding meshes\x\ex_common_plat_cent.nif
+Decoding meshes\x\ex_common_plat_rail.nif
+Decoding meshes\x\ex_common_plat_corn.nif
+Decoding meshes\x\ex_common_tavern_01.nif
+Decoding meshes\x\ex_common_lighthouse.nif
+Decoding meshes\x\ex_common_balcony_01.nif
+Decoding meshes\x\ex_common_dormer_round.nif
+Decoding meshes\x\ex_common_house_addon.nif
+Decoding meshes\x\ex_common_building_01.nif
+Decoding meshes\x\ex_common_building_03.nif
+Decoding meshes\x\ex_common_tower_thatch.nif
+Decoding meshes\x\ex_common_chimney_tall.nif
+Decoding meshes\x\ex_common_entrance_02.nif
+Decoding meshes\x\ex_common_building_02.nif
+Decoding meshes\x\ex_common_entrance_01.nif
+Decoding meshes\x\ex_common_dormer_square.nif
+Decoding meshes\x\ex_common_house_tall_01.nif
+Decoding meshes\x\ex_common_awning_wood_01.nif
+Decoding meshes\x\ex_common_house_tall_02.nif
+Decoding meshes\x\ex_common_trellis_withvine.nif
+Decoding meshes\x\ex_common_house_mixedroofs.nif
+Decoding meshes\x\ex_t_playertower_sprout.nif
+Decoding meshes\f\furn_t_fireplace_01.nif
+Decoding meshes\f\furn_c_t_dibella_01.nif
+Decoding meshes\f\furn_c_t_stendarr_01.nif
+Decoding meshes\f\furn_c_t_akatosh_01.nif
+Decoding meshes\f\furn_c_t_warrior_01.nif
+Decoding meshes\f\furn_c_t_kynareth_01.nif
+Decoding meshes\f\furn_c_t_julianos_01.nif
+Decoding meshes\f\furn_c_t_zenithar_01.nif
+Decoding meshes\f\furn_c_t_apprentice_01.nif
+Decoding meshes\f\active_blight_medium.nif
+Decoding meshes\f\active_triolith_01a.nif
+Decoding meshes\f\active_sign_c_inn_02.nif
+Decoding meshes\f\active_com_bar_door.nif
+Decoding meshes\f\active_blight_large.nif
+Decoding meshes\f\active_blight_small.nif
+Decoding meshes\f\active_sign_c_goods_02.nif
+Decoding meshes\f\active_sign_c_pwan_01.nif
+Decoding meshes\f\active_sign_c_arms_01.nif
+Decoding meshes\f\active_sign_c_goods_01.nif
+Decoding meshes\f\active_sign_c_arms_02.nif
+Decoding meshes\f\active_sign_c_alchemy_01.nif
+Decoding meshes\f\active_sign_c_guildm_01.nif
+Decoding meshes\f\active_sign_c_guildf_01.nif
+Decoding meshes\f\active_sign_c_clothing_01.nif
+Decoding meshes\c\c_ring_extravagant_1.nif
+Decoding meshes\c\c_ring_extravagant_2.nif
+Decoding meshes\x\ex_v_vivecstatue_01.nif
+Decoding meshes\x\ex_v_vivecstatue_02.nif
+Decoding meshes\x\ex_v_sign_stdeyln_01.nif
+Decoding meshes\x\ex_v_sign_stolms_01.nif
+Decoding meshes\x\ex_v_sign_hlaalu_01.nif
+Decoding meshes\x\ex_v_sign_redoran_01.nif
+Decoding meshes\x\ex_v_sign_telvanni_01.nif
+Decoding meshes\x\ex_lighthouse_stone.nif
+Decoding meshes\x\ex_c_chimney_tall_02.nif
+Decoding meshes\x\ex_gg_gateswitch_01.nif
+Decoding meshes\x\ex_gg_gatetriolith_01.nif
+Decoding meshes\i\in_dwrv_tower_int00.nif
+Decoding meshes\w\w_6th_hammer.nif
+Decoding meshes\x\ex_cavern_padlock00.nif
+Decoding meshes\x\ex_cave_coastrock00.nif
+Decoding meshes\x\ex_cave_entrance_10.nif
+Decoding meshes\x\ex_vivec_w_slope_01.nif
+Decoding meshes\x\ex_vivec_ent_telt_01.nif
+Decoding meshes\x\ex_vivec_buttress_01.nif
+Decoding meshes\x\ex_vivec_p_water_01.nif
+Decoding meshes\x\ex_vivec_b_gap_t_01.nif
+Decoding meshes\x\ex_vivec_b_gap_b_01.nif
+Decoding meshes\x\ex_vivec_b_gap_b_02.nif
+Decoding meshes\x\ex_vivec_b_wb_gap_01.nif
+Decoding meshes\x\ex_vivec_wspout_d_02.nif
+Decoding meshes\x\ex_vivec_bridgew_01.nif
+Decoding meshes\x\ex_vivec_waterfall_03.nif
+Decoding meshes\x\ex_vivec_waterfall_05.nif
+Decoding meshes\x\ex_vivec_prisonmoon_01.nif
+Decoding meshes\x\ex_vivec_waterspout_02.nif
+Decoding meshes\x\ex_vivec_waterfall_01.nif
+Decoding meshes\x\ex_vivec_bridgewgap_01.nif
+Decoding meshes\x\ex_vivec_waterspout_05.nif
+Decoding meshes\x\ex_vivec_waterspout_01.nif
+Decoding meshes\x\ex_vivec_waterspout_03.nif
+Decoding meshes\x\ex_waterfall_mist_01.nif
+Decoding meshes\x\ex_waterfall_mist_s_01.nif
+Decoding meshes\x\ex_ropebridge_512_01.nif
+Decoding meshes\x\ex_ropebridge_1024_01.nif
+Decoding meshes\x\ex_ropebridge_2048_01.nif
+Decoding meshes\x\ex_ropebridge_stake_01.nif
+Decoding meshes\i\in_t_stairs_strt_256.nif
+Decoding meshes\i\in_c_stone_room_side.nif
+Decoding meshes\i\in_c_stone_room_corner.nif
+Decoding meshes\i\in_c_stone_room_center.nif
+Decoding meshes\i\in_c_stone_room_entry.nif
+Decoding meshes\i\in_c_stone_stair_short.nif
+Decoding meshes\i\in_c_stone_hall_small.nif
+Decoding meshes\i\in_t_stairs_strt_wiz_256.nif
+Decoding meshes\i\in_c_stair_rich_tall_01.nif
+Decoding meshes\i\in_c_stair_rich_pend_01.nif
+Decoding meshes\i\in_c_stair_plain_tall_01.nif
+Decoding meshes\i\in_c_stair_plain_tall_02.nif
+Decoding meshes\i\in_c_stair_rich_tall_02.nif
+Decoding meshes\i\in_c_stair_rich_pend_02.nif
+Decoding meshes\i\in_c_stone_room_c_con_01.nif
+Decoding meshes\m\repair_journeyman_01.nif
+Decoding meshes\m\repair_secretmaster_01.nif
+Decoding meshes\m\repair_grandmaster_01.nif
+Decoding meshes\x\ex_t_menhir_crystal.nif
+Decoding meshes\i\in_c_stair_thatch_pend_01.nif
+Decoding meshes\i\in_c_stair_thatch_pend_02.nif
+Decoding meshes\i\in_c_stair_thatch_tall_01.nif
+Decoding meshes\i\in_c_stair_thatch_tall_02.nif
+Decoding meshes\i\in_c_stairs_rich_ptall_02.nif
+Decoding meshes\i\in_c_stairs_rich_ptall_01.nif
+Decoding meshes\x\ex_de_docks_centerb.nif
+Decoding meshes\x\ex_de_docks_centers.nif
+Decoding meshes\x\ex_de_docks_centersb.nif
+Decoding meshes\x\ex_de_docks_steps_01.nif
+Decoding meshes\x\ex_de_docks_pilings.nif
+Decoding meshes\x\ex_de_docks_pilingb.nif
+Decoding meshes\x\ex_de_docks_cornerb_01.nif
+Decoding meshes\x\ex_de_docks_corners_03.nif
+Decoding meshes\x\ex_de_docks_corners_01.nif
+Decoding meshes\x\ex_de_docks_corner_02.nif
+Decoding meshes\x\ex_de_docks_cornerb_02.nif
+Decoding meshes\x\ex_de_docks_corners_02.nif
+Decoding meshes\x\ex_de_docks_corner_01.nif
+Decoding meshes\x\ex_de_docks_piling_01.nif
+Decoding meshes\x\ex_de_docks_cornersb_02.nif
+Decoding meshes\x\ex_de_docks_cornersb_01.nif
+Decoding meshes\x\ex_de_docks_cornersb_03.nif
+Decoding meshes\x\ex_nord_doorrocks_01.nif
+Decoding meshes\x\ex_nord_houseshed_01.nif
+Decoding meshes\x\ex_daed_wall_512_01.nif
+Decoding meshes\x\ex_daed_pillar_claw_01.nif
+Decoding meshes\x\ex_bc_cave_entrance.nif
+Decoding meshes\x\ex_ac_cave_entrance_01.nif
+Decoding meshes\x\ex_bc_cave_entrance_01.nif
+Decoding meshes\x\ex_ma_cave_entrance.nif
+Decoding meshes\x\ex_ma_cave_entrance_01.nif
+Decoding meshes\x\ex_wg_cave_entrance_01.nif
+Decoding meshes\x\ex_de_cave_entrance_01.nif
+Decoding meshes\x\ex_ai_cave_entrance_01.nif
+Decoding meshes\x\ex_rm_cave_entrance_01.nif
+Decoding meshes\x\ex_gl_cave_entrance_01.nif
+Decoding meshes\x\ex_al_cave_entrance_01.nif
+Decoding meshes\x\ex_ma_cave_entrance_lava.nif
+Decoding meshes\i\in_ar_shellbottom_01.nif
+Decoding meshes\i\in_de_shack_trapdoor.nif
+Decoding meshes\i\in_de_shipwreckll_lg.nif
+Decoding meshes\i\in_de_shipwreck_top.nif
+Decoding meshes\i\in_de_shipwreckul_lg.nif
+Decoding meshes\i\in_de_ship_upperlevel.nif
+Decoding meshes\i\in_de_ship_lowerlevel.nif
+Decoding meshes\i\in_de_shack_trapdoor_01.nif
+Decoding meshes\i\in_t_ls_hall_connect.nif
+Decoding meshes\i\in_t_ls_hall_connect_01.nif
+Decoding meshes\n\potion_local_brew_01.nif
+Decoding meshes\n\potion_t_bug_musk_01.nif
+Decoding meshes\n\potion_local_liquor_01.nif
+Decoding meshes\n\potion_cyro_brandy_01.nif
+Decoding meshes\n\potion_cyro_whiskey_01.nif
+Decoding meshes\n\potion_comberry_wine_01.nif
+Decoding meshes\i\in_impsmall_hall_02.nif
+Decoding meshes\i\in_impsmall_hall_03.nif
+Decoding meshes\i\in_impsmall_hall_01.nif
+Decoding meshes\i\in_impsmall_wall_01.nif
+Decoding meshes\i\in_impsmall_3way_01.nif
+Decoding meshes\i\in_impsmall_4way_01.nif
+Decoding meshes\i\in_impsmall_door_01.nif
+Decoding meshes\i\in_impsmall_r_3way_01.nif
+Decoding meshes\i\in_impsmall_r_entr_04.nif
+Decoding meshes\i\in_impsmall_r_entr_01.nif
+Decoding meshes\i\in_impsmall_r_entr_02.nif
+Decoding meshes\i\in_impsmall_corner_01.nif
+Decoding meshes\i\in_impsmall_shutter_01.nif
+Decoding meshes\i\in_impsmall_dj_cave_01.nif
+Decoding meshes\i\in_impsmall_endcap_01.nif
+Decoding meshes\i\in_impsmall_spiral_01.nif
+Decoding meshes\i\in_impsmall_doorjam_01.nif
+Decoding meshes\i\in_impsmall_stairs_01.nif
+Decoding meshes\i\in_impsmall_r_entr_03.nif
+Decoding meshes\i\in_impsmall_r_corner_02.nif
+Decoding meshes\i\in_impsmall_trapdoor_01a.nif
+Decoding meshes\i\in_impsmall_r_pillar_01.nif
+Decoding meshes\i\in_impsmall_dj_hidden_01.nif
+Decoding meshes\i\in_impsmall_r_corner_01.nif
+Decoding meshes\i\in_impsmall_r_corner_03.nif
+Decoding meshes\i\in_impsmall_trapdoor_01.nif
+Decoding meshes\i\in_impsmall_r_center_01.nif
+Decoding meshes\n\potion_comberry_brandy_01.nif
+Decoding meshes\i\in_impsmall_spiral_end_01.nif
+Decoding meshes\i\in_impsmall_spiral_bot_01.nif
+Decoding meshes\x\ex_lavacave_spout10.nif
+Decoding meshes\x\ex_v_palace_steps_01.nif
+Decoding meshes\x\ex_v_ban_redoran_01.nif
+Decoding meshes\x\ex_v_ban_telvanni_01.nif
+Decoding meshes\x\ex_v_ban_stdeyln_01.nif
+Decoding meshes\x\ex_v_ban_serving_01.nif
+Decoding meshes\x\ex_v_ban_comfort_01.nif
+Decoding meshes\x\ex_v_ban_tribunal_01.nif
+Decoding meshes\x\ex_hlaalu_bridge_06.nif
+Decoding meshes\x\ex_hlaalu_bridge_07.nif
+Decoding meshes\x\ex_hlaalu_bridge_04.nif
+Decoding meshes\x\ex_hlaalu_bridge_05.nif
+Decoding meshes\x\ex_hlaalu_bridge_02.nif
+Decoding meshes\x\ex_hlaalu_bridge_03.nif
+Decoding meshes\x\ex_hlaalu_bridge_10.nif
+Decoding meshes\x\ex_hlaalu_bridge_01.nif
+Decoding meshes\x\ex_hlaalu_wall_up_01.nif
+Decoding meshes\x\ex_hlaalu_dsteps_01.nif
+Decoding meshes\x\ex_hlaalu_dsteps_02.nif
+Decoding meshes\x\ex_hlaalu_dsteps_03.nif
+Decoding meshes\x\ex_hlaalu_wall_up_02.nif
+Decoding meshes\x\ex_hlaalu_balcony_02.nif
+Decoding meshes\x\ex_hlaalu_balcony_01.nif
+Decoding meshes\x\ex_hlaalu_buttress_04.nif
+Decoding meshes\x\ex_hlaalu_wall_end_01.nif
+Decoding meshes\x\ex_hlaalu_buttress_05.nif
+Decoding meshes\x\ex_hlaalu_wall_gate_03.nif
+Decoding meshes\x\ex_hlaalu_wall_gate_01.nif
+Decoding meshes\x\ex_hlaalu_buttress_03.nif
+Decoding meshes\x\ex_hlaalu_wall_gate_04.nif
+Decoding meshes\x\ex_hlaalu_wall_gate_02.nif
+Decoding meshes\x\ex_hlaalu_buttress_01.nif
+Decoding meshes\x\ex_hlaalu_wall_curve_01.nif
+Decoding meshes\x\ex_hlaalu_striderport_01.nif
+Decoding meshes\x\ex_holamayan_cover_01.nif
+Decoding meshes\i\in_t_council_beams_02.nif
+Decoding meshes\i\in_c_connect_stair_short.nif
+Decoding meshes\i\in_redoran_ladder_01.nif
+Decoding meshes\i\in_redoran_ashpit_02.nif
+Decoding meshes\i\in_redoran_s_3way_01.nif
+Decoding meshes\i\in_redoran_s_4way_01.nif
+Decoding meshes\i\in_redoran_tower_01.nif
+Decoding meshes\i\in_redoran_l_join_01.nif
+Decoding meshes\i\in_redoran_s_hall_01.nif
+Decoding meshes\i\in_redoran_window_01.nif
+Decoding meshes\i\in_redoran_tavern_01.nif
+Decoding meshes\i\in_redoran_l_hall_01.nif
+Decoding meshes\i\in_redoran_steps_02.nif
+Decoding meshes\i\in_redoran_steps_03.nif
+Decoding meshes\i\in_redoran_l_cap_01.nif
+Decoding meshes\i\in_redoran_l_3way_01.nif
+Decoding meshes\i\in_redoran_l_4way_01.nif
+Decoding meshes\i\in_redoran_s_hall_02.nif
+Decoding meshes\i\in_redoran_s_cap_01.nif
+Decoding meshes\i\in_redoran_ashpit_01.nif
+Decoding meshes\i\in_redoran_barracks_01.nif
+Decoding meshes\i\in_redoran_window2_01.nif
+Decoding meshes\i\in_redoran_hut_jamb_01.nif
+Decoding meshes\i\in_redoran_s_corner_01.nif
+Decoding meshes\i\in_redoran_l_corner_01.nif
+Decoding meshes\i\in_redoran_woodrail_01.nif
+Decoding meshes\i\in_t_doorjamb_hall_small.nif
+Decoding meshes\i\in_redoran_staircase_03.nif
+Decoding meshes\i\in_redoran_hut_bfloor_02.nif
+Decoding meshes\i\in_redoran_hut_bfloor_01.nif
+Decoding meshes\i\in_redoran_hut_tfloor_01.nif
+Decoding meshes\i\in_redoran_l_doorjamb_01.nif
+Decoding meshes\i\in_redoran_staircase_02.nif
+Decoding meshes\i\in_redoran_staircase_04.nif
+Decoding meshes\i\in_dagoth_scaffold00.nif
+Decoding meshes\i\in_t_housepod_stairs.nif
+Decoding meshes\i\in_t_housepod_01_hall.nif
+Decoding meshes\i\in_t_housepod_pole_01.nif
+Decoding meshes\i\in_t_housepod_pole_02.nif
+Decoding meshes\i\in_t_housepod_pole_04.nif
+Decoding meshes\i\in_t_housepod_pole_03.nif
+Decoding meshes\i\in_t_housepod_2ndfloor.nif
+Decoding meshes\i\in_t_housepod_2flr_stair.nif
+Decoding meshes\i\in_t_housepod_01_hall_02.nif
+Decoding meshes\i\in_t_housepod_djamb_exit.nif
+Decoding meshes\i\in_velothismall_ws_01.nif
+Decoding meshes\i\in_velothilarge_con_01.nif
+Decoding meshes\i\in_velothismall_cap_02.nif
+Decoding meshes\i\in_velothilarge_cap_01.nif
+Decoding meshes\i\in_velothismall_pit_01.nif
+Decoding meshes\i\in_velothismall_mid_01.nif
+Decoding meshes\i\in_velothi_platform_01.nif
+Decoding meshes\i\in_velothismall_dj_01.nif
+Decoding meshes\i\in_velothismall_cap_01.nif
+Decoding meshes\i\in_velothismall_pit_02.nif
+Decoding meshes\i\in_velothismall_pitd_02.nif
+Decoding meshes\i\in_velothismall_pitd_04.nif
+Decoding meshes\i\in_velothismall_pitd_06.nif
+Decoding meshes\i\in_velothi_s_pitstep_01.nif
+Decoding meshes\i\in_velothi_s_stairsl_01.nif
+Decoding meshes\i\in_velothismall_hall_04.nif
+Decoding meshes\i\in_velothismall_rail_01.nif
+Decoding meshes\i\in_velothismall_ramp_01.nif
+Decoding meshes\i\in_velothismall_ramp_03.nif
+Decoding meshes\i\in_velothismall_room_11.nif
+Decoding meshes\i\in_velothismall_r8_ramp.nif
+Decoding meshes\i\in_velothilarge_ramp_01.nif
+Decoding meshes\i\in_velothismall_room_08.nif
+Decoding meshes\i\in_velothismall_room_02.nif
+Decoding meshes\i\in_velothismall_room_06.nif
+Decoding meshes\i\in_velothismall_room_04.nif
+Decoding meshes\i\in_velothismall_4way_01.nif
+Decoding meshes\i\in_velothismall_3way_01.nif
+Decoding meshes\i\in_velothi_s_ceiling_01.nif
+Decoding meshes\i\in_velothi_s_ramplong_01.nif
+Decoding meshes\i\in_velothi_s_ramplong_02.nif
+Decoding meshes\i\in_velothismall_wall_03.nif
+Decoding meshes\i\in_velothismall_wall_01.nif
+Decoding meshes\i\in_velothi_s_doorjam_01.nif
+Decoding meshes\i\in_velothismall_pitd_01.nif
+Decoding meshes\i\in_velothismall_pitd_03.nif
+Decoding meshes\i\in_velothismall_pitd_05.nif
+Decoding meshes\i\in_velothi_s_pitstep_02.nif
+Decoding meshes\i\in_velothismall_hall_01.nif
+Decoding meshes\i\in_velothismall_hall_03.nif
+Decoding meshes\i\in_velothismall_ramp_02.nif
+Decoding meshes\i\in_velothilarge_4way_01.nif
+Decoding meshes\i\in_velothilarge_3way_01.nif
+Decoding meshes\i\in_velothismall_room_10.nif
+Decoding meshes\i\in_velothismall_room_12.nif
+Decoding meshes\i\in_velothismall_door_01.nif
+Decoding meshes\i\in_velothilarge_hall_01.nif
+Decoding meshes\i\in_velothilarge_ramp_02.nif
+Decoding meshes\i\in_velothismall_room_09.nif
+Decoding meshes\i\in_velothismall_room_03.nif
+Decoding meshes\i\in_velothismall_room_01.nif
+Decoding meshes\i\in_velothismall_room_07.nif
+Decoding meshes\i\in_velothismall_room_05.nif
+Decoding meshes\i\in_velothismall_3way_02.nif
+Decoding meshes\i\in_velothismall_dome_01.nif
+Decoding meshes\i\in_velothismall_curve_02.nif
+Decoding meshes\i\in_velothismall_curve_01.nif
+Decoding meshes\i\in_velothismall_wall_02.nif
+Decoding meshes\i\in_velothismall_r8_dome.nif
+Decoding meshes\i\in_velothi_s_halfhall_01.nif
+Decoding meshes\i\in_stronghold_hall03.nif
+Decoding meshes\i\in_stronghold_hall00.nif
+Decoding meshes\i\in_stronghold_wall00.nif
+Decoding meshes\i\in_stronghold_wall10.nif
+Decoding meshes\i\in_stronghold_dome00.nif
+Decoding meshes\i\in_stronghold_arch00.nif
+Decoding meshes\i\in_stronghold_hall04.nif
+Decoding meshes\i\in_strong_balcony10.nif
+Decoding meshes\i\in_strong_balcony00.nif
+Decoding meshes\i\in_strong_archway00.nif
+Decoding meshes\i\in_strong_archway01.nif
+Decoding meshes\i\in_strong_shutter00.nif
+Decoding meshes\i\in_strongruin_hall02.nif
+Decoding meshes\i\in_strong_doorjam00.nif
+Decoding meshes\i\in_strong_hallpill00.nif
+Decoding meshes\i\in_strongruin_hall01.nif
+Decoding meshes\i\in_stronghold_hall02.nif
+Decoding meshes\i\in_strong_dualpill00.nif
+Decoding meshes\i\in_stronghold_hall01.nif
+Decoding meshes\i\in_strongruin_hall00.nif
+Decoding meshes\i\in_strongruin_wall10.nif
+Decoding meshes\i\in_strongruin_wall00.nif
+Decoding meshes\i\in_strongruin2_hall02.nif
+Decoding meshes\i\in_stronghold_corr2_05.nif
+Decoding meshes\i\in_stronghold_corr2_07.nif
+Decoding meshes\i\in_stronghold_corr2_01.nif
+Decoding meshes\i\in_stronghold_corr2_03.nif
+Decoding meshes\i\in_stronghold_corr3_01.nif
+Decoding meshes\i\in_stronghold_stairs00.nif
+Decoding meshes\i\in_stronghold_hcorr00.nif
+Decoding meshes\i\in_stronghold_hcorr02.nif
+Decoding meshes\i\in_stronghold_lpill00.nif
+Decoding meshes\i\in_strongruin_corr2_07.nif
+Decoding meshes\i\in_strongruin_corr2_05.nif
+Decoding meshes\i\in_strongruin_corr2_03.nif
+Decoding meshes\i\in_strongruin_corr2_01.nif
+Decoding meshes\i\in_strongruin_corr3_01.nif
+Decoding meshes\i\in_strongruin_hcorr00.nif
+Decoding meshes\i\in_stronghold_corr4_00.nif
+Decoding meshes\i\in_stronghold_corr2_04.nif
+Decoding meshes\i\in_stronghold_corr2_06.nif
+Decoding meshes\i\in_stronghold_corr2_00.nif
+Decoding meshes\i\in_stronghold_corr2_02.nif
+Decoding meshes\i\in_strongruin2_ramp10.nif
+Decoding meshes\i\in_stronghold_corr3_00.nif
+Decoding meshes\i\in_strongruin_hcorr01.nif
+Decoding meshes\i\in_strongruin_hcorr02.nif
+Decoding meshes\i\in_strongruin2_hall01.nif
+Decoding meshes\i\in_strongruin2_hall04.nif
+Decoding meshes\i\in_strongruin2_hall03.nif
+Decoding meshes\i\in_stronghold_hcorr01.nif
+Decoding meshes\i\in_strongruin2_hall00.nif
+Decoding meshes\i\in_strong_vaultdoor00.nif
+Decoding meshes\i\in_strongruin_corr2_06.nif
+Decoding meshes\i\in_strongruin_corr2_04.nif
+Decoding meshes\i\in_strongruin_corr2_02.nif
+Decoding meshes\i\in_strongruin_corr2_00.nif
+Decoding meshes\i\in_strongruin_corr3_00.nif
+Decoding meshes\i\in_strongruin_stairs00.nif
+Decoding meshes\i\in_strongruin2_corr2_04.nif
+Decoding meshes\i\in_strongruin2_corr2_02.nif
+Decoding meshes\i\in_strongruin2_corr2_00.nif
+Decoding meshes\i\in_strongruin2_corr3_02.nif
+Decoding meshes\i\in_strongruin2_corr3_00.nif
+Decoding meshes\i\in_strongruin2_corr1_00.nif
+Decoding meshes\i\in_strongruin2_corr4_00.nif
+Decoding meshes\i\in_stronghold_divider10.nif
+Decoding meshes\i\in_strongruin_divider10.nif
+Decoding meshes\i\in_strongruin2_shutter00.nif
+Decoding meshes\i\in_strongruin_roofext00.nif
+Decoding meshes\i\in_strongruin_hallpill00.nif
+Decoding meshes\i\in_strongruin_collapse10.nif
+Decoding meshes\i\in_strongruin2_corr2_03.nif
+Decoding meshes\i\in_strongruin2_corr2_01.nif
+Decoding meshes\i\in_strongruin2_corr3_01.nif
+Decoding meshes\i\in_stronghold_divider00.nif
+Decoding meshes\i\in_strong_portal_chamber.nif
+Decoding meshes\i\in_stronghold_roofext00.nif
+Decoding meshes\i\in_strongruin2_balcony00.nif
+Decoding meshes\i\in_strongruin_divider00.nif
+Decoding meshes\i\in_strongruin2_balcony10.nif
+Decoding meshes\i\in_strongruin2_dualpill00.nif
+Decoding meshes\i\in_strongruin2_hallpill00.nif
+Decoding meshes\i\in_strongruin2_collapse00.nif
+Decoding meshes\i\in_velothismall_pittw_b_01.nif
+Decoding meshes\i\in_velothismall_pittd_b_01.nif
+Decoding meshes\i\in_velothismall_pitct_b_01.nif
+Decoding meshes\i\in_velothismall_pitcb_b_01.nif
+Decoding meshes\i\in_velothismall_pitbd_b_01.nif
+Decoding meshes\i\in_velothilarge_corner_01.nif
+Decoding meshes\i\in_velothismall_pitmw_b_01.nif
+Decoding meshes\i\in_velothismall_pittop_02.nif
+Decoding meshes\i\in_velothismall_pittop_01.nif
+Decoding meshes\i\in_velothismall_fresco_01.nif
+Decoding meshes\i\in_velothismall_fresco_02.nif
+Decoding meshes\i\in_velothismall_pitc_b_01.nif
+Decoding meshes\i\in_velothismall_pitbot_02.nif
+Decoding meshes\i\in_velothismall_pitbot_01.nif
+Decoding meshes\i\in_velothismall_pitb_b_01.nif
+Decoding meshes\i\in_velothismall_column_01.nif
+Decoding meshes\i\in_velothismall_column_03.nif
+Decoding meshes\i\in_velothismall_column_02.nif
+Decoding meshes\i\in_velothilarge_stairs_01.nif
+Decoding meshes\i\in_velothismall_pitd_b_02.nif
+Decoding meshes\i\in_velothismall_pitd_b_03.nif
+Decoding meshes\i\in_velothismall_pitd_b_01.nif
+Decoding meshes\i\in_velothismall_corner_01.nif
+Decoding meshes\i\in_velothismall_stairs_01.nif
+Decoding meshes\i\in_velothismall_pitmid_02.nif
+Decoding meshes\i\in_velothismall_pitmid_01.nif
+Decoding meshes\i\in_t_monor01_cap_topright.nif
+Decoding meshes\d\ex_v_palace_grate_02.nif
+Decoding meshes\d\ex_v_palace_grate_01.nif
+Decoding meshes\i\in_ashl_tent_banner_08.nif
+Decoding meshes\i\in_ashl_tent_banner_04.nif
+Decoding meshes\i\in_ashl_tent_banner_06.nif
+Decoding meshes\i\in_ashl_tent_banner_02.nif
+Decoding meshes\i\in_ashl_tent_banner_16.nif
+Decoding meshes\i\in_ashl_tent_banner_14.nif
+Decoding meshes\i\in_ashl_tent_banner_12.nif
+Decoding meshes\i\in_ashl_tent_banner_10.nif
+Decoding meshes\i\in_ashl_tent_banner_09.nif
+Decoding meshes\i\in_ashl_tent_banner_05.nif
+Decoding meshes\i\in_ashl_tent_banner_07.nif
+Decoding meshes\i\in_ashl_tent_banner_01.nif
+Decoding meshes\i\in_ashl_tent_banner_03.nif
+Decoding meshes\i\in_ashl_tent_banner_15.nif
+Decoding meshes\i\in_ashl_tent_banner_13.nif
+Decoding meshes\i\in_ashl_tent_banner_11.nif
+Decoding meshes\i\in_c_plain_room_side.nif
+Decoding meshes\i\in_c_plain_room_center.nif
+Decoding meshes\i\in_c_plain_room_corner.nif
+Decoding meshes\i\in_c_plain_room_entry.nif
+Decoding meshes\i\in_c_plain_hall_small.nif
+Decoding meshes\i\in_c_plain_stair_short.nif
+Decoding meshes\i\in_c_plain_r_cwin_bay_02.nif
+Decoding meshes\i\in_c_plain_r_cwin_bay_01.nif
+Decoding meshes\i\in_c_plain_r_swin_bay_01.nif
+Decoding meshes\i\in_c_plain_room_cwin_02.nif
+Decoding meshes\i\in_c_plain_r_cwin_rec_02.nif
+Decoding meshes\i\in_c_plain_r_cwin_rec_03.nif
+Decoding meshes\i\in_c_plain_r_cwin_rec_01.nif
+Decoding meshes\i\in_c_plain_r_cwin_rec_04.nif
+Decoding meshes\i\in_c_plain_r_swin_rec_01.nif
+Decoding meshes\i\in_c_plain_r_cwin_tri_01.nif
+Decoding meshes\i\in_c_plain_r_cwin_tri_02.nif
+Decoding meshes\i\in_c_plain_r_swin_tri_01.nif
+Decoding meshes\i\in_c_plain_room_swin_01.nif
+Decoding meshes\i\in_c_plain_room_cwin_01.nif
+Decoding meshes\d\hlaalu_loaddoor_ 02.nif
+Decoding meshes\d\hlaalu_loaddoor_ 01.nif
+Decoding meshes\i\in_common_lighthouse.nif
+Decoding meshes\i\in_common_room_corner.nif
+Decoding meshes\i\in_common_tower_thatch.nif
+Decoding meshes\i\in_common_tower_thatch3.nif
+Decoding meshes\i\in_common_tower_thatch2.nif
+Decoding meshes\a\a_boot_hvy_leather_gnd.nif
+Decoding meshes\i\in_c_djamb_rp_arched.nif
+Decoding meshes\i\in_c_djamb_rich_arched.nif
+Decoding meshes\i\in_c_djamb_plain_arched.nif
+Decoding meshes\i\in_c_djamb_stone_square.nif
+Decoding meshes\i\in_c_djamb_plain_square.nif
+Decoding meshes\i\in_c_djamb_stone_arched.nif
+Decoding meshes\menu_scroll_button_up.nif
+Decoding meshes\menu_scroll_button_down.nif
+Decoding meshes\menu_scroll_button_right.nif
+Decoding meshes\menu_scroll_button_left.nif
+Decoding meshes\c\c_belt_extravagant_1.nif
+Decoding meshes\c\c_belt_extravagant_2.nif
+Decoding meshes\i\in_c_thatch_room_pside.nif
+Decoding meshes\i\in_c_thatch_room_pcorner.nif
+Decoding meshes\i\in_c_thatch_room_pentry.nif
+Decoding meshes\i\in_c_thatch_room_pcenter.nif
+Decoding meshes\menu_small_energy_bar.nif
+Decoding meshes\i\in_c_thatch_room_pcorner2.nif
+Decoding meshes\i\in_c_thatch_room_pendside.nif
+Decoding meshes\i\in_c_thatch_room_pendside2.nif
+Decoding meshes\i\in_c_pillar_wood_tall.nif
+Decoding meshes\i\in_c_rich_room_pside.nif
+Decoding meshes\i\in_c_rich_room_side.nif
+Decoding meshes\i\in_c_rich_room_entry.nif
+Decoding meshes\i\in_c_rich_room_center.nif
+Decoding meshes\i\in_c_rich_room_pcenter.nif
+Decoding meshes\i\in_c_rich_room_pcorner.nif
+Decoding meshes\i\in_c_rich_room_pentry.nif
+Decoding meshes\i\in_c_rich_room_corner.nif
+Decoding meshes\i\in_c_rich_r_swin_bay_01.nif
+Decoding meshes\i\in_c_rich_r_cwin_bay_01.nif
+Decoding meshes\i\in_c_rich_rp_cwin_bay_04.nif
+Decoding meshes\i\in_c_rich_rp_cwin_bay_03.nif
+Decoding meshes\i\in_c_rich_rp_cwin_bay_02.nif
+Decoding meshes\i\in_c_rich_rp_cwin_bay_01.nif
+Decoding meshes\i\in_c_rich_r_cwin_tri_02.nif
+Decoding meshes\i\in_c_rich_r_cwin_rec_01.nif
+Decoding meshes\i\in_c_rich_r_cwin_rec_03.nif
+Decoding meshes\i\in_c_rich_r_swin_rec_01.nif
+Decoding meshes\i\in_c_rich_room_pcorner2.nif
+Decoding meshes\i\in_c_rich_r_cwin_bay_02.nif
+Decoding meshes\i\in_c_rich_room_pendside2.nif
+Decoding meshes\i\in_c_rich_r_swin_tri_01.nif
+Decoding meshes\i\in_c_rich_r_cwin_tri_01.nif
+Decoding meshes\i\in_c_rich_room_pendside.nif
+Decoding meshes\i\in_c_rich_r_cwin_rec_02.nif
+Decoding meshes\i\in_c_rich_r_cwin_rec_04.nif
+Decoding meshes\x\ex_redwall_corner_02.nif
+Decoding meshes\x\ex_redwall_corner_01.nif
+Decoding meshes\x\ex_redwall_corner_03.nif
+Decoding meshes\x\ex_redwall_straight_02.nif
+Decoding meshes\x\ex_redwall_straight_01.nif
+Decoding meshes\x\ex_dwrv_ruin_tower00.nif
+Decoding meshes\x\ex_dwrv_steamstack00.nif
+Decoding meshes\x\ex_dwrv_pipefitting00.nif
+Decoding meshes\i\in_moldcave_sroom4_05.nif
+Decoding meshes\i\in_moldcave_lroom3_02.nif
+Decoding meshes\i\in_moldcave_lroom4_02.nif
+Decoding meshes\i\in_moldcave_nat_exit00.nif
+Decoding meshes\i\in_moldcave_sroom4_01.nif
+Decoding meshes\i\in_moldcave_sroom3_01.nif
+Decoding meshes\i\in_moldcave_lroom4_03.nif
+Decoding meshes\i\in_moldcave_lroom4_01.nif
+Decoding meshes\i\in_moldcave_lroom3_01.nif
+Decoding meshes\i\in_moldcave_sroom4_04.nif
+Decoding meshes\i\in_moldcave_lroom2_00.nif
+Decoding meshes\i\in_moldcave_lroom3_00.nif
+Decoding meshes\i\in_moldcave_lroom4_05.nif
+Decoding meshes\i\in_moldcave_sroom4_03.nif
+Decoding meshes\i\in_moldcave_doorway00.nif
+Decoding meshes\i\in_moldcave_sroom3_02.nif
+Decoding meshes\i\in_moldcave_sroom4_02.nif
+Decoding meshes\i\in_moldcave_sroom2_00.nif
+Decoding meshes\i\in_moldcave_sroom3_00.nif
+Decoding meshes\i\in_moldcave_lroom4_04.nif
+Decoding meshes\i\in_nord_ladder_01_a.nif
+Decoding meshes\i\in_nord_fireplace_01.nif
+Decoding meshes\i\in_nord_rafterboards_01.nif
+Decoding meshes\x\ex_t_stair_90_l_short.nif
+Decoding meshes\x\ex_t_stair_90_r_short.nif
+Decoding meshes\i\in_bonecave_sroom4_05.nif
+Decoding meshes\i\in_bonecave_lroom3_02.nif
+Decoding meshes\i\in_bonecave_lroom4_02.nif
+Decoding meshes\i\in_bonecave_sroom4_01.nif
+Decoding meshes\i\in_bonecave_sroom3_01.nif
+Decoding meshes\i\in_bonecave_lroom4_03.nif
+Decoding meshes\i\in_bonecave_lroom4_01.nif
+Decoding meshes\i\in_bonecave_lroom3_01.nif
+Decoding meshes\i\in_bonecave_sroom4_04.nif
+Decoding meshes\i\in_bonecave_lroom2_00.nif
+Decoding meshes\i\in_bonecave_lroom3_00.nif
+Decoding meshes\i\in_bonecave_lroom4_05.nif
+Decoding meshes\i\in_bonecave_sroom4_03.nif
+Decoding meshes\i\in_bonecave_doorway00.nif
+Decoding meshes\i\in_bonecave_sroom3_02.nif
+Decoding meshes\i\in_bonecave_sroom4_02.nif
+Decoding meshes\i\in_bonecave_sroom2_00.nif
+Decoding meshes\i\in_bonecave_sroom3_00.nif
+Decoding meshes\i\in_bonecave_lroom4_04.nif
+Decoding meshes\x\in_c_stair_plain_tall_01.nif
+Decoding meshes\x\in_c_stair_plain_tall_02.nif
+Decoding meshes\i\in_sewer_collapse00.nif
+Decoding meshes\i\in_sewer_anchorlock00.nif
+Decoding meshes\i\in_vivec_waterspout_01.nif
+Decoding meshes\i\in_cavern_wallmount00.nif
+Decoding meshes\x\ex_de_shack_plank_03.nif
+Decoding meshes\x\ex_de_shack_plank_04.nif
+Decoding meshes\x\ex_de_shack_plank_02.nif
+Decoding meshes\x\ex_de_ship_oarright.nif
+Decoding meshes\x\ex_de_shack_plank_01.nif
+Decoding meshes\x\ex_de_shack_steps_01.nif
+Decoding meshes\x\ex_de_shack_awning_01.nif
+Decoding meshes\x\ex_de_shack_awning_06.nif
+Decoding meshes\x\ex_de_shack_awning_03.nif
+Decoding meshes\x\ex_de_shack_awning_04.nif
+Decoding meshes\x\ex_de_shack_awning_02.nif
+Decoding meshes\x\ex_de_shack_awning_05.nif
+Decoding meshes\d\in_redoran_hut_door_01.nif
+Decoding meshes\d\in_t_door_small_load.nif
+Decoding meshes\d\in_c_door_wood_square.nif
+Decoding meshes\d\in_t_housepod_door_exit.nif
+Decoding meshes\d\in_t_housepod_djamb_exit.nif
+Decoding meshes\d\in_velothismall_ndoor_01.nif
+Decoding meshes\x\ex_drystonewall_d_01.nif
+Decoding meshes\x\ex_drystonewall_c_01.nif
+Decoding meshes\x\ex_drystonewall_s_01.nif
+Decoding meshes\x\ex_drystonewall_end_01.nif
+Decoding meshes\d\in_strong_vaultdoor00.nif
+Decoding meshes\d\ex_redoran_hut_01_a.nif
+Decoding meshes\d\ex_t_door_sphere_01.nif
+Decoding meshes\d\ex_t_door_slavepod_01.nif
+Decoding meshes\d\ex_t_door_stone_large.nif
+Decoding meshes\d\ex_redoran_barracks_01_a.nif
+Decoding meshes\d\ex_velothi_loaddoor_02.nif
+Decoding meshes\d\ex_velothi_loaddoor_01.nif
+Decoding meshes\d\ex_velothi_entrance_03_a.nif
+Decoding meshes\d\ex_velothi_entrance_01_a.nif
+Decoding meshes\x\ex_gnisis_roadmarker_01.nif
+Decoding meshes\d\in_redoran_barrackdoor_01.nif
+Decoding meshes\i\in_mudcave_sroom4_05.nif
+Decoding meshes\i\in_mudcave_lroom3_02.nif
+Decoding meshes\i\in_mudcave_lroom4_02.nif
+Decoding meshes\i\in_mudcave_sroom4_01.nif
+Decoding meshes\i\in_mudcave_sroom3_01.nif
+Decoding meshes\i\in_mudcave_lroom4_03.nif
+Decoding meshes\i\in_mudcave_lroom4_01.nif
+Decoding meshes\i\in_mudcave_lroom3_01.nif
+Decoding meshes\i\in_mudcave_sroom4_04.nif
+Decoding meshes\i\in_mudcave_lroom2_00.nif
+Decoding meshes\i\in_mudcave_lroom3_00.nif
+Decoding meshes\i\in_mudcave_lroom4_05.nif
+Decoding meshes\i\in_mudcave_sroom4_03.nif
+Decoding meshes\i\in_mudcave_doorway00.nif
+Decoding meshes\i\in_mudcave_sroom3_02.nif
+Decoding meshes\i\in_mudcave_sroom4_02.nif
+Decoding meshes\i\in_mudcave_sroom2_00.nif
+Decoding meshes\i\in_mudcave_sroom3_00.nif
+Decoding meshes\i\in_mudcave_lroom4_04.nif
+Decoding meshes\i\in_mudcave_nat_exit00.nif
+Decoding meshes\x\ex_terrain_woodstep_01.nif
+Decoding meshes\x\ex_t_bridge_lcurved.nif
+Decoding meshes\n\ingred_dreugh_wax_01.nif
+Decoding meshes\n\ingred_ectoplasm_01.nif
+Decoding meshes\n\ingred_fire_salts_01.nif
+Decoding meshes\n\ingred_whickwheat_01.nif
+Decoding meshes\n\ingred_ash_salts_01.nif
+Decoding meshes\n\ingred_hound_meat_01.nif
+Decoding meshes\n\ingred_scathecraw_01.nif
+Decoding meshes\n\ingred_sload_soap_01.nif
+Decoding meshes\n\ingred_bc_spore_pod.nif
+Decoding meshes\n\ingred_scamp_skin_01.nif
+Decoding meshes\n\ingred_trama_root_01.nif
+Decoding meshes\n\ingred_moon_sugar_01.nif
+Decoding meshes\n\ingred_guar_hide_01.nif
+Decoding meshes\n\ingred_alit_hide_01.nif
+Decoding meshes\n\ingred_gold_kanet_01.nif
+Decoding meshes\n\ingred_chokeweed_01.nif
+Decoding meshes\n\ingred_void_salts_01.nif
+Decoding meshes\n\ingred_blonemeal_01.nif
+Decoding meshes\n\ingred_fire_petal_01.nif
+Decoding meshes\n\ingred_raw_glass_01.nif
+Decoding meshes\n\ingred_red_lichen_01.nif
+Decoding meshes\n\ingred_gravedust_01.nif
+Decoding meshes\n\ingred_hackle-lo_01.nif
+Decoding meshes\n\ingred_frost_salts_01.nif
+Decoding meshes\n\ingred_ghoul_heart_01.nif
+Decoding meshes\n\ingred_green_lichen_01.nif
+Decoding meshes\n\ingred_daedra_heart_01.nif
+Decoding meshes\n\ingred_daedra_skin_01.nif
+Decoding meshes\n\ingred_bc_coda_flower.nif
+Decoding meshes\n\ingred_black_anther_01.nif
+Decoding meshes\n\ingred_black_lichen_01.nif
+Decoding meshes\n\ingred_bc_ampoule_pod.nif
+Decoding meshes\n\ingred_bittergreen_01.nif
+Decoding meshes\n\ingred_bc_hypha_facia.nif
+Decoding meshes\n\ingred_marshmerrow_01.nif
+Decoding meshes\n\ingred_kresh_fiber_01.nif
+Decoding meshes\n\ingred_kagouti_hide_01.nif
+Decoding meshes\n\ingred_kwama_cuttle_01.nif
+Decoding meshes\n\ingred_vampire_dust_01.nif
+Decoding meshes\n\ingred_racer_plumes_01.nif
+Decoding meshes\n\ingred_shalk_resin_01.nif
+Decoding meshes\n\ingred_scrib_jelly_01.nif
+Decoding meshes\n\ingred_scrib_jerky_01.nif
+Decoding meshes\n\ingred_scrap_metal_01.nif
+Decoding meshes\n\ingred_stoneflower_01.nif
+Decoding meshes\n\ingred_6th_corpusmeat_01.nif
+Decoding meshes\n\ingred_6th_corpusmeat_02.nif
+Decoding meshes\n\ingred_6th_corpusmeat_03.nif
+Decoding meshes\n\ingred_6th_corpusmeat_04.nif
+Decoding meshes\n\ingred_6th_corpusmeat_05.nif
+Decoding meshes\n\ingred_6th_corpusmeat_06.nif
+Decoding meshes\n\ingred_6th_corpusmeat_07.nif
+Decoding meshes\n\ingred_willow_anther_01.nif
+Decoding meshes\n\ingred_corkbulb_root_01.nif
+Decoding meshes\n\ingred_bc_bungler's_bane.nif
+Decoding meshes\n\ingred_netch_leather_01.nif
+Decoding meshes\d\ex_common_door_balcony.nif
+Decoding meshes\n\ingred_corprus_weeping_01.nif
+Decoding meshes\x\ex_gg_portcullis_01.nif
+Decoding meshes\a\a_iron_gauntlet_gnd.nif
+Decoding meshes\a\a_iron_pauldron_gnd.nif
+Decoding meshes\d\in_de_llshipdoor_large.nif
+Decoding meshes\i\in_hlaalu_room_door3.nif
+Decoding meshes\i\in_hlaalu_hall_rail.nif
+Decoding meshes\i\in_hlaalu_room_post.nif
+Decoding meshes\i\in_hlaalu_room_side.nif
+Decoding meshes\i\in_hlaalu_roomt_post.nif
+Decoding meshes\i\in_hlaalu_hallt_end.nif
+Decoding meshes\i\in_hlaalu_hall_3way.nif
+Decoding meshes\i\in_hlaalu_room_rail.nif
+Decoding meshes\i\in_hlaalu_hallt_3way.nif
+Decoding meshes\i\in_hlaalu_hallt_4way.nif
+Decoding meshes\i\in_hlaalu_roomt_side.nif
+Decoding meshes\i\in_hlaalu_hall_4way.nif
+Decoding meshes\i\in_hlaalu_room_door2.nif
+Decoding meshes\i\in_hlaalu_room_entry.nif
+Decoding meshes\i\in_hlaalu_room_door4.nif
+Decoding meshes\i\in_hlaalu_room_door1.nif
+Decoding meshes\i\in_hlaalu_ashpit_02.nif
+Decoding meshes\i\in_hlaalu_ashpit_01.nif
+Decoding meshes\i\in_hlaalu_hallway_ramp.nif
+Decoding meshes\i\in_hlaalu_hallt_3wayd.nif
+Decoding meshes\i\in_hlaalu_roomt_entry.nif
+Decoding meshes\i\in_hlaalu_platform_02.nif
+Decoding meshes\i\in_hlaalu_roomt_sided.nif
+Decoding meshes\i\in_hlaalu_loaddoor_01.nif
+Decoding meshes\i\in_hlaalu_hall_stairsr.nif
+Decoding meshes\i\in_hlaalu_hall_stairsl.nif
+Decoding meshes\i\in_hlaalu_loaddoor_02.nif
+Decoding meshes\i\in_hlaalu_room_center.nif
+Decoding meshes\i\in_hlaalu_hallway_end.nif
+Decoding meshes\i\in_hlaalu_hallt_center.nif
+Decoding meshes\i\in_hlaalu_room_stairsl.nif
+Decoding meshes\i\in_hlaalu_room_stairsr.nif
+Decoding meshes\i\in_hlaalu_platform_01.nif
+Decoding meshes\i\in_hlaalu_hallt_entry.nif
+Decoding meshes\i\in_hlaalu_room_corner.nif
+Decoding meshes\i\in_hlaalu_roomt_center.nif
+Decoding meshes\i\in_hlaalu_roomt_cornd_02.nif
+Decoding meshes\i\in_hlaalu_roomt_cornd_01.nif
+Decoding meshes\i\in_hlaalu_room_center_01.nif
+Decoding meshes\i\in_hlaalu_hallway_stairs.nif
+Decoding meshes\i\in_hlaalu_hallt_centerd.nif
+Decoding meshes\i\in_hlaalu_hallway_center.nif
+Decoding meshes\i\in_hlaalu_hall_corner_01.nif
+Decoding meshes\i\in_hlaalu_hallt_cornd_02.nif
+Decoding meshes\i\in_hlaalu_hallt_cornd_01.nif
+Decoding meshes\i\in_hlaalu_doorjamb_load.nif
+Decoding meshes\i\in_pycave_lroom3_01.nif
+Decoding meshes\i\in_pycave_sroom3_01.nif
+Decoding meshes\i\in_pycave_lroom3_00.nif
+Decoding meshes\i\in_pycave_sroom3_00.nif
+Decoding meshes\i\in_pycave_lroom3_02.nif
+Decoding meshes\i\in_pycave_sroom3_02.nif
+Decoding meshes\i\in_pycave_doorway00.nif
+Decoding meshes\i\in_pycave_lroom2_00.nif
+Decoding meshes\i\in_pycave_sroom2_00.nif
+Decoding meshes\i\in_pycave_nat_exit00.nif
+Decoding meshes\i\in_pycave_sroom4_04.nif
+Decoding meshes\i\in_pycave_lroom4_04.nif
+Decoding meshes\i\in_pycave_sroom4_05.nif
+Decoding meshes\i\in_pycave_lroom4_05.nif
+Decoding meshes\i\in_pycave_sroom4_01.nif
+Decoding meshes\i\in_pycave_lroom4_01.nif
+Decoding meshes\i\in_pycave_sroom4_02.nif
+Decoding meshes\i\in_pycave_lroom4_02.nif
+Decoding meshes\i\in_pycave_sroom4_03.nif
+Decoding meshes\i\in_pycave_lroom4_03.nif
+Decoding meshes\i\in_t_cave_conect_wiz.nif
+Decoding meshes\i\in_t_cave_conect_plain.nif
+Decoding meshes\i\in_t_cave_conect_endcap.nif
+Decoding meshes\a\shield_netch_leather.nif
+Decoding meshes\a\shield_nordic_leather.nif
+Decoding meshes\i\in_t_manor01_leftcap.nif
+Decoding meshes\i\in_t_manor02_backcap.nif
+Decoding meshes\i\in_t_manor01_backcap.nif
+Decoding meshes\i\in_t_manor_floor_01.nif
+Decoding meshes\i\in_t_manor_stairs_01.nif
+Decoding meshes\i\in_t_manor02_leftcap.nif
+Decoding meshes\i\in_t_manor01_rightcap.nif
+Decoding meshes\i\in_t_manor02_cap_right.nif
+Decoding meshes\i\in_t_manor01_frontcap.nif
+Decoding meshes\i\in_t_manor01_entry_top.nif
+Decoding meshes\i\in_t_manor02_rightcap.nif
+Decoding meshes\i\in_t_manor02_floor_01.nif
+Decoding meshes\i\in_t_manor01_floor_01.nif
+Decoding meshes\i\in_t_manor_djamb_exit.nif
+Decoding meshes\i\in_t_manor01_cap_left.nif
+Decoding meshes\i\in_t_manor02_cap_left.nif
+Decoding meshes\i\in_t_manor02_entry_right.nif
+Decoding meshes\i\in_t_manor01_entry_left.nif
+Decoding meshes\i\in_t_manor02_entry_left.nif
+Decoding meshes\i\in_t_railing_pole_rails.nif
+Decoding meshes\i\in_lava_blacksquare.nif
+Decoding meshes\i\in_lavacave_sroom4_05.nif
+Decoding meshes\i\in_lavacave_lroom3_02.nif
+Decoding meshes\i\in_lavacave_lroom4_02.nif
+Decoding meshes\i\in_lavacave_nat_exit00.nif
+Decoding meshes\i\in_lavacave_sroom4_01.nif
+Decoding meshes\i\in_lavacave_sroom3_01.nif
+Decoding meshes\i\in_lavacave_lroom4_03.nif
+Decoding meshes\i\in_lavacave_lroom4_01.nif
+Decoding meshes\i\in_lavacave_lroom3_01.nif
+Decoding meshes\i\in_lavacave_sroom4_04.nif
+Decoding meshes\i\in_lavacave_lroom2_00.nif
+Decoding meshes\i\in_lavacave_lroom3_00.nif
+Decoding meshes\i\in_lavacave_lroom4_05.nif
+Decoding meshes\i\in_lavacave_sroom4_03.nif
+Decoding meshes\i\in_lavacave_doorway00.nif
+Decoding meshes\i\in_lavacave_sroom3_02.nif
+Decoding meshes\i\in_lavacave_sroom4_02.nif
+Decoding meshes\i\in_lavacave_sroom2_00.nif
+Decoding meshes\i\in_lavacave_sroom3_00.nif
+Decoding meshes\i\in_lavacave_lroom4_04.nif
+Decoding meshes\i\in_t_manor02_entry_right01.nif
+Decoding meshes\i\in_t_manor01_cap_topright.nif
+Decoding meshes\i\in_hlaalu_roomt_corner_01.nif
+Decoding meshes\i\in_hlaalu_roomt_corner_02.nif
+Decoding meshes\i\in_hlaalu_roomt_corner_03.nif
+Decoding meshes\i\in_hlaalu_hallt_corner_01.nif
+Decoding meshes\r\xg_centurionspider.nif
+Decoding meshes\x\ex_gg_fence_s_h_01.nif
+Decoding meshes\x\ex_gg_particles_01.nif
+Decoding meshes\x\ex_de_docks_3wayb.nif
+Decoding meshes\x\ex_de_docks_4wayb.nif
+Decoding meshes\x\ex_de_docks_3ways.nif
+Decoding meshes\x\ex_de_shack_steps.nif
+Decoding meshes\x\ex_de_docks_cleat.nif
+Decoding meshes\x\ex_de_docks_center.nif
+Decoding meshes\x\ex_de_ship_oarleft.nif
+Decoding meshes\x\ex_de_scaffold_03.nif
+Decoding meshes\x\ex_de_scaffold_02.nif
+Decoding meshes\x\ex_de_scaffold_01.nif
+Decoding meshes\x\ex_de_docks_3waysb.nif
+Decoding meshes\x\ex_de_docks_steps.nif
+Decoding meshes\x\ex_de_docks_piling.nif
+Decoding meshes\x\ex_cave_stoneyb00.nif
+Decoding meshes\x\ex_vivec_sbase_01.nif
+Decoding meshes\x\ex_vivec_bridge_06.nif
+Decoding meshes\x\ex_vivec_bridge_07.nif
+Decoding meshes\x\ex_vivec_bridge_04.nif
+Decoding meshes\x\ex_vivec_bridge_02.nif
+Decoding meshes\x\ex_vivec_bridge_01.nif
+Decoding meshes\x\ex_vivec_bt_tb_01.nif
+Decoding meshes\x\ex_vivec_palace_01.nif
+Decoding meshes\x\ex_vivec_ent_t_01.nif
+Decoding meshes\x\ex_vivec_ent_t_02.nif
+Decoding meshes\x\ex_vivec_ent_b_01.nif
+Decoding meshes\x\ex_t_stair_spiral.nif
+Decoding meshes\x\ex_t_stair_rcurve.nif
+Decoding meshes\x\ex_t_stair_lcurve.nif
+Decoding meshes\x\ex_v_sign_hallj_01.nif
+Decoding meshes\x\ex_v_sign_arena_01.nif
+Decoding meshes\x\ex_v_sign_hallw_01.nif
+Decoding meshes\x\ex_dwrv_boulder20.nif
+Decoding meshes\x\ex_dwrv_boulder30.nif
+Decoding meshes\x\ex_dwrv_boulder00.nif
+Decoding meshes\x\ex_dwrv_boulder10.nif
+Decoding meshes\x\ex_nord_chimney_01.nif
+Decoding meshes\x\in_c_doorframe_01.nif
+Decoding meshes\x\ex_t_root_arch_01.nif
+Decoding meshes\x\ex_imp_towerb_top.nif
+Decoding meshes\x\ex_imp_statue_base.nif
+Decoding meshes\x\ex_imp_mooring_01.nif
+Decoding meshes\x\ex_imp_entrance_01.nif
+Decoding meshes\x\ex_imp_wallent_01.nif
+Decoding meshes\x\ex_imp_wallent_02.nif
+Decoding meshes\x\ex_t_gateway_great.nif
+Decoding meshes\x\ex_v_foundation_01.nif
+Decoding meshes\x\ex_v_foundation_02.nif
+Decoding meshes\x\ex_v_foundation_03.nif
+Decoding meshes\x\ex_dae_b_bo_cape2.nif
+Decoding meshes\x\ex_dae_b_bo_cape3.nif
+Decoding meshes\x\ex_dae_b_bo_cape1.nif
+Decoding meshes\x\ex_dae_wall_256_09.nif
+Decoding meshes\x\ex_dae_wall_256_08.nif
+Decoding meshes\x\ex_dae_wall_256_01.nif
+Decoding meshes\x\ex_dae_wall_256_10.nif
+Decoding meshes\x\ex_dae_wall_256_03.nif
+Decoding meshes\x\ex_dae_wall_256_13.nif
+Decoding meshes\x\ex_dae_wall_256_02.nif
+Decoding meshes\x\ex_dae_wall_256_05.nif
+Decoding meshes\x\ex_dae_wall_256_04.nif
+Decoding meshes\x\ex_dae_wall_256_07.nif
+Decoding meshes\x\ex_dae_wall_256_06.nif
+Decoding meshes\x\ex_dae_wall_512_09.nif
+Decoding meshes\x\ex_dae_wall_512_08.nif
+Decoding meshes\x\ex_dae_wall_512_05.nif
+Decoding meshes\x\ex_dae_wall_512_04.nif
+Decoding meshes\x\ex_dae_wall_512_07.nif
+Decoding meshes\x\ex_dae_wall_512_06.nif
+Decoding meshes\x\ex_dae_wall_512_01.nif
+Decoding meshes\x\ex_dae_wall_512_10.nif
+Decoding meshes\x\ex_dae_wall_512_03.nif
+Decoding meshes\x\ex_dae_wall_512_02.nif
+Decoding meshes\x\ex_dae_azura_small.nif
+Decoding meshes\x\ex_dae_sheogorath.nif
+Decoding meshes\x\ex_dae_b_bo_ubody.nif
+Decoding meshes\x\ex_dae_b_bo_lbody.nif
+Decoding meshes\x\ex_redoran_hut_02.nif
+Decoding meshes\x\ex_redoran_hut_01.nif
+Decoding meshes\x\ex_redwall_post_01.nif
+Decoding meshes\x\ex_redwall_arch_01.nif
+Decoding meshes\x\ex_t_clawgrowth_02.nif
+Decoding meshes\x\ex_t_clawgrowth_01.nif
+Decoding meshes\x\ex_v_ban_vivec_01.nif
+Decoding meshes\x\ex_v_ban_vivec_02.nif
+Decoding meshes\x\ex_v_ban_arena_01.nif
+Decoding meshes\x\ex_v_ban_faith_01.nif
+Decoding meshes\x\ex_v_ban_stolms_01.nif
+Decoding meshes\x\ex_v_ban_count_01.nif
+Decoding meshes\x\ex_v_ban_hlaalu_01.nif
+Decoding meshes\x\ex_v_ban_child_01.nif
+Decoding meshes\x\ex_v_ban_speak_01.nif
+Decoding meshes\x\ex_scave2_enter10.nif
+Decoding meshes\x\ex_hlaalu_pole_01.nif
+Decoding meshes\x\ex_hlaalu_pole_02.nif
+Decoding meshes\x\ex_hlaalu_wall_01.nif
+Decoding meshes\x\ex_hlaalu_wall_02.nif
+Decoding meshes\x\ex_hlaalu_canal_05.nif
+Decoding meshes\x\ex_hlaalu_canal_04.nif
+Decoding meshes\x\ex_hlaalu_canal_07.nif
+Decoding meshes\x\ex_hlaalu_canal_06.nif
+Decoding meshes\x\ex_hlaalu_canal_11.nif
+Decoding meshes\x\ex_hlaalu_canal_01.nif
+Decoding meshes\x\ex_hlaalu_canal_10.nif
+Decoding meshes\x\ex_hlaalu_canal_13.nif
+Decoding meshes\x\ex_hlaalu_canal_03.nif
+Decoding meshes\x\ex_hlaalu_canal_12.nif
+Decoding meshes\x\ex_hlaalu_canal_02.nif
+Decoding meshes\x\ex_hlaalu_canal_09.nif
+Decoding meshes\x\ex_hlaalu_canal_08.nif
+Decoding meshes\x\ex_hlaalu_steps_02.nif
+Decoding meshes\x\ex_hlaalu_steps_03.nif
+Decoding meshes\x\ex_hlaalu_steps_01.nif
+Decoding meshes\x\ex_hlaalu_steps_06.nif
+Decoding meshes\x\ex_hlaalu_steps_07.nif
+Decoding meshes\x\ex_hlaalu_steps_04.nif
+Decoding meshes\x\ex_hlaalu_steps_05.nif
+Decoding meshes\x\ex_common_trellis.nif
+Decoding meshes\x\ex_common_plat_lrg.nif
+Decoding meshes\x\ex_common_plat_end.nif
+Decoding meshes\x\ex_velothi_dome_01.nif
+Decoding meshes\x\ex_siltstrider_01.nif
+Decoding meshes\x\ex_siltstrider_02.nif
+Decoding meshes\x\ex_siltstrider_03.nif
+Decoding meshes\x\ex_siltstrider_04.nif
+Decoding meshes\x\ex_siltstrider_05.nif
+Decoding meshes\x\ex_ruin_boulder01.nif
+Decoding meshes\x\ex_ruin_boulder00.nif
+Decoding meshes\x\ex_ruin_boulder03.nif
+Decoding meshes\x\ex_ruin_boulder02.nif
+Decoding meshes\x\ex_ruin_boulder05.nif
+Decoding meshes\x\ex_ruin_boulder04.nif
+Decoding meshes\x\ex_ashl_banner_01.nif
+Decoding meshes\x\ex_t_housestem_02.nif
+Decoding meshes\x\ex_t_housestem_03.nif
+Decoding meshes\x\ex_t_housestem_01.nif
+Decoding meshes\x\ex_ashl_door_01.nif
+Decoding meshes\x\ex_ashl_tent_03.nif
+Decoding meshes\x\ex_ashl_tent_01.nif
+Decoding meshes\x\ex_ashl_door_02.nif
+Decoding meshes\x\ex_ashl_tent_04.nif
+Decoding meshes\x\ex_ashl_tent_02.nif
+Decoding meshes\x\ex_trellis_vine.nif
+Decoding meshes\x\ex_dwrv_walker20.nif
+Decoding meshes\x\ex_dwrv_walker10.nif
+Decoding meshes\x\ex_dwrv_walker00.nif
+Decoding meshes\x\ex_dwrv_observ00.nif
+Decoding meshes\x\ex_dwrv_block10.nif
+Decoding meshes\x\ex_dwrv_block20.nif
+Decoding meshes\x\ex_dwrv_header00.nif
+Decoding meshes\x\ex_dwrv_statue00.nif
+Decoding meshes\x\ex_dwrv_enter00.nif
+Decoding meshes\x\ex_dwrv_bridge10.nif
+Decoding meshes\x\ex_dwrv_bridge00.nif
+Decoding meshes\x\ex_dwrv_block00.nif
+Decoding meshes\x\ex_dwrv_alcove10.nif
+Decoding meshes\x\ex_dwrv_cosmo00.nif
+Decoding meshes\x\ex_dwrv_block30.nif
+Decoding meshes\x\ex_dwrv_alcove00.nif
+Decoding meshes\x\ex_ruin_rubble20.nif
+Decoding meshes\x\ex_ruin_rubble00.nif
+Decoding meshes\x\ex_ruin_rubble10.nif
+Decoding meshes\x\ex_dae_pillar_02.nif
+Decoding meshes\x\ex_dae_pillar_03.nif
+Decoding meshes\x\ex_dae_pillar_01.nif
+Decoding meshes\x\ex_dae_b_bo_head.nif
+Decoding meshes\x\ex_dae_boethiah.nif
+Decoding meshes\x\ex_daed_ruin_01.nif
+Decoding meshes\x\ex_dae_b_bo_axe.nif
+Decoding meshes\x\ex_dae_molagbal.nif
+Decoding meshes\x\ex_barnacles_01.nif
+Decoding meshes\x\ex_barnacles_03.nif
+Decoding meshes\x\ex_barnacles_05.nif
+Decoding meshes\x\ex_barnacles_02.nif
+Decoding meshes\x\ex_barnacles_04.nif
+Decoding meshes\x\ex_barnacles_06.nif
+Decoding meshes\x\ex_cave_grass00.nif
+Decoding meshes\x\ex_cave_scrub00.nif
+Decoding meshes\x\ex_gg_fence_s_04.nif
+Decoding meshes\x\ex_gg_fence_s_03.nif
+Decoding meshes\x\ex_gg_fence_s_02.nif
+Decoding meshes\x\ex_gg_fence_s_01.nif
+Decoding meshes\x\ex_redwall_up_01.nif
+Decoding meshes\x\ex_redwall_b_01.nif
+Decoding meshes\x\ex_redwall_b_03.nif
+Decoding meshes\x\ex_redwall_b_05.nif
+Decoding meshes\x\ex_redwall_p_01.nif
+Decoding meshes\x\ex_redwall_p_03.nif
+Decoding meshes\x\ex_redwall_p_05.nif
+Decoding meshes\x\ex_redwall_b_02.nif
+Decoding meshes\x\ex_redwall_b_04.nif
+Decoding meshes\x\ex_redwall_b_06.nif
+Decoding meshes\x\ex_redwall_p_02.nif
+Decoding meshes\x\ex_redwall_p_04.nif
+Decoding meshes\x\ex_de_constr_03.nif
+Decoding meshes\x\ex_de_constr_01.nif
+Decoding meshes\x\ex_de_constr_05.nif
+Decoding meshes\x\ex_de_railing_01.nif
+Decoding meshes\x\ex_de_railing_03.nif
+Decoding meshes\x\ex_de_railing_02.nif
+Decoding meshes\x\ex_de_railing_05.nif
+Decoding meshes\x\ex_de_railing_04.nif
+Decoding meshes\x\ex_de_railing_06.nif
+Decoding meshes\x\ex_de_docks_128.nif
+Decoding meshes\x\ex_de_shipwreck.nif
+Decoding meshes\x\ex_de_docks_3way.nif
+Decoding meshes\x\ex_de_docks_4way.nif
+Decoding meshes\x\ex_de_docks_endc.nif
+Decoding meshes\x\ex_de_docks_ends.nif
+Decoding meshes\x\ex_de_constr_02.nif
+Decoding meshes\x\ex_de_constr_06.nif
+Decoding meshes\x\ex_de_constr_04.nif
+Decoding meshes\x\ex_de_docks_end.nif
+Decoding meshes\x\ex_de_docks_gate.nif
+Decoding meshes\x\ex_de_shack_door.nif
+Decoding meshes\x\ex_vivec_hfq_03.nif
+Decoding meshes\x\ex_vivec_hfq_01.nif
+Decoding meshes\x\ex_vivec_pqs_02.nif
+Decoding meshes\x\ex_vivec_b_tb_01.nif
+Decoding meshes\x\ex_vivec_b_wb_01.nif
+Decoding meshes\x\ex_vivec_g_r_01.nif
+Decoding meshes\x\ex_vivec_hfq_04.nif
+Decoding meshes\x\ex_vivec_hfq_02.nif
+Decoding meshes\x\ex_vivec_pqs_01.nif
+Decoding meshes\x\ex_vivec_w_c_01.nif
+Decoding meshes\x\ex_vivec_w_g_01.nif
+Decoding meshes\x\ex_vivec_w_e_01.nif
+Decoding meshes\x\ex_vivec_telt_01.nif
+Decoding meshes\x\ex_vivec_g_r_02.nif
+Decoding meshes\x\ex_vivec_b_t_01.nif
+Decoding meshes\x\ex_ci_doorjam_01.nif
+Decoding meshes\x\ex_volcano_steam.nif
+Decoding meshes\x\ex_ropebridge_01.nif
+Decoding meshes\x\ex_nord_rock_01.nif
+Decoding meshes\x\ex_nord_well_01.nif
+Decoding meshes\x\ex_nord_door_02.nif
+Decoding meshes\x\ex_nord_rock_02.nif
+Decoding meshes\x\ex_nord_house_03.nif
+Decoding meshes\x\ex_nord_house_02.nif
+Decoding meshes\x\ex_nord_house_01.nif
+Decoding meshes\x\ex_nord_house_05.nif
+Decoding meshes\x\ex_nord_house_04.nif
+Decoding meshes\x\ex_nord_doorf_01.nif
+Decoding meshes\x\ex_holamayan_01.nif
+Decoding meshes\x\ex_emp_tower_01.nif
+Decoding meshes\x\ex_imp_arrowslit.nif
+Decoding meshes\x\ex_imp_bridge_01.nif
+Decoding meshes\x\ex_imp_bridge_02.nif
+Decoding meshes\x\ex_hlaalu_fb_01.nif
+Decoding meshes\x\ex_hlaalu_win_03.nif
+Decoding meshes\x\ex_hlaalu_win_02.nif
+Decoding meshes\x\ex_hlaalu_win_01.nif
+Decoding meshes\x\ex_hlaalu_bal_02.nif
+Decoding meshes\x\ex_hlaalu_bal_01.nif
+Decoding meshes\x\ex_hlaalu_fb_02.nif
+Decoding meshes\x\ex_t_housepod_01.nif
+Decoding meshes\x\ex_t_housepod_03.nif
+Decoding meshes\x\ex_t_housepod_02.nif
+Decoding meshes\x\ex_t_housepod_04.nif
+Decoding meshes\x\ex_t_spiralramp.nif
+Decoding meshes\x\ex_t_councilhall.nif
+Decoding meshes\x\ex_t_pole_hooked.nif
+Decoding meshes\x\ex_t_tower_bent.nif
+Decoding meshes\x\ex_t_menhir_l_01.nif
+Decoding meshes\x\ex_t_doorway_01.nif
+Decoding meshes\x\ex_t_root_xl_03.nif
+Decoding meshes\x\ex_t_root_xl_01.nif
+Decoding meshes\x\ex_t_bigroot_01.nif
+Decoding meshes\x\ex_t_slavemarket.nif
+Decoding meshes\x\ex_t_slavepod_01.nif
+Decoding meshes\x\ex_t_platform_01.nif
+Decoding meshes\x\ex_t_platform_02.nif
+Decoding meshes\x\ex_t_doorway_02.nif
+Decoding meshes\x\ex_t_root_xl_02.nif
+Decoding meshes\x\ex_t_bigroot_02.nif
+Decoding meshes\x\ex_v_ban_walk_01.nif
+Decoding meshes\x\ex_v_2_floor_01.nif
+Decoding meshes\x\ex_v_sign_fq_01.nif
+Decoding meshes\x\ex_v_1_corner_01.nif
+Decoding meshes\x\ex_v_2_corner_01.nif
+Decoding meshes\x\ex_v_ban_imp_01.nif
+Decoding meshes\x\ex_v_stdeyln_01.nif
+Decoding meshes\x\ex_v_ban_avs_01.nif
+Decoding meshes\x\ex_v_2_stairs_01.nif
+Decoding meshes\x\ex_t_turret_02.nif
+Decoding meshes\x\ex_t_menhir_01.nif
+Decoding meshes\x\ex_t_dormer_01.nif
+Decoding meshes\x\ex_t_stair_01.nif
+Decoding meshes\x\ex_t_root_stem.nif
+Decoding meshes\x\ex_t_awning_02.nif
+Decoding meshes\x\ex_t_manor_01.nif
+Decoding meshes\x\ex_t_manor_02.nif
+Decoding meshes\x\ex_t_dock_main.nif
+Decoding meshes\x\ex_t_brace_01.nif
+Decoding meshes\x\ex_t_turret_03.nif
+Decoding meshes\x\ex_t_turret_01.nif
+Decoding meshes\x\ex_t_dormer_02.nif
+Decoding meshes\x\ex_t_awning_01.nif
+Decoding meshes\x\ex_t_rootball.nif
+Decoding meshes\x\ex_t_root_hook.nif
+Decoding meshes\x\ex_vivec_h_02.nif
+Decoding meshes\x\ex_vivec_h_12.nif
+Decoding meshes\x\ex_vivec_c_02.nif
+Decoding meshes\x\ex_vivec_g_02.nif
+Decoding meshes\x\ex_vivec_w_02.nif
+Decoding meshes\x\ex_v_banner_02.nif
+Decoding meshes\x\ex_vivec_h_14.nif
+Decoding meshes\x\ex_vivec_h_15.nif
+Decoding meshes\x\ex_vivec_c_03.nif
+Decoding meshes\x\ex_vivec_h_03.nif
+Decoding meshes\x\ex_vivec_w_03.nif
+Decoding meshes\x\ex_vivec_h_06.nif
+Decoding meshes\x\ex_vivec_hf_02.nif
+Decoding meshes\x\ex_vivec_hf_04.nif
+Decoding meshes\x\ex_vivec_h_07.nif
+Decoding meshes\x\ex_vivec_pq_02.nif
+Decoding meshes\x\ex_vivec_pq_04.nif
+Decoding meshes\x\ex_vivec_lp_02.nif
+Decoding meshes\x\ex_vivec_h_05.nif
+Decoding meshes\x\ex_vivec_h_17.nif
+Decoding meshes\x\ex_vivec_h_16.nif
+Decoding meshes\x\ex_vivec_t_04.nif
+Decoding meshes\x\ex_vivec_h_04.nif
+Decoding meshes\x\ex_vivec_c_04.nif
+Decoding meshes\x\ex_v_bpost_01.nif
+Decoding meshes\x\ex_v_bpost_03.nif
+Decoding meshes\x\ex_v_stolms_01.nif
+Decoding meshes\x\ex_v_bpost_02.nif
+Decoding meshes\x\ex_vivec_h_09.nif
+Decoding meshes\x\ex_v_banner_01.nif
+Decoding meshes\x\ex_v_banner_03.nif
+Decoding meshes\x\ex_vivec_w_01.nif
+Decoding meshes\x\ex_vivec_t_01.nif
+Decoding meshes\x\ex_vivec_h_11.nif
+Decoding meshes\x\ex_vivec_h_01.nif
+Decoding meshes\x\ex_vivec_g_01.nif
+Decoding meshes\x\ex_vivec_b_01.nif
+Decoding meshes\x\ex_vivec_c_01.nif
+Decoding meshes\x\ex_vivec_h_10.nif
+Decoding meshes\x\ex_vivec_h_13.nif
+Decoding meshes\x\ex_v_1_wall_01.nif
+Decoding meshes\x\ex_v_2_wall_01.nif
+Decoding meshes\x\ex_vivec_hf_01.nif
+Decoding meshes\x\ex_vivec_hf_03.nif
+Decoding meshes\x\ex_vivec_pd_01.nif
+Decoding meshes\x\ex_vivec_h_08.nif
+Decoding meshes\x\ex_vivec_ps_01.nif
+Decoding meshes\x\ex_vivec_pq_01.nif
+Decoding meshes\x\ex_vivec_pq_03.nif
+Decoding meshes\x\ex_vivec_lp_01.nif
+Decoding meshes\x\ex_vivec_bt_01.nif
+Decoding meshes\x\ex_rope_short.nif
+Decoding meshes\x\ex_ship_plank.nif
+Decoding meshes\x\ex_scrapwood04.nif
+Decoding meshes\x\ex_scrapwood02.nif
+Decoding meshes\x\ex_ship_stair.nif
+Decoding meshes\x\ex_scrapwood05.nif
+Decoding meshes\x\ex_scrapwood03.nif
+Decoding meshes\x\ex_scrapwood01.nif
+Decoding meshes\x\ex_dwrv_ruin50.nif
+Decoding meshes\x\ex_dae_ruin_04.nif
+Decoding meshes\x\ex_dae_ruin_02.nif
+Decoding meshes\x\ex_dwrv_ruin20.nif
+Decoding meshes\x\ex_de_shack_05.nif
+Decoding meshes\x\ex_de_shack_03.nif
+Decoding meshes\x\ex_de_shack_01.nif
+Decoding meshes\x\ex_dwrv_wall20.nif
+Decoding meshes\x\ex_dwrv_wall30.nif
+Decoding meshes\x\ex_dwrv_wall50.nif
+Decoding meshes\x\ex_dwrv_wall60.nif
+Decoding meshes\x\ex_dwrv_wall40.nif
+Decoding meshes\x\ex_dwrv_wall10.nif
+Decoding meshes\x\ex_dae_claw_02.nif
+Decoding meshes\x\ex_dwrv_ruin10.nif
+Decoding meshes\x\ex_dwrv_pipe10.nif
+Decoding meshes\x\ex_dwrv_ruin40.nif
+Decoding meshes\x\ex_dwrv_ruin00.nif
+Decoding meshes\x\ex_dae_ruin_01.nif
+Decoding meshes\x\ex_dae_ruin_03.nif
+Decoding meshes\x\ex_de_shack_04.nif
+Decoding meshes\x\ex_de_shack_02.nif
+Decoding meshes\x\ex_dwrv_wall00.nif
+Decoding meshes\x\ex_de_sn_gate.nif
+Decoding meshes\x\ex_dwrv_ruin30.nif
+Decoding meshes\x\ex_dwrv_ruin60.nif
+Decoding meshes\x\ex_dwrv_pipe00.nif
+Decoding meshes\x\ex_dwrv_ruin80.nif
+Decoding meshes\x\ex_dae_claw_01.nif
+Decoding meshes\x\ex_de_rowboat.nif
+Decoding meshes\x\ex_dwrv_ruin70.nif
+Decoding meshes\x\ex_gg_fence_02.nif
+Decoding meshes\x\ex_gg_fence_04.nif
+Decoding meshes\x\ex_gg_pylon_01.nif
+Decoding meshes\x\ex_gondola_01.nif
+Decoding meshes\x\ex_gg_fence_03.nif
+Decoding meshes\x\ex_gg_fence_01.nif
+Decoding meshes\x\ex_gg_pylon_02.nif
+Decoding meshes\x\ex_ashl_banner.nif
+Decoding meshes\x\ex_coiled_rope.nif
+Decoding meshes\x\ex_cave_sand00.nif
+Decoding meshes\x\ex_cave_ash10.nif
+Decoding meshes\x\ex_cave_ash00.nif
+Decoding meshes\x\ex_lavaspark01.nif
+Decoding meshes\x\ex_lavaspark03.nif
+Decoding meshes\x\ex_longboat02.nif
+Decoding meshes\x\ex_lavasteam00.nif
+Decoding meshes\x\ex_lavaspark02.nif
+Decoding meshes\x\ex_longboatwrk.nif
+Decoding meshes\x\ex_longboat01.nif
+Decoding meshes\x\ex_nord_win_01.nif
+Decoding meshes\x\ex_nord_win_02.nif
+Decoding meshes\x\ex_hlaalu_b_04.nif
+Decoding meshes\x\ex_hlaalu_b_06.nif
+Decoding meshes\x\ex_hlaalu_b_02.nif
+Decoding meshes\x\ex_hlaalu_b_08.nif
+Decoding meshes\x\ex_hlaalu_b_18.nif
+Decoding meshes\x\ex_hlaalu_b_12.nif
+Decoding meshes\x\ex_hlaalu_b_10.nif
+Decoding meshes\x\ex_hlaalu_b_16.nif
+Decoding meshes\x\ex_hlaalu_b_14.nif
+Decoding meshes\x\ex_hlaalu_b_25.nif
+Decoding meshes\x\ex_hlaalu_b_27.nif
+Decoding meshes\x\ex_hlaalu_b_21.nif
+Decoding meshes\x\ex_hlaalu_b_23.nif
+Decoding meshes\x\ex_hlaalu_b_05.nif
+Decoding meshes\x\ex_hlaalu_b_07.nif
+Decoding meshes\x\ex_hlaalu_b_01.nif
+Decoding meshes\x\ex_hlaalu_b_03.nif
+Decoding meshes\x\ex_hlaalu_b_09.nif
+Decoding meshes\x\ex_hlaalu_b_19.nif
+Decoding meshes\x\ex_hlaalu_b_13.nif
+Decoding meshes\x\ex_hlaalu_b_11.nif
+Decoding meshes\x\ex_hlaalu_b_17.nif
+Decoding meshes\x\ex_hlaalu_b_15.nif
+Decoding meshes\x\ex_hlaalu_b_24.nif
+Decoding meshes\x\ex_hlaalu_b_26.nif
+Decoding meshes\x\ex_hlaalu_b_20.nif
+Decoding meshes\x\ex_hlaalu_b_22.nif
+Decoding meshes\x\ex_hlaalu_b_28.nif
+Decoding meshes\x\ex_imp_wall_01.nif
+Decoding meshes\x\ex_imp_keep_02.nif
+Decoding meshes\x\ex_imp_plat_01.nif
+Decoding meshes\x\ex_imp_pool_01.nif
+Decoding meshes\x\ex_imp_dock_01.nif
+Decoding meshes\x\ex_imp_keep_01.nif
+Decoding meshes\x\ex_imp_dock_02.nif
+Decoding meshes\x\ex_t_root_02.nif
+Decoding meshes\x\ex_t_root_04.nif
+Decoding meshes\x\ex_dae_azura.nif
+Decoding meshes\x\ex_t_root_05.nif
+Decoding meshes\x\ex_gg_ent_01.nif
+Decoding meshes\x\ex_t_root_06.nif
+Decoding meshes\x\ex_t_tunnel.nif
+Decoding meshes\x\ex_t_root_01.nif
+Decoding meshes\x\ex_t_pole_01.nif
+Decoding meshes\x\ex_longboat.nif
+Decoding meshes\x\ex_imp_plaza.nif
+Decoding meshes\x\ex_t_root_03.nif
+Decoding meshes\x\ex_cave_mt00.nif
+Decoding meshes\x\ex_v_dock_01.nif
+Decoding meshes\x\ex_t_dock_01.nif
+Decoding meshes\f\ex_ashl_e_banner_r.nif
+Decoding meshes\f\ex_ashl_a_banner_r.nif
+Decoding meshes\f\ex_ashl_z_banner_r.nif
+Decoding meshes\f\ex_ashl_u_banner_r.nif
+Decoding meshes\f\ex_ashl_z_banner.nif
+Decoding meshes\f\ex_ashl_a_banner.nif
+Decoding meshes\f\ex_ashl_u_banner.nif
+Decoding meshes\f\ex_ashl_e_banner.nif
+Decoding meshes\f\ex_ashl_banner.nif
+Decoding meshes\f\ex_boulder02.nif
+Decoding meshes\f\ex_boulder04.nif
+Decoding meshes\f\ex_boulder00.nif
+Decoding meshes\f\ex_boulder05.nif
+Decoding meshes\f\ex_boulder08.nif
+Decoding meshes\f\ex_boulder07.nif
+Decoding meshes\f\ex_boulder06.nif
+Decoding meshes\f\ex_boulder03.nif
+Decoding meshes\f\ex_boulder01.nif
+Decoding meshes\d\ex_vivec_grate_01.nif
+Decoding meshes\d\ex_emp_tower_01_b.nif
+Decoding meshes\d\ex_emp_tower_01_a.nif
+Decoding meshes\d\ex_imp_loaddoor_03.nif
+Decoding meshes\d\ex_imp_loaddoor_02.nif
+Decoding meshes\d\ex_imp_loaddoor_01.nif
+Decoding meshes\d\in_t_s_plain_door.nif
+Decoding meshes\d\in_v_s_trapdoor_02.nif
+Decoding meshes\d\in_v_s_trapdoor_01.nif
+Decoding meshes\d\in_v_s_jaildoor_01.nif
+Decoding meshes\d\ex_v_cantondoor_01.nif
+Decoding meshes\d\in_vivec_grate_01.nif
+Decoding meshes\d\ex_common_door_01.nif
+Decoding meshes\d\in_de_cabindoor.nif
+Decoding meshes\d\in_t_door_small.nif
+Decoding meshes\d\in_r_trapdoor_01.nif
+Decoding meshes\d\in_c_door_arched.nif
+Decoding meshes\d\in_h_trapdoor_01.nif
+Decoding meshes\d\in_t_l_door_01.nif
+Decoding meshes\d\in_r_s_door_01.nif
+Decoding meshes\d\in_dae_door_01.nif
+Decoding meshes\d\in_ar_door_01.nif
+Decoding meshes\d\in_ci_door_01.nif
+Decoding meshes\d\in_hlaalu_door.nif
+Decoding meshes\d\ex_cave_door_01.nif
+Decoding meshes\d\ex_de_ship_door.nif
+Decoding meshes\d\ex_nord_door_01.nif
+Decoding meshes\d\ex_nord_door_02.nif
+Decoding meshes\d\ex_r_trapdoor_01.nif
+Decoding meshes\d\ex_h_trapdoor_01.nif
+Decoding meshes\d\ex_t_door_02.nif
+Decoding meshes\d\ex_t_door_01.nif
+Decoding meshes\l\light_6th_candle_05.nif
+Decoding meshes\l\light_6th_candle_04.nif
+Decoding meshes\l\light_6th_candle_06.nif
+Decoding meshes\l\light_6th_candle_01.nif
+Decoding meshes\l\light_6th_candle_03.nif
+Decoding meshes\l\light_6th_candle_02.nif
+Decoding meshes\base_anim_female.1st.nif
+Decoding meshes\base_anim_female.nif
+Decoding meshes\base_animkna.1st.nif
+Decoding meshes\base_anim.1st.nif
+Decoding meshes\anim_dancinggirl.nif
+Decoding meshes\x\ex_common_tower_thatchedroof.nif
+Decoding meshes\menu_scroll_hort_bar.nif
+Decoding meshes\menu_scroll_vert_bar.nif
+Decoding meshes\menu_scroll_scroller.nif
+Decoding meshes\menu_rightbuttondown.nif
+Decoding meshes\main_menu_button.nif
+Decoding meshes\menu_button_frame.nif
+Decoding meshes\menu_thick_border.nif
+Decoding meshes\menu_rightbuttonup.nif
+Decoding meshes\menu_scroll_slider.nif
+Decoding meshes\menu_messagebox.nif
+Decoding meshes\menu_icon_frame.nif
+Decoding meshes\menu_head_block.nif
+Decoding meshes\menu_thin_border.nif
+Decoding meshes\menu_scroll_bar.nif
+Decoding meshes\menu_contents.nif
+Decoding meshes\menu_scroll_da.nif
+Decoding meshes\menu_scroll_ua.nif
+Decoding meshes\i\in_vs_pitfloor_01.nif
+Decoding meshes\i\in_ar_shelltop_01.nif
+Decoding meshes\i\in_ar_platform_10.nif
+Decoding meshes\i\in_ar_platform_04.nif
+Decoding meshes\i\in_ar_platform_05.nif
+Decoding meshes\i\in_ar_platform_06.nif
+Decoding meshes\i\in_ar_platform_07.nif
+Decoding meshes\i\in_ar_platform_01.nif
+Decoding meshes\i\in_ar_platform_02.nif
+Decoding meshes\i\in_ar_platform_03.nif
+Decoding meshes\i\in_ar_platform_08.nif
+Decoding meshes\i\in_ar_platform_09.nif
+Decoding meshes\i\in_hlaalu_hall_128.nif
+Decoding meshes\i\in_hlaalu_door_01.nif
+Decoding meshes\i\in_hlaalu_doorjamb.nif
+Decoding meshes\i\in_t_council_beams.nif
+Decoding meshes\i\in_t_balconyscreen.nif
+Decoding meshes\i\in_dae_doorjamb_01.nif
+Decoding meshes\i\in_mudcave2_s_end.nif
+Decoding meshes\i\in_mudcave_stal10.nif
+Decoding meshes\i\in_mudcave_stal00.nif
+Decoding meshes\i\in_mudcave_stal30.nif
+Decoding meshes\i\in_mudcave_stal20.nif
+Decoding meshes\i\in_mudcave_stal50.nif
+Decoding meshes\i\in_mudcave_stal40.nif
+Decoding meshes\i\in_mudcave_exit00.nif
+Decoding meshes\i\in_mudcave_form00.nif
+Decoding meshes\i\in_mudcave_form10.nif
+Decoding meshes\i\in_mudcave_form20.nif
+Decoding meshes\i\in_mudcave_form30.nif
+Decoding meshes\i\in_redoran_hut_01.nif
+Decoding meshes\i\in_c_doorframe_01.nif
+Decoding meshes\i\in_dagoth_bridge00.nif
+Decoding meshes\i\in_dagoth_plate10.nif
+Decoding meshes\i\in_dagoth_plate00.nif
+Decoding meshes\i\in_dagoth_plate20.nif
+Decoding meshes\i\in_r_guild_mage_01.nif
+Decoding meshes\i\in_com_traptop_01.nif
+Decoding meshes\i\in_com_wincover_02.nif
+Decoding meshes\i\in_com_wincover_01.nif
+Decoding meshes\i\in_vsl_pitbd_b_01.nif
+Decoding meshes\i\in_vsl_pitmd_b_01.nif
+Decoding meshes\i\in_moldcave2_s_00.nif
+Decoding meshes\i\in_moldcave2_s_01.nif
+Decoding meshes\i\in_moldcave2_s_02.nif
+Decoding meshes\i\in_moldcave2_s_03.nif
+Decoding meshes\i\in_moldcave2_s_04.nif
+Decoding meshes\i\in_moldcave2_s_05.nif
+Decoding meshes\i\in_moldcave2_s_06.nif
+Decoding meshes\i\in_moldcave_exit00.nif
+Decoding meshes\i\in_moldcave2_s_end.nif
+Decoding meshes\i\in_moldcave4_s_00.nif
+Decoding meshes\i\in_moldcave_stal00.nif
+Decoding meshes\i\in_moldcave_stal10.nif
+Decoding meshes\i\in_moldcave_stal20.nif
+Decoding meshes\i\in_moldcave_stal30.nif
+Decoding meshes\i\in_moldcave_stal40.nif
+Decoding meshes\i\in_moldcave_stal50.nif
+Decoding meshes\i\in_moldcave_form20.nif
+Decoding meshes\i\in_moldcave_form30.nif
+Decoding meshes\i\in_moldcave_form00.nif
+Decoding meshes\i\in_moldcave_form10.nif
+Decoding meshes\i\in_velothicave_01.nif
+Decoding meshes\i\in_velothicave_03.nif
+Decoding meshes\i\in_velothicave_02.nif
+Decoding meshes\i\in_velothicave_04.nif
+Decoding meshes\i\in_t_l_room_corner.nif
+Decoding meshes\i\in_t_l_room_entry.nif
+Decoding meshes\i\in_t_l_hall_corner.nif
+Decoding meshes\i\in_t_l_room_floor.nif
+Decoding meshes\i\in_t_l_doorjamb_01.nif
+Decoding meshes\i\in_v_l_int_wall_01.nif
+Decoding meshes\i\in_v_l_int_wall_02.nif
+Decoding meshes\i\in_v_l_int_rail_01.nif
+Decoding meshes\i\in_r_l_int_wall_01.nif
+Decoding meshes\i\in_r_l_int_wall_02.nif
+Decoding meshes\i\in_r_l_doorjamb_02.nif
+Decoding meshes\i\in_r_l_int_rail_01.nif
+Decoding meshes\i\in_r_l_int_arch_01.nif
+Decoding meshes\i\in_bonecave2_s_08.nif
+Decoding meshes\i\in_bonecave2_s_09.nif
+Decoding meshes\i\in_bonecave2_s_00.nif
+Decoding meshes\i\in_bonecave2_s_01.nif
+Decoding meshes\i\in_bonecave2_s_02.nif
+Decoding meshes\i\in_bonecave2_s_03.nif
+Decoding meshes\i\in_bonecave2_s_04.nif
+Decoding meshes\i\in_bonecave2_s_05.nif
+Decoding meshes\i\in_bonecave2_s_06.nif
+Decoding meshes\i\in_bonecave2_s_07.nif
+Decoding meshes\i\in_bonecave2_s_end.nif
+Decoding meshes\i\in_bonecave4_s_00.nif
+Decoding meshes\i\in_bonecave_stal00.nif
+Decoding meshes\i\in_bonecave_stal10.nif
+Decoding meshes\i\in_bonecave_stal20.nif
+Decoding meshes\i\in_bonecave_stal30.nif
+Decoding meshes\i\in_bonecave_stal40.nif
+Decoding meshes\i\in_bonecave_stal50.nif
+Decoding meshes\i\in_bonecave_form20.nif
+Decoding meshes\i\in_bonecave_form30.nif
+Decoding meshes\i\in_bonecave_form00.nif
+Decoding meshes\i\in_bonecave_form10.nif
+Decoding meshes\i\in_bonecave_exit00.nif
+Decoding meshes\i\in_impbig_4way_01.nif
+Decoding meshes\i\in_impbig_wall_01.nif
+Decoding meshes\i\in_impbig_blend_01.nif
+Decoding meshes\i\in_c_plain_corner.nif
+Decoding meshes\i\in_c_pillar_stone.nif
+Decoding meshes\i\in_c_plain_endcap.nif
+Decoding meshes\i\in_t_s_room_corner.nif
+Decoding meshes\i\in_t_stairs_wiz_01.nif
+Decoding meshes\i\in_t_stairs_wiz_03.nif
+Decoding meshes\i\in_t_stairs_wiz_02.nif
+Decoding meshes\i\in_t_s_room_entry.nif
+Decoding meshes\i\in_t_s_shaft_6way.nif
+Decoding meshes\i\in_t_s_djamb_plain.nif
+Decoding meshes\i\in_t_s_room_center.nif
+Decoding meshes\i\in_v_s_int_wall_01.nif
+Decoding meshes\i\in_v_s_int_wall_03.nif
+Decoding meshes\i\in_v_s_int_wall_02.nif
+Decoding meshes\i\in_v_s_int_wall_04.nif
+Decoding meshes\i\in_v_s_domeroom_01.nif
+Decoding meshes\i\in_r_s_int_wall_01.nif
+Decoding meshes\i\in_r_s_int_wall_02.nif
+Decoding meshes\i\in_r_s_doorjamb_01.nif
+Decoding meshes\i\in_r_s_int_rail_01.nif
+Decoding meshes\i\in_r_s_int_rail_02.nif
+Decoding meshes\i\in_r_s_int_arch_01.nif
+Decoding meshes\i\in_r_s_int_arch_02.nif
+Decoding meshes\i\in_c_stone_corner.nif
+Decoding meshes\i\in_c_stone_endcap.nif
+Decoding meshes\i\in_dwrv_doorjam00.nif
+Decoding meshes\i\in_dwrv_oilslick00.nif
+Decoding meshes\i\in_strong_corr2_03.nif
+Decoding meshes\i\in_strong_corr2_02.nif
+Decoding meshes\i\in_strong_corr2_01.nif
+Decoding meshes\i\in_strong_corr2_00.nif
+Decoding meshes\i\in_strong_corr2_04.nif
+Decoding meshes\i\in_strong_corr4_04.nif
+Decoding meshes\i\in_strong_corr4_03.nif
+Decoding meshes\i\in_strong_corr4_02.nif
+Decoding meshes\i\in_strong_corr4_01.nif
+Decoding meshes\i\in_strong_corr4_00.nif
+Decoding meshes\i\in_strong_corr1_00.nif
+Decoding meshes\i\in_strong_rubble01.nif
+Decoding meshes\i\in_strong_rubble00.nif
+Decoding meshes\i\in_strong_corr3_02.nif
+Decoding meshes\i\in_strong_corr3_01.nif
+Decoding meshes\i\in_strong_corr3_00.nif
+Decoding meshes\i\in_nord_ladder_02.nif
+Decoding meshes\i\in_nord_ladder_01.nif
+Decoding meshes\i\in_t_railing_pole.nif
+Decoding meshes\i\in_t_root_wrap_02.nif
+Decoding meshes\i\in_t_root_wrap_01.nif
+Decoding meshes\i\in_t_t_room_center.nif
+Decoding meshes\i\in_sewer_bcorr4_00.nif
+Decoding meshes\i\in_sewer_scorr4_00.nif
+Decoding meshes\i\in_sewer_bcorr2_03.nif
+Decoding meshes\i\in_sewer_bcorr2_02.nif
+Decoding meshes\i\in_sewer_bcorr2_01.nif
+Decoding meshes\i\in_sewer_bcorr2_00.nif
+Decoding meshes\i\in_sewer_bcorr2_05.nif
+Decoding meshes\i\in_sewer_bcorr2_04.nif
+Decoding meshes\i\in_sewer_scorr2_02.nif
+Decoding meshes\i\in_sewer_scorr2_01.nif
+Decoding meshes\i\in_sewer_scorr2_00.nif
+Decoding meshes\i\in_sewer_anchor00.nif
+Decoding meshes\i\in_sewer_pillar00.nif
+Decoding meshes\i\in_sewer_bcorr3_00.nif
+Decoding meshes\i\in_sewer_scorr1_00.nif
+Decoding meshes\i\in_sewer_bcorr1_01.nif
+Decoding meshes\i\in_sewer_bcorr1_00.nif
+Decoding meshes\i\in_vivec_ent_s_01.nif
+Decoding meshes\i\in_cavern_stairs00.nif
+Decoding meshes\i\in_cavern_roots10.nif
+Decoding meshes\i\in_cavern_roots00.nif
+Decoding meshes\i\in_lavacave2_s_00.nif
+Decoding meshes\i\in_lavacave2_s_01.nif
+Decoding meshes\i\in_lavacave2_s_02.nif
+Decoding meshes\i\in_lavacave2_s_03.nif
+Decoding meshes\i\in_lavacave2_s_04.nif
+Decoding meshes\i\in_lavacave2_s_05.nif
+Decoding meshes\i\in_lavacave2_s_06.nif
+Decoding meshes\i\in_lavacave_form20.nif
+Decoding meshes\i\in_lavacave_form30.nif
+Decoding meshes\i\in_lavacave_form00.nif
+Decoding meshes\i\in_lavacave_form10.nif
+Decoding meshes\i\in_lavacave_exit00.nif
+Decoding meshes\i\in_lavacave2_s_end.nif
+Decoding meshes\i\in_lavacave4_s_00.nif
+Decoding meshes\i\in_lavacave_stal00.nif
+Decoding meshes\i\in_lavacave_stal10.nif
+Decoding meshes\i\in_lavacave_stal20.nif
+Decoding meshes\i\in_lavacave_stal30.nif
+Decoding meshes\i\in_lavacave_stal40.nif
+Decoding meshes\i\in_lavacave_stal50.nif
+Decoding meshes\i\in_pycave2_s_end.nif
+Decoding meshes\i\in_pycave_stal10.nif
+Decoding meshes\i\in_pycave_stal00.nif
+Decoding meshes\i\in_pycave2_s_05.nif
+Decoding meshes\i\in_pycave2_s_03.nif
+Decoding meshes\i\in_pycave2_s_01.nif
+Decoding meshes\i\in_pycave_stal30.nif
+Decoding meshes\i\in_pycave_stal20.nif
+Decoding meshes\i\in_pycave_stal50.nif
+Decoding meshes\i\in_pycave_stal40.nif
+Decoding meshes\i\in_pycave_exit00.nif
+Decoding meshes\i\in_pycave4_s_00.nif
+Decoding meshes\i\in_pycave2_s_06.nif
+Decoding meshes\i\in_pycave2_s_04.nif
+Decoding meshes\i\in_pycave2_s_02.nif
+Decoding meshes\i\in_pycave2_s_00.nif
+Decoding meshes\i\in_pycave_form00.nif
+Decoding meshes\i\in_pycave_form10.nif
+Decoding meshes\i\in_pycave_form20.nif
+Decoding meshes\i\in_pycave_form30.nif
+Decoding meshes\i\in_dwrv_scope50.nif
+Decoding meshes\i\in_dwrv_shaft10.nif
+Decoding meshes\i\in_dwrv_shaft00.nif
+Decoding meshes\i\in_dwrv_crank00.nif
+Decoding meshes\i\in_dwrv_scope20.nif
+Decoding meshes\i\in_dwrv_obsrv10.nif
+Decoding meshes\i\in_dwrv_corr4_01.nif
+Decoding meshes\i\in_dwrv_corr4_00.nif
+Decoding meshes\i\in_dwrv_corr4_03.nif
+Decoding meshes\i\in_dwrv_corr4_02.nif
+Decoding meshes\i\in_dwrv_corr4_05.nif
+Decoding meshes\i\in_dwrv_corr4_04.nif
+Decoding meshes\i\in_dwrv_corr2_01.nif
+Decoding meshes\i\in_dwrv_corr2_00.nif
+Decoding meshes\i\in_dwrv_corr2_03.nif
+Decoding meshes\i\in_dwrv_corr2_02.nif
+Decoding meshes\i\in_dwrv_corr2_05.nif
+Decoding meshes\i\in_dwrv_corr2_04.nif
+Decoding meshes\i\in_dwrv_corr2_07.nif
+Decoding meshes\i\in_dwrv_corr2_06.nif
+Decoding meshes\i\in_dwrv_corr3_01.nif
+Decoding meshes\i\in_dwrv_corr3_00.nif
+Decoding meshes\i\in_dwrv_corr3_02.nif
+Decoding meshes\i\in_dwrv_corr1_00.nif
+Decoding meshes\i\in_dwrv_corr2_30.nif
+Decoding meshes\i\in_dwrv_scope40.nif
+Decoding meshes\i\in_dwrv_hall4_03.nif
+Decoding meshes\i\in_dwrv_hall4_02.nif
+Decoding meshes\i\in_dwrv_hall4_01.nif
+Decoding meshes\i\in_dwrv_hall4_00.nif
+Decoding meshes\i\in_dwrv_hall2_00.nif
+Decoding meshes\i\in_dwrv_hall3_02.nif
+Decoding meshes\i\in_dwrv_hall3_00.nif
+Decoding meshes\i\in_dwrv_scope30.nif
+Decoding meshes\i\in_dwrv_scope00.nif
+Decoding meshes\i\in_dwrv_obsrv00.nif
+Decoding meshes\i\in_dwrv_scope10.nif
+Decoding meshes\i\in_mudcave2_s_00.nif
+Decoding meshes\i\in_mudcave2_s_01.nif
+Decoding meshes\i\in_mudcave2_s_02.nif
+Decoding meshes\i\in_mudcave2_s_03.nif
+Decoding meshes\i\in_mudcave2_s_04.nif
+Decoding meshes\i\in_mudcave2_s_05.nif
+Decoding meshes\i\in_mudcave2_s_06.nif
+Decoding meshes\i\in_mudcave4_s_00.nif
+Decoding meshes\i\in_mudboulder04.nif
+Decoding meshes\i\in_mudboulder00.nif
+Decoding meshes\i\in_mudboulder02.nif
+Decoding meshes\i\in_mudboulder05.nif
+Decoding meshes\i\in_mudboulder01.nif
+Decoding meshes\i\in_mudboulder03.nif
+Decoding meshes\i\in_mudcave2_end.nif
+Decoding meshes\i\in_mudcave_28_1.nif
+Decoding meshes\i\in_mudcave_21_1.nif
+Decoding meshes\i\in_strong_ramp10.nif
+Decoding meshes\i\in_strong_ramp00.nif
+Decoding meshes\i\in_strong_hall02.nif
+Decoding meshes\i\in_strong_hall03.nif
+Decoding meshes\i\in_strong_hall00.nif
+Decoding meshes\i\in_strong_hall01.nif
+Decoding meshes\i\in_strong_hall06.nif
+Decoding meshes\i\in_strong_hall04.nif
+Decoding meshes\i\in_strong_hall05.nif
+Decoding meshes\i\in_vs_pitct_b_01.nif
+Decoding meshes\i\in_vs_pitd_b_02.nif
+Decoding meshes\i\in_vs_pittw_b_01.nif
+Decoding meshes\i\in_vs_pitmw_b_01.nif
+Decoding meshes\i\in_vs_pitb_b_01.nif
+Decoding meshes\i\in_vs_pitbw_b_01.nif
+Decoding meshes\i\in_vs_pitceil_01.nif
+Decoding meshes\i\in_vs_pitcb_b_01.nif
+Decoding meshes\i\in_vs_pitd_b_01.nif
+Decoding meshes\i\in_vs_pitd_b_03.nif
+Decoding meshes\i\in_vs_pittd_b_01.nif
+Decoding meshes\i\in_vs_pitcm_b_01.nif
+Decoding meshes\i\in_vsl_pitc_b_01.nif
+Decoding meshes\i\in_ashl_door_01.nif
+Decoding meshes\i\in_ashl_tent_05.nif
+Decoding meshes\i\in_ashl_tent_03.nif
+Decoding meshes\i\in_ashl_tent_01.nif
+Decoding meshes\i\in_ashl_door_02.nif
+Decoding meshes\i\in_ashl_tent_04.nif
+Decoding meshes\i\in_ashl_tent_02.nif
+Decoding meshes\i\in_ar_bridge_10.nif
+Decoding meshes\i\in_ar_bridge_09.nif
+Decoding meshes\i\in_ar_bridge_01.nif
+Decoding meshes\i\in_ar_bridge_03.nif
+Decoding meshes\i\in_ar_bridge_05.nif
+Decoding meshes\i\in_ar_bridge_07.nif
+Decoding meshes\i\in_ar_bridge_08.nif
+Decoding meshes\i\in_ar_bridge_02.nif
+Decoding meshes\i\in_ar_bridge_04.nif
+Decoding meshes\i\in_ar_bridge_06.nif
+Decoding meshes\i\in_bone_rock_25.nif
+Decoding meshes\i\in_bone_rock_27.nif
+Decoding meshes\i\in_bone_rock_21.nif
+Decoding meshes\i\in_bone_rock_23.nif
+Decoding meshes\i\in_bonecave2_11.nif
+Decoding meshes\i\in_bonecave2_end.nif
+Decoding meshes\i\in_bone_rock_18.nif
+Decoding meshes\i\in_bone_rock_12.nif
+Decoding meshes\i\in_bone_rock_10.nif
+Decoding meshes\i\in_bone_rock_16.nif
+Decoding meshes\i\in_bone_rock_14.nif
+Decoding meshes\i\in_bonecave4_00.nif
+Decoding meshes\i\in_bonecave2_02.nif
+Decoding meshes\i\in_bonecave2_00.nif
+Decoding meshes\i\in_bonecave2_06.nif
+Decoding meshes\i\in_bonecave2_04.nif
+Decoding meshes\i\in_bonecave2_08.nif
+Decoding meshes\i\in_bone_rock_05.nif
+Decoding meshes\i\in_bone_rock_07.nif
+Decoding meshes\i\in_bone_rock_01.nif
+Decoding meshes\i\in_bone_rock_03.nif
+Decoding meshes\i\in_bone_rock_09.nif
+Decoding meshes\i\in_boneboulder01.nif
+Decoding meshes\i\in_boneboulder00.nif
+Decoding meshes\i\in_boneboulder03.nif
+Decoding meshes\i\in_boneboulder02.nif
+Decoding meshes\i\in_boneboulder05.nif
+Decoding meshes\i\in_boneboulder04.nif
+Decoding meshes\i\in_bonetrans_00.nif
+Decoding meshes\i\in_bone_rock_24.nif
+Decoding meshes\i\in_bone_rock_26.nif
+Decoding meshes\i\in_bone_rock_20.nif
+Decoding meshes\i\in_bone_rock_22.nif
+Decoding meshes\i\in_bone_rock_28.nif
+Decoding meshes\i\in_bonecave_28_1.nif
+Decoding meshes\i\in_bonecave2_10.nif
+Decoding meshes\i\in_bonecave2_12.nif
+Decoding meshes\i\in_bonecave_21_1.nif
+Decoding meshes\i\in_bone_rock_19.nif
+Decoding meshes\i\in_bone_rock_13.nif
+Decoding meshes\i\in_bone_rock_11.nif
+Decoding meshes\i\in_bone_rock_17.nif
+Decoding meshes\i\in_bone_rock_15.nif
+Decoding meshes\i\in_bonecave2_03.nif
+Decoding meshes\i\in_bonecave2_01.nif
+Decoding meshes\i\in_bonecave2_07.nif
+Decoding meshes\i\in_bonecave2_05.nif
+Decoding meshes\i\in_bonecave2_09.nif
+Decoding meshes\i\in_bone_rock_04.nif
+Decoding meshes\i\in_bone_rock_06.nif
+Decoding meshes\i\in_bone_rock_02.nif
+Decoding meshes\i\in_bone_rock_08.nif
+Decoding meshes\i\in_moldcave_21_1.nif
+Decoding meshes\i\in_mold_rock_25.nif
+Decoding meshes\i\in_mold_rock_27.nif
+Decoding meshes\i\in_mold_rock_21.nif
+Decoding meshes\i\in_mold_rock_23.nif
+Decoding meshes\i\in_mold_rock_19.nif
+Decoding meshes\i\in_mold_rock_13.nif
+Decoding meshes\i\in_mold_rock_11.nif
+Decoding meshes\i\in_mold_rock_17.nif
+Decoding meshes\i\in_mold_rock_15.nif
+Decoding meshes\i\in_moldcave2_02.nif
+Decoding meshes\i\in_moldcave2_00.nif
+Decoding meshes\i\in_moldcave2_06.nif
+Decoding meshes\i\in_moldcave2_04.nif
+Decoding meshes\i\in_moldcave2_08.nif
+Decoding meshes\i\in_moldcave4_00.nif
+Decoding meshes\i\in_mold_rock_07.nif
+Decoding meshes\i\in_mold_rock_05.nif
+Decoding meshes\i\in_mold_rock_03.nif
+Decoding meshes\i\in_mold_rock_01.nif
+Decoding meshes\i\in_mold_rock_09.nif
+Decoding meshes\i\in_moldtrans_00.nif
+Decoding meshes\i\in_moldboulder05.nif
+Decoding meshes\i\in_moldboulder04.nif
+Decoding meshes\i\in_moldboulder03.nif
+Decoding meshes\i\in_moldboulder02.nif
+Decoding meshes\i\in_moldboulder01.nif
+Decoding meshes\i\in_moldboulder00.nif
+Decoding meshes\i\in_mold_rock_24.nif
+Decoding meshes\i\in_mold_rock_26.nif
+Decoding meshes\i\in_mold_rock_20.nif
+Decoding meshes\i\in_mold_rock_22.nif
+Decoding meshes\i\in_mold_rock_28.nif
+Decoding meshes\i\in_moldcave2_end.nif
+Decoding meshes\i\in_mold_rock_18.nif
+Decoding meshes\i\in_mold_rock_12.nif
+Decoding meshes\i\in_mold_rock_10.nif
+Decoding meshes\i\in_mold_rock_16.nif
+Decoding meshes\i\in_mold_rock_14.nif
+Decoding meshes\i\in_moldcave2_03.nif
+Decoding meshes\i\in_moldcave2_01.nif
+Decoding meshes\i\in_moldcave2_07.nif
+Decoding meshes\i\in_moldcave2_05.nif
+Decoding meshes\i\in_moldcave2_09.nif
+Decoding meshes\i\in_moldcave_28_1.nif
+Decoding meshes\i\in_mold_rock_06.nif
+Decoding meshes\i\in_mold_rock_04.nif
+Decoding meshes\i\in_mold_rock_02.nif
+Decoding meshes\i\in_mold_rock_08.nif
+Decoding meshes\i\in_nord_house_03.nif
+Decoding meshes\i\in_nord_house_02.nif
+Decoding meshes\i\in_nord_house_01.nif
+Decoding meshes\i\in_nord_house_05.nif
+Decoding meshes\i\in_nord_house_04.nif
+Decoding meshes\i\in_nord_doorf_01.nif
+Decoding meshes\i\in_nord_doorf_02.nif
+Decoding meshes\i\in_shipwreck_top.nif
+Decoding meshes\i\in_sewer_canal00.nif
+Decoding meshes\i\in_sewer_raft00.nif
+Decoding meshes\i\in_sewer_union00.nif
+Decoding meshes\i\in_de_shipcabin.nif
+Decoding meshes\i\in_de_shack_door.nif
+Decoding meshes\i\in_dae_hall_l_03.nif
+Decoding meshes\i\in_dae_hall_l_02.nif
+Decoding meshes\i\in_dae_hall_l_01.nif
+Decoding meshes\i\in_cavern_ramp10.nif
+Decoding meshes\i\in_cavern_ramp00.nif
+Decoding meshes\i\in_cave_plant00.nif
+Decoding meshes\i\in_cavern_beam40.nif
+Decoding meshes\i\in_cavern_beam00.nif
+Decoding meshes\i\in_cavern_beam10.nif
+Decoding meshes\i\in_cavern_beam20.nif
+Decoding meshes\i\in_cavern_beam30.nif
+Decoding meshes\i\in_cave_plant10.nif
+Decoding meshes\i\in_cavern_rail00.nif
+Decoding meshes\i\in_cavern_rail10.nif
+Decoding meshes\i\in_lava_rock_23.nif
+Decoding meshes\i\in_lava_rock_21.nif
+Decoding meshes\i\in_lava_rock_27.nif
+Decoding meshes\i\in_lava_rock_25.nif
+Decoding meshes\i\in_lavacave_21_1.nif
+Decoding meshes\i\in_lavatrans_00.nif
+Decoding meshes\i\in_lavacave2_end.nif
+Decoding meshes\i\in_lava_rock_16.nif
+Decoding meshes\i\in_lava_rock_14.nif
+Decoding meshes\i\in_lava_rock_12.nif
+Decoding meshes\i\in_lava_rock_10.nif
+Decoding meshes\i\in_lava_rock_18.nif
+Decoding meshes\i\in_lavacave2_05.nif
+Decoding meshes\i\in_lavacave2_07.nif
+Decoding meshes\i\in_lavacave2_01.nif
+Decoding meshes\i\in_lavacave2_03.nif
+Decoding meshes\i\in_lavacave2_09.nif
+Decoding meshes\i\in_lava_rock_09.nif
+Decoding meshes\i\in_lava_rock_05.nif
+Decoding meshes\i\in_lava_rock_07.nif
+Decoding meshes\i\in_lava_rock_01.nif
+Decoding meshes\i\in_lava_rock_03.nif
+Decoding meshes\i\in_lava_rock_28.nif
+Decoding meshes\i\in_lava_rock_22.nif
+Decoding meshes\i\in_lava_rock_20.nif
+Decoding meshes\i\in_lava_rock_26.nif
+Decoding meshes\i\in_lava_rock_24.nif
+Decoding meshes\i\in_lavacave_28_1.nif
+Decoding meshes\i\in_lava_1024_01.nif
+Decoding meshes\i\in_lavaboulder02.nif
+Decoding meshes\i\in_lavaboulder03.nif
+Decoding meshes\i\in_lavaboulder00.nif
+Decoding meshes\i\in_lavaboulder01.nif
+Decoding meshes\i\in_lavaboulder04.nif
+Decoding meshes\i\in_lavaboulder05.nif
+Decoding meshes\i\in_lava_rock_17.nif
+Decoding meshes\i\in_lava_rock_15.nif
+Decoding meshes\i\in_lava_rock_13.nif
+Decoding meshes\i\in_lava_rock_11.nif
+Decoding meshes\i\in_lava_rock_19.nif
+Decoding meshes\i\in_lavacave4_00.nif
+Decoding meshes\i\in_lavacave2_04.nif
+Decoding meshes\i\in_lavacave2_06.nif
+Decoding meshes\i\in_lavacave2_00.nif
+Decoding meshes\i\in_lavacave2_02.nif
+Decoding meshes\i\in_lavacave2_08.nif
+Decoding meshes\i\in_lava_rock_08.nif
+Decoding meshes\i\in_lava_rock_04.nif
+Decoding meshes\i\in_lava_rock_06.nif
+Decoding meshes\i\in_lava_rock_02.nif
+Decoding meshes\i\in_t_crystal_02.nif
+Decoding meshes\i\in_t_s_shaft_01.nif
+Decoding meshes\i\in_t_l_hall_3way.nif
+Decoding meshes\i\in_t_housepod_01.nif
+Decoding meshes\i\in_t_housepod_02.nif
+Decoding meshes\i\in_t_s_pillar_04.nif
+Decoding meshes\i\in_t_s_pillar_02.nif
+Decoding meshes\i\in_t_s_hall_4way.nif
+Decoding meshes\i\in_t_s_hall_3way.nif
+Decoding meshes\i\in_t_councilhall.nif
+Decoding meshes\i\in_t_gatesymbol.nif
+Decoding meshes\i\in_t_l_pillar_01.nif
+Decoding meshes\i\in_t_crystal_01.nif
+Decoding meshes\i\in_t_l_room_side.nif
+Decoding meshes\i\in_t_platform_01.nif
+Decoding meshes\i\in_t_platform_02.nif
+Decoding meshes\i\in_t_s_turret_03.nif
+Decoding meshes\i\in_t_s_turret_02.nif
+Decoding meshes\i\in_t_sidewall_01.nif
+Decoding meshes\i\in_t_sidewall_02.nif
+Decoding meshes\i\in_v_roomhol_01.nif
+Decoding meshes\i\in_r_building_01.nif
+Decoding meshes\i\in_c_stone_4way.nif
+Decoding meshes\i\in_c_stone_3way.nif
+Decoding meshes\i\in_c_railing_01.nif
+Decoding meshes\i\in_c_wall_plain.nif
+Decoding meshes\i\in_c_plain_4way.nif
+Decoding meshes\i\in_c_plain_3way.nif
+Decoding meshes\i\in_c_pillar_wood.nif
+Decoding meshes\i\in_t_l_hall_01.nif
+Decoding meshes\i\in_t_s_hall_01.nif
+Decoding meshes\i\in_t_gasket_01.nif
+Decoding meshes\i\in_t_manor_01.nif
+Decoding meshes\i\in_t_manor_02.nif
+Decoding meshes\i\in_t_l_door_01.nif
+Decoding meshes\i\in_v_palace_01.nif
+Decoding meshes\i\in_v_plaza_01.nif
+Decoding meshes\i\in_v_roomhf_01.nif
+Decoding meshes\i\in_v_arena_01.nif
+Decoding meshes\i\in_v_plaza_02.nif
+Decoding meshes\i\in_pytrans_00.nif
+Decoding meshes\i\in_py_rock_15.nif
+Decoding meshes\i\in_pycave_28_1.nif
+Decoding meshes\i\in_pycave_21_1.nif
+Decoding meshes\i\in_py_rock_19.nif
+Decoding meshes\i\in_py_rock_09.nif
+Decoding meshes\i\in_pycave2_06.nif
+Decoding meshes\i\in_py_rock_02.nif
+Decoding meshes\i\in_py_rock_12.nif
+Decoding meshes\i\in_py_rock_22.nif
+Decoding meshes\i\in_pycave2_00.nif
+Decoding meshes\i\in_pycave4_00.nif
+Decoding meshes\i\in_py_rock_05.nif
+Decoding meshes\i\in_py_rock_23.nif
+Decoding meshes\i\in_py_rock_03.nif
+Decoding meshes\i\in_pycave2_01.nif
+Decoding meshes\i\in_py_rock_06.nif
+Decoding meshes\i\in_pycave2_07.nif
+Decoding meshes\i\in_py_rock_16.nif
+Decoding meshes\i\in_py_rock_20.nif
+Decoding meshes\i\in_py_rock_10.nif
+Decoding meshes\i\in_pycave2_04.nif
+Decoding meshes\i\in_py_rock_26.nif
+Decoding meshes\i\in_py_rock_27.nif
+Decoding meshes\i\in_pyboulder04.nif
+Decoding meshes\i\in_pyboulder00.nif
+Decoding meshes\i\in_pyboulder02.nif
+Decoding meshes\i\in_py_rock_25.nif
+Decoding meshes\i\in_pycave2_03.nif
+Decoding meshes\i\in_py_rock_07.nif
+Decoding meshes\i\in_pycave2_02.nif
+Decoding meshes\i\in_py_rock_24.nif
+Decoding meshes\i\in_py_rock_17.nif
+Decoding meshes\i\in_prison_ship.nif
+Decoding meshes\i\in_pycave2_09.nif
+Decoding meshes\i\in_py_rock_14.nif
+Decoding meshes\i\in_py_rock_13.nif
+Decoding meshes\i\in_pycave2_05.nif
+Decoding meshes\i\in_pyboulder05.nif
+Decoding meshes\i\in_pyboulder01.nif
+Decoding meshes\i\in_pyboulder03.nif
+Decoding meshes\i\in_py_rock_28.nif
+Decoding meshes\i\in_py_rock_18.nif
+Decoding meshes\i\in_pycave2_end.nif
+Decoding meshes\i\in_py_rock_08.nif
+Decoding meshes\i\in_py_rock_04.nif
+Decoding meshes\i\in_pycave2_08.nif
+Decoding meshes\i\in_py_rock_11.nif
+Decoding meshes\i\in_py_rock_01.nif
+Decoding meshes\i\in_py_rock_21.nif
+Decoding meshes\i\in_r_s_room_02.nif
+Decoding meshes\i\in_r_s_room_01.nif
+Decoding meshes\i\in_r_s_room_03.nif
+Decoding meshes\i\in_dwrv_gear10.nif
+Decoding meshes\i\in_de_shack_05.nif
+Decoding meshes\i\in_de_shack_03.nif
+Decoding meshes\i\in_de_shack_01.nif
+Decoding meshes\i\in_dwrv_gear00.nif
+Decoding meshes\i\in_dwrv_wall10.nif
+Decoding meshes\i\in_dwrv_lift00.nif
+Decoding meshes\i\in_de_shack_04.nif
+Decoding meshes\i\in_de_shack_02.nif
+Decoding meshes\i\in_dwrv_wall00.nif
+Decoding meshes\i\in_dwrv_gear20.nif
+Decoding meshes\i\in_dae_claw_01.nif
+Decoding meshes\i\in_dwrv_gear30.nif
+Decoding meshes\i\in_akulacave00.nif
+Decoding meshes\i\in_akulakhan00.nif
+Decoding meshes\i\in_bonecave_17.nif
+Decoding meshes\i\in_bonecave_15.nif
+Decoding meshes\i\in_bonecave_13.nif
+Decoding meshes\i\in_bonecave_11.nif
+Decoding meshes\i\in_bonecave_19.nif
+Decoding meshes\i\in_bonecave_21.nif
+Decoding meshes\i\in_bonecave_27.nif
+Decoding meshes\i\in_bonecave_25.nif
+Decoding meshes\i\in_bonecave_08.nif
+Decoding meshes\i\in_bonecave_06.nif
+Decoding meshes\i\in_bonecave_04.nif
+Decoding meshes\i\in_bonecave_02.nif
+Decoding meshes\i\in_bonecave_00.nif
+Decoding meshes\i\in_bonecave_16.nif
+Decoding meshes\i\in_bonecave_14.nif
+Decoding meshes\i\in_bonecave_12.nif
+Decoding meshes\i\in_bonecave_10.nif
+Decoding meshes\i\in_bonecave_18.nif
+Decoding meshes\i\in_bonecave_26.nif
+Decoding meshes\i\in_bonecave_09.nif
+Decoding meshes\i\in_bonecave_07.nif
+Decoding meshes\i\in_bonecave_05.nif
+Decoding meshes\i\in_bonecave_03.nif
+Decoding meshes\i\in_bonecave_01.nif
+Decoding meshes\i\in_ci_mummy_04.nif
+Decoding meshes\i\in_ci_mummy_06.nif
+Decoding meshes\i\in_ci_mummy_02.nif
+Decoding meshes\i\in_ci_azura_01.nif
+Decoding meshes\i\in_c_wall_rich.nif
+Decoding meshes\i\in_ci_mummy_05.nif
+Decoding meshes\i\in_ci_mummy_01.nif
+Decoding meshes\i\in_ci_mummy_03.nif
+Decoding meshes\i\in_lavacave_17.nif
+Decoding meshes\i\in_lavacave_15.nif
+Decoding meshes\i\in_lavacave_13.nif
+Decoding meshes\i\in_lavacave_11.nif
+Decoding meshes\i\in_lavacave_19.nif
+Decoding meshes\i\in_lavalayer00.nif
+Decoding meshes\i\in_lavacave_08.nif
+Decoding meshes\i\in_lavacave_06.nif
+Decoding meshes\i\in_lavacave_04.nif
+Decoding meshes\i\in_lavacave_02.nif
+Decoding meshes\i\in_lavacave_00.nif
+Decoding meshes\i\in_lavacave_21.nif
+Decoding meshes\i\in_lavacave_27.nif
+Decoding meshes\i\in_lavacave_25.nif
+Decoding meshes\i\in_lavacave_16.nif
+Decoding meshes\i\in_lavacave_14.nif
+Decoding meshes\i\in_lavacave_12.nif
+Decoding meshes\i\in_lavacave_10.nif
+Decoding meshes\i\in_lavacave_18.nif
+Decoding meshes\i\in_lavacave_09.nif
+Decoding meshes\i\in_lavacave_07.nif
+Decoding meshes\i\in_lavacave_05.nif
+Decoding meshes\i\in_lavacave_03.nif
+Decoding meshes\i\in_lavacave_01.nif
+Decoding meshes\i\in_lavacave_26.nif
+Decoding meshes\i\in_mudcave_14.nif
+Decoding meshes\i\in_mudcave_07.nif
+Decoding meshes\i\in_mudcave_17.nif
+Decoding meshes\i\in_mudtrans_00.nif
+Decoding meshes\i\in_mudcave_27.nif
+Decoding meshes\i\in_moldcave_17.nif
+Decoding meshes\i\in_moldcave_15.nif
+Decoding meshes\i\in_moldcave_13.nif
+Decoding meshes\i\in_moldcave_11.nif
+Decoding meshes\i\in_moldcave_19.nif
+Decoding meshes\i\in_mudcave_10.nif
+Decoding meshes\i\in_mudcave_00.nif
+Decoding meshes\i\in_mudcave_03.nif
+Decoding meshes\i\in_mudcave2_08.nif
+Decoding meshes\i\in_mudcave2_04.nif
+Decoding meshes\i\in_mudcave2_06.nif
+Decoding meshes\i\in_mudcave2_00.nif
+Decoding meshes\i\in_mudcave2_02.nif
+Decoding meshes\i\in_mudcave4_00.nif
+Decoding meshes\i\in_mud_rock_21.nif
+Decoding meshes\i\in_mud_rock_23.nif
+Decoding meshes\i\in_mud_rock_25.nif
+Decoding meshes\i\in_mud_rock_27.nif
+Decoding meshes\i\in_mudcave_04.nif
+Decoding meshes\i\in_mudcave_21.nif
+Decoding meshes\i\in_mudcave_11.nif
+Decoding meshes\i\in_mudcave_01.nif
+Decoding meshes\i\in_mudcave_18.nif
+Decoding meshes\i\in_mudcave_08.nif
+Decoding meshes\i\in_mud_rock_13.nif
+Decoding meshes\i\in_mud_rock_11.nif
+Decoding meshes\i\in_mud_rock_17.nif
+Decoding meshes\i\in_mud_rock_15.nif
+Decoding meshes\i\in_mud_rock_19.nif
+Decoding meshes\i\in_moldcave_21.nif
+Decoding meshes\i\in_moldcave_27.nif
+Decoding meshes\i\in_moldcave_25.nif
+Decoding meshes\i\in_moldcave_08.nif
+Decoding meshes\i\in_moldcave_06.nif
+Decoding meshes\i\in_moldcave_04.nif
+Decoding meshes\i\in_moldcave_02.nif
+Decoding meshes\i\in_moldcave_00.nif
+Decoding meshes\i\in_mud_rock_07.nif
+Decoding meshes\i\in_mud_rock_05.nif
+Decoding meshes\i\in_mud_rock_03.nif
+Decoding meshes\i\in_mud_rock_01.nif
+Decoding meshes\i\in_mud_rock_09.nif
+Decoding meshes\i\in_mudcave_26.nif
+Decoding meshes\i\in_moldcave_16.nif
+Decoding meshes\i\in_moldcave_14.nif
+Decoding meshes\i\in_moldcave_12.nif
+Decoding meshes\i\in_moldcave_10.nif
+Decoding meshes\i\in_moldcave_18.nif
+Decoding meshes\i\in_mudcave_13.nif
+Decoding meshes\i\in_mudcave_06.nif
+Decoding meshes\i\in_mudcave_25.nif
+Decoding meshes\i\in_mudcave2_09.nif
+Decoding meshes\i\in_mudcave2_05.nif
+Decoding meshes\i\in_mudcave2_07.nif
+Decoding meshes\i\in_mudcave2_01.nif
+Decoding meshes\i\in_mudcave2_03.nif
+Decoding meshes\i\in_mudcave_16.nif
+Decoding meshes\i\in_mud_rock_20.nif
+Decoding meshes\i\in_mud_rock_22.nif
+Decoding meshes\i\in_mud_rock_24.nif
+Decoding meshes\i\in_mud_rock_26.nif
+Decoding meshes\i\in_mud_rock_28.nif
+Decoding meshes\i\in_mudcave_15.nif
+Decoding meshes\i\in_mud_rock_12.nif
+Decoding meshes\i\in_mud_rock_10.nif
+Decoding meshes\i\in_mud_rock_16.nif
+Decoding meshes\i\in_mud_rock_14.nif
+Decoding meshes\i\in_mud_rock_18.nif
+Decoding meshes\i\in_moldcave_26.nif
+Decoding meshes\i\in_moldcave_09.nif
+Decoding meshes\i\in_moldcave_07.nif
+Decoding meshes\i\in_moldcave_05.nif
+Decoding meshes\i\in_moldcave_03.nif
+Decoding meshes\i\in_moldcave_01.nif
+Decoding meshes\i\in_mud_rock_06.nif
+Decoding meshes\i\in_mud_rock_04.nif
+Decoding meshes\i\in_mud_rock_02.nif
+Decoding meshes\i\in_mud_rock_08.nif
+Decoding meshes\i\in_mudcave_05.nif
+Decoding meshes\i\in_mudcave_02.nif
+Decoding meshes\i\in_mudcave_12.nif
+Decoding meshes\i\in_mudcave_19.nif
+Decoding meshes\i\in_mudcave_09.nif
+Decoding meshes\i\in_hlaalu_door.nif
+Decoding meshes\i\in_hlaalu_wall.nif
+Decoding meshes\i\in_icesheet_01.nif
+Decoding meshes\i\in_icesheet_02.nif
+Decoding meshes\i\in_pycave_15.nif
+Decoding meshes\i\in_pycave_02.nif
+Decoding meshes\i\in_pycave_12.nif
+Decoding meshes\i\in_lava_256a.nif
+Decoding meshes\i\in_pycave_05.nif
+Decoding meshes\i\in_lava_oval.nif
+Decoding meshes\i\in_pycave_06.nif
+Decoding meshes\i\in_pycave_03.nif
+Decoding meshes\i\in_pycave_16.nif
+Decoding meshes\i\in_pycave_19.nif
+Decoding meshes\i\in_pycave_26.nif
+Decoding meshes\i\in_pycave_09.nif
+Decoding meshes\i\in_pycave_27.nif
+Decoding meshes\i\in_pycave_18.nif
+Decoding meshes\i\in_pycave_25.nif
+Decoding meshes\i\in_pycave_08.nif
+Decoding meshes\i\in_pycave_10.nif
+Decoding meshes\i\in_pycave_00.nif
+Decoding meshes\i\in_pycave_07.nif
+Decoding meshes\i\in_pycave_17.nif
+Decoding meshes\i\in_lava_256.nif
+Decoding meshes\i\in_t_l_4way.nif
+Decoding meshes\i\in_t_edge_01.nif
+Decoding meshes\i\in_pycave_14.nif
+Decoding meshes\i\in_lava_512.nif
+Decoding meshes\i\in_pycave_13.nif
+Decoding meshes\i\in_lava_1024.nif
+Decoding meshes\i\in_pycave_11.nif
+Decoding meshes\i\in_pycave_01.nif
+Decoding meshes\i\in_pycave_21.nif
+Decoding meshes\i\in_pycave_04.nif
+Decoding meshes\i\in_c_skywalk.nif
+Decoding meshes\i\in_6th_chalk30.nif
+Decoding meshes\i\in_6th_chalk20.nif
+Decoding meshes\i\in_6th_chalk00.nif
+Decoding meshes\i\in_6th_chalk10.nif
+Decoding meshes\i\tx_crystal_03.nif
+Decoding meshes\i\tx_crystal_02.nif
+Decoding meshes\i\ex_dae_ruin_04.nif
+Decoding meshes\i\ex_dae_ruin_02.nif
+Decoding meshes\a\a_imperial_f_shoe.nif
+Decoding meshes\a\a_imperial_a_boot.nif
+Decoding meshes\a\a_iron_cuirass_gnd.nif
+Decoding meshes\a\a_iron_pauldron_fa.nif
+Decoding meshes\a\a_iron_pauldron_ua.nif
+Decoding meshes\a\a_iron_pauldron_cl.nif
+Decoding meshes\a\a_iron_greaves_gnd.nif
+Decoding meshes\a\a_iron_greaves_ul.nif
+Decoding meshes\a\a_indoril_m_helmet.nif
+Decoding meshes\a\a_indoril_m_f_boot.nif
+Decoding meshes\a\a_indoril_m_a_boot.nif
+Decoding meshes\a\a_indoril_m_skins.nif
+Decoding meshes\a\shield_art_auriel.nif
+Decoding meshes\marker_creature.nif
+Decoding meshes\marker_travel.nif
+Decoding meshes\marker_temple.nif
+Decoding meshes\marker_prison.nif
+Decoding meshes\marker_radius.nif
+Decoding meshes\marker_divine.nif
+Decoding meshes\m\skeleton_key.nif
+Decoding meshes\a\shield_bonemold.nif
+Decoding meshes\a\shield_dwemer.nif
+Decoding meshes\a\shield_daedric.nif
+Decoding meshes\a\shield_dreugh.nif
+Decoding meshes\a\shield_indoril.nif
+Decoding meshes\a\shield_chitin.nif
+Decoding meshes\a\shield_ebony.nif
+Decoding meshes\a\shield_steel.nif
+Decoding meshes\a\shield_glass.nif
+Decoding meshes\a\shield_iron.nif
+Decoding meshes\a\a_imperial_skins.nif
+Decoding meshes\a\a_imperial_skirt.nif
+Decoding meshes\a\a_iron_boot_gnd.nif
+Decoding meshes\a\a_iron_greaves_g.nif
+Decoding meshes\a\a_iron_greaves_k.nif
+Decoding meshes\a\a_iron_bracer_w.nif
+Decoding meshes\a\a_iron_hands.1st.nif
+Decoding meshes\a\a_iron_boot_f.nif
+Decoding meshes\a\a_iron_skinned.nif
+Decoding meshes\a\a_iron_boot_a.nif
+Decoding meshes\a\a_iron_helm_01.nif
+Decoding meshes\f\flora_bc_grass_02.nif
+Decoding meshes\f\flora_bc_grass_01.nif
+Decoding meshes\f\flora_comberry_01.nif
+Decoding meshes\f\flora_chokeweed_01.nif
+Decoding meshes\f\flora_roobrush_01.nif
+Decoding meshes\f\flora_corkbulb_01.nif
+Decoding meshes\n\ingred_emerald_01.nif
+Decoding meshes\n\ingred_comberry_01.nif
+Decoding meshes\n\ingred_ash_yam_01.nif
+Decoding meshes\n\ingred_russula_01.nif
+Decoding meshes\n\ingred_coprinus_01.nif
+Decoding meshes\n\ingred_scuttle_01.nif
+Decoding meshes\n\ingred_saltrice_01.nif
+Decoding meshes\n\ingred_roobrush_01.nif
+Decoding meshes\n\ingred_heather_01.nif
+Decoding meshes\n\ingred_rat_meat_01.nif
+Decoding meshes\n\ingred_crabmeat_01.nif
+Decoding meshes\n\ingred_rawebony_01.nif
+Decoding meshes\n\ingred_diamond_01.nif
+Decoding meshes\f\food_kwama_egg_01.nif
+Decoding meshes\f\food_kwama_egg_02.nif
+Decoding meshes\n\ingred_pearl_01.nif
+Decoding meshes\n\ingred_bread_01.nif
+Decoding meshes\n\ingred_scales_01.nif
+Decoding meshes\n\ingred_resin_01.nif
+Decoding meshes\n\ingred_muck_01.nif
+Decoding meshes\n\ingred_ruby_01.nif
+Decoding meshes\f\flora_root_wg_07.nif
+Decoding meshes\f\flora_root_wg_06.nif
+Decoding meshes\f\flora_root_wg_05.nif
+Decoding meshes\f\flora_root_wg_04.nif
+Decoding meshes\f\flora_root_wg_03.nif
+Decoding meshes\f\flora_root_wg_02.nif
+Decoding meshes\f\flora_root_wg_01.nif
+Decoding meshes\f\flora_root_wg_08.nif
+Decoding meshes\f\flora_bc_log_02.nif
+Decoding meshes\f\flora_muckpod_04.nif
+Decoding meshes\f\flora_muckpod_05.nif
+Decoding meshes\f\flora_muckpod_02.nif
+Decoding meshes\f\flora_muckpod_03.nif
+Decoding meshes\f\flora_muckpod_01.nif
+Decoding meshes\f\flora_bc_moss_03.nif
+Decoding meshes\f\flora_bc_moss_02.nif
+Decoding meshes\f\flora_bc_moss_01.nif
+Decoding meshes\f\flora_bc_moss_07.nif
+Decoding meshes\f\flora_bc_moss_06.nif
+Decoding meshes\f\flora_bc_moss_05.nif
+Decoding meshes\f\flora_bc_moss_04.nif
+Decoding meshes\f\flora_bc_moss_09.nif
+Decoding meshes\f\flora_bc_moss_08.nif
+Decoding meshes\f\flora_bc_moss_13.nif
+Decoding meshes\f\flora_bc_moss_12.nif
+Decoding meshes\f\flora_bc_moss_11.nif
+Decoding meshes\f\flora_bc_moss_10.nif
+Decoding meshes\f\flora_bc_moss_17.nif
+Decoding meshes\f\flora_bc_moss_16.nif
+Decoding meshes\f\flora_bc_moss_15.nif
+Decoding meshes\f\flora_bc_moss_14.nif
+Decoding meshes\f\flora_bc_moss_19.nif
+Decoding meshes\f\flora_bc_moss_18.nif
+Decoding meshes\f\flora_bc_moss_21.nif
+Decoding meshes\f\flora_bc_moss_20.nif
+Decoding meshes\f\flora_bc_tree_13.nif
+Decoding meshes\f\flora_bc_tree_12.nif
+Decoding meshes\f\flora_bc_tree_11.nif
+Decoding meshes\f\flora_bc_tree_10.nif
+Decoding meshes\f\flora_bc_tree_03.nif
+Decoding meshes\f\flora_bc_tree_02.nif
+Decoding meshes\f\flora_bc_tree_01.nif
+Decoding meshes\f\flora_bc_tree_07.nif
+Decoding meshes\f\flora_bc_tree_06.nif
+Decoding meshes\f\flora_bc_tree_05.nif
+Decoding meshes\f\flora_bc_tree_04.nif
+Decoding meshes\f\flora_bc_tree_09.nif
+Decoding meshes\f\flora_bc_tree_08.nif
+Decoding meshes\f\flora_bc_vine_02.nif
+Decoding meshes\f\flora_bc_vine_03.nif
+Decoding meshes\f\flora_bc_vine_01.nif
+Decoding meshes\f\flora_bc_vine_06.nif
+Decoding meshes\f\flora_bc_vine_07.nif
+Decoding meshes\f\flora_bc_vine_04.nif
+Decoding meshes\f\flora_bc_vine_05.nif
+Decoding meshes\f\flora_bc_fern_04.nif
+Decoding meshes\f\flora_bc_fern_03.nif
+Decoding meshes\f\flora_bc_fern_02.nif
+Decoding meshes\f\flora_bc_fern_01.nif
+Decoding meshes\f\flora_bc_log_01.nif
+Decoding meshes\f\flora_tree_gl_11.nif
+Decoding meshes\f\flora_tree_gl_10.nif
+Decoding meshes\f\flora_tree_ac_04.nif
+Decoding meshes\f\flora_tree_ac_01.nif
+Decoding meshes\f\flora_tree_ac_03.nif
+Decoding meshes\f\flora_tree_ac_02.nif
+Decoding meshes\f\flora_tree_wg_08.nif
+Decoding meshes\f\flora_tree_wg_01.nif
+Decoding meshes\f\flora_tree_wg_03.nif
+Decoding meshes\f\flora_tree_wg_02.nif
+Decoding meshes\f\flora_tree_wg_05.nif
+Decoding meshes\f\flora_tree_wg_04.nif
+Decoding meshes\f\flora_tree_wg_07.nif
+Decoding meshes\f\flora_tree_wg_06.nif
+Decoding meshes\f\flora_tree_ai_05.nif
+Decoding meshes\f\flora_tree_ai_06.nif
+Decoding meshes\f\flora_tree_gl_09.nif
+Decoding meshes\f\flora_tree_gl_08.nif
+Decoding meshes\f\flora_tree_gl_01.nif
+Decoding meshes\f\flora_tree_gl_03.nif
+Decoding meshes\f\flora_tree_gl_02.nif
+Decoding meshes\f\flora_tree_gl_05.nif
+Decoding meshes\f\flora_tree_gl_04.nif
+Decoding meshes\f\flora_tree_gl_07.nif
+Decoding meshes\f\flora_tree_gl_06.nif
+Decoding meshes\f\flora_heather_01.nif
+Decoding meshes\f\flora_bc_knee_04.nif
+Decoding meshes\f\flora_bc_knee_01.nif
+Decoding meshes\f\flora_bc_knee_03.nif
+Decoding meshes\f\flora_bc_knee_02.nif
+Decoding meshes\f\flora_tree_01.nif
+Decoding meshes\f\flora_kelp_02.nif
+Decoding meshes\f\flora_grass_04.nif
+Decoding meshes\f\flora_grass_02.nif
+Decoding meshes\f\flora_kelp_03.nif
+Decoding meshes\f\flora_tree_02.nif
+Decoding meshes\f\flora_tree_04.nif
+Decoding meshes\f\flora_tree_03.nif
+Decoding meshes\f\flora_grass_03.nif
+Decoding meshes\f\flora_grass_01.nif
+Decoding meshes\f\flora_kelp_01.nif
+Decoding meshes\f\flora_bush_01.nif
+Decoding meshes\f\flora_kelp_04.nif
+Decoding meshes\f\flora_ivy_01.nif
+Decoding meshes\f\flora_ivy_02.nif
+Decoding meshes\a\a_helm_colovian.nif
+Decoding meshes\a\a_helm_silver.nif
+Decoding meshes\f\velothi_sewer_door.nif
+Decoding meshes\x\terrain_rock_ma_5a.nif
+Decoding meshes\x\terrain_rock_ma_39.nif
+Decoding meshes\x\terrain_rock_ma_19.nif
+Decoding meshes\x\terrain_rock_ma_09.nif
+Decoding meshes\x\terrain_rock_ma_79.nif
+Decoding meshes\x\terrain_rock_ma_59.nif
+Decoding meshes\x\terrain_rock_ma_49.nif
+Decoding meshes\x\terrain_rock_ma_58.nif
+Decoding meshes\x\terrain_rock_ma_53.nif
+Decoding meshes\x\terrain_rock_ma_43.nif
+Decoding meshes\x\terrain_rock_ma_02.nif
+Decoding meshes\x\terrain_rock_ma_01.nif
+Decoding meshes\x\terrain_rock_ma_51.nif
+Decoding meshes\x\terrain_rock_ma_10.nif
+Decoding meshes\x\terrain_rock_ma_40.nif
+Decoding meshes\x\terrain_rock_ma_07.nif
+Decoding meshes\x\terrain_rock_ma_67.nif
+Decoding meshes\x\terrain_rock_ma_57.nif
+Decoding meshes\x\terrain_rock_ma_06.nif
+Decoding meshes\x\terrain_rock_ma_56.nif
+Decoding meshes\x\terrain_rock_ma_25.nif
+Decoding meshes\x\terrain_rock_ma_05.nif
+Decoding meshes\x\terrain_rock_ma_55.nif
+Decoding meshes\x\terrain_rock_ma_04.nif
+Decoding meshes\x\terrain_rock_ma_54.nif
+Decoding meshes\x\terrain_ashmire_01.nif
+Decoding meshes\x\terrain_ma_rock_51.nif
+Decoding meshes\x\terrain_ma_rock_53.nif
+Decoding meshes\x\terrain_ma_rock_13.nif
+Decoding meshes\x\terrain_rock_ma31.nif
+Decoding meshes\x\terrain_rock_ma41.nif
+Decoding meshes\x\terrain_ma_rock13.nif
+Decoding meshes\x\terrain_ma_bridge.nif
+Decoding meshes\x\terrain_ma_rock22.nif
+Decoding meshes\x\terrain_ma_rock32.nif
+Decoding meshes\x\terrain_ma_rock62.nif
+Decoding meshes\x\terrain_lavapot_02.nif
+Decoding meshes\x\terrain_lavapot_03.nif
+Decoding meshes\x\terrain_lava_vent.nif
+Decoding meshes\x\furn_sign_arms_01.nif
+Decoding meshes\x\furn_tikitorch_out.nif
+Decoding meshes\x\furn_com_fence_02.nif
+Decoding meshes\x\furn_com_fence_03.nif
+Decoding meshes\x\furn_com_fence_01.nif
+Decoding meshes\editormarker_box_01.nif
+Decoding meshes\x\terrain_lavapot.nif
+Decoding meshes\x\terrain_lava_pot.nif
+Decoding meshes\x\terrain_volcano.nif
+Decoding meshes\x\terrain_ma_60.nif
+Decoding meshes\x\terrain_ma_20.nif
+Decoding meshes\x\terrain_ma_30.nif
+Decoding meshes\x\furn_sign_inn_01.nif
+Decoding meshes\x\furn_flagpole_01.nif
+Decoding meshes\x\furn_signbase_01.nif
+Decoding meshes\e\frost_shield.nif
+Decoding meshes\l\light_com_torch_01.nif
+Decoding meshes\l\light_com_torch_02.nif
+Decoding meshes\l\light_tikitorch00.nif
+Decoding meshes\l\light_de_candle_08.nif
+Decoding meshes\l\light_de_candle_18.nif
+Decoding meshes\l\light_de_candle_09.nif
+Decoding meshes\l\light_de_candle_19.nif
+Decoding meshes\l\light_de_candle_24.nif
+Decoding meshes\l\light_de_candle_04.nif
+Decoding meshes\l\light_de_candle_14.nif
+Decoding meshes\l\light_de_candle_25.nif
+Decoding meshes\l\light_de_candle_05.nif
+Decoding meshes\l\light_de_candle_15.nif
+Decoding meshes\l\light_de_candle_26.nif
+Decoding meshes\l\light_de_candle_06.nif
+Decoding meshes\l\light_de_candle_16.nif
+Decoding meshes\l\light_de_candle_07.nif
+Decoding meshes\l\light_de_candle_17.nif
+Decoding meshes\l\light_de_candle_20.nif
+Decoding meshes\l\light_de_candle_10.nif
+Decoding meshes\l\light_de_candle_21.nif
+Decoding meshes\l\light_de_candle_01.nif
+Decoding meshes\l\light_de_candle_11.nif
+Decoding meshes\l\light_de_candle_22.nif
+Decoding meshes\l\light_de_candle_02.nif
+Decoding meshes\l\light_de_candle_12.nif
+Decoding meshes\l\light_de_candle_23.nif
+Decoding meshes\l\light_de_candle_03.nif
+Decoding meshes\l\light_de_candle_13.nif
+Decoding meshes\l\light_com_lamp_01.nif
+Decoding meshes\l\light_com_lamp_02.nif
+Decoding meshes\l\light_redware_lamp.nif
+Decoding meshes\l\light_6th_brazier.nif
+Decoding meshes\l\light_fire_nosmoke.nif
+Decoding meshes\l\light_dwrv_neon00.nif
+Decoding meshes\d\door_dwrv_double01.nif
+Decoding meshes\d\door_dwrv_double00.nif
+Decoding meshes\d\door_dwrv_loadup00.nif
+Decoding meshes\d\door_dwrv_inner00.nif
+Decoding meshes\d\door_dwrv_load00.nif
+Decoding meshes\d\door_dwrv_main00.nif
+Decoding meshes\l\light_dae_censer.nif
+Decoding meshes\l\light_common_01.nif
+Decoding meshes\l\light_logpile10.nif
+Decoding meshes\l\light_pitfire00.nif
+Decoding meshes\l\light_buglamp_01.nif
+Decoding meshes\l\light_de_lamp_01.nif
+Decoding meshes\l\light_de_lamp_03.nif
+Decoding meshes\l\light_de_lamp_02.nif
+Decoding meshes\l\light_de_lamp_05.nif
+Decoding meshes\l\light_de_lamp_04.nif
+Decoding meshes\l\light_de_lamp_07.nif
+Decoding meshes\l\light_de_lamp_06.nif
+Decoding meshes\l\light_de_lamp_09.nif
+Decoding meshes\l\light_de_lamp_08.nif
+Decoding meshes\l\light_dae_jet.nif
+Decoding meshes\l\light_stand_01.nif
+Decoding meshes\l\light_tikilamp.nif
+Decoding meshes\l\light_torch10.nif
+Decoding meshes\l\light_sconce10.nif
+Decoding meshes\l\light_sconce00.nif
+Decoding meshes\l\light_torch_01.nif
+Decoding meshes\b\b_n_breton_m_skins.nif
+Decoding meshes\b\b_n_breton_f_skins.nif
+Decoding meshes\b\b_n_breton_f_ankle.nif
+Decoding meshes\b\b_n_breton_m_ankle.nif
+Decoding meshes\b\b_n_breton_m_groin.nif
+Decoding meshes\b\b_n_breton_f_groin.nif
+Decoding meshes\b\b_n_breton_f_wrist.nif
+Decoding meshes\b\b_n_breton_m_wrist.nif
+Decoding meshes\b\b_n_breton_f_neck.nif
+Decoding meshes\b\b_n_breton_m_neck.nif
+Decoding meshes\b\b_n_breton_f_foot.nif
+Decoding meshes\b\b_n_breton_m_foot.nif
+Decoding meshes\b\b_n_breton_f_knee.nif
+Decoding meshes\b\b_n_breton_m_knee.nif
+Decoding meshes\b\b_n_orc_f_forearm.nif
+Decoding meshes\b\b_n_orc_m_forearm.nif
+Decoding meshes\b\b_n_orc_m_head_04.nif
+Decoding meshes\b\b_n_orc_m_head_01.nif
+Decoding meshes\b\b_n_orc_m_head_02.nif
+Decoding meshes\b\b_n_orc_m_head_03.nif
+Decoding meshes\b\b_n_orc_m_hair_04.nif
+Decoding meshes\b\b_n_orc_m_hair_05.nif
+Decoding meshes\b\b_n_orc_m_hair_01.nif
+Decoding meshes\b\b_n_orc_m_hair_02.nif
+Decoding meshes\b\b_n_orc_m_hair_03.nif
+Decoding meshes\b\b_n_orc_f_head_01.nif
+Decoding meshes\b\b_n_orc_f_head_02.nif
+Decoding meshes\b\b_n_orc_f_head_03.nif
+Decoding meshes\a\a_m_chitin_a_boot.nif
+Decoding meshes\a\a_m_chitin_skinned.nif
+Decoding meshes\a\a_m_chitin_forearm.nif
+Decoding meshes\a\a_m_chitin_f_boots.nif
+Decoding meshes\a\a_m_chitin_helmet.nif
+Decoding meshes\b\b_n_khajiit_m_knee.nif
+Decoding meshes\b\b_n_khajiit_f_knee.nif
+Decoding meshes\b\b_n_khajiit_m_neck.nif
+Decoding meshes\b\b_n_khajiit_f_neck.nif
+Decoding meshes\b\b_n_nord_m_hair03.nif
+Decoding meshes\b\b_n_nord_m_hair02.nif
+Decoding meshes\b\b_n_nord_m_hair01.nif
+Decoding meshes\b\b_n_nord_m_hair00.nif
+Decoding meshes\b\b_n_nord_m_hair07.nif
+Decoding meshes\b\b_n_nord_m_hair06.nif
+Decoding meshes\b\b_n_nord_m_hair05.nif
+Decoding meshes\b\b_n_nord_m_hair04.nif
+Decoding meshes\b\b_n_nord_f_head_01.nif
+Decoding meshes\b\b_n_nord_m_head_01.nif
+Decoding meshes\b\b_n_nord_m_head_02.nif
+Decoding meshes\b\b_n_nord_f_head_03.nif
+Decoding meshes\b\b_n_nord_m_head_03.nif
+Decoding meshes\b\b_n_nord_f_head_02.nif
+Decoding meshes\b\b_n_nord_m_head_04.nif
+Decoding meshes\b\b_n_nord_f_head_05.nif
+Decoding meshes\b\b_n_nord_m_head_05.nif
+Decoding meshes\b\b_n_nord_f_head_04.nif
+Decoding meshes\b\b_n_nord_m_head_06.nif
+Decoding meshes\b\b_n_nord_f_head_07.nif
+Decoding meshes\b\b_n_nord_m_head_07.nif
+Decoding meshes\b\b_n_nord_f_head_06.nif
+Decoding meshes\b\b_n_nord_m_head_08.nif
+Decoding meshes\b\b_n_nord_f_head_08.nif
+Decoding meshes\b\b_n_nord_f_hair_03.nif
+Decoding meshes\b\b_n_nord_f_hair_02.nif
+Decoding meshes\b\b_n_nord_f_hair_01.nif
+Decoding meshes\b\b_n_nord_f_hair_05.nif
+Decoding meshes\b\b_n_nord_f_hair_04.nif
+Decoding meshes\b\b_n_nord_f_forearm.nif
+Decoding meshes\b\b_n_nord_m_forearm.nif
+Decoding meshes\cursor_drop_ground.nif
+Decoding meshes\a\a_moragtong_helm.nif
+Decoding meshes\b\b_n_nord_f_foot.nif
+Decoding meshes\b\b_n_nord_m_ankle.nif
+Decoding meshes\b\b_n_nord_m_foot.nif
+Decoding meshes\b\b_n_nord_f_ankle.nif
+Decoding meshes\b\b_n_nord_m_knee.nif
+Decoding meshes\b\b_n_nord_f_knee.nif
+Decoding meshes\b\b_n_nord_m_skins.nif
+Decoding meshes\b\b_n_nord_m_neck.nif
+Decoding meshes\b\b_n_nord_f_skins.nif
+Decoding meshes\b\b_n_nord_m_groin.nif
+Decoding meshes\b\b_n_nord_f_neck.nif
+Decoding meshes\b\b_n_nord_f_wrist.nif
+Decoding meshes\b\b_n_nord_m_wrist.nif
+Decoding meshes\b\b_n_nord_f_groin.nif
+Decoding meshes\b\b_n_orc_m_wrist.nif
+Decoding meshes\b\b_n_orc_f_groin.nif
+Decoding meshes\b\b_n_orc_m_groin.nif
+Decoding meshes\b\b_n_orc_f_hair03.nif
+Decoding meshes\b\b_n_orc_f_hair02.nif
+Decoding meshes\b\b_n_orc_f_hair01.nif
+Decoding meshes\b\b_n_orc_f_hair05.nif
+Decoding meshes\b\b_n_orc_f_hair04.nif
+Decoding meshes\b\b_n_orc_f_wrist.nif
+Decoding meshes\b\b_n_orc_m_skins.nif
+Decoding meshes\b\b_n_orc_f_skins.nif
+Decoding meshes\b\b_n_orc_f_ankle.nif
+Decoding meshes\b\b_n_orc_m_ankle.nif
+Decoding meshes\a\a_molecrab_helm.nif
+Decoding meshes\b\b_n_orc_f_knee.nif
+Decoding meshes\b\b_n_orc_f_neck.nif
+Decoding meshes\b\b_n_orc_f_foot.nif
+Decoding meshes\b\b_n_orc_m_foot.nif
+Decoding meshes\b\b_n_orc_m_knee.nif
+Decoding meshes\b\b_n_orc_m_neck.nif
+Decoding meshes\chimney_smoke_green.nif
+Decoding meshes\chimney_smoke_small.nif
+Decoding meshes\chimney_smoke02.nif
+Decoding meshes\a\a_orcish_greaves_g.nif
+Decoding meshes\a\a_orcish_greaves_k.nif
+Decoding meshes\a\a_orcish_bracer_w.nif
+Decoding meshes\a\a_orcish_cuirass_c.nif
+Decoding meshes\a\a_orcish_boots_gnd.nif
+Decoding meshes\c\c_m_robe_common_3.nif
+Decoding meshes\c\c_m_robe_common_4.nif
+Decoding meshes\c\c_m_robe_common_5.nif
+Decoding meshes\c\c_m_robe_extrav_1c.nif
+Decoding meshes\c\c_m_robe_extrav_1b.nif
+Decoding meshes\c\c_m_robe_extrav_1a.nif
+Decoding meshes\c\c_m_robe_extrav_1h.nif
+Decoding meshes\c\c_m_robe_extrav_1t.nif
+Decoding meshes\c\c_m_robe_extrav_1r.nif
+Decoding meshes\c\c_m_robe_common_01.nif
+Decoding meshes\c\c_m_robe_common_02.nif
+Decoding meshes\c\c_m_robe_extrav_2.nif
+Decoding meshes\c\c_m_robe_extrav_1.nif
+Decoding meshes\c\c_m_robe_expens_3.nif
+Decoding meshes\m\pick_apprentice_01.nif
+Decoding meshes\m\pick_journeyman_01.nif
+Decoding meshes\m\pick_master_01.nif
+Decoding meshes\a\a_orcish_helmet.nif
+Decoding meshes\a\a_orcish_boots_f.nif
+Decoding meshes\a\a_orcish_boots_a.nif
+Decoding meshes\a\a_newstscale_c_gnd.nif
+Decoding meshes\a\a_netch_m_cuirass2.nif
+Decoding meshes\a\a_netch_m_boot_gnd.nif
+Decoding meshes\a\a_netch_m_greave_g.nif
+Decoding meshes\a\a_netch_m_skinned.nif
+Decoding meshes\a\a_nordicfur_boot_f.nif
+Decoding meshes\a\a_nordicfur_boot_a.nif
+Decoding meshes\a\a_nordiciron_helm.nif
+Decoding meshes\a\a_nordicfur_helmet.nif
+Decoding meshes\a\a_nordiciron_c_gnd.nif
+Decoding meshes\argonian_swimkna.nif
+Decoding meshes\a\a_netch_m_helmet.nif
+Decoding meshes\a\a_netch_m_boot_f.nif
+Decoding meshes\a\a_netch_m_boot_a.nif
+Decoding meshes\a\a_nordiciron_c.nif
+Decoding meshes\a\a_apostle_boots_f.nif
+Decoding meshes\o\flora_saltrice_02.nif
+Decoding meshes\o\flora_saltrice_01.nif
+Decoding meshes\o\flora_fire_fern_01.nif
+Decoding meshes\o\flora_fire_fern_02.nif
+Decoding meshes\o\flora_fire_fern_03.nif
+Decoding meshes\o\flora_chokeweed_01.nif
+Decoding meshes\o\flora_roobrush_01.nif
+Decoding meshes\o\flora_wickwheat_03.nif
+Decoding meshes\o\flora_wickwheat_02.nif
+Decoding meshes\o\flora_wickwheat_01.nif
+Decoding meshes\o\flora_wickwheat_04.nif
+Decoding meshes\o\flora_kreshweed_03.nif
+Decoding meshes\o\flora_kreshweed_02.nif
+Decoding meshes\o\flora_kreshweed_01.nif
+Decoding meshes\o\flora_hackle-lo_01.nif
+Decoding meshes\o\flora_hackle-lo_02.nif
+Decoding meshes\a\a_art_wraithguard.nif
+Decoding meshes\box.nif
+Decoding meshes\r\corprus_stalker.nif
+Decoding meshes\o\flora_ash_yam_02.nif
+Decoding meshes\o\flora_ash_yam_01.nif
+Decoding meshes\r\xgreatbonewalker.nif
+Decoding meshes\r\atronach_fire.nif
+Decoding meshes\r\atronach_storm.nif
+Decoding meshes\r\atronach_frost.nif
+Decoding meshes\a\a_art_dragon_gnd.nif
+Decoding meshes\w\w_wakizashi_iron.nif
+Decoding meshes\w\w_waraxe_daedric.nif
+Decoding meshes\w\w_warhammer_iron.nif
+Decoding meshes\w\w_waraxe_glass.nif
+Decoding meshes\w\w_waraxe_iron.nif
+Decoding meshes\w\w_waraxe_ebony.nif
+Decoding meshes\w\w_waraxe_steel.nif
+Decoding meshes\w\w_wooden_staff.nif
+Decoding meshes\w\w_wakizashi.nif
+Decoding meshes\w\w_warhammer.nif
+Decoding meshes\c\c_belt_exquisite_1.nif
+Decoding meshes\c\c_belt_expensive_1.nif
+Decoding meshes\c\c_belt_expensive_3.nif
+Decoding meshes\c\c_belt_expensive_2.nif
+Decoding meshes\o\contain_drawer_02.nif
+Decoding meshes\o\contain_drawer_03.nif
+Decoding meshes\o\contain_drawer_01.nif
+Decoding meshes\o\contain_de_desk_01.nif
+Decoding meshes\o\contain_barrel_01.nif
+Decoding meshes\o\contain_couldron10.nif
+Decoding meshes\o\contain_corpse20.nif
+Decoding meshes\o\contain_corpse00.nif
+Decoding meshes\o\contain_corpse10.nif
+Decoding meshes\o\contain_chest10.nif
+Decoding meshes\o\contain_barrel10.nif
+Decoding meshes\o\contain_crate_01.nif
+Decoding meshes\o\contain_crate_02.nif
+Decoding meshes\o\contain_chest11.nif
+Decoding meshes\o\contain_sack00.nif
+Decoding meshes\o\contain_urn_04.nif
+Decoding meshes\o\contain_urn_02.nif
+Decoding meshes\o\contain_urn_05.nif
+Decoding meshes\o\contain_urn_01.nif
+Decoding meshes\o\contain_urn_03.nif
+Decoding meshes\o\contain_pot_01.nif
+Decoding meshes\r\xascendedsleeper.nif
+Decoding meshes\r\xashvampire.nif
+Decoding meshes\c\c_belt_common_4.nif
+Decoding meshes\c\c_belt_common_2.nif
+Decoding meshes\c\c_belt_common_5.nif
+Decoding meshes\c\c_belt_common_1.nif
+Decoding meshes\c\c_belt_common_3.nif
+Decoding meshes\c\c_belt_erabin.nif
+Decoding meshes\e\magic_area_conjure.nif
+Decoding meshes\e\magic_cast_poison.nif
+Decoding meshes\e\magic_cast_conjure.nif
+Decoding meshes\e\magic_cast_restore.nif
+Decoding meshes\e\magic_cast_fortify.nif
+Decoding meshes\e\magic_hit_levitate.nif
+Decoding meshes\e\magic_area_poison.nif
+Decoding meshes\e\magic_hit_conjure.nif
+Decoding meshes\a\a_cephalopod_helm.nif
+Decoding meshes\r\xsphere_centurions.nif
+Decoding meshes\c\c_art_ring_mentor.nif
+Decoding meshes\c\c_art_ring_khajiit.nif
+Decoding meshes\c\c_art_ring_warlock.nif
+Decoding meshes\e\lightning_shield.nif
+Decoding meshes\e\lightningbolts.nif
+Decoding meshes\e\lightning_area.nif
+Decoding meshes\c\c_m_shirt_expensive_1_u_gnd.nif
+Decoding meshes\e\magic_area_rest.nif
+Decoding meshes\e\magic_hit_frost.nif
+Decoding meshes\e\magic_area_drain.nif
+Decoding meshes\e\magic_hit_poison.nif
+Decoding meshes\e\magic_area_myst.nif
+Decoding meshes\e\magic_cast_myst.nif
+Decoding meshes\e\magic_area_frost.nif
+Decoding meshes\e\magic_cast_frost.nif
+Decoding meshes\e\magic_hit_alter.nif
+Decoding meshes\e\magic_hit_ill.nif
+Decoding meshes\e\magic_hit_rest.nif
+Decoding meshes\e\magic_hit_dst.nif
+Decoding meshes\e\magic_reflect.nif
+Decoding meshes\e\magic_area_ill.nif
+Decoding meshes\e\magic_area_alt.nif
+Decoding meshes\e\magic_cast_ill.nif
+Decoding meshes\e\magic_cast_alt.nif
+Decoding meshes\e\magic_cast_dst.nif
+Decoding meshes\e\magic_area_dst.nif
+Decoding meshes\e\magic_hit_myst.nif
+Decoding meshes\e\magic_hit_s.nif
+Decoding meshes\e\magic_cast_l.nif
+Decoding meshes\e\magic_cast_s.nif
+Decoding meshes\e\magic_summon.nif
+Decoding meshes\m\probe_master_01.nif
+Decoding meshes\c\c_art_ring_wind.nif
+Decoding meshes\a\a_bonemold_boots_a.nif
+Decoding meshes\a\a_bonemold_boots_f.nif
+Decoding meshes\a\a_bonemold_helmet.nif
+Decoding meshes\w\w_tanto_daedric.nif
+Decoding meshes\w\w_tanto_iron.nif
+Decoding meshes\w\w_shortbow_chitin.nif
+Decoding meshes\w\w_shortsword_ebony.nif
+Decoding meshes\w\w_silver_claymore.nif
+Decoding meshes\c\c_glove_expensive1.nif
+Decoding meshes\c\c_glove_moragtong.nif
+Decoding meshes\w\w_steel_battleaxe.nif
+Decoding meshes\a\a_ebony_greaves_ul.nif
+Decoding meshes\a\a_ebony_greaves_k.nif
+Decoding meshes\a\a_ebony_g_greaves.nif
+Decoding meshes\r\cavemudcrab.nif
+Decoding meshes\w\w_staff_daedric.nif
+Decoding meshes\w\w_spear_daedric.nif
+Decoding meshes\w\w_silver_dagger.nif
+Decoding meshes\w\w_silver_waraxe.nif
+Decoding meshes\w\w_shortbow_steel.nif
+Decoding meshes\a\a_ebony_cuirass.nif
+Decoding meshes\a\a_ebony_bracer_w.nif
+Decoding meshes\a\a_ebony_boot_gnd.nif
+Decoding meshes\c\c_glove_common1.nif
+Decoding meshes\w\w_staff_ebony.nif
+Decoding meshes\w\w_steel_arrow.nif
+Decoding meshes\w\w_steel_star .nif
+Decoding meshes\w\w_steel_knife.nif
+Decoding meshes\w\w_staff_glass.nif
+Decoding meshes\w\w_shortsword00.nif
+Decoding meshes\w\w_silver_staff.nif
+Decoding meshes\w\w_silver_arrow.nif
+Decoding meshes\w\w_silver_star.nif
+Decoding meshes\w\w_silver_spear.nif
+Decoding meshes\w\w_star_ebony.nif
+Decoding meshes\w\w_saber_iron.nif
+Decoding meshes\w\w_spikedclub.nif
+Decoding meshes\w\w_star_glass.nif
+Decoding meshes\w\w_spear_iron.nif
+Decoding meshes\a\a_ebony_helmet.nif
+Decoding meshes\a\a_ebony_boot_a.nif
+Decoding meshes\a\a_ebony_boot_f.nif
+Decoding meshes\a\a_daedric_boots_f.nif
+Decoding meshes\a\a_daedric_boots_a.nif
+Decoding meshes\a\a_dragonscale_helm.nif
+Decoding meshes\a\a_dwemer_greaves_g.nif
+Decoding meshes\a\a_dwemer_boots_gnd.nif
+Decoding meshes\a\a_dwemer_cuirass_c.nif
+Decoding meshes\a\a_dwemer_cuir_gnd.nif
+Decoding meshes\a\a_dwemer_bracer_w.nif
+Decoding meshes\f\xact_banner_khull.nif
+Decoding meshes\r\xkwama worker.nif
+Decoding meshes\r\xkwama warior.nif
+Decoding meshes\r\xkwama forager.nif
+Decoding meshes\r\xkwama queen.nif
+Decoding meshes\i\in_dae_hall_l_stair_curve_01.nif
+Decoding meshes\i\in_dae_hall_l_staircurve_01.nif
+Decoding meshes\r\xdwarvenspecter.nif
+Decoding meshes\f\xact_banner_vos.nif
+Decoding meshes\c\c_m_pants_expensive_1_u_gnd.nif
+Decoding meshes\i\in_t_s_hallshaft_ceilingcap.nif
+Decoding meshes\a\a_dustadept_helm.nif
+Decoding meshes\a\a_dwemer_boots_f.nif
+Decoding meshes\a\a_dwemer_boots_a.nif
+Decoding meshes\a\a_dwemer_helmet.nif
+Decoding meshes\a\a_dreugh_cuirass.nif
+Decoding meshes\a\a_daedric_skins.nif
+Decoding meshes\a\a_daedric_god_h.nif
+Decoding meshes\a\a_dreugh_helm.nif
+Decoding meshes\a\a_glass_bracer_gnd.nif
+Decoding meshes\a\a_glass_greaves_ul.nif
+Decoding meshes\a\a_glass_greaves_g.nif
+Decoding meshes\a\a_glass_greaves_k.nif
+Decoding meshes\a\a_glass_boots_gnd.nif
+Decoding meshes\r\xsteam_centurions.nif
+Decoding meshes\r\netch_betty.nif
+Decoding meshes\r\xatronach_storm.nif
+Decoding meshes\r\xatronach_frost.nif
+Decoding meshes\r\xatronach_fire.nif
+Decoding meshes\a\a_glass_cuirass.nif
+Decoding meshes\a\a_glass_boots_a.nif
+Decoding meshes\a\a_glass_bracer_w.nif
+Decoding meshes\a\a_glass_boots_f.nif
+Decoding meshes\a\a_glass_helmet.nif
+Decoding meshes\a\a_fur_cuirass_gnd.nif
+Decoding meshes\i\xin_akulakhan00.nif
+Decoding meshes\r\xguar_withpack.nif
+Decoding meshes\r\xguar_white.nif
+Decoding meshes\inventory_window.nif
+Decoding meshes\a\a_fur_cuirass.nif
+Decoding meshes\w\w_orcish_battleaxe.nif
+Decoding meshes\w\w_orcish_warhammer.nif
+Decoding meshes\w\w_nordic_claymore.nif
+Decoding meshes\w\w_nordic_battleaxe.nif
+Decoding meshes\w\w_n_claymore.nif
+Decoding meshes\i\test _cavern_i_01.nif
+Decoding meshes\r\sphere_centurions.nif
+Decoding meshes\n\potion_skooma_01.nif
+Decoding meshes\w\w_mace_daedric.nif
+Decoding meshes\w\w_mace_ebony.nif
+Decoding meshes\w\w_miner_pick.nif
+Decoding meshes\w\w_mace_iron.nif
+Decoding meshes\w\w_longbow_daedric.nif
+Decoding meshes\w\w_longsword_silver.nif
+Decoding meshes\w\w_longspear_ebony.nif
+Decoding meshes\w\w_longbow_bonemold.nif
+Decoding meshes\w\w_longsword_ebony.nif
+Decoding meshes\r\xwingedtwilight.nif
+Decoding meshes\r\xsiltstrider.nif
+Decoding meshes\w\w_longbow_ariel.nif
+Decoding meshes\w\w_longbow_steel.nif
+Decoding meshes\r\wingedtwilight.nif
+Decoding meshes\o\misc_sack00.nif
+Decoding meshes\o\misc_chest11.nif
+Decoding meshes\r\xancestorghost.nif
+Decoding meshes\w\w_katana_daedric.nif
+Decoding meshes\w\w_knife_glass.nif
+Decoding meshes\w\w_knife_iron.nif
+Decoding meshes\i\active_port_valen.nif
+Decoding meshes\i\active_port_falen.nif
+Decoding meshes\i\active_port_maran.nif
+Decoding meshes\i\active_port_beran.nif
+Decoding meshes\i\active_port_telas.nif
+Decoding meshes\i\active_port_falag.nif
+Decoding meshes\i\active_port_falas.nif
+Decoding meshes\i\active_dag_port10.nif
+Decoding meshes\i\active_port_andra.nif
+Decoding meshes\i\in_c_stair_thatch_pend_tall_01.nif
+Decoding meshes\i\in_c_stair_thatch_pend_tall_02.nif
+Decoding meshes\e\vfx_pattern06.nif
+Decoding meshes\e\vfx_pattern07.nif
+Decoding meshes\e\vfx_pattern02.nif
+Decoding meshes\e\vfx_pattern04.nif
+Decoding meshes\e\vfx_pattern03.nif
+Decoding meshes\e\vfx_pattern05.nif
+Decoding meshes\e\vfx_pattern08.nif
+Decoding meshes\m\repair_master_01.nif
+Decoding meshes\i\active_port_hlor.nif
+Decoding meshes\i\active_port_indo.nif
+Decoding meshes\i\active_port_roth.nif
+Decoding meshes\r\xcorprus_stalker.nif
+Decoding meshes\r\xbonewalker.nif
+Decoding meshes\xbase_anim_female.nif
+Decoding meshes\xbase_animkna.1st.nif
+Decoding meshes\xbase_animkna.nif
+Decoding meshes\xbase_anim.1st.nif
+Decoding meshes\a\a_m_imperialchain_pauldron_gnd.nif
+Decoding meshes\a\a_m_imperialchain_greaves_gnd.nif
+Decoding meshes\a\a_m_imperialchain_pauldron_ua.nif
+Decoding meshes\a\a_m_imperialchain_greaves_g.nif
+Decoding meshes\a\a_m_imperialchain_greaves_ul.nif
+Decoding meshes\w\w_iron_shortsword.nif
+Decoding meshes\m\misc_foldedcloth00.nif
+Decoding meshes\m\misc_de_tankard_01.nif
+Decoding meshes\m\misc_de_goblet_09.nif
+Decoding meshes\m\misc_de_goblet_08.nif
+Decoding meshes\m\misc_de_goblet_01.nif
+Decoding meshes\m\misc_de_goblet_03.nif
+Decoding meshes\m\misc_de_goblet_02.nif
+Decoding meshes\m\misc_de_goblet_05.nif
+Decoding meshes\m\misc_de_goblet_04.nif
+Decoding meshes\m\misc_de_goblet_07.nif
+Decoding meshes\m\misc_de_goblet_06.nif
+Decoding meshes\m\misc_dwrv_goblet00.nif
+Decoding meshes\m\misc_dwrv_goblet10.nif
+Decoding meshes\m\misc_de_pitcher_01.nif
+Decoding meshes\m\misc_de_basket_01.nif
+Decoding meshes\m\misc_bowl_white_01.nif
+Decoding meshes\m\misc_com_bucket_01.nif
+Decoding meshes\m\misc_com_pillow_01.nif
+Decoding meshes\m\misc_com_basket_02.nif
+Decoding meshes\m\misc_com_basket_01.nif
+Decoding meshes\m\misc_com_bottle_06.nif
+Decoding meshes\m\misc_com_bottle_07.nif
+Decoding meshes\m\misc_com_bottle_14.nif
+Decoding meshes\m\misc_com_bottle_04.nif
+Decoding meshes\m\misc_com_bottle_15.nif
+Decoding meshes\m\misc_com_bottle_05.nif
+Decoding meshes\m\misc_com_bottle_12.nif
+Decoding meshes\m\misc_com_bottle_02.nif
+Decoding meshes\m\misc_com_bottle_13.nif
+Decoding meshes\m\misc_com_bottle_03.nif
+Decoding meshes\m\misc_com_bottle_10.nif
+Decoding meshes\m\misc_com_bottle_11.nif
+Decoding meshes\m\misc_com_bottle_01.nif
+Decoding meshes\m\misc_com_bottle_08.nif
+Decoding meshes\m\misc_com_bottle_09.nif
+Decoding meshes\m\misc_com_wood_fork.nif
+Decoding meshes\m\misc_com_broom_01.nif
+Decoding meshes\m\misc_com_plate_06.nif
+Decoding meshes\m\misc_com_plate_07.nif
+Decoding meshes\m\misc_com_plate_04.nif
+Decoding meshes\m\misc_com_plate_05.nif
+Decoding meshes\m\misc_com_plate_02.nif
+Decoding meshes\m\misc_com_plate_03.nif
+Decoding meshes\m\misc_com_plate_01.nif
+Decoding meshes\m\misc_com_plate_08.nif
+Decoding meshes\m\misc_wheatbundle00.nif
+Decoding meshes\m\misc_uni_pillow_02.nif
+Decoding meshes\m\misc_redware_bowl.nif
+Decoding meshes\m\misc_redware_flask.nif
+Decoding meshes\m\misc_rollingpin_01.nif
+Decoding meshes\m\misc_redware_vase.nif
+Decoding meshes\m\misc_redware_plate.nif
+Decoding meshes\m\misc_redware_lamp.nif
+Decoding meshes\m\misc_soulgem_grand.nif
+Decoding meshes\m\misc_soulgem_petty.nif
+Decoding meshes\m\misc_pot_green_01.nif
+Decoding meshes\m\misc_portal_shard.nif
+Decoding meshes\f\xex_ashl_z_banner.nif
+Decoding meshes\f\xex_ashl_a_banner.nif
+Decoding meshes\f\xex_ashl_u_banner.nif
+Decoding meshes\f\xex_ashl_e_banner.nif
+Decoding meshes\l\furn_de_firepit_f.nif
+Decoding meshes\r\golden saint.nif
+Decoding meshes\m\misc_scrapwood01.nif
+Decoding meshes\m\misc_scrapwood03.nif
+Decoding meshes\m\misc_scrapwood02.nif
+Decoding meshes\m\misc_scrapwood05.nif
+Decoding meshes\m\misc_scrapwood04.nif
+Decoding meshes\m\misc_lw_platter.nif
+Decoding meshes\m\misc_dwrv_bowl00.nif
+Decoding meshes\m\misc_de_drum_02.nif
+Decoding meshes\m\misc_shackles00.nif
+Decoding meshes\m\misc_redware_cup.nif
+Decoding meshes\m\misc_ropecoil00.nif
+Decoding meshes\m\misc_wickwheat00.nif
+Decoding meshes\m\misc_de_bowl_01.nif
+Decoding meshes\m\misc_pot_blue_01.nif
+Decoding meshes\m\misc_pot_blue_02.nif
+Decoding meshes\m\misc_de_lute_01.nif
+Decoding meshes\m\misc_dwrv_coin00.nif
+Decoding meshes\m\misc_de_drum_01.nif
+Decoding meshes\m\misc_6th_goblet.nif
+Decoding meshes\m\misc_dwrv_gear00.nif
+Decoding meshes\m\misc_placemat_01.nif
+Decoding meshes\m\misc_dwrv_mug00.nif
+Decoding meshes\r\siltstrider.nif
+Decoding meshes\m\misc_flask_02.nif
+Decoding meshes\m\misc_hammer10.nif
+Decoding meshes\m\misc_prongs00.nif
+Decoding meshes\m\misc_shears_01.nif
+Decoding meshes\m\misc_beaker_01.nif
+Decoding meshes\m\misc_spool_01.nif
+Decoding meshes\m\misc_flask_01.nif
+Decoding meshes\m\misc_bellows10.nif
+Decoding meshes\m\misc_flask_04.nif
+Decoding meshes\m\misc_lw_flask.nif
+Decoding meshes\m\misc_flask_03.nif
+Decoding meshes\m\misc_lw_cup.nif
+Decoding meshes\m\misc_cloth10.nif
+Decoding meshes\m\misc_lw_bowl.nif
+Decoding meshes\m\misc_inkwell.nif
+Decoding meshes\m\misc_cloth11.nif
+Decoding meshes\m\misc_skull00.nif
+Decoding meshes\m\misc_skull10.nif
+Decoding meshes\r\xclannfear_daddy.nif
+Decoding meshes\r\xcliffracer.nif
+Decoding meshes\steam_bluegreen.nif
+Decoding meshes\steam_lavariver.nif
+Decoding meshes\l\furn_de_chair_02.nif
+Decoding meshes\r\xslaughterfish.nif
+Decoding meshes\w\w_iron_claymore.nif
+Decoding meshes\w\w_iron_longsword.nif
+Decoding meshes\w\w_iron_dagger.nif
+Decoding meshes\w\w_iron_arrow.nif
+Decoding meshes\sky_moon_small.nif
+Decoding meshes\sky_moon_large.nif
+Decoding meshes\l\misc_candle_red_01.nif
+Decoding meshes\r\lame_corprus.nif
+Decoding meshes\w\w_halberd_glass.nif
+Decoding meshes\w\w_halberd_steel.nif
+Decoding meshes\w\w_halberd_iron.nif
+Decoding meshes\c\c_shirt_aralor_fa.nif
+Decoding meshes\c\c_shirt_aralor_ua.nif
+Decoding meshes\c\c_shirt_aralor_gnd.nif
+Decoding meshes\sky_clouds_01_no_tex.nif
+Decoding meshes\w\magic_target_myst.nif
+Decoding meshes\w\magic_target_rest.nif
+Decoding meshes\w\magic_target_frost.nif
+Decoding meshes\c\c_shoes_extrav_2_f.nif
+Decoding meshes\c\c_shoes_extrav_1_f.nif
+Decoding meshes\c\c_shoes_rilms_gnd.nif
+Decoding meshes\e\soultraphit.nif
+Decoding meshes\w\magic_target_ill.nif
+Decoding meshes\w\magic_target_dst.nif
+Decoding meshes\w\magic_target_alt.nif
+Decoding meshes\w\magic_target.nif
+Decoding meshes\c\c_shoes_common_3.nif
+Decoding meshes\c\c_shoes_common_4.nif
+Decoding meshes\c\c_shoes_common_5.nif
+Decoding meshes\c\c_shirt_aralor_c.nif
+Decoding meshes\c\c_shirt_aralor_w.nif
+Decoding meshes\c\c_skirt_common_5.nif
+Decoding meshes\c\c_skirt_common_2.nif
+Decoding meshes\c\c_skirt_common_3.nif
+Decoding meshes\w\w_glass_arrow.nif
+Decoding meshes\sky_clouds_01.nif
+Decoding meshes\c\c_slave_bracer.nif
+Decoding meshes\c\c_shoes_rilms.nif
+Decoding meshes\r\g_centurionspider.nif
+Decoding meshes\c\c_ring_exquisite_1.nif
+Decoding meshes\c\c_ring_expensive_1.nif
+Decoding meshes\c\c_ring_expensive_3.nif
+Decoding meshes\c\c_ring_expensive_2.nif
+Decoding meshes\r\ancestorghost.nif
+Decoding meshes\r\ascendedsleeper.nif
+Decoding meshes\r\xscamp_fetch.nif
+Decoding meshes\c\c_ring_common05.nif
+Decoding meshes\c\c_ring_common01.nif
+Decoding meshes\c\c_ring_common03.nif
+Decoding meshes\c\c_ring_moonnstar.nif
+Decoding meshes\c\c_ring_common04.nif
+Decoding meshes\c\c_ring_common02.nif
+Decoding meshes\c\c_ring_namira.nif
+Decoding meshes\a\a_silver_duke_gnd.nif
+Decoding meshes\a\a_silver_cuir_gnd.nif
+Decoding meshes\a\a_steel_greaves_ul.nif
+Decoding meshes\a\a_steel_greaves_g.nif
+Decoding meshes\a\a_steel_greaves_k.nif
+Decoding meshes\a\a_steel_hands.1st.nif
+Decoding meshes\a\a_steel_boots_gnd.nif
+Decoding meshes\a\a_shield_imperial.nif
+Decoding meshes\f\active_de_bar_door.nif
+Decoding meshes\f\active_akhul_steam.nif
+Decoding meshes\f\active_signpost_01.nif
+Decoding meshes\f\active_signpost_02.nif
+Decoding meshes\f\active_de_bedroll.nif
+Decoding meshes\f\act_banner_hla_oad.nif
+Decoding meshes\f\act_banner_tel_fyr.nif
+Decoding meshes\f\act_banner_tel_vos.nif
+Decoding meshes\f\active_de_bed_19.nif
+Decoding meshes\f\active_de_bed_18.nif
+Decoding meshes\f\active_de_bed_13.nif
+Decoding meshes\f\active_de_bed_12.nif
+Decoding meshes\f\active_de_bed_11.nif
+Decoding meshes\f\active_de_bed_10.nif
+Decoding meshes\f\active_de_bed_17.nif
+Decoding meshes\f\active_de_bed_16.nif
+Decoding meshes\f\active_de_bed_15.nif
+Decoding meshes\f\active_de_bed_14.nif
+Decoding meshes\f\active_de_bed_09.nif
+Decoding meshes\f\active_de_bed_08.nif
+Decoding meshes\f\active_de_bed_03.nif
+Decoding meshes\f\active_de_bed_02.nif
+Decoding meshes\f\active_de_bed_01.nif
+Decoding meshes\f\active_de_bed_07.nif
+Decoding meshes\f\active_de_bed_06.nif
+Decoding meshes\f\active_de_bed_05.nif
+Decoding meshes\f\active_de_bed_04.nif
+Decoding meshes\f\active_de_bed_30.nif
+Decoding meshes\f\active_de_bed_29.nif
+Decoding meshes\f\active_de_bed_28.nif
+Decoding meshes\f\active_de_bed_23.nif
+Decoding meshes\f\active_de_bed_22.nif
+Decoding meshes\f\active_de_bed_21.nif
+Decoding meshes\f\active_de_bed_20.nif
+Decoding meshes\f\active_de_bed_27.nif
+Decoding meshes\f\active_de_bed_26.nif
+Decoding meshes\f\active_de_bed_25.nif
+Decoding meshes\f\active_de_bed_24.nif
+Decoding meshes\f\active_bubbles00.nif
+Decoding meshes\f\active_button_01.nif
+Decoding meshes\f\act_banner_khull.nif
+Decoding meshes\f\act_banner_vos.nif
+Decoding meshes\f\active_gong_01.nif
+Decoding meshes\a\a_silver_cuirass.nif
+Decoding meshes\w\w_ebony_arrow.nif
+Decoding meshes\vfx_defaultcast.nif
+Decoding meshes\vfx_defaultarea.nif
+Decoding meshes\vfx_defaulthit.nif
+Decoding meshes\sky_atmosphere.nif
+Decoding meshes\a\a_steel_helmet.nif
+Decoding meshes\a\a_steel_boot_f.nif
+Decoding meshes\a\a_steel_boot_a.nif
+Decoding meshes\a\a_steel_skin.nif
+Decoding meshes\f\xfurn_imp_flag_01.nif
+Decoding meshes\a\a_ringmail_cuirass.nif
+Decoding meshes\w\w_dwemer_warhammer.nif
+Decoding meshes\w\w_dwemer_claymore.nif
+Decoding meshes\w\w_dwemer_battleaxe.nif
+Decoding meshes\w\w_dwemer_longspear.nif
+Decoding meshes\a\a_redoranmaster_h.nif
+Decoding meshes\f\sound_dummy00.nif
+Decoding meshes\r\clannfear_daddy.nif
+Decoding meshes\r\slaughterfish.nif
+Decoding meshes\r\xlame_corprus.nif
+Decoding meshes\r\heart_akulakhan.nif
+Decoding meshes\r\leastkagouti.nif
+Decoding meshes\r\scamp_fetch.nif
+Decoding meshes\r\xcavemudcrab.nif
+Decoding meshes\r\dwarvenspecter.nif
+Decoding meshes\r\kwama forager.nif
+Decoding meshes\r\kwama worker.nif
+Decoding meshes\r\kwama queen.nif
+Decoding meshes\r\kwama warior.nif
+Decoding meshes\r\guar_withpack.nif
+Decoding meshes\w\w_dwemer_waraxe.nif
+Decoding meshes\w\w_dwemer_halberd.nif
+Decoding meshes\w\w_daedric_arrow.nif
+Decoding meshes\w\w_dagger_dragon.nif
+Decoding meshes\w\w_dagger_daedric.nif
+Decoding meshes\w\w_dagger_chitin.nif
+Decoding meshes\w\w_dwemer_spear.nif
+Decoding meshes\w\w_dwemer_mace.nif
+Decoding meshes\w\w_dreugh_staff.nif
+Decoding meshes\w\w_dreugh_club.nif
+Decoding meshes\w\w_dagger_glass.nif
+Decoding meshes\w\w_dart_silver.nif
+Decoding meshes\w\w_dart_daedric.nif
+Decoding meshes\w\w_dart_ebony.nif
+Decoding meshes\w\w_dart_steel.nif
+Decoding meshes\w\w_daikatana.nif
+Decoding meshes\b\b_v_orc_m_head_01.nif
+Decoding meshes\b\b_v_orc_f_head_01.nif
+Decoding meshes\w\w_crossbow_dwemer.nif
+Decoding meshes\w\w_claymore_crystal.nif
+Decoding meshes\w\w_claymore_daedric.nif
+Decoding meshes\b\b_v_nord_f_head_01.nif
+Decoding meshes\b\b_v_nord_m_head_01.nif
+Decoding meshes\f\terrain_bc_scum_01.nif
+Decoding meshes\f\terrain_bc_scum_03.nif
+Decoding meshes\f\terrain_bc_scum_02.nif
+Decoding meshes\f\terrain_rock_ac_08.nif
+Decoding meshes\f\terrain_rock_bc_08.nif
+Decoding meshes\f\terrain_rock_bc_18.nif
+Decoding meshes\f\terrain_rock_ac_09.nif
+Decoding meshes\f\terrain_rock_bc_09.nif
+Decoding meshes\f\terrain_rock_ac_02.nif
+Decoding meshes\f\terrain_rock_ac_12.nif
+Decoding meshes\f\terrain_rock_bc_02.nif
+Decoding meshes\f\terrain_rock_bc_12.nif
+Decoding meshes\f\terrain_rock_ac_03.nif
+Decoding meshes\f\terrain_rock_bc_03.nif
+Decoding meshes\f\terrain_rock_bc_13.nif
+Decoding meshes\f\terrain_rock_ac_10.nif
+Decoding meshes\f\terrain_rock_bc_10.nif
+Decoding meshes\f\terrain_rock_ac_01.nif
+Decoding meshes\f\terrain_rock_ac_11.nif
+Decoding meshes\f\terrain_rock_bc_01.nif
+Decoding meshes\f\terrain_rock_bc_11.nif
+Decoding meshes\f\terrain_rock_ac_06.nif
+Decoding meshes\f\terrain_rock_bc_06.nif
+Decoding meshes\f\terrain_rock_bc_16.nif
+Decoding meshes\f\terrain_rock_ac_07.nif
+Decoding meshes\f\terrain_rock_bc_07.nif
+Decoding meshes\f\terrain_rock_bc_17.nif
+Decoding meshes\f\terrain_rock_ac_04.nif
+Decoding meshes\f\terrain_rock_bc_04.nif
+Decoding meshes\f\terrain_rock_bc_14.nif
+Decoding meshes\f\terrain_rock_ac_05.nif
+Decoding meshes\f\terrain_rock_bc_05.nif
+Decoding meshes\f\terrain_rock_bc_15.nif
+Decoding meshes\f\terrain_rock_ai_09.nif
+Decoding meshes\f\terrain_rock_ai_08.nif
+Decoding meshes\f\terrain_rock_ai_01.nif
+Decoding meshes\f\terrain_rock_ai_11.nif
+Decoding meshes\f\terrain_rock_ai_10.nif
+Decoding meshes\f\terrain_rock_ai_03.nif
+Decoding meshes\f\terrain_rock_ai_02.nif
+Decoding meshes\f\terrain_rock_ai_12.nif
+Decoding meshes\f\terrain_rock_ai_05.nif
+Decoding meshes\f\terrain_rock_ai_04.nif
+Decoding meshes\f\terrain_rock_ai_07.nif
+Decoding meshes\f\terrain_rock_ai_06.nif
+Decoding meshes\f\terrain_rock_wg_09.nif
+Decoding meshes\f\terrain_rock_wg_18.nif
+Decoding meshes\f\terrain_rock_wg_08.nif
+Decoding meshes\f\terrain_rock_wg_15.nif
+Decoding meshes\f\terrain_rock_wg_05.nif
+Decoding meshes\f\terrain_rock_wg_14.nif
+Decoding meshes\f\terrain_rock_wg_04.nif
+Decoding meshes\f\terrain_rock_wg_17.nif
+Decoding meshes\f\terrain_rock_wg_07.nif
+Decoding meshes\f\terrain_rock_wg_16.nif
+Decoding meshes\f\terrain_rock_wg_06.nif
+Decoding meshes\f\terrain_rock_wg_11.nif
+Decoding meshes\f\terrain_rock_wg_01.nif
+Decoding meshes\f\terrain_rock_wg_10.nif
+Decoding meshes\f\terrain_rock_wg_13.nif
+Decoding meshes\f\terrain_rock_wg_03.nif
+Decoding meshes\f\terrain_rock_wg_12.nif
+Decoding meshes\f\terrain_rock_wg_02.nif
+Decoding meshes\f\terrain_rock_ma_01.nif
+Decoding meshes\f\terrain_boulder_02.nif
+Decoding meshes\f\terrain_boulder_03.nif
+Decoding meshes\f\terrain_boulder_01.nif
+Decoding meshes\f\terrain_boulder_04.nif
+Decoding meshes\f\terrain_boulder_05.nif
+Decoding meshes\f\terrain_rock_gl_03.nif
+Decoding meshes\f\terrain_rock_gl_02.nif
+Decoding meshes\f\terrain_rock_gl_12.nif
+Decoding meshes\f\terrain_rock_gl_01.nif
+Decoding meshes\f\terrain_rock_gl_11.nif
+Decoding meshes\f\terrain_rock_gl_10.nif
+Decoding meshes\f\terrain_rock_gl_07.nif
+Decoding meshes\f\terrain_rock_gl_06.nif
+Decoding meshes\f\terrain_rock_gl_05.nif
+Decoding meshes\f\terrain_rock_gl_04.nif
+Decoding meshes\f\terrain_rock_gl_09.nif
+Decoding meshes\f\terrain_rock_gl_08.nif
+Decoding meshes\f\terrain_rock_rm_09.nif
+Decoding meshes\f\terrain_rock_rm_19.nif
+Decoding meshes\f\terrain_rock_rm_08.nif
+Decoding meshes\f\terrain_rock_rm_18.nif
+Decoding meshes\f\terrain_rock_rm_07.nif
+Decoding meshes\f\terrain_rock_rm_17.nif
+Decoding meshes\f\terrain_rock_rm_06.nif
+Decoding meshes\f\terrain_rock_rm_16.nif
+Decoding meshes\f\terrain_rock_rm_05.nif
+Decoding meshes\f\terrain_rock_rm_15.nif
+Decoding meshes\f\terrain_rock_rm_04.nif
+Decoding meshes\f\terrain_rock_rm_14.nif
+Decoding meshes\f\terrain_rock_rm_24.nif
+Decoding meshes\f\terrain_rock_rm_03.nif
+Decoding meshes\f\terrain_rock_rm_13.nif
+Decoding meshes\f\terrain_rock_rm_23.nif
+Decoding meshes\f\terrain_rock_rm_02.nif
+Decoding meshes\f\terrain_rock_rm_12.nif
+Decoding meshes\f\terrain_rock_rm_22.nif
+Decoding meshes\f\terrain_rock_rm_01.nif
+Decoding meshes\f\terrain_rock_rm_11.nif
+Decoding meshes\f\terrain_rock_rm_21.nif
+Decoding meshes\f\terrain_rock_rm_10.nif
+Decoding meshes\f\terrain_rock_rm_20.nif
+Decoding meshes\f\furn_roped_pole_01.nif
+Decoding meshes\f\furn_rail_broke00.nif
+Decoding meshes\f\furn_rail_elbow_00.nif
+Decoding meshes\f\furn_rail_slope_00.nif
+Decoding meshes\f\furn_stickbundle00.nif
+Decoding meshes\f\furn_smokestack00.nif
+Decoding meshes\f\furn_pathspear_03.nif
+Decoding meshes\f\furn_pathspear_02.nif
+Decoding meshes\f\furn_pathspear_01.nif
+Decoding meshes\f\furn_pathspear_04.nif
+Decoding meshes\f\furn_pycave_pool00.nif
+Decoding meshes\f\furn_wallscreen_01.nif
+Decoding meshes\f\furn_wallscreen_02.nif
+Decoding meshes\f\furn_triolith_01a.nif
+Decoding meshes\f\furn_halfbarrel01.nif
+Decoding meshes\f\furn_halfbarrel00.nif
+Decoding meshes\f\furn_imp_altar_01.nif
+Decoding meshes\f\furn_imp_metalring.nif
+Decoding meshes\f\dwrv_mechlfrarm00.nif
+Decoding meshes\f\dwrv_mechrthigh00.nif
+Decoding meshes\f\dwrv_mechlthigh00.nif
+Decoding meshes\f\furn_bone_stake00.nif
+Decoding meshes\f\furn_bone_skull_01.nif
+Decoding meshes\f\furn_bannerpost_02.nif
+Decoding meshes\f\furn_bannerpost_01.nif
+Decoding meshes\f\furn_c_t_shadow_01.nif
+Decoding meshes\f\furn_c_t_theif_01.nif
+Decoding meshes\f\furn_com_bar_door.nif
+Decoding meshes\f\furn_c_t_tower_01.nif
+Decoding meshes\f\furn_c_t_arkay_01.nif
+Decoding meshes\f\furn_com_kegstand.nif
+Decoding meshes\f\furn_clothbolt_02.nif
+Decoding meshes\f\furn_clothbolt_03.nif
+Decoding meshes\f\furn_clothbolt_01.nif
+Decoding meshes\f\furn_com_table_01.nif
+Decoding meshes\f\furn_com_table_03.nif
+Decoding meshes\f\furn_com_table_02.nif
+Decoding meshes\f\furn_com_table_05.nif
+Decoding meshes\f\furn_com_table_04.nif
+Decoding meshes\f\furn_com_stool_01.nif
+Decoding meshes\f\furn_com_stool_02.nif
+Decoding meshes\f\furn_com_barstool.nif
+Decoding meshes\f\furn_c_t_ritual_01.nif
+Decoding meshes\f\furn_c_t_wizard_01.nif
+Decoding meshes\f\furn_c_t_lover_01.nif
+Decoding meshes\f\furn_crate_lid_01.nif
+Decoding meshes\f\furn_com_chair_01.nif
+Decoding meshes\f\furn_com_chair_03.nif
+Decoding meshes\f\furn_com_chair_02.nif
+Decoding meshes\f\furn_com_winerack.nif
+Decoding meshes\f\furn_c_t_steed_01.nif
+Decoding meshes\f\furn_c_t_golem_01.nif
+Decoding meshes\f\furn_com_shelf_04.nif
+Decoding meshes\f\furn_com_shelf_03.nif
+Decoding meshes\f\furn_com_shelf_02.nif
+Decoding meshes\f\furn_com_shelf_01.nif
+Decoding meshes\f\furn_crate_open_04.nif
+Decoding meshes\f\furn_crate_open_05.nif
+Decoding meshes\f\furn_crate_open_01.nif
+Decoding meshes\f\furn_com_bench_02.nif
+Decoding meshes\f\furn_com_bench_01.nif
+Decoding meshes\f\furn_ashl_bugbowl.nif
+Decoding meshes\f\furn_ashl_chime_03.nif
+Decoding meshes\f\furn_ashl_chime_02.nif
+Decoding meshes\f\furn_ashl_chime_01.nif
+Decoding meshes\f\furn_ashl_chime_07.nif
+Decoding meshes\f\furn_ashl_chime_06.nif
+Decoding meshes\f\furn_ashl_chime_05.nif
+Decoding meshes\f\furn_ashl_chime_04.nif
+Decoding meshes\f\furn_ashl_chime_08.nif
+Decoding meshes\f\furn_dwrv_bench10.nif
+Decoding meshes\f\furn_dwrv_bench00.nif
+Decoding meshes\f\furn_dae_rubble_07.nif
+Decoding meshes\f\furn_dae_rubble_06.nif
+Decoding meshes\f\furn_dae_rubble_05.nif
+Decoding meshes\f\furn_de_ashl_post.nif
+Decoding meshes\f\furn_de_shack_post.nif
+Decoding meshes\f\furn_dwrv_steam_00.nif
+Decoding meshes\f\furn_dwrv_dynamo00.nif
+Decoding meshes\f\furn_de_bellows_01.nif
+Decoding meshes\f\furn_dwrv_table10.nif
+Decoding meshes\f\furn_dwrv_table00.nif
+Decoding meshes\f\furn_de_firepit_01.nif
+Decoding meshes\f\furn_dwrv_bucket00.nif
+Decoding meshes\f\furn_dwrv_table20.nif
+Decoding meshes\f\furn_de_shack_hook.nif
+Decoding meshes\f\furn_dwrv_chair00.nif
+Decoding meshes\f\furn_dwrv_stove00.nif
+Decoding meshes\f\furn_dwrv_stove10.nif
+Decoding meshes\f\furn_dwrv_tranny01.nif
+Decoding meshes\f\furn_dwrv_tranny00.nif
+Decoding meshes\f\furn_dwrv_stool00.nif
+Decoding meshes\f\furn_dwrv_stool10.nif
+Decoding meshes\x\collision01.nif
+Decoding meshes\m\key_standard_01.nif
+Decoding meshes\m\key_temple_01.nif
+Decoding meshes\f\dwrv_mechrarm00.nif
+Decoding meshes\f\dwrv_mechhead00.nif
+Decoding meshes\f\dwrv_mechlhand00.nif
+Decoding meshes\f\dwrv_mechlfoot00.nif
+Decoding meshes\f\dwrv_mechrfoot00.nif
+Decoding meshes\f\dwrv_mechrfarm00.nif
+Decoding meshes\f\dwrv_mechhips00.nif
+Decoding meshes\f\dwrv_mechrhand00.nif
+Decoding meshes\f\dwrv_mechtorso00.nif
+Decoding meshes\f\dwrv_mechlarm00.nif
+Decoding meshes\f\furn_c_t_lord_01.nif
+Decoding meshes\f\furn_de_winerack.nif
+Decoding meshes\f\furn_de_shelf_02.nif
+Decoding meshes\f\furn_de_shelf_01.nif
+Decoding meshes\f\furn_overhang_09.nif
+Decoding meshes\f\furn_overhang_01.nif
+Decoding meshes\f\furn_overhang_03.nif
+Decoding meshes\f\furn_overhang_02.nif
+Decoding meshes\f\furn_overhang_05.nif
+Decoding meshes\f\furn_overhang_04.nif
+Decoding meshes\f\furn_overhang_07.nif
+Decoding meshes\f\furn_overhang_06.nif
+Decoding meshes\f\furn_spinwheel00.nif
+Decoding meshes\f\furn_overhang_18.nif
+Decoding meshes\f\furn_c_t_mara_01.nif
+Decoding meshes\f\furn_6th_banner.nif
+Decoding meshes\f\furn_de_chair_01.nif
+Decoding meshes\f\furn_de_chair_02.nif
+Decoding meshes\f\furn_de_chair_03.nif
+Decoding meshes\f\furn_de_forge_01.nif
+Decoding meshes\f\furn_pottedplant.nif
+Decoding meshes\f\furn_triolith_01.nif
+Decoding meshes\f\furn_coalpile00.nif
+Decoding meshes\f\furn_de_table_07.nif
+Decoding meshes\f\furn_de_table_06.nif
+Decoding meshes\f\furn_de_table_05.nif
+Decoding meshes\f\furn_de_table_04.nif
+Decoding meshes\f\furn_de_table_03.nif
+Decoding meshes\f\furn_de_table_02.nif
+Decoding meshes\f\furn_de_table_01.nif
+Decoding meshes\f\furn_de_table_09.nif
+Decoding meshes\f\furn_de_table_08.nif
+Decoding meshes\f\furn_cistern_01.nif
+Decoding meshes\f\furn_com_bar_02.nif
+Decoding meshes\f\furn_com_bar_04.nif
+Decoding meshes\f\furn_com_bar_06.nif
+Decoding meshes\f\furn_tapestry10.nif
+Decoding meshes\f\furn_rug_big_08.nif
+Decoding meshes\f\furn_rug_big_02.nif
+Decoding meshes\f\furn_rug_big_04.nif
+Decoding meshes\f\furn_rug_big_06.nif
+Decoding meshes\f\furn_cot_rug_02.nif
+Decoding meshes\f\furn_de_bench_03.nif
+Decoding meshes\f\furn_de_bench_02.nif
+Decoding meshes\f\furn_de_bench_01.nif
+Decoding meshes\f\furn_de_bench_04.nif
+Decoding meshes\f\furn_com_bed_02.nif
+Decoding meshes\f\furn_com_bed_04.nif
+Decoding meshes\f\furn_com_bed_06.nif
+Decoding meshes\f\furn_de_loom_01.nif
+Decoding meshes\f\furn_planter_01.nif
+Decoding meshes\f\furn_planter_03.nif
+Decoding meshes\f\furn_de_rope_05.nif
+Decoding meshes\f\furn_de_rope_07.nif
+Decoding meshes\f\furn_de_rope_03.nif
+Decoding meshes\f\furn_c_t_lady_01.nif
+Decoding meshes\f\furn_dwrv_bed00.nif
+Decoding meshes\f\furn_rail_end00.nif
+Decoding meshes\f\furn_woodbar_01.nif
+Decoding meshes\f\furn_de_kegstand.nif
+Decoding meshes\f\furn_woodpost_01.nif
+Decoding meshes\f\furn_woodpole_01.nif
+Decoding meshes\f\furn_tapestry00.nif
+Decoding meshes\f\furn_tapestry20.nif
+Decoding meshes\f\furn_de_stool_01.nif
+Decoding meshes\f\furn_de_stool_02.nif
+Decoding meshes\f\furn_de_firepit.nif
+Decoding meshes\f\furn_bone_rib_01.nif
+Decoding meshes\f\furn_bed_rug_01.nif
+Decoding meshes\f\furn_imp_flag_01.nif
+Decoding meshes\f\furn_com_bunk_02.nif
+Decoding meshes\f\furn_com_bunk_01.nif
+Decoding meshes\f\furn_de_lecturn.nif
+Decoding meshes\f\furn_guarcart00.nif
+Decoding meshes\f\furn_com_planter.nif
+Decoding meshes\f\furn_fireplace10.nif
+Decoding meshes\f\furn_signbase_02.nif
+Decoding meshes\f\furn_com_bar_01.nif
+Decoding meshes\f\furn_com_bar_03.nif
+Decoding meshes\f\furn_com_bar_05.nif
+Decoding meshes\f\furn_tapestry30.nif
+Decoding meshes\f\furn_dwrv_well00.nif
+Decoding meshes\f\furn_rug_big_09.nif
+Decoding meshes\f\furn_rug_big_01.nif
+Decoding meshes\f\furn_rug_big_03.nif
+Decoding meshes\f\furn_rug_big_05.nif
+Decoding meshes\f\furn_rug_big_07.nif
+Decoding meshes\f\furn_cot_rug_01.nif
+Decoding meshes\f\furn_cot_rug_03.nif
+Decoding meshes\f\furn_com_bed_01.nif
+Decoding meshes\f\furn_com_bed_03.nif
+Decoding meshes\f\furn_com_bed_05.nif
+Decoding meshes\f\furn_com_bed_07.nif
+Decoding meshes\f\furn_planter_02.nif
+Decoding meshes\f\furn_planter_04.nif
+Decoding meshes\f\furn_de_rope_04.nif
+Decoding meshes\f\furn_de_rope_06.nif
+Decoding meshes\f\furn_netramp_01.nif
+Decoding meshes\f\furn_rope1_01.nif
+Decoding meshes\f\furn_rope2_01.nif
+Decoding meshes\f\furn_winekeg00.nif
+Decoding meshes\f\furn_bedmat_01.nif
+Decoding meshes\f\furn_basin_01.nif
+Decoding meshes\f\furn_basket_01.nif
+Decoding meshes\f\furn_logpile10.nif
+Decoding meshes\f\furn_6th_bell2.nif
+Decoding meshes\f\furn_6th_bell4.nif
+Decoding meshes\f\furn_6th_bell6.nif
+Decoding meshes\f\furn_de_bar_02.nif
+Decoding meshes\f\furn_de_bar_04.nif
+Decoding meshes\f\furn_de_bar_06.nif
+Decoding meshes\f\furn_stool_01.nif
+Decoding meshes\f\furn_com_de_01.nif
+Decoding meshes\f\furn_ashpit_02.nif
+Decoding meshes\f\furn_skull_01.nif
+Decoding meshes\f\furn_grill_01.nif
+Decoding meshes\f\furn_table_01.nif
+Decoding meshes\f\furn_chair_02.nif
+Decoding meshes\f\furn_pillow_01.nif
+Decoding meshes\f\furn_sconce_01.nif
+Decoding meshes\f\furn_banner_01.nif
+Decoding meshes\f\furn_cabinet10.nif
+Decoding meshes\f\furn_firepit00.nif
+Decoding meshes\f\furn_burial10.nif
+Decoding meshes\f\furn_burial00.nif
+Decoding meshes\f\furn_burial20.nif
+Decoding meshes\f\furn_6th_bells.nif
+Decoding meshes\f\furn_6th_bell1.nif
+Decoding meshes\f\furn_6th_bell3.nif
+Decoding meshes\f\furn_6th_bell5.nif
+Decoding meshes\f\furn_de_bar_01.nif
+Decoding meshes\f\furn_de_bar_03.nif
+Decoding meshes\f\furn_de_bar_05.nif
+Decoding meshes\f\furn_ashpit_01.nif
+Decoding meshes\f\furn_bucket10.nif
+Decoding meshes\f\furn_shell10.nif
+Decoding meshes\f\furn_shell00.nif
+Decoding meshes\f\furn_shell20.nif
+Decoding meshes\f\furn_mist512.nif
+Decoding meshes\f\furn_rug_04.nif
+Decoding meshes\f\furn_rug_01.nif
+Decoding meshes\f\furn_rug_03.nif
+Decoding meshes\f\furn_rug_02.nif
+Decoding meshes\f\furn_torch00.nif
+Decoding meshes\f\furn_bone_01.nif
+Decoding meshes\f\furn_mist256.nif
+Decoding meshes\f\furn_hook_01.nif
+Decoding meshes\f\furn_log_04.nif
+Decoding meshes\f\furn_log_01.nif
+Decoding meshes\f\furn_log_03.nif
+Decoding meshes\f\furn_log_02.nif
+Decoding meshes\f\furn_table10.nif
+Decoding meshes\f\furn_cart00.nif
+Decoding meshes\f\furn_anvil00.nif
+Decoding meshes\f\furn_well00.nif
+Decoding meshes\f\furn_tray_01.nif
+Decoding meshes\f\furn_6th_troth_01.nif
+Decoding meshes\f\furn_6th_troth_02.nif
+Decoding meshes\f\furn_6th_ashaltar.nif
+Decoding meshes\f\furn_6th_ashstatue.nif
+Decoding meshes\f\furn_6th_ashpillar.nif
+Decoding meshes\f\furn_6th_platform.nif
+Decoding meshes\w\w_crossbow_steel.nif
+Decoding meshes\w\w_corkbulb_arrow.nif
+Decoding meshes\w\w_club_daedric.nif
+Decoding meshes\w\w_chitin_arrow.nif
+Decoding meshes\w\w_chitin_star.nif
+Decoding meshes\w\w_chitin_club.nif
+Decoding meshes\w\w_chitin_spear.nif
+Decoding meshes\w\w_chitin_axe.nif
+Decoding meshes\w\w_club_iron.nif
+Decoding meshes\a\a_tenpaceboot_gnd.nif
+Decoding meshes\a\a_templar_m_f_boot.nif
+Decoding meshes\a\a_templar_m_a_boot.nif
+Decoding meshes\a\a_templar_m_helmet.nif
+Decoding meshes\a\a_templar_w_bracer.nif
+Decoding meshes\a\a_templar_m_skins.nif
+Decoding meshes\w\w_broadsword_iron.nif
+Decoding meshes\w\w_broadsword_ebony.nif
+Decoding meshes\m\text_folio_open_04.nif
+Decoding meshes\m\text_folio_open_01.nif
+Decoding meshes\m\text_folio_open_02.nif
+Decoding meshes\m\text_folio_open_03.nif
+Decoding meshes\m\text_paper_roll_01.nif
+Decoding meshes\m\text_parchment_02.nif
+Decoding meshes\m\text_parchment_01.nif
+Decoding meshes\xanim_dancinggirl.nif
+Decoding meshes\m\text_octavo_05.nif
+Decoding meshes\m\text_octavo_07.nif
+Decoding meshes\m\text_octavo_01.nif
+Decoding meshes\m\text_octavo_03.nif
+Decoding meshes\m\text_quarto_03.nif
+Decoding meshes\m\text_quarto_01.nif
+Decoding meshes\m\text_folio_01.nif
+Decoding meshes\m\text_scroll_02.nif
+Decoding meshes\m\text_folio_04.nif
+Decoding meshes\m\text_folio_03.nif
+Decoding meshes\m\text_folio_02.nif
+Decoding meshes\m\text_octavo_04.nif
+Decoding meshes\m\text_octavo_06.nif
+Decoding meshes\m\text_octavo_02.nif
+Decoding meshes\m\text_octavo_08.nif
+Decoding meshes\m\text_quarto_02.nif
+Decoding meshes\m\text_quarto_04.nif
+Decoding meshes\m\text_scroll_01.nif
+Decoding meshes\m\text_scroll_03.nif
+Decoding meshes\m\text_note_02.nif
+Decoding meshes\m\text_note_01.nif
+Decoding meshes\w\w_bolt_bonemold.nif
+Decoding meshes\w\w_bolt_corkbulb.nif
+Decoding meshes\w\w_bonemold_arrow.nif
+Decoding meshes\w\w_battleaxe_iron.nif
+Decoding meshes\a\a_trollbone_cuir.nif
+Decoding meshes\a\a_trollbone_helm.nif
+Decoding meshes\w\w_bolt_silver.nif
+Decoding meshes\w\w_bolt_orcish.nif
+Decoding meshes\w\w_bolt_steel.nif
+Decoding meshes\w\w_bolt_iron.nif
+Decoding meshes\a\a_tenpaceboot.nif
+Decoding meshes\w\w_art_queenofbats.nif
+Decoding meshes\w\w_art_dagger_fang.nif
+Decoding meshes\w\w_art_spear_mercy.nif
+Decoding meshes\w\w_art_staff_magnus.nif
+Decoding meshes\w\w_art_mace_scourge.nif
+Decoding meshes\w\shadowbluntonehand.nif
+Decoding meshes\w\shadowblunttwowide.nif
+Decoding meshes\w\shadow_towershield.nif
+Decoding meshes\w\shadowspeartwowide.nif
+Decoding meshes\w\shadowmarksmanbow.nif
+Decoding meshes\w\shadowaxetwoclose.nif
+Decoding meshes\a\towershield_steel.nif
+Decoding meshes\a\towershield_orcish.nif
+Decoding meshes\a\towershield_ebony.nif
+Decoding meshes\a\towershield_hlaluu.nif
+Decoding meshes\a\towershield_glass.nif
+Decoding meshes\a\towershield_chitin.nif
+Decoding meshes\c\amulet_expensive_1.nif
+Decoding meshes\c\amulet_expensive_3.nif
+Decoding meshes\c\amulet_expensive_2.nif
+Decoding meshes\c\amulet_exquisit_1.nif
+Decoding meshes\a\towershield_iron.nif
+Decoding meshes\c\amulet_common_4.nif
+Decoding meshes\c\amulet_common_2.nif
+Decoding meshes\c\amulet_madstone.nif
+Decoding meshes\c\amulet_common_5.nif
+Decoding meshes\c\amulet_common_1.nif
+Decoding meshes\c\amulet_common_3.nif
+Decoding meshes\c\amulet_usheeja.nif
+Decoding meshes\w\shadowaxeonehand.nif
+Decoding meshes\w\shadow_shield.nif
+Decoding meshes\w\shadowshield.nif
+Decoding meshes\a\a_watchmanshelm.nif
+Decoding meshes\w\w_art_azurastar .nif
+Decoding meshes\w\w_art_azurastar.nif
+Decoding meshes\w\w_art_volendrung.nif
+Decoding meshes\w\w_art_keening.nif
+Decoding meshes\w\w_art_sunder.nif
+Decoding meshes\x\flora_t_podbud_04.nif
+Decoding meshes\x\flora_t_podbud_01.nif
+Decoding meshes\x\flora_t_podbud_02.nif
+Decoding meshes\x\flora_t_podbud_03.nif
+Decoding meshes\r\xnetch_betty.nif
+Decoding meshes\r\xnetch_bull.nif
+Decoding meshes\x\flora_ashtree_03.nif
+Decoding meshes\x\flora_ashtree_02.nif
+Decoding meshes\x\flora_ashtree_01.nif
+Decoding meshes\x\flora_ashtree_07.nif
+Decoding meshes\x\flora_ashtree_06.nif
+Decoding meshes\x\flora_ashtree_05.nif
+Decoding meshes\x\flora_ashtree_04.nif
+Decoding meshes\x\flora_ash_log_03.nif
+Decoding meshes\x\flora_ash_log_02.nif
+Decoding meshes\x\flora_ash_log_01.nif
+Decoding meshes\x\flora_ash_log_04.nif
+Decoding meshes\r\xleastkagouti.nif
+Decoding meshes\e\fire_shield.nif
+Decoding meshes\r\xheart_akulakhan.nif
+Decoding meshes\xargonian_swimkna.nif
+Decoding meshes\r\steam_centurions.nif
+Decoding meshes\r\greatbonewalker.nif
diff --git a/components/nif/tests/test.sh b/components/nif/tests/test.sh
new file mode 100755
index 0000000000..2d07708adc
--- /dev/null
+++ b/components/nif/tests/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+make || exit
+
+mkdir -p output
+
+PROGS=*_test
+
+for a in $PROGS; do
+ if [ -f "output/$a.out" ]; then
+ echo "Running $a:"
+ ./$a | diff output/$a.out -
+ else
+ echo "Creating $a.out"
+ ./$a > "output/$a.out"
+ git add "output/$a.out"
+ fi
+done
diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp
new file mode 100644
index 0000000000..9c4fee7a09
--- /dev/null
+++ b/components/nifbullet/bulletnifloader.cpp
@@ -0,0 +1,303 @@
+ /*
+OpenMW - The completely unofficial reimplementation of Morrowind
+Copyright (C) 2008-2010 Nicolay Korslund
+Email: < korslund@gmail.com >
+WWW: http://openmw.sourceforge.net/
+
+This file (ogre_nif_loader.cpp) is part of the OpenMW package.
+
+OpenMW is distributed as free software: you can redistribute it
+and/or modify it under the terms of the GNU General Public License
+version 3, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+version 3 along with this program. If not, see
+http://www.gnu.org/licenses/ .
+
+*/
+
+#include "bulletnifloader.hpp"
+
+#include <cstdio>
+
+
+#include <components/misc/stringops.hpp>
+
+#include "../nif/niffile.hpp"
+#include "../nif/node.hpp"
+#include "../nif/data.hpp"
+#include "../nif/property.hpp"
+#include "../nif/controller.hpp"
+#include "../nif/extra.hpp"
+#include <libs/platform/strings.h>
+
+#include <vector>
+#include <list>
+// For warning messages
+#include <iostream>
+
+// float infinity
+#include <limits>
+
+typedef unsigned char ubyte;
+
+namespace NifBullet
+{
+
+struct TriangleMeshShape : public btBvhTriangleMeshShape
+{
+ TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression)
+ : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression)
+ {
+ }
+
+ virtual ~TriangleMeshShape()
+ {
+ delete getTriangleInfoMap();
+ delete m_meshInterface;
+ }
+};
+
+ManualBulletShapeLoader::~ManualBulletShapeLoader()
+{
+}
+
+
+btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 const &v)
+{
+ return btVector3(v[0], v[1], v[2]);
+}
+
+void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
+{
+ mShape = static_cast<OEngine::Physic::BulletShape *>(resource);
+ mResourceName = mShape->getName();
+ mShape->mCollide = false;
+ mBoundingBox = NULL;
+ mShape->mBoxTranslation = Ogre::Vector3(0,0,0);
+ mShape->mBoxRotation = Ogre::Quaternion::IDENTITY;
+ mHasShape = false;
+
+ btTriangleMesh* mesh1 = new btTriangleMesh();
+
+ // Load the NIF. TODO: Wrap this in a try-catch block once we're out
+ // of the early stages of development. Right now we WANT to catch
+ // every error as early and intrusively as possible, as it's most
+ // likely a sign of incomplete code rather than faulty input.
+ Nif::NIFFile::ptr pnif (Nif::NIFFile::create (mResourceName.substr(0, mResourceName.length()-7)));
+ Nif::NIFFile & nif = *pnif.get ();
+ if (nif.numRoots() < 1)
+ {
+ warn("Found no root nodes in NIF.");
+ return;
+ }
+
+ Nif::Record *r = nif.getRoot(0);
+ assert(r != NULL);
+
+ Nif::Node *node = dynamic_cast<Nif::Node*>(r);
+ if (node == NULL)
+ {
+ warn("First root in file was not a node, but a " +
+ r->recName + ". Skipping file.");
+ return;
+ }
+
+ mShape->mHasCollisionNode = hasRootCollisionNode(node);
+
+ //do a first pass
+ handleNode(mesh1, node,0,false,false,false);
+
+ if(mBoundingBox != NULL)
+ {
+ mShape->mCollisionShape = mBoundingBox;
+ delete mesh1;
+ }
+ else if (mHasShape && mShape->mCollide)
+ {
+ mShape->mCollisionShape = new TriangleMeshShape(mesh1,true);
+ }
+ else
+ delete mesh1;
+
+ //second pass which create a shape for raycasting.
+ mResourceName = mShape->getName();
+ mShape->mCollide = false;
+ mBoundingBox = NULL;
+ mShape->mBoxTranslation = Ogre::Vector3(0,0,0);
+ mShape->mBoxRotation = Ogre::Quaternion::IDENTITY;
+ mHasShape = false;
+
+ btTriangleMesh* mesh2 = new btTriangleMesh();
+
+ handleNode(mesh2, node,0,true,true,false);
+
+ if(mBoundingBox != NULL)
+ {
+ mShape->mRaycastingShape = mBoundingBox;
+ delete mesh2;
+ }
+ else if (mHasShape)
+ {
+ mShape->mRaycastingShape = new TriangleMeshShape(mesh2,true);
+ }
+ else
+ delete mesh2;
+}
+
+bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node)
+{
+ if(node->recType == Nif::RC_RootCollisionNode)
+ return true;
+
+ const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
+ if(ninode)
+ {
+ const Nif::NodeList &list = ninode->children;
+ for(size_t i = 0;i < list.length();i++)
+ {
+ if(!list[i].empty())
+ {
+ if(hasRootCollisionNode(list[i].getPtr()))
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *node, int flags,
+ bool isCollisionNode,
+ bool raycasting, bool isMarker)
+{
+ // Accumulate the flags from all the child nodes. This works for all
+ // the flags we currently use, at least.
+ flags |= node->flags;
+
+ if (!raycasting)
+ isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode);
+ else
+ isCollisionNode = isCollisionNode && (node->recType != Nif::RC_RootCollisionNode);
+
+ // Don't collide with AvoidNode shapes
+ if(node->recType == Nif::RC_AvoidNode)
+ flags |= 0x800;
+
+ // Marker objects
+ /// \todo don't do this in the editor
+ std::string nodename = node->name;
+ Misc::StringUtils::toLower(nodename);
+ if (nodename.find("marker") != std::string::npos)
+ isMarker = true;
+
+ // Check for extra data
+ Nif::Extra const *e = node;
+ while (!e->extra.empty())
+ {
+ // Get the next extra data in the list
+ e = e->extra.getPtr();
+ assert(e != NULL);
+
+ if (e->recType == Nif::RC_NiStringExtraData)
+ {
+ // String markers may contain important information
+ // affecting the entire subtree of this node
+ Nif::NiStringExtraData *sd = (Nif::NiStringExtraData*)e;
+
+ // not sure what the difference between NCO and NCC is, or if there even is one
+ if (sd->string == "NCO" || sd->string == "NCC")
+ {
+ // No collision. Use an internal flag setting to mark this.
+ flags |= 0x800;
+ }
+ else if (sd->string == "MRK")
+ // Marker objects. These are only visible in the
+ // editor. Until and unless we add an editor component to
+ // the engine, just skip this entire node.
+ isMarker = true;
+ }
+ }
+
+ if ( (isCollisionNode || (!mShape->mHasCollisionNode && !raycasting))
+ && (!isMarker || (mShape->mHasCollisionNode && !raycasting)))
+ {
+ if(node->hasBounds)
+ {
+ mShape->mBoxTranslation = node->boundPos;
+ mShape->mBoxRotation = node->boundRot;
+ mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ));
+ }
+ else if(node->recType == Nif::RC_NiTriShape)
+ {
+ mShape->mCollide = !(flags&0x800);
+ handleNiTriShape(mesh, static_cast<const Nif::NiTriShape*>(node), flags, node->getWorldTransform(), raycasting);
+ }
+ }
+
+ // For NiNodes, loop through children
+ const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
+ if(ninode)
+ {
+ const Nif::NodeList &list = ninode->children;
+ for(size_t i = 0;i < list.length();i++)
+ {
+ if(!list[i].empty())
+ handleNode(mesh, list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker);
+ }
+ }
+}
+
+void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform,
+ bool raycasting)
+{
+ assert(shape != NULL);
+
+ // Interpret flags
+ bool hidden = (flags&Nif::NiNode::Flag_Hidden) != 0;
+ bool collide = (flags&Nif::NiNode::Flag_MeshCollision) != 0;
+ bool bbcollide = (flags&Nif::NiNode::Flag_BBoxCollision) != 0;
+
+ // If the object was marked "NCO" earlier, it shouldn't collide with
+ // anything. So don't do anything.
+ if ((flags & 0x800) && !raycasting)
+ {
+ collide = false;
+ bbcollide = false;
+ return;
+ }
+
+ if (!collide && !bbcollide && hidden && !raycasting)
+ // This mesh apparently isn't being used for anything, so don't
+ // bother setting it up.
+ return;
+
+ mHasShape = true;
+
+ const Nif::NiTriShapeData *data = shape->data.getPtr();
+ const std::vector<Ogre::Vector3> &vertices = data->vertices;
+ const short *triangles = &data->triangles[0];
+ for(size_t i = 0;i < data->triangles.size();i+=3)
+ {
+ Ogre::Vector3 b1 = transform*vertices[triangles[i+0]];
+ Ogre::Vector3 b2 = transform*vertices[triangles[i+1]];
+ Ogre::Vector3 b3 = transform*vertices[triangles[i+2]];
+ mesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z));
+ }
+}
+
+void ManualBulletShapeLoader::load(const std::string &name,const std::string &group)
+{
+ // Check if the resource already exists
+ Ogre::ResourcePtr ptr = OEngine::Physic::BulletShapeManager::getSingleton().getByName(name, group);
+ if (!ptr.isNull())
+ return;
+ OEngine::Physic::BulletShapeManager::getSingleton().create(name,group,true,this);
+}
+
+} // namespace NifBullet
diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp
new file mode 100644
index 0000000000..d1e8763053
--- /dev/null
+++ b/components/nifbullet/bulletnifloader.hpp
@@ -0,0 +1,113 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (ogre_nif_loader.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP
+#define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP
+
+#include <cassert>
+#include <string>
+#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
+#include <BulletCollision/CollisionShapes/btConvexTriangleMeshShape.h>
+#include <btBulletDynamicsCommon.h>
+#include <openengine/bullet/BulletShapeLoader.h>
+
+// For warning messages
+#include <iostream>
+
+namespace Nif
+{
+ class Node;
+ class Transformation;
+ class NiTriShape;
+}
+
+namespace NifBullet
+{
+
+/**
+*Load bulletShape from NIF files.
+*/
+class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader
+{
+public:
+ ManualBulletShapeLoader()
+ : mShape(NULL)
+ , mBoundingBox(NULL)
+ , mHasShape(false)
+ {
+ }
+
+ virtual ~ManualBulletShapeLoader();
+
+ void warn(const std::string &msg)
+ {
+ std::cerr << "NIFLoader: Warn:" << msg << "\n";
+ }
+
+ void fail(const std::string &msg)
+ {
+ std::cerr << "NIFLoader: Fail: "<< msg << std::endl;
+ abort();
+ }
+
+ /**
+ *This function should not be called manualy. Use load instead. (this is called by the BulletShapeManager when you use load).
+ */
+ void loadResource(Ogre::Resource *resource);
+
+ /**
+ *This function load a new bulletShape from a NIF file into the BulletShapeManager.
+ *When the file is loaded, you can then use BulletShapeManager::getByName() to retrive the bulletShape.
+ *Warning: this function will just crash if the resourceGroup doesn't exist!
+ */
+ void load(const std::string &name,const std::string &group);
+
+private:
+ btVector3 getbtVector(Ogre::Vector3 const &v);
+
+ /**
+ *Parse a node.
+ */
+ void handleNode(btTriangleMesh* mesh, Nif::Node const *node, int flags, bool isCollisionNode, bool raycasting, bool isMarker);
+
+ /**
+ *Helper function
+ */
+ bool hasRootCollisionNode(const Nif::Node *node);
+
+ /**
+ *convert a NiTriShape to a bullet trishape.
+ */
+ void handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting);
+
+ std::string mResourceName;
+
+ OEngine::Physic::BulletShape* mShape;//current shape
+ btBoxShape *mBoundingBox;
+
+ bool mHasShape;
+};
+
+}
+
+#endif
diff --git a/components/nifbullet/test/test.cpp b/components/nifbullet/test/test.cpp
new file mode 100644
index 0000000000..261edf512a
--- /dev/null
+++ b/components/nifbullet/test/test.cpp
@@ -0,0 +1,209 @@
+#include "bullet_nif_loader.hpp"
+#include "..\nifogre\ogre_nif_loader.hpp"
+#include "..\bsa\bsa_archive.hpp"
+#include "..\nifogre\ogre_nif_loader.hpp"
+#include <Ogre.h>
+#include <OIS.h>
+#include <btBulletDynamicsCommon.h>
+#include <btBulletCollisionCommon.h>
+#include "BtOgrePG.h"
+#include "BtOgreGP.h"
+#include "BtOgreExtras.h"
+
+const char* mesh = "meshes\\x\\ex_hlaalu_b_24.nif";
+
+class MyMotionState : public btMotionState {
+public:
+ MyMotionState(const btTransform &initialpos, Ogre::SceneNode *node) {
+ mVisibleobj = node;
+ mPos1 = initialpos;
+ node->setPosition(initialpos.getOrigin().x(),initialpos.getOrigin().y(),initialpos.getOrigin().z());
+ }
+
+ virtual ~MyMotionState() {
+ }
+
+ void setNode(Ogre::SceneNode *node) {
+ mVisibleobj = node;
+ }
+
+ virtual void getWorldTransform(btTransform &worldTrans) const {
+ worldTrans = mPos1;
+ }
+
+ virtual void setWorldTransform(const btTransform &worldTrans) {
+ if(NULL == mVisibleobj) return; // silently return before we set a node
+ btQuaternion rot = worldTrans.getRotation();
+ mVisibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
+ btVector3 pos = worldTrans.getOrigin();
+ mVisibleobj->setPosition(pos.x(), pos.y(), pos.z());
+ }
+
+protected:
+ Ogre::SceneNode *mVisibleobj;
+ btTransform mPos1;
+};
+
+
+int main()
+{
+ try
+ {
+ //Ogre stuff
+
+ Ogre::Root* pRoot = new Ogre::Root();
+ pRoot->showConfigDialog();
+
+ BulletShapeManager* manag = new BulletShapeManager();
+
+ Ogre::RenderWindow* win = pRoot->initialise(true,"test");
+ Ogre::SceneManager* scmg = pRoot->createSceneManager(Ogre::ST_GENERIC,"MonGestionnaireDeScene");
+ Ogre::Camera* pCamera = scmg->createCamera("test");
+ Ogre::Viewport* pViewport = win->addViewport(pCamera);
+ pCamera->setPosition(-50,0,0);
+ pCamera->setFarClipDistance(10000);
+ pCamera->setNearClipDistance(1.);
+ pCamera->lookAt(0,0,0);
+ //Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/models","FileSystem","General");
+ Ogre::ResourceGroupManager::getSingleton().addResourceLocation("","FileSystem","General");
+ /*Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/scripts","FileSystem","General");
+ Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/textures","FileSystem","General");
+ Ogre::ResourceGroupManager::getSingleton().addResourceLocation("C++/OgreSK/media/materials/programs","FileSystem","General");*/
+
+
+ //OIS stuff
+ OIS::ParamList pl;
+ size_t windowHnd = 0;
+ std::ostringstream windowHndStr;
+ win->getCustomAttribute("WINDOW", &windowHnd);
+ windowHndStr << windowHnd;
+ pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
+ OIS::InputManager *pInputManager = OIS::InputManager::createInputSystem( pl );
+ OIS::Mouse *pMouse = static_cast<OIS::Mouse*>(pInputManager->createInputObject(OIS::OISMouse, false));
+ OIS::Keyboard* pKeyboard = static_cast<OIS::Keyboard*>(pInputManager->createInputObject(OIS::OISKeyboard, false));
+ unsigned int width, height, depth;
+ int top, left;
+ win->getMetrics(width, height, depth, left, top);
+ const OIS::MouseState &ms = pMouse->getMouseState();
+ ms.width = width;
+ ms.height = height;
+
+
+ //Ressources stuff
+ Bsa::addBSA("Morrowind.bsa");
+ //Ogre::ResourceGroupManager::getSingleton().createResourceGroup("general");
+
+ Ogre::ResourcePtr ptr = BulletShapeManager::getSingleton().getByName(mesh,"General");
+ NifBullet::ManualBulletShapeLoader* ShapeLoader = new NifBullet::ManualBulletShapeLoader();
+
+ ShapeLoader->load(mesh,"General");
+ //BulletShapeManager::getSingleton().unload(mesh);
+ //ShapeLoader->load(mesh,"General");
+
+ NIFLoader::load(mesh);
+
+ Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
+ //BulletShapeManager::getSingleton().
+ BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(mesh,"General");
+ BulletShapeManager::getSingleton().load(mesh,"General");
+ BulletShapeManager::getSingleton().unload(mesh);
+ BulletShapeManager::getSingleton().load(mesh,"General");
+ BulletShapeManager::getSingleton().load(mesh,"General");
+ //shape->load();
+ //shape->unload();
+ //shape->load();
+
+ //Bullet init
+ btBroadphaseInterface* broadphase = new btDbvtBroadphase();
+
+ // Set up the collision configuration and dispatcher
+ btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
+ btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);
+
+ // The actual physics solver
+ btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
+
+ // The world.
+ btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);
+ dynamicsWorld->setGravity(btVector3(0,-10,0));
+
+
+
+ //le sol?
+ Ogre::SceneNode *node = scmg->getRootSceneNode()->createChildSceneNode("node");
+ Ogre::Entity *ent = scmg->createEntity("Mesh1",mesh);
+ node->attachObject(ent);
+ MyMotionState* mst = new MyMotionState(btTransform::getIdentity(),node);
+
+ btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(0,mst,shape->Shape,btVector3(0,0,0));
+ btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
+ dynamicsWorld->addRigidBody(groundRigidBody);
+
+ //une balle:
+ Ogre::SceneNode *node2 = scmg->getRootSceneNode()->createChildSceneNode("node2");
+ Ogre::Entity *ent2 = scmg->createEntity("Mesh2","ogrehead.mesh");
+ node2->attachObject(ent2);
+ node2->setPosition(0,500,0);
+ btTransform iT;
+ iT.setIdentity();
+ iT.setOrigin(btVector3(0,5000,0));
+ MyMotionState* mst2 = new MyMotionState(btTransform::getIdentity(),node2);
+
+ btSphereShape* sphereshape = new btSphereShape(10);
+ btRigidBody::btRigidBodyConstructionInfo sphereCI(10,mst2,sphereshape,btVector3(0,0,0));
+ btRigidBody* sphere = new btRigidBody(sphereCI);
+ dynamicsWorld->addRigidBody(sphere);
+
+
+ //btOgre!
+ BtOgre::DebugDrawer* mDebugDrawer = new BtOgre::DebugDrawer(scmg->getRootSceneNode(), dynamicsWorld);
+ dynamicsWorld->setDebugDrawer(mDebugDrawer);
+
+ Ogre::Timer timer;
+ timer.reset();
+ bool cont = true;
+ while(cont)
+ {
+ if(timer.getMilliseconds()>30)
+ {
+ pMouse->capture();
+ pKeyboard->capture();
+
+ Ogre::Vector3 a(0,0,0);
+
+ if(pKeyboard->isKeyDown(OIS::KC_UP))
+ {
+ a = a + Ogre::Vector3(0,0,-20);
+ }
+ if(pKeyboard->isKeyDown(OIS::KC_DOWN))
+ {
+ a = a + Ogre::Vector3(0,0,20);
+ }
+ if(pKeyboard->isKeyDown(OIS::KC_ESCAPE))
+ {
+ cont = false;
+ }
+ OIS::MouseState MS = pMouse->getMouseState();
+ pCamera->yaw(-Ogre::Degree(MS.X.rel));
+ pCamera->pitch(-Ogre::Degree(MS.Y.rel));
+ pCamera->moveRelative(a);
+
+ pRoot->renderOneFrame();
+ mDebugDrawer->step();
+ timer.reset();
+ dynamicsWorld->stepSimulation(0.03);
+ }
+ }
+ std::cout << "cool";
+ delete manag;
+ delete pRoot;
+ char a;
+ std::cin >> a;
+ }
+ catch(Ogre::Exception& e)
+ {
+ std::cout << e.getFullDescription();
+ char a;
+ std::cin >> a;
+ }
+}
diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp
new file mode 100644
index 0000000000..55f064c555
--- /dev/null
+++ b/components/nifogre/material.cpp
@@ -0,0 +1,394 @@
+#include "material.hpp"
+
+#include <components/nif/node.hpp>
+#include <components/misc/stringops.hpp>
+#include <components/settings/settings.hpp>
+#include <components/nifoverrides/nifoverrides.hpp>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+#include <OgreMaterialManager.h>
+#include <OgreMaterial.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/functional/hash.hpp>
+
+
+namespace NifOgre
+{
+
+// Conversion of blend / test mode from NIF
+static const char *getBlendFactor(int mode)
+{
+ switch(mode)
+ {
+ case 0: return "one";
+ case 1: return "zero";
+ case 2: return "src_colour";
+ case 3: return "one_minus_src_colour";
+ case 4: return "dest_colour";
+ case 5: return "one_minus_dest_colour";
+ case 6: return "src_alpha";
+ case 7: return "one_minus_src_alpha";
+ case 8: return "dest_alpha";
+ case 9: return "one_minus_dest_alpha";
+ case 10: return "src_alpha_saturate";
+ }
+ std::cerr<< "Unexpected blend mode: "<<mode <<std::endl;
+ return "src_alpha";
+}
+
+static const char *getTestMode(int mode)
+{
+ switch(mode)
+ {
+ case 0: return "always_pass";
+ case 1: return "less";
+ case 2: return "equal";
+ case 3: return "less_equal";
+ case 4: return "greater";
+ case 5: return "not_equal";
+ case 6: return "greater_equal";
+ case 7: return "always_fail";
+ }
+ std::cerr<< "Unexpected test mode: "<<mode <<std::endl;
+ return "less_equal";
+}
+
+
+std::string NIFMaterialLoader::findTextureName(const std::string &filename)
+{
+ /* Bethesda at some point converted all their BSA
+ * textures from tga to dds for increased load speed, but all
+ * texture file name references were kept as .tga.
+ */
+ static const char path[] = "textures\\";
+ static const char path2[] = "textures/";
+
+ std::string texname = filename;
+ Misc::StringUtils::toLower(texname);
+
+ if(texname.compare(0, sizeof(path)-1, path) != 0 &&
+ texname.compare(0, sizeof(path2)-1, path2) != 0)
+ texname = path + texname;
+
+ Ogre::String::size_type pos = texname.rfind('.');
+ if(pos != Ogre::String::npos && texname.compare(pos, texname.length() - pos, ".dds") != 0)
+ {
+ // since we know all (GOTY edition or less) textures end
+ // in .dds, we change the extension
+ texname.replace(pos, texname.length(), ".dds");
+
+ // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods)
+ // verify, and revert if false (this call succeeds quickly, but fails slowly)
+ if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texname))
+ {
+ texname = filename;
+ Misc::StringUtils::toLower(texname);
+ if(texname.compare(0, sizeof(path)-1, path) != 0 &&
+ texname.compare(0, sizeof(path2)-1, path2) != 0)
+ texname = path + texname;
+ }
+ }
+
+ return texname;
+}
+
+Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
+ const Ogre::String &name, const Ogre::String &group,
+ const Nif::NiTexturingProperty *texprop,
+ const Nif::NiMaterialProperty *matprop,
+ const Nif::NiAlphaProperty *alphaprop,
+ const Nif::NiVertexColorProperty *vertprop,
+ const Nif::NiZBufferProperty *zprop,
+ const Nif::NiSpecularProperty *specprop,
+ const Nif::NiWireframeProperty *wireprop,
+ bool &needTangents)
+{
+ Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton();
+ Ogre::MaterialPtr material = matMgr.getByName(name);
+ if(!material.isNull())
+ return name;
+
+ Ogre::Vector3 ambient(1.0f);
+ Ogre::Vector3 diffuse(1.0f);
+ Ogre::Vector3 specular(0.0f);
+ Ogre::Vector3 emissive(0.0f);
+ float glossiness = 0.0f;
+ float alpha = 1.0f;
+ int alphaFlags = 0;
+ int alphaTest = 0;
+ int vertMode = 2;
+ //int lightMode = 1;
+ int depthFlags = 3;
+ // Default should be 1, but Bloodmoon's models are broken
+ int specFlags = 0;
+ int wireFlags = 0;
+ Ogre::String texName[7];
+
+ bool vertexColour = (shapedata->colors.size() != 0);
+
+ // Texture
+ if(texprop)
+ {
+ for(int i = 0;i < 7;i++)
+ {
+ if(!texprop->textures[i].inUse)
+ continue;
+ if(texprop->textures[i].texture.empty())
+ {
+ warn("Texture layer "+Ogre::StringConverter::toString(i)+" is in use but empty in "+name);
+ continue;
+ }
+
+ const Nif::NiSourceTexture *st = texprop->textures[i].texture.getPtr();
+ if(st->external)
+ texName[i] = findTextureName(st->filename);
+ else
+ warn("Found internal texture, ignoring.");
+ }
+
+ Nif::ControllerPtr ctrls = texprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled texture controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+ needTangents = !texName[Nif::NiTexturingProperty::BumpTexture].empty();
+
+ // Alpha modifiers
+ if(alphaprop)
+ {
+ alphaFlags = alphaprop->flags;
+ alphaTest = alphaprop->data.threshold;
+
+ Nif::ControllerPtr ctrls = alphaprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled alpha controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ // Vertex color handling
+ if(vertprop)
+ {
+ vertMode = vertprop->data.vertmode;
+ // FIXME: Handle lightmode?
+ //lightMode = vertprop->data.lightmode;
+
+ Nif::ControllerPtr ctrls = vertprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled vertex color controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ if(zprop)
+ {
+ depthFlags = zprop->flags;
+ // Depth function???
+
+ Nif::ControllerPtr ctrls = zprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled depth controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ if(specprop)
+ {
+ specFlags = specprop->flags;
+
+ Nif::ControllerPtr ctrls = specprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled specular controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ if(wireprop)
+ {
+ wireFlags = wireprop->flags;
+
+ Nif::ControllerPtr ctrls = wireprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled wireframe controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ // Material
+ if(matprop)
+ {
+ ambient = matprop->data.ambient;
+ diffuse = matprop->data.diffuse;
+ specular = matprop->data.specular;
+ emissive = matprop->data.emissive;
+ glossiness = matprop->data.glossiness;
+ alpha = matprop->data.alpha;
+
+ Nif::ControllerPtr ctrls = matprop->controller;
+ while(!ctrls.empty())
+ {
+ warn("Unhandled material controller "+ctrls->recName+" in "+name);
+ ctrls = ctrls->next;
+ }
+ }
+
+ {
+ // Generate a hash out of all properties that can affect the material.
+ size_t h = 0;
+ boost::hash_combine(h, ambient.x);
+ boost::hash_combine(h, ambient.y);
+ boost::hash_combine(h, ambient.z);
+ boost::hash_combine(h, diffuse.x);
+ boost::hash_combine(h, diffuse.y);
+ boost::hash_combine(h, diffuse.z);
+ boost::hash_combine(h, alpha);
+ boost::hash_combine(h, specular.x);
+ boost::hash_combine(h, specular.y);
+ boost::hash_combine(h, specular.z);
+ boost::hash_combine(h, glossiness);
+ boost::hash_combine(h, emissive.x);
+ boost::hash_combine(h, emissive.y);
+ boost::hash_combine(h, emissive.z);
+ for(int i = 0;i < 7;i++)
+ {
+ if(!texName[i].empty())
+ boost::hash_combine(h, texName[i]);
+ }
+ boost::hash_combine(h, vertexColour);
+ boost::hash_combine(h, alphaFlags);
+ boost::hash_combine(h, alphaTest);
+ boost::hash_combine(h, vertMode);
+ boost::hash_combine(h, depthFlags);
+ boost::hash_combine(h, specFlags);
+ boost::hash_combine(h, wireFlags);
+
+ std::map<size_t,std::string>::iterator itr = sMaterialMap.find(h);
+ if (itr != sMaterialMap.end())
+ {
+ // a suitable material exists already - use it
+ return itr->second;
+ }
+ // not found, create a new one
+ sMaterialMap.insert(std::make_pair(h, name));
+ }
+
+ // No existing material like this. Create a new one.
+ sh::MaterialInstance *instance = sh::Factory::getInstance().createMaterialInstance(name, "openmw_objects_base");
+ if(vertMode == 0 || !vertexColour)
+ {
+ instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1)));
+ instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha)));
+ instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1)));
+ instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("0")));
+ }
+ else if(vertMode == 1)
+ {
+ instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1)));
+ instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha)));
+ instance->setProperty("emissive", sh::makeProperty(new sh::StringValue("vertexcolour")));
+ instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("1")));
+ }
+ else if(vertMode == 2)
+ {
+ instance->setProperty("ambient", sh::makeProperty(new sh::StringValue("vertexcolour")));
+ instance->setProperty("diffuse", sh::makeProperty(new sh::StringValue("vertexcolour")));
+ instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1)));
+ instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("2")));
+ }
+ else
+ std::cerr<< "Unhandled vertex mode: "<<vertMode <<std::endl;
+
+ if(specFlags)
+ {
+ instance->setProperty("specular", sh::makeProperty(
+ new sh::Vector4(specular.x, specular.y, specular.z, glossiness)));
+ }
+
+ if(wireFlags)
+ {
+ instance->setProperty("polygon_mode", sh::makeProperty(new sh::StringValue("wireframe")));
+ }
+
+ instance->setProperty("diffuseMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BaseTexture]));
+ 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]));
+ if (!texName[Nif::NiTexturingProperty::GlowTexture].empty())
+ {
+ instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true)));
+ instance->setProperty("emissiveMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::GlowTexture].uvSet)));
+ }
+ if (!texName[Nif::NiTexturingProperty::DetailTexture].empty())
+ {
+ 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)));
+ }
+
+ for(int i = 0;i < 7;i++)
+ {
+ if(i == Nif::NiTexturingProperty::BaseTexture ||
+ i == Nif::NiTexturingProperty::DetailTexture ||
+ i == Nif::NiTexturingProperty::BumpTexture ||
+ i == Nif::NiTexturingProperty::GlowTexture)
+ continue;
+ if(!texName[i].empty())
+ warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i));
+ }
+
+ 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)
+ {
+ alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */
+ alphaTest = result.second;
+ depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on
+ }
+
+ if((alphaFlags&1))
+ {
+ std::string blend_mode;
+ blend_mode += getBlendFactor((alphaFlags>>1)&0xf);
+ blend_mode += " ";
+ blend_mode += getBlendFactor((alphaFlags>>5)&0xf);
+ instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue(blend_mode)));
+ }
+
+ if((alphaFlags>>9)&1)
+ {
+ std::string reject;
+ reject += getTestMode((alphaFlags>>10)&0x7);
+ reject += " ";
+ reject += Ogre::StringConverter::toString(alphaTest);
+ instance->setProperty("alpha_rejection", sh::makeProperty(new sh::StringValue(reject)));
+ }
+ else
+ instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha");
+
+ // Ogre usually only sorts if depth write is disabled, so we want "force" instead of "on"
+ instance->setProperty("transparent_sorting", sh::makeProperty(new sh::StringValue(
+ ((alphaFlags&1) && !((alphaFlags>>13)&1)) ? "force" : "off")));
+
+ instance->setProperty("depth_check", sh::makeProperty(new sh::StringValue((depthFlags&1) ? "on" : "off")));
+ instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off")));
+ // depth_func???
+
+ sh::Factory::getInstance()._ensureMaterial(name, "Default");
+ return name;
+}
+
+std::map<size_t,std::string> NIFMaterialLoader::sMaterialMap;
+
+}
diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp
new file mode 100644
index 0000000000..8843ac6c6c
--- /dev/null
+++ b/components/nifogre/material.hpp
@@ -0,0 +1,57 @@
+#ifndef COMPONENTS_NIFOGRE_MATERIAL_HPP
+#define COMPONENTS_NIFOGRE_MATERIAL_HPP
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <cassert>
+
+#include <OgreString.h>
+
+namespace Nif
+{
+ class ShapeData;
+ class NiTexturingProperty;
+ class NiMaterialProperty;
+ class NiAlphaProperty;
+ class NiVertexColorProperty;
+ class NiZBufferProperty;
+ class NiSpecularProperty;
+ class NiWireframeProperty;
+}
+
+namespace NifOgre
+{
+
+class NIFMaterialLoader {
+ static void warn(const std::string &msg)
+ {
+ std::cerr << "NIFMaterialLoader: Warn: " << msg << std::endl;
+ }
+
+ static void fail(const std::string &msg)
+ {
+ std::cerr << "NIFMaterialLoader: Fail: "<< msg << std::endl;
+ abort();
+ }
+
+ static std::map<size_t,std::string> sMaterialMap;
+
+ 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,
+ const Nif::NiMaterialProperty *matprop,
+ const Nif::NiAlphaProperty *alphaprop,
+ const Nif::NiVertexColorProperty *vertprop,
+ const Nif::NiZBufferProperty *zprop,
+ const Nif::NiSpecularProperty *specprop,
+ const Nif::NiWireframeProperty *wireprop,
+ bool &needTangents);
+};
+
+}
+
+#endif
diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp
new file mode 100644
index 0000000000..ca92f62d49
--- /dev/null
+++ b/components/nifogre/mesh.cpp
@@ -0,0 +1,387 @@
+#include "mesh.hpp"
+
+#include <limits>
+
+#include <OgreMeshManager.h>
+#include <OgreMesh.h>
+#include <OgreSubMesh.h>
+#include <OgreBone.h>
+#include <OgreHardwareBufferManager.h>
+#include <OgreMaterialManager.h>
+#include <OgreSkeletonManager.h>
+#include <OgreRenderSystem.h>
+#include <OgreRoot.h>
+
+#include <components/nif/node.hpp>
+#include <components/misc/stringops.hpp>
+
+#include "material.hpp"
+
+namespace NifOgre
+{
+
+// Helper class that computes the bounding box and of a mesh
+class BoundsFinder
+{
+ struct MaxMinFinder
+ {
+ float max, min;
+
+ MaxMinFinder()
+ {
+ min = std::numeric_limits<float>::infinity();
+ max = -min;
+ }
+
+ void add(float f)
+ {
+ if (f > max) max = f;
+ if (f < min) min = f;
+ }
+
+ // Return Max(max**2, min**2)
+ float getMaxSquared()
+ {
+ float m1 = max*max;
+ float m2 = min*min;
+ if (m1 >= m2) return m1;
+ return m2;
+ }
+ };
+
+ MaxMinFinder X, Y, Z;
+
+public:
+ // Add 'verts' vertices to the calculation. The 'data' pointer is
+ // expected to point to 3*verts floats representing x,y,z for each
+ // point.
+ void add(float *data, int verts)
+ {
+ for (int i=0;i<verts;i++)
+ {
+ X.add(*(data++));
+ Y.add(*(data++));
+ Z.add(*(data++));
+ }
+ }
+
+ // True if this structure has valid values
+ bool isValid()
+ {
+ return
+ minX() <= maxX() &&
+ minY() <= maxY() &&
+ minZ() <= maxZ();
+ }
+
+ // Compute radius
+ float getRadius()
+ {
+ assert(isValid());
+
+ // The radius is computed from the origin, not from the geometric
+ // center of the mesh.
+ return sqrt(X.getMaxSquared() + Y.getMaxSquared() + Z.getMaxSquared());
+ }
+
+ float minX() {
+ return X.min;
+ }
+ float maxX() {
+ return X.max;
+ }
+ float minY() {
+ return Y.min;
+ }
+ float maxY() {
+ return Y.max;
+ }
+ float minZ() {
+ return Z.min;
+ }
+ float maxZ() {
+ return Z.max;
+ }
+};
+
+
+NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders;
+
+void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape)
+{
+ const Nif::NiTriShapeData *data = shape->data.getPtr();
+ const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr());
+ std::vector<Ogre::Vector3> srcVerts = data->vertices;
+ std::vector<Ogre::Vector3> srcNorms = data->normals;
+ 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;
+ vertShadowBuffer = true;
+
+ // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be
+ // explicitly attached later.
+ mesh->setSkeletonName(mName);
+
+ // Convert vertices and normals to bone space from bind position. It would be
+ // better to transform the bones into bind position, but there doesn't seem to
+ // be a reliable way to do that.
+ std::vector<Ogre::Vector3> newVerts(srcVerts.size(), Ogre::Vector3(0.0f));
+ std::vector<Ogre::Vector3> newNorms(srcNorms.size(), Ogre::Vector3(0.0f));
+
+ const Nif::NiSkinData *data = skin->data.getPtr();
+ const Nif::NodeList &bones = skin->bones;
+ for(size_t b = 0;b < bones.length();b++)
+ {
+ Ogre::Matrix4 mat;
+ mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale),
+ Ogre::Quaternion(data->bones[b].trafo.rotation));
+ mat = bones[b]->getWorldTransform() * mat;
+
+ const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[b].weights;
+ for(size_t i = 0;i < weights.size();i++)
+ {
+ size_t index = weights[i].vertex;
+ float weight = weights[i].weight;
+
+ newVerts.at(index) += (mat*srcVerts[index]) * weight;
+ if(newNorms.size() > index)
+ {
+ Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f);
+ vec4 = mat*vec4 * weight;
+ newNorms[index] += Ogre::Vector3(&vec4[0]);
+ }
+ }
+ }
+
+ srcVerts = newVerts;
+ srcNorms = newNorms;
+ }
+ else
+ {
+ Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr();
+ if(skelMgr->getByName(mName).isNull())
+ {
+ // No skinning and no skeleton, so just transform the vertices and
+ // normals into position.
+ Ogre::Matrix4 mat4 = shape->getWorldTransform();
+ for(size_t i = 0;i < srcVerts.size();i++)
+ {
+ Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f);
+ vec4 = mat4*vec4;
+ srcVerts[i] = Ogre::Vector3(&vec4[0]);
+ }
+ for(size_t i = 0;i < srcNorms.size();i++)
+ {
+ Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f);
+ vec4 = mat4*vec4;
+ srcNorms[i] = Ogre::Vector3(&vec4[0]);
+ }
+ }
+ }
+
+ // Set the bounding box first
+ BoundsFinder bounds;
+ bounds.add(&srcVerts[0][0], srcVerts.size());
+ if(!bounds.isValid())
+ {
+ float v[3] = { 0.0f, 0.0f, 0.0f };
+ bounds.add(&v[0], 1);
+ }
+
+ mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f,
+ bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f));
+ mesh->_setBoundingSphereRadius(bounds.getRadius());
+
+ // This function is just one long stream of Ogre-barf, but it works
+ // great.
+ Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr();
+ Ogre::HardwareVertexBufferSharedPtr vbuf;
+ Ogre::HardwareIndexBufferSharedPtr ibuf;
+ Ogre::VertexBufferBinding *bind;
+ Ogre::VertexDeclaration *decl;
+ int nextBuf = 0;
+
+ Ogre::SubMesh *sub = mesh->createSubMesh();
+
+ // Add vertices
+ sub->useSharedVertices = false;
+ sub->vertexData = new Ogre::VertexData();
+ sub->vertexData->vertexStart = 0;
+ sub->vertexData->vertexCount = srcVerts.size();
+
+ decl = sub->vertexData->vertexDeclaration;
+ bind = sub->vertexData->vertexBufferBinding;
+ if(srcVerts.size())
+ {
+ vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
+ srcVerts.size(), vertUsage, vertShadowBuffer);
+ vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true);
+
+ decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
+ bind->setBinding(nextBuf++, vbuf);
+ }
+
+ // Vertex normals
+ if(srcNorms.size())
+ {
+ vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
+ srcNorms.size(), vertUsage, vertShadowBuffer);
+ vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true);
+
+ decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
+ bind->setBinding(nextBuf++, vbuf);
+ }
+
+ // Vertex colors
+ const std::vector<Ogre::Vector4> &colors = data->colors;
+ if(colors.size())
+ {
+ Ogre::RenderSystem *rs = Ogre::Root::getSingleton().getRenderSystem();
+ std::vector<Ogre::RGBA> colorsRGB(colors.size());
+ for(size_t i = 0;i < colorsRGB.size();i++)
+ {
+ Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]);
+ rs->convertColourValue(clr, &colorsRGB[i]);
+ }
+ vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
+ colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC);
+ vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true);
+ decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
+ bind->setBinding(nextBuf++, vbuf);
+ }
+
+ // Texture UV coordinates
+ size_t numUVs = data->uvlist.size();
+ if (numUVs)
+ {
+ size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);
+
+ for(size_t i = 0; i < numUVs; i++)
+ decl->addElement(nextBuf, elemSize*i, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, i);
+
+ vbuf = hwBufMgr->createVertexBuffer(decl->getVertexSize(nextBuf), srcVerts.size(),
+ Ogre::HardwareBuffer::HBU_STATIC);
+
+ std::vector<Ogre::Vector2> allUVs;
+ allUVs.reserve(srcVerts.size()*numUVs);
+ for (size_t vert = 0; vert<srcVerts.size(); ++vert)
+ for(size_t i = 0; i < numUVs; i++)
+ allUVs.push_back(data->uvlist[i][vert]);
+
+ vbuf->writeData(0, elemSize*srcVerts.size()*numUVs, &allUVs[0], true);
+
+ bind->setBinding(nextBuf++, vbuf);
+ }
+
+ // Triangle faces
+ const std::vector<short> &srcIdx = data->triangles;
+ if(srcIdx.size())
+ {
+ ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(),
+ Ogre::HardwareBuffer::HBU_STATIC);
+ ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true);
+ sub->indexData->indexBuffer = ibuf;
+ sub->indexData->indexCount = srcIdx.size();
+ sub->indexData->indexStart = 0;
+ }
+
+ // Assign bone weights for this TriShape
+ if(skin != NULL)
+ {
+ Ogre::SkeletonPtr skel = Ogre::SkeletonManager::getSingleton().getByName(mName);
+
+ const Nif::NiSkinData *data = skin->data.getPtr();
+ const Nif::NodeList &bones = skin->bones;
+ for(size_t i = 0;i < bones.length();i++)
+ {
+ Ogre::VertexBoneAssignment boneInf;
+ boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle();
+
+ const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
+ for(size_t j = 0;j < weights.size();j++)
+ {
+ boneInf.vertexIndex = weights[j].vertex;
+ boneInf.weight = weights[j].weight;
+ sub->addBoneAssignment(boneInf);
+ }
+ }
+ }
+
+ 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;
+ bool needTangents = false;
+
+ shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
+ std::string matname = NIFMaterialLoader::getMaterial(data, mesh->getName(), mGroup,
+ texprop, matprop, alphaprop,
+ vertprop, zprop, specprop,
+ wireprop, needTangents);
+ if(matname.length() > 0)
+ sub->setMaterialName(matname);
+
+ // build tangents if the material needs them
+ if (needTangents)
+ {
+ unsigned short src,dest;
+ if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest))
+ mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest);
+ }
+}
+
+
+NIFMeshLoader::NIFMeshLoader(const std::string &name, const std::string &group, size_t idx)
+ : mName(name), mGroup(group), mShapeIndex(idx)
+{
+}
+
+void NIFMeshLoader::loadResource(Ogre::Resource *resource)
+{
+ Ogre::Mesh *mesh = dynamic_cast<Ogre::Mesh*>(resource);
+ OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!");
+
+ Nif::NIFFile::ptr nif = Nif::NIFFile::create(mName);
+ if(mShapeIndex >= nif->numRecords())
+ {
+ Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr();
+ if(!skelMgr->getByName(mName).isNull())
+ mesh->setSkeletonName(mName);
+ return;
+ }
+
+ const Nif::Record *record = nif->getRecord(mShapeIndex);
+ createSubMesh(mesh, dynamic_cast<const Nif::NiTriShape*>(record));
+}
+
+
+void NIFMeshLoader::createMesh(const std::string &name, const std::string &fullname, const std::string &group, size_t idx)
+{
+ NIFMeshLoader::LoaderMap::iterator loader;
+ loader = sLoaders.insert(std::make_pair(fullname, NIFMeshLoader(name, group, idx))).first;
+
+ Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
+ Ogre::MeshPtr mesh = meshMgr.createManual(fullname, group, &loader->second);
+ mesh->setAutoBuildEdgeLists(false);
+}
+
+}
diff --git a/components/nifogre/mesh.hpp b/components/nifogre/mesh.hpp
new file mode 100644
index 0000000000..731e49c903
--- /dev/null
+++ b/components/nifogre/mesh.hpp
@@ -0,0 +1,55 @@
+#ifndef COMPONENTS_NIFOGRE_MESH_HPP
+#define COMPONENTS_NIFOGRE_MESH_HPP
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <cassert>
+
+#include <OgreResource.h>
+
+namespace Nif
+{
+ class NiTriShape;
+}
+
+namespace NifOgre
+{
+
+/** Manual resource loader for NiTriShapes. This is the main class responsible
+ * for translating the internal NIF meshes into something Ogre can use.
+ */
+class NIFMeshLoader : Ogre::ManualResourceLoader
+{
+ static void warn(const std::string &msg)
+ {
+ std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl;
+ }
+
+ static void fail(const std::string &msg)
+ {
+ std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl;
+ abort();
+ }
+
+ std::string mName;
+ std::string mGroup;
+ size_t mShapeIndex;
+
+ // Convert NiTriShape to Ogre::SubMesh
+ void createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape);
+
+ typedef std::map<std::string,NIFMeshLoader> LoaderMap;
+ static LoaderMap sLoaders;
+
+ NIFMeshLoader(const std::string &name, const std::string &group, size_t idx);
+
+ virtual void loadResource(Ogre::Resource *resource);
+
+public:
+ static void createMesh(const std::string &name, const std::string &fullname, const std::string &group, size_t idx);
+};
+
+}
+
+#endif
diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp
new file mode 100644
index 0000000000..3bb9ea2309
--- /dev/null
+++ b/components/nifogre/ogrenifloader.cpp
@@ -0,0 +1,998 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (ogre_nif_loader.cpp) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#include "ogrenifloader.hpp"
+
+#include <algorithm>
+
+#include <OgreTechnique.h>
+#include <OgreRoot.h>
+#include <OgreEntity.h>
+#include <OgreSubEntity.h>
+#include <OgreTagPoint.h>
+#include <OgreParticleSystem.h>
+#include <OgreParticleEmitter.h>
+#include <OgreParticleAffector.h>
+#include <OgreMeshManager.h>
+#include <OgreSkeletonManager.h>
+#include <OgreControllerManager.h>
+
+#include <components/nif/node.hpp>
+#include <components/misc/stringops.hpp>
+
+#include "skeleton.hpp"
+#include "material.hpp"
+#include "mesh.hpp"
+
+namespace NifOgre
+{
+
+// FIXME: Should not be here.
+class DefaultFunction : public Ogre::ControllerFunction<Ogre::Real>
+{
+private:
+ float mFrequency;
+ float mPhase;
+ float mStartTime;
+ 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;
+ }
+};
+
+class VisController
+{
+public:
+ class Value : public NodeTargetValue<Ogre::Real>
+ {
+ private:
+ std::vector<Nif::NiVisData::VisData> mData;
+
+ bool calculate(Ogre::Real time) const
+ {
+ if(mData.size() == 0)
+ return true;
+
+ for(size_t i = 1;i < mData.size();i++)
+ {
+ if(mData[i].time > time)
+ return mData[i-1].isSet;
+ }
+ 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();
+ while(iter.hasMoreElements())
+ {
+ node = iter.getNext();
+ setVisible(node, vis);
+
+ Ogre::TagPoint *tag = dynamic_cast<Ogre::TagPoint*>(node);
+ if(tag != NULL)
+ {
+ Ogre::MovableObject *obj = tag->getChildObject();
+ if(obj != NULL)
+ obj->setVisible(vis);
+ }
+ }
+ }
+
+ public:
+ Value(Ogre::Node *target, const Nif::NiVisData *data)
+ : NodeTargetValue<Ogre::Real>(target)
+ , mData(data->mVis)
+ { }
+
+ virtual Ogre::Quaternion getRotation(float time) const
+ { return Ogre::Quaternion(); }
+
+ virtual Ogre::Vector3 getTranslation(float time) const
+ { return Ogre::Vector3(0.0f); }
+
+ virtual Ogre::Vector3 getScale(float time) const
+ { return Ogre::Vector3(1.0f); }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real time)
+ {
+ bool vis = calculate(time);
+ setVisible(mNode, vis);
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class KeyframeController
+{
+public:
+ class Value : public NodeTargetValue<Ogre::Real>
+ {
+ 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;
+ }
+
+ static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time)
+ {
+ if(time <= keys.front().mTime)
+ return keys.front().mValue;
+
+ Nif::QuaternionKeyList::VecType::const_iterator iter(keys.begin()+1);
+ for(;iter != keys.end();iter++)
+ {
+ if(iter->mTime < time)
+ continue;
+
+ Nif::QuaternionKeyList::VecType::const_iterator last(iter-1);
+ float a = (time-last->mTime) / (iter->mTime-last->mTime);
+ return Ogre::Quaternion::nlerp(a, last->mValue, iter->mValue);
+ }
+ return keys.back().mValue;
+ }
+
+ public:
+ Value(Ogre::Node *target, const Nif::NiKeyframeData *data)
+ : NodeTargetValue<Ogre::Real>(target)
+ , mRotations(data->mRotations)
+ , mTranslations(data->mTranslations)
+ , mScales(data->mScales)
+ { }
+
+ virtual Ogre::Quaternion getRotation(float time) const
+ {
+ if(mRotations.mKeys.size() > 0)
+ return interpKey(mRotations.mKeys, time);
+ return mNode->getOrientation();
+ }
+
+ virtual Ogre::Vector3 getTranslation(float time) const
+ {
+ if(mTranslations.mKeys.size() > 0)
+ return interpKey(mTranslations.mKeys, time);
+ return mNode->getPosition();
+ }
+
+ virtual Ogre::Vector3 getScale(float time) const
+ {
+ if(mScales.mKeys.size() > 0)
+ return Ogre::Vector3(interpKey(mScales.mKeys, time));
+ return mNode->getScale();
+ }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real time)
+ {
+ if(mRotations.mKeys.size() > 0)
+ mNode->setOrientation(interpKey(mRotations.mKeys, time));
+ if(mTranslations.mKeys.size() > 0)
+ mNode->setPosition(interpKey(mTranslations.mKeys, time));
+ if(mScales.mKeys.size() > 0)
+ mNode->setScale(Ogre::Vector3(interpKey(mScales.mKeys, time)));
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class UVController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>
+ {
+ private:
+ Ogre::MaterialPtr mMaterial;
+ 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;
+ }
+
+ public:
+ Value(const Ogre::MaterialPtr &material, const Nif::NiUVData *data)
+ : mMaterial(material)
+ , mUTrans(data->mKeyList[0])
+ , mVTrans(data->mKeyList[1])
+ , mUScale(data->mKeyList[2])
+ , mVScale(data->mKeyList[3])
+ { }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 1.0f;
+ }
+
+ 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);
+
+ Ogre::Material::TechniqueIterator techs = mMaterial->getTechniqueIterator();
+ while(techs.hasMoreElements())
+ {
+ Ogre::Technique *tech = techs.getNext();
+ Ogre::Technique::PassIterator passes = tech->getPassIterator();
+ while(passes.hasMoreElements())
+ {
+ Ogre::Pass *pass = passes.getNext();
+ Ogre::TextureUnitState *tex = pass->getTextureUnitState(0);
+ tex->setTextureScroll(uTrans, vTrans);
+ tex->setTextureScale(uScale, vScale);
+ }
+ }
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class ParticleSystemController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>
+ {
+ private:
+ Ogre::ParticleSystem *mParticleSys;
+ float mEmitStart;
+ float mEmitStop;
+
+ public:
+ Value(Ogre::ParticleSystem *psys, const Nif::NiParticleSystemController *pctrl)
+ : mParticleSys(psys)
+ , mEmitStart(pctrl->startTime)
+ , mEmitStop(pctrl->stopTime)
+ {
+ }
+
+ Ogre::Real getValue() const
+ { return 0.0f; }
+
+ void setValue(Ogre::Real value)
+ {
+ mParticleSys->setEmitting(value >= mEmitStart && value < mEmitStop);
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+class GeomMorpherController
+{
+public:
+ class Value : public Ogre::ControllerValue<Ogre::Real>
+ {
+ private:
+ Ogre::SubEntity *mSubEntity;
+ std::vector<Nif::NiMorphData::MorphData> mMorphs;
+
+ public:
+ Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data)
+ : mSubEntity(subent)
+ , mMorphs(data->mMorphs)
+ { }
+
+ virtual Ogre::Real getValue() const
+ {
+ // Should not be called
+ return 0.0f;
+ }
+
+ virtual void setValue(Ogre::Real value)
+ {
+ // TODO: Implement
+ }
+ };
+
+ typedef DefaultFunction Function;
+};
+
+
+/** Object creator for NIFs. This is the main class responsible for creating
+ * "live" Ogre objects (entities, particle systems, controllers, etc) from
+ * their NIF equivalents.
+ */
+class NIFObjectLoader
+{
+ static void warn(const std::string &msg)
+ {
+ 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,
+ const Nif::Node *node, int flags, int animflags)
+ {
+ const Nif::NiTriShape *shape = static_cast<const Nif::NiTriShape*>(node);
+
+ std::string fullname = name+"@index="+Ogre::StringConverter::toString(shape->recIndex);
+ if(shape->name.length() > 0)
+ fullname += "@shape="+shape->name;
+ Misc::StringUtils::toLower(fullname);
+
+ Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
+ if(meshMgr.getByName(fullname).isNull())
+ NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex);
+
+ Ogre::Entity *entity = sceneMgr->createEntity(fullname);
+ entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden));
+
+ objectlist.mEntities.push_back(entity);
+ if(objectlist.mSkelBase)
+ {
+ if(entity->hasSkeleton())
+ entity->shareSkeletonInstanceWith(objectlist.mSkelBase);
+ else
+ {
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex);
+ Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
+ objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), entity);
+ }
+ }
+
+ Nif::ControllerPtr ctrl = node->controller;
+ while(!ctrl.empty())
+ {
+ if(ctrl->recType == Nif::RC_NiUVController)
+ {
+ 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)));
+
+ objectlist.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)));
+
+ objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ ctrl = ctrl->next;
+ }
+ }
+
+
+ static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl)
+ {
+ Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif");
+ emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f,
+ partctrl->velocity + partctrl->velocityRandom*0.5f);
+ emitter->setEmissionRate(partctrl->emitRate);
+ emitter->setTimeToLive(partctrl->lifetime - partctrl->lifetimeRandom*0.5f,
+ partctrl->lifetime + partctrl->lifetimeRandom*0.5f);
+ emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x));
+ emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y));
+ emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z));
+ emitter->setParameter("vertical_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalDir).valueDegrees()));
+ emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees()));
+ emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees()));
+ emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees()));
+
+ Nif::ExtraPtr e = partctrl->extra;
+ while(!e.empty())
+ {
+ if(e->recType == Nif::RC_NiParticleGrowFade)
+ {
+ const Nif::NiParticleGrowFade *gf = static_cast<const Nif::NiParticleGrowFade*>(e.getPtr());
+
+ Ogre::ParticleAffector *affector = partsys->addAffector("GrowFade");
+ affector->setParameter("grow_time", Ogre::StringConverter::toString(gf->growTime));
+ affector->setParameter("fade_time", Ogre::StringConverter::toString(gf->fadeTime));
+ }
+ else if(e->recType == Nif::RC_NiGravity)
+ {
+ const Nif::NiGravity *gr = static_cast<const Nif::NiGravity*>(e.getPtr());
+
+ Ogre::ParticleAffector *affector = partsys->addAffector("Gravity");
+ affector->setParameter("force", Ogre::StringConverter::toString(gr->mForce));
+ 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));
+ }
+ else if(e->recType == Nif::RC_NiParticleColorModifier)
+ {
+ const Nif::NiParticleColorModifier *cl = static_cast<const Nif::NiParticleColorModifier*>(e.getPtr());
+ const Nif::NiColorData *clrdata = cl->data.getPtr();
+
+ Ogre::ParticleAffector *affector = partsys->addAffector("ColourInterpolator");
+ size_t num_colors = std::min<size_t>(6, clrdata->mKeyList.mKeys.size());
+ for(size_t i = 0;i < num_colors;i++)
+ {
+ Ogre::ColourValue color;
+ color.r = clrdata->mKeyList.mKeys[i].mValue[0];
+ color.g = clrdata->mKeyList.mKeys[i].mValue[1];
+ color.b = clrdata->mKeyList.mKeys[i].mValue[2];
+ color.a = clrdata->mKeyList.mKeys[i].mValue[3];
+ affector->setParameter("colour"+Ogre::StringConverter::toString(i),
+ Ogre::StringConverter::toString(color));
+ affector->setParameter("time"+Ogre::StringConverter::toString(i),
+ Ogre::StringConverter::toString(clrdata->mKeyList.mKeys[i].mTime));
+ }
+ }
+ else if(e->recType == Nif::RC_NiParticleRotation)
+ {
+ // TODO: Implement (Ogre::RotationAffector?)
+ }
+ else
+ warn("Unhandled particle modifier "+e->recName);
+ e = e->extra;
+ }
+ }
+
+ static void createParticleSystem(const std::string &name, const std::string &group,
+ Ogre::SceneManager *sceneMgr, ObjectList &objectlist,
+ const Nif::Node *partnode, int flags, int partflags)
+ {
+ const Nif::NiAutoNormalParticlesData *particledata = NULL;
+ if(partnode->recType == Nif::RC_NiAutoNormalParticles)
+ particledata = static_cast<const Nif::NiAutoNormalParticles*>(partnode)->data.getPtr();
+ else if(partnode->recType == Nif::RC_NiRotatingParticles)
+ particledata = static_cast<const Nif::NiRotatingParticles*>(partnode)->data.getPtr();
+
+ std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex);
+ if(partnode->name.length() > 0)
+ fullname += "@type="+partnode->name;
+ Misc::StringUtils::toLower(fullname);
+
+ Ogre::ParticleSystem *partsys = sceneMgr->createParticleSystem();
+
+ 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;
+ bool needTangents = false;
+
+ partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
+ partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group,
+ texprop, matprop, alphaprop,
+ 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);
+
+ Nif::ControllerPtr ctrl = partnode->controller;
+ while(!ctrl.empty())
+ {
+ if(ctrl->recType == Nif::RC_NiParticleSystemController)
+ {
+ const Nif::NiParticleSystemController *partctrl = static_cast<const Nif::NiParticleSystemController*>(ctrl.getPtr());
+
+ createParticleEmitterAffectors(partsys, partctrl);
+ if(!partctrl->emitter.empty() && !partsys->isAttached())
+ {
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex);
+ Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid);
+ objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys);
+ }
+
+ 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));
+ }
+ 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);
+ }
+
+
+ static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectList &objectlist, int animflags)
+ {
+ do {
+ if(ctrl->recType == Nif::RC_NiVisController)
+ {
+ 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::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));
+ }
+ else if(ctrl->recType == Nif::RC_NiKeyframeController)
+ {
+ const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
+ if(!key->data.empty())
+ {
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
+ Ogre::Bone *trgtbone = objectlist.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)));
+
+ objectlist.mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ }
+ ctrl = ctrl->next;
+ } while(!ctrl.empty());
+ }
+
+
+ static void extractTextKeys(const Nif::NiTextKeyExtraData *tk, TextKeyMap &textkeys)
+ {
+ for(size_t i = 0;i < tk->list.size();i++)
+ {
+ const std::string &str = tk->list[i].text;
+ std::string::size_type pos = 0;
+ while(pos < str.length())
+ {
+ if(::isspace(str[pos]))
+ {
+ pos++;
+ continue;
+ }
+
+ std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos));
+ if(nextpos != std::string::npos)
+ {
+ do {
+ nextpos--;
+ } while(nextpos > pos && ::isspace(str[nextpos]));
+ nextpos++;
+ }
+ else if(::isspace(*str.rbegin()))
+ {
+ std::string::const_iterator last = str.end();
+ do {
+ last--;
+ } while(last != str.begin() && ::isspace(*last));
+ nextpos = std::distance(str.begin(), ++last);
+ }
+ std::string result = str.substr(pos, nextpos-pos);
+ textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result)));
+
+ pos = nextpos;
+ }
+ }
+ }
+
+
+ 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)
+ {
+ // Do not create objects for the collision shape (includes all children)
+ if(node->recType == Nif::RC_RootCollisionNode)
+ return;
+
+ // Marker objects: just skip the entire node branch
+ /// \todo don't do this in the editor
+ if (node->name.find("marker") != std::string::npos)
+ return;
+
+ if(node->recType == Nif::RC_NiBSAnimationNode)
+ animflags |= node->flags;
+ else if(node->recType == Nif::RC_NiBSParticleNode)
+ partflags |= node->flags;
+ else
+ flags |= node->flags;
+
+ Nif::ExtraPtr e = node->extra;
+ while(!e.empty())
+ {
+ if(e->recType == Nif::RC_NiTextKeyExtraData)
+ {
+ const Nif::NiTextKeyExtraData *tk = static_cast<const Nif::NiTextKeyExtraData*>(e.getPtr());
+
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex);
+ extractTextKeys(tk, objectlist.mTextKeys[trgtid]);
+ }
+ else if(e->recType == Nif::RC_NiStringExtraData)
+ {
+ const Nif::NiStringExtraData *sd = static_cast<const Nif::NiStringExtraData*>(e.getPtr());
+ // String markers may contain important information
+ // affecting the entire subtree of this obj
+ if(sd->string == "MRK")
+ {
+ // Marker objects. These meshes are only visible in the
+ // editor.
+ flags |= 0x80000000;
+ }
+ }
+
+ e = e->extra;
+ }
+
+ if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode))
+ createNodeControllers(name, node->controller, objectlist, animflags);
+
+ if(node->recType == Nif::RC_NiCamera)
+ {
+ /* Ignored */
+ }
+
+ if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000))
+ {
+ createEntity(name, group, sceneMgr, objectlist, node, flags, animflags);
+ }
+
+ if((node->recType == Nif::RC_NiAutoNormalParticles ||
+ node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000))
+ {
+ createParticleSystem(name, group, sceneMgr, objectlist, node, flags, partflags);
+ }
+
+ const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
+ if(ninode)
+ {
+ const Nif::NodeList &children = ninode->children;
+ for(size_t i = 0;i < children.length();i++)
+ {
+ if(!children[i].empty())
+ createObjects(name, group, sceneMgr, children[i].getPtr(), objectlist, flags, animflags, partflags);
+ }
+ }
+ }
+
+ static void createSkelBase(const std::string &name, const std::string &group,
+ Ogre::SceneManager *sceneMgr, const Nif::Node *node,
+ ObjectList &objectlist)
+ {
+ /* 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
+ * other entities are attached to bones and not skinned. */
+ Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
+ if(meshMgr.getByName(name).isNull())
+ NIFMeshLoader::createMesh(name, name, group, ~(size_t)0);
+
+ objectlist.mSkelBase = sceneMgr->createEntity(name);
+ objectlist.mEntities.push_back(objectlist.mSkelBase);
+ }
+
+public:
+ static void load(Ogre::SceneManager *sceneMgr, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0)
+ {
+ Nif::NIFFile::ptr nif = Nif::NIFFile::create(name);
+ if(nif->numRoots() < 1)
+ {
+ nif->warn("Found no root nodes in "+name+".");
+ return;
+ }
+
+ const Nif::Record *r = nif->getRoot(0);
+ assert(r != NULL);
+
+ const Nif::Node *node = dynamic_cast<const Nif::Node*>(r);
+ if(node == NULL)
+ {
+ nif->warn("First root in "+name+" was not a node, but a "+
+ r->recName+".");
+ return;
+ }
+
+ if(Ogre::SkeletonManager::getSingleton().resourceExists(name) ||
+ !NIFSkeletonLoader::createSkeleton(name, group, node).isNull())
+ {
+ // Create a base skeleton entity if this NIF needs one
+ createSkelBase(name, group, sceneMgr, node, objectlist);
+ }
+ createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0);
+ }
+
+ static void loadKf(Ogre::Skeleton *skel, const std::string &name,
+ TextKeyMap &textKeys, std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
+ {
+ Nif::NIFFile::ptr nif = Nif::NIFFile::create(name);
+ if(nif->numRoots() < 1)
+ {
+ nif->warn("Found no root nodes in "+name+".");
+ return;
+ }
+
+ const Nif::Record *r = nif->getRoot(0);
+ assert(r != NULL);
+
+ if(r->recType != Nif::RC_NiSequenceStreamHelper)
+ {
+ nif->warn("First root was not a NiSequenceStreamHelper, but a "+
+ r->recName+".");
+ return;
+ }
+ const Nif::NiSequenceStreamHelper *seq = static_cast<const Nif::NiSequenceStreamHelper*>(r);
+
+ Nif::ExtraPtr extra = seq->extra;
+ if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData)
+ {
+ nif->warn("First extra data was not a NiTextKeyExtraData, but a "+
+ (extra.empty() ? std::string("nil") : extra->recName)+".");
+ return;
+ }
+
+ extractTextKeys(static_cast<const Nif::NiTextKeyExtraData*>(extra.getPtr()), textKeys);
+
+ extra = extra->extra;
+ Nif::ControllerPtr ctrl = seq->controller;
+ for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next))
+ {
+ if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController)
+ {
+ nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName);
+ continue;
+ }
+
+ const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
+ const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
+
+ if(key->data.empty())
+ continue;
+ if(!skel->hasBone(strdata->string))
+ continue;
+
+ Ogre::Bone *trgtbone = skel->getBone(strdata->string);
+ Ogre::ControllerValueRealPtr srcval;
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr()));
+ Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, false));
+
+ ctrls.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ }
+};
+
+
+ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
+{
+ ObjectList objectlist;
+
+ Misc::StringUtils::toLower(name);
+ NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group);
+
+ for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ {
+ Ogre::Entity *entity = objectlist.mEntities[i];
+ if(!entity->isAttached())
+ parentNode->attachObject(entity);
+ }
+
+ return objectlist;
+}
+
+ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonename,
+ Ogre::SceneNode *parentNode,
+ std::string name, const std::string &group)
+{
+ ObjectList objectlist;
+
+ Misc::StringUtils::toLower(name);
+ NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group);
+
+ bool isskinned = false;
+ for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ {
+ Ogre::Entity *ent = objectlist.mEntities[i];
+ if(objectlist.mSkelBase != ent && ent->hasSkeleton())
+ {
+ isskinned = true;
+ break;
+ }
+ }
+
+ Ogre::Vector3 scale(1.0f);
+ if(bonename.find("Left") != std::string::npos)
+ scale.x *= -1.0f;
+
+ if(isskinned)
+ {
+ std::string filter = "@shape=tri "+bonename;
+ Misc::StringUtils::toLower(filter);
+ for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ {
+ Ogre::Entity *entity = objectlist.mEntities[i];
+ if(entity->hasSkeleton())
+ {
+ if(entity == objectlist.mSkelBase ||
+ entity->getMesh()->getName().find(filter) != std::string::npos)
+ parentNode->attachObject(entity);
+ }
+ else
+ {
+ if(entity->getMesh()->getName().find(filter) == std::string::npos)
+ entity->detachFromParent();
+ }
+ }
+ }
+ else
+ {
+ for(size_t i = 0;i < objectlist.mEntities.size();i++)
+ {
+ Ogre::Entity *entity = objectlist.mEntities[i];
+ if(!entity->isAttached())
+ {
+ Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity);
+ tag->setScale(scale);
+ }
+ }
+ }
+
+ return objectlist;
+}
+
+
+ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
+{
+ ObjectList objectlist;
+
+ Misc::StringUtils::toLower(name);
+ NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group, 0xC0000000);
+
+ if(objectlist.mSkelBase)
+ parentNode->attachObject(objectlist.mSkelBase);
+
+ return objectlist;
+}
+
+
+void Loader::createKfControllers(Ogre::Entity *skelBase,
+ const std::string &name,
+ TextKeyMap &textKeys,
+ std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
+{
+ NIFObjectLoader::loadKf(skelBase->getSkeleton(), name, textKeys, ctrls);
+}
+
+
+} // namespace NifOgre
diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp
new file mode 100644
index 0000000000..edad13a9a3
--- /dev/null
+++ b/components/nifogre/ogrenifloader.hpp
@@ -0,0 +1,103 @@
+/*
+ OpenMW - The completely unofficial reimplementation of Morrowind
+ Copyright (C) 2008-2010 Nicolay Korslund
+ Email: < korslund@gmail.com >
+ WWW: http://openmw.sourceforge.net/
+
+ This file (ogre_nif_loader.h) is part of the OpenMW package.
+
+ OpenMW is distributed as free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License
+ version 3, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ version 3 along with this program. If not, see
+ http://www.gnu.org/licenses/ .
+
+ */
+
+#ifndef OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP
+#define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP
+
+#include <OgreResource.h>
+#include <OgreController.h>
+
+#include <vector>
+#include <string>
+#include <map>
+
+
+// FIXME: This namespace really doesn't do anything Nif-specific. Any supportable
+// model format should go through this.
+namespace NifOgre
+{
+
+typedef std::multimap<float,std::string> TextKeyMap;
+static const char sTextKeyExtraDataID[] = "TextKeyExtraData";
+struct ObjectList {
+ Ogre::Entity *mSkelBase;
+ std::vector<Ogre::Entity*> mEntities;
+ std::vector<Ogre::ParticleSystem*> mParticles;
+ std::vector<Ogre::Light*> mLights;
+
+ std::map<int,TextKeyMap> mTextKeys;
+
+ std::vector<Ogre::Controller<Ogre::Real> > mControllers;
+
+ ObjectList() : mSkelBase(0)
+ { }
+};
+
+
+class Loader
+{
+public:
+ static ObjectList 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,
+ std::string name,
+ const std::string &group="General");
+
+ static ObjectList createObjectBase(Ogre::SceneNode *parentNode,
+ std::string name,
+ const std::string &group="General");
+
+ static void createKfControllers(Ogre::Entity *skelBase,
+ const std::string &name,
+ TextKeyMap &textKeys,
+ std::vector<Ogre::Controller<Ogre::Real> > &ctrls);
+};
+
+// FIXME: Should be with other general Ogre extensions.
+template<typename T>
+class NodeTargetValue : public Ogre::ControllerValue<T>
+{
+protected:
+ Ogre::Node *mNode;
+
+public:
+ NodeTargetValue(Ogre::Node *target) : mNode(target)
+ { }
+
+ virtual Ogre::Quaternion getRotation(T value) const = 0;
+ virtual Ogre::Vector3 getTranslation(T value) const = 0;
+ virtual Ogre::Vector3 getScale(T value) const = 0;
+
+ void setNode(Ogre::Node *target)
+ { mNode = target; }
+ Ogre::Node *getNode() const
+ { return mNode; }
+};
+typedef Ogre::SharedPtr<NodeTargetValue<Ogre::Real> > NodeTargetValueRealPtr;
+
+}
+
+#endif
diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp
new file mode 100644
index 0000000000..04bffdeab4
--- /dev/null
+++ b/components/nifogre/skeleton.cpp
@@ -0,0 +1,153 @@
+#include "skeleton.hpp"
+
+#include <OgreSkeletonManager.h>
+#include <OgreResource.h>
+#include <OgreSkeleton.h>
+#include <OgreBone.h>
+
+#include <components/nif/node.hpp>
+#include <components/misc/stringops.hpp>
+
+namespace NifOgre
+{
+
+void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent)
+{
+ Ogre::Bone *bone;
+ if(!skel->hasBone(node->name))
+ bone = skel->createBone(node->name);
+ else
+ bone = skel->createBone();
+ if(parent) parent->addChild(bone);
+ mNifToOgreHandleMap[node->recIndex] = bone->getHandle();
+
+ bone->setOrientation(node->trafo.rotation);
+ bone->setPosition(node->trafo.pos);
+ bone->setScale(Ogre::Vector3(node->trafo.scale));
+ bone->setBindingPose();
+
+ if(!(node->recType == Nif::RC_NiNode || /* Nothing special; children traversed below */
+ 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_NiBSParticleNode ||
+ node->recType == Nif::RC_NiCamera ||
+ node->recType == Nif::RC_NiAutoNormalParticles ||
+ node->recType == Nif::RC_NiRotatingParticles
+ ))
+ warn("Unhandled "+node->recName+" "+node->name+" in "+skel->getName());
+
+ Nif::ControllerPtr ctrl = node->controller;
+ while(!ctrl.empty())
+ {
+ if(!(ctrl->recType == Nif::RC_NiParticleSystemController ||
+ ctrl->recType == Nif::RC_NiVisController ||
+ ctrl->recType == Nif::RC_NiUVController ||
+ ctrl->recType == Nif::RC_NiKeyframeController ||
+ ctrl->recType == Nif::RC_NiGeomMorpherController
+ ))
+ warn("Unhandled "+ctrl->recName+" from node "+node->name+" in "+skel->getName());
+ ctrl = ctrl->next;
+ }
+
+ const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
+ if(ninode)
+ {
+ const Nif::NodeList &children = ninode->children;
+ for(size_t i = 0;i < children.length();i++)
+ {
+ if(!children[i].empty())
+ buildBones(skel, children[i].getPtr(), bone);
+ }
+ }
+}
+
+void NIFSkeletonLoader::loadResource(Ogre::Resource *resource)
+{
+ Ogre::Skeleton *skel = dynamic_cast<Ogre::Skeleton*>(resource);
+ OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!");
+
+ Nif::NIFFile::ptr nif(Nif::NIFFile::create(skel->getName()));
+ const Nif::Node *node = static_cast<const Nif::Node*>(nif->getRoot(0));
+
+ try {
+ buildBones(skel, node);
+ }
+ catch(std::exception &e) {
+ std::cerr<< "Exception while loading "<<skel->getName() <<std::endl;
+ std::cerr<< e.what() <<std::endl;
+ return;
+ }
+}
+
+
+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
+ * 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")
+ return true;
+
+ if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode)
+ {
+ const Nif::NiNode *ninode = static_cast<const Nif::NiNode*>(node);
+ const Nif::NodeList &children = ninode->children;
+ for(size_t i = 0;i < children.length();i++)
+ {
+ if(!children[i].empty())
+ {
+ if(needSkeleton(children[i].getPtr()))
+ return true;
+ }
+ }
+ return false;
+ }
+ if(node->recType == Nif::RC_NiTriShape)
+ return false;
+
+ return true;
+}
+
+Ogre::SkeletonPtr NIFSkeletonLoader::createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node)
+{
+ bool forceskel = false;
+ std::string::size_type extpos = name.rfind('.');
+ if(extpos != std::string::npos && name.compare(extpos, name.size()-extpos, ".nif") == 0)
+ {
+ Ogre::ResourceGroupManager &resMgr = Ogre::ResourceGroupManager::getSingleton();
+ forceskel = resMgr.resourceExistsInAnyGroup(name.substr(0, extpos)+".kf");
+ }
+
+ if(forceskel || needSkeleton(node))
+ {
+ Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton();
+ return skelMgr.create(name, group, true, &sLoaders[name]);
+ }
+
+ return Ogre::SkeletonPtr();
+}
+
+// Looks up an Ogre Bone handle ID from a NIF's record index. Should only be
+// used when the bone name is insufficient as this is a relatively slow lookup
+int NIFSkeletonLoader::lookupOgreBoneHandle(const std::string &nifname, int idx)
+{
+ LoaderMap::const_iterator loader = sLoaders.find(nifname);
+ if(loader != sLoaders.end())
+ {
+ std::map<int,int>::const_iterator entry = loader->second.mNifToOgreHandleMap.find(idx);
+ if(entry != loader->second.mNifToOgreHandleMap.end())
+ return entry->second;
+ }
+ throw std::runtime_error("Invalid NIF record lookup ("+nifname+", index "+Ogre::StringConverter::toString(idx)+")");
+}
+
+NIFSkeletonLoader::LoaderMap NIFSkeletonLoader::sLoaders;
+
+}
diff --git a/components/nifogre/skeleton.hpp b/components/nifogre/skeleton.hpp
new file mode 100644
index 0000000000..9ec3a0c825
--- /dev/null
+++ b/components/nifogre/skeleton.hpp
@@ -0,0 +1,62 @@
+#ifndef COMPONENTS_NIFOGRE_SKELETON_HPP
+#define COMPONENTS_NIFOGRE_SKELETON_HPP
+
+#include <iostream>
+#include <string>
+#include <cassert>
+
+#include <OgreResource.h>
+
+#include "ogrenifloader.hpp"
+
+namespace Nif
+{
+ class NiTextKeyExtraData;
+ class Node;
+ class NiKeyframeController;
+}
+
+namespace NifOgre
+{
+
+/** Manual resource loader for NIF skeletons. This is the main class
+ responsible for translating the internal NIF skeleton structure into
+ something Ogre can use (includes animations and node TextKeyData).
+ */
+class NIFSkeletonLoader : public Ogre::ManualResourceLoader
+{
+ static void warn(const std::string &msg)
+ {
+ std::cerr << "NIFSkeletonLoader: Warn: " << msg << std::endl;
+ }
+
+ static void fail(const std::string &msg)
+ {
+ std::cerr << "NIFSkeletonLoader: Fail: "<< msg << std::endl;
+ abort();
+ }
+
+ void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent=NULL);
+
+ static bool needSkeleton(const Nif::Node *node);
+
+ // Lookup to retrieve an Ogre bone handle for a given Nif record index
+ std::map<int,int> mNifToOgreHandleMap;
+
+ typedef std::map<std::string,NIFSkeletonLoader> LoaderMap;
+ static LoaderMap sLoaders;
+
+public:
+ void loadResource(Ogre::Resource *resource);
+
+ static Ogre::SkeletonPtr createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node);
+
+ // Looks up an Ogre Bone handle ID from a NIF's record index. Should only
+ // be used when the bone name is insufficient as this is a relatively slow
+ // lookup
+ static int lookupOgreBoneHandle(const std::string &nifname, int idx);
+};
+
+}
+
+#endif
diff --git a/components/nifogre/tests/Makefile b/components/nifogre/tests/Makefile
new file mode 100644
index 0000000000..a7c50d1003
--- /dev/null
+++ b/components/nifogre/tests/Makefile
@@ -0,0 +1,19 @@
+GCC=g++
+
+all: ogre_manualresource_test ogre_nif_test ogre_skeleton_test
+
+I_OGRE=$(shell pkg-config --cflags OGRE)
+L_OGRE=$(shell pkg-config --libs OGRE)
+
+ogre_manualresource_test: ogre_manualresource_test.cpp
+ $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE)
+
+ogre_skeleton_test: ogre_skeleton_test.cpp
+ $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE)
+
+ogre_nif_test: ogre_nif_test.cpp ../../nif/nif_file.cpp ../../bsa/bsa_file.cpp ../../bsa/bsa_archive.cpp ../../tools/stringops.cpp ../../mangle/vfs/servers/ogre_vfs.cpp ../ogre_nif_loader.cpp
+ $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE)
+
+clean:
+ rm *_test
+
diff --git a/components/nifogre/tests/ogre_common.cpp b/components/nifogre/tests/ogre_common.cpp
new file mode 100644
index 0000000000..657913f30f
--- /dev/null
+++ b/components/nifogre/tests/ogre_common.cpp
@@ -0,0 +1,97 @@
+#include <Ogre.h>
+#include <iostream>
+
+using namespace std;
+using namespace Ogre;
+
+Root *root;
+RenderWindow *window;
+SceneManager *mgr;
+
+int shot = 0;
+
+// Lets you quit by closing the window
+struct QuitListener : FrameListener
+{
+ bool frameStarted(const FrameEvent& evt)
+ {
+#ifdef SCREENSHOT
+ if(shot == 1) window->writeContentsToFile("nif.png");
+ if(shot < 2) shot++;
+#endif
+
+ if(window->isClosed())
+ return false;
+ return true;
+ }
+} qlistener;
+
+// This has to be packaged in a struct because C++ sucks
+struct C
+{
+ static void doTest();
+};
+
+int main(int argc, char**args)
+{
+ // Disable Ogre logging
+ new LogManager;
+ Log *log = LogManager::getSingleton().createLog("");
+ log->setDebugOutputEnabled(false);
+
+ // Set up Root.
+ root = new Root("plugins.cfg","ogre.cfg","");
+
+ if(!root->restoreConfig())
+ {
+ cout << "WARNING: we do NOT recommend fullscreen mode!\n";
+ if(!root->showConfigDialog())
+ return 1;
+ }
+
+ mgr = root->createSceneManager(ST_GENERIC);
+
+ // Only render if there are arguments on the command line (we don't
+ // care what they are.)
+ bool render = (argc>=2);
+
+ // Create a window
+ window = root->initialise(true, "Test");
+ if(render)
+ {
+ // More initialization
+ Camera *cam = mgr->createCamera("cam");
+ Viewport *vp = window->addViewport(cam);
+ cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
+ cam->setFOVy(Degree(55));
+ cam->setPosition(0,0,0);
+ cam->lookAt(0,0,10);
+ cam->setNearClipDistance(1);
+
+ root->addFrameListener(&qlistener);
+
+ // Background color
+ vp->setBackgroundColour(ColourValue(0.5,0.5,0.5));
+
+ mgr->setAmbientLight(ColourValue(1,1,1));
+ }
+
+ // Run the actual test
+ C::doTest();
+
+ // Render loop
+ if(render)
+ {
+ cout << "Rendering. Close the window to exit.\n";
+ root->startRendering();
+ }
+
+ // Cleanup
+ delete root;
+ return 0;
+}
+
+void doTest()
+{
+ cout << "hello\n";
+}
diff --git a/components/nifogre/tests/ogre_manualresource_test.cpp b/components/nifogre/tests/ogre_manualresource_test.cpp
new file mode 100644
index 0000000000..75e169d540
--- /dev/null
+++ b/components/nifogre/tests/ogre_manualresource_test.cpp
@@ -0,0 +1,40 @@
+/*
+ This is a test of the manual resource loader interface to Ogre,
+ applied to manually created meshes. It defines a simple mesh
+ consisting of two triangles, and creates three instances of it as
+ different meshes using the same loader. It is a precursor to the NIF
+ loading code. If the Ogre interface changes and you have to change
+ this test, then you will also have to change parts of the NIF
+ loader.
+ */
+
+#include "ogre_mesh_common.cpp"
+
+void C::doTest()
+{
+ // Create a couple of manual meshes
+ makeMesh("mesh1.mm");
+ makeMesh("mesh2.mm");
+ makeMesh("mesh3.mm");
+
+ // Display the meshes
+ {
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node");
+ Entity *ent = mgr->createEntity("Mesh1", "mesh1.mm");
+ node->attachObject(ent);
+ node->setPosition(3,1,8);
+ }
+
+ {
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node2");
+ Entity *ent = mgr->createEntity("Mesh2", "mesh2.mm");
+ node->attachObject(ent);
+ node->setPosition(-3,1,8);
+ }
+ {
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node3");
+ Entity *ent = mgr->createEntity("Mesh3", "mesh3.mm");
+ node->attachObject(ent);
+ node->setPosition(0,-2,8);
+ }
+}
diff --git a/components/nifogre/tests/ogre_mesh_common.cpp b/components/nifogre/tests/ogre_mesh_common.cpp
new file mode 100644
index 0000000000..72e51e3317
--- /dev/null
+++ b/components/nifogre/tests/ogre_mesh_common.cpp
@@ -0,0 +1,69 @@
+#include "ogre_common.cpp"
+
+struct MyMeshLoader : ManualResourceLoader
+{
+ void loadResource(Resource *resource)
+ {
+ Mesh *mesh = dynamic_cast<Mesh*>(resource);
+ assert(mesh);
+
+ const String& name = mesh->getName();
+ cout << "Manually loading mesh " << name << endl;
+
+ // Create the mesh here
+ int numVerts = 4;
+ int numFaces = 2*3;
+ const float vertices[] =
+ { -1,-1,0, 1,-1,0,
+ 1,1,0, -1,1,0 };
+
+ const short faces[] =
+ { 0,2,1, 0,3,2 };
+
+ mesh->sharedVertexData = new VertexData();
+ mesh->sharedVertexData->vertexCount = numVerts;
+
+ VertexDeclaration* decl = mesh->sharedVertexData->vertexDeclaration;
+
+ decl->addElement(0, 0, VET_FLOAT3, VES_POSITION);
+
+ HardwareVertexBufferSharedPtr vbuf =
+ HardwareBufferManager::getSingleton().createVertexBuffer(
+ VertexElement::getTypeSize(VET_FLOAT3),
+ numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
+
+ // Upload the vertex data to the card
+ vbuf->writeData(0, vbuf->getSizeInBytes(), vertices, true);
+
+ // Set vertex buffer binding so buffer 0 is bound to our vertex buffer
+ VertexBufferBinding* bind = mesh->sharedVertexData->vertexBufferBinding;
+ bind->setBinding(0, vbuf);
+
+ /// Allocate index buffer of the requested number of faces
+ HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton().
+ createIndexBuffer(HardwareIndexBuffer::IT_16BIT,
+ numFaces,
+ HardwareBuffer::HBU_STATIC_WRITE_ONLY);
+
+ /// Upload the index data to the card
+ ibuf->writeData(0, ibuf->getSizeInBytes(), faces, true);
+
+ SubMesh* sub = mesh->createSubMesh(name+"tris");
+ sub->useSharedVertices = true;
+
+ /// Set parameters of the submesh
+ sub->indexData->indexBuffer = ibuf;
+ sub->indexData->indexCount = numFaces;
+ sub->indexData->indexStart = 0;
+
+ mesh->_setBounds(AxisAlignedBox(-1.1,-1.1,-1.1,1.1,1.1,1.1));
+ mesh->_setBoundingSphereRadius(2);
+ }
+};
+
+MyMeshLoader mml;
+
+MeshPtr makeMesh(const string &name)
+{
+ return MeshManager::getSingleton().createManual(name, "General", &mml);
+}
diff --git a/components/nifogre/tests/ogre_nif_test.cpp b/components/nifogre/tests/ogre_nif_test.cpp
new file mode 100644
index 0000000000..decd43df57
--- /dev/null
+++ b/components/nifogre/tests/ogre_nif_test.cpp
@@ -0,0 +1,50 @@
+#include "../ogre_nif_loader.hpp"
+#include "../../bsa/bsa_archive.hpp"
+
+//#define SCREENSHOT
+
+#include "ogre_common.cpp"
+
+//const char* mesh = "meshes\\a\\towershield_steel.nif";
+//const char* mesh = "meshes\\r\\bonelord.nif";
+//const char* mesh = "meshes\\m\\text_scroll_open_01.nif";
+const char* mesh = "meshes\\f\\ex_ashl_a_banner_r.nif";
+
+void C::doTest()
+{
+ // Add Morrowind.bsa resource location
+ Bsa::addBSA("../../data/Morrowind.bsa");
+
+ // Insert the mesh
+ NifOgre::NIFLoader::load(mesh);
+ NifOgre::NIFLoader::load(mesh);
+
+ /*
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node");
+ Entity *ent = mgr->createEntity("Mesh1", mesh);
+ node->attachObject(ent);
+
+ // Works great for the scroll
+ node->setPosition(0,4,50);
+ node->pitch(Degree(20));
+ node->roll(Degree(10));
+ node->yaw(Degree(-10));
+
+ /* Bone lord
+ node->setPosition(0,-70,170);
+ node->pitch(Degree(-90));
+ */
+
+ // Display it from two different angles - shield and banner
+ const int sep = 45;
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node");
+ Entity *ent = mgr->createEntity("Mesh1", mesh);
+ node->attachObject(ent);
+ node->setPosition(sep,0,130);
+ node = node->createChildSceneNode("node2");
+ ent = mgr->createEntity("Mesh2", mesh);
+ node->attachObject(ent);
+ node->setPosition(-2*sep,0,0);
+ node->yaw(Degree(180));
+ //*/
+}
diff --git a/components/nifogre/tests/ogre_skeleton_test.cpp b/components/nifogre/tests/ogre_skeleton_test.cpp
new file mode 100644
index 0000000000..df9139b95b
--- /dev/null
+++ b/components/nifogre/tests/ogre_skeleton_test.cpp
@@ -0,0 +1,21 @@
+#include "ogre_common.cpp"
+
+void C::doTest()
+{
+ SkeletonManager &skm = SkeletonManager::getSingleton();
+
+ SkeletonPtr skp = skm.create("MySkel", "General");
+
+ cout << "hello\n";
+ /*
+ MeshPtr msh = makeMesh("mesh1");
+
+ // Display the mesh
+ {
+ SceneNode *node = mgr->getRootSceneNode()->createChildSceneNode("node");
+ Entity *ent = mgr->createEntity("Mesh1", "mesh1");
+ node->attachObject(ent);
+ node->setPosition(0,0,4);
+ }
+ */
+}
diff --git a/components/nifogre/tests/output/ogre_manualresource_test.out b/components/nifogre/tests/output/ogre_manualresource_test.out
new file mode 100644
index 0000000000..2eab2d50dd
--- /dev/null
+++ b/components/nifogre/tests/output/ogre_manualresource_test.out
@@ -0,0 +1,3 @@
+Manually loading mesh mesh1.mm
+Manually loading mesh mesh2.mm
+Manually loading mesh mesh3.mm
diff --git a/components/nifogre/tests/output/ogre_nif_test.out b/components/nifogre/tests/output/ogre_nif_test.out
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/components/nifogre/tests/output/ogre_nif_test.out
diff --git a/components/nifogre/tests/output/ogre_skeleton_test.out b/components/nifogre/tests/output/ogre_skeleton_test.out
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/components/nifogre/tests/output/ogre_skeleton_test.out
@@ -0,0 +1 @@
+hello
diff --git a/components/nifogre/tests/plugins.cfg b/components/nifogre/tests/plugins.cfg
new file mode 100644
index 0000000000..9133aec32d
--- /dev/null
+++ b/components/nifogre/tests/plugins.cfg
@@ -0,0 +1,12 @@
+# Defines plugins to load
+
+# Define plugin folder
+PluginFolder=/usr/local/lib/OGRE
+
+# Define plugins
+Plugin=RenderSystem_GL
+Plugin=Plugin_ParticleFX
+Plugin=Plugin_OctreeSceneManager
+
+
+
diff --git a/components/nifogre/tests/test.sh b/components/nifogre/tests/test.sh
new file mode 100755
index 0000000000..2d07708adc
--- /dev/null
+++ b/components/nifogre/tests/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+make || exit
+
+mkdir -p output
+
+PROGS=*_test
+
+for a in $PROGS; do
+ if [ -f "output/$a.out" ]; then
+ echo "Running $a:"
+ ./$a | diff output/$a.out -
+ else
+ echo "Creating $a.out"
+ ./$a > "output/$a.out"
+ git add "output/$a.out"
+ fi
+done
diff --git a/components/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp
new file mode 100644
index 0000000000..191b4ac2fe
--- /dev/null
+++ b/components/nifoverrides/nifoverrides.cpp
@@ -0,0 +1,38 @@
+#include "nifoverrides.hpp"
+
+#include <OgreStringConverter.h>
+
+#include <../components/misc/stringops.hpp>
+
+
+using namespace NifOverrides;
+
+Ogre::ConfigFile Overrides::mTransparencyOverrides = Ogre::ConfigFile();
+
+void Overrides::loadTransparencyOverrides (const std::string& file)
+{
+ mTransparencyOverrides.load(file);
+}
+
+TransparencyResult Overrides::getTransparencyOverride(const std::string& texture)
+{
+ TransparencyResult result;
+ result.first = false;
+
+ std::string tex = texture;
+ Misc::StringUtils::toLower(tex);
+
+ Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator();
+ while (seci.hasMoreElements())
+ {
+ Ogre::String sectionName = seci.peekNextKey();
+ if (sectionName == tex)
+ {
+ result.first = true;
+ result.second = Ogre::StringConverter::parseInt(mTransparencyOverrides.getSetting("alphaRejectValue", sectionName));
+ break;
+ }
+ seci.getNext();
+ }
+ return result;
+}
diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp
new file mode 100644
index 0000000000..ba2e4cc3c3
--- /dev/null
+++ b/components/nifoverrides/nifoverrides.hpp
@@ -0,0 +1,23 @@
+#ifndef OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP
+#define OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP
+
+#include <OgreConfigFile.h>
+
+namespace NifOverrides
+{
+
+ typedef std::pair<bool, int> TransparencyResult;
+
+ /// \brief provide overrides for some model / texture properties that bethesda has chosen poorly
+ class Overrides
+ {
+ public:
+ static Ogre::ConfigFile mTransparencyOverrides;
+ void loadTransparencyOverrides (const std::string& file);
+
+ static TransparencyResult getTransparencyOverride(const std::string& texture);
+ };
+
+}
+
+#endif
diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp
new file mode 100644
index 0000000000..13015e39c9
--- /dev/null
+++ b/components/settings/settings.cpp
@@ -0,0 +1,169 @@
+#include "settings.hpp"
+
+#include <fstream>
+#include <stdexcept>
+
+#include <OgreResourceGroupManager.h>
+#include <OgreStringConverter.h>
+
+using namespace Settings;
+
+Ogre::ConfigFile Manager::mFile = Ogre::ConfigFile();
+Ogre::ConfigFile Manager::mDefaultFile = Ogre::ConfigFile();
+CategorySettingVector Manager::mChangedSettings = CategorySettingVector();
+CategorySettingValueMap Manager::mNewSettings = CategorySettingValueMap();
+
+void Manager::loadUser (const std::string& file)
+{
+ mFile.load(file);
+}
+
+void Manager::loadDefault (const std::string& file)
+{
+ mDefaultFile.load(file);
+}
+
+void Manager::saveUser(const std::string& file)
+{
+ std::fstream fout(file.c_str(), std::ios::out);
+
+ Ogre::ConfigFile::SectionIterator seci = mFile.getSectionIterator();
+
+ while (seci.hasMoreElements())
+ {
+ Ogre::String sectionName = seci.peekNextKey();
+
+ if (sectionName.length() > 0)
+ fout << '\n' << '[' << seci.peekNextKey() << ']' << '\n';
+
+ Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
+ Ogre::ConfigFile::SettingsMultiMap::iterator i;
+ for (i = settings->begin(); i != settings->end(); ++i)
+ {
+ fout << i->first.c_str() << " = " << i->second.c_str() << '\n';
+ }
+
+ CategorySettingValueMap::iterator it = mNewSettings.begin();
+ while (it != mNewSettings.end())
+ {
+ if (it->first.first == sectionName)
+ {
+ fout << it->first.second << " = " << it->second << '\n';
+ mNewSettings.erase(it++);
+ }
+ else
+ ++it;
+ }
+ }
+
+ std::string category = "";
+ for (CategorySettingValueMap::iterator it = mNewSettings.begin();
+ it != mNewSettings.end(); ++it)
+ {
+ if (category != it->first.first)
+ {
+ category = it->first.first;
+ fout << '\n' << '[' << category << ']' << '\n';
+ }
+ fout << it->first.second << " = " << it->second << '\n';
+ }
+
+ fout.close();
+}
+
+const std::string Manager::getString (const std::string& setting, const std::string& category)
+{
+ if (mNewSettings.find(std::make_pair(category, setting)) != mNewSettings.end())
+ return mNewSettings[std::make_pair(category, setting)];
+
+ std::string defaultval = mDefaultFile.getSetting(setting, category, "NOTFOUND");
+ std::string val = mFile.getSetting(setting, category, defaultval);
+
+ if (val == "NOTFOUND")
+ throw std::runtime_error("Trying to retrieve a non-existing setting: " + setting + " Make sure the settings-default.cfg file was properly installed.");
+ return val;
+}
+
+const float Manager::getFloat (const std::string& setting, const std::string& category)
+{
+ return Ogre::StringConverter::parseReal( getString(setting, category) );
+}
+
+const int Manager::getInt (const std::string& setting, const std::string& category)
+{
+ return Ogre::StringConverter::parseInt( getString(setting, category) );
+}
+
+const bool Manager::getBool (const std::string& setting, const std::string& category)
+{
+ return Ogre::StringConverter::parseBool( getString(setting, category) );
+}
+
+void Manager::setString (const std::string& setting, const std::string& category, const std::string& value)
+{
+ CategorySetting s = std::make_pair(category, setting);
+
+ bool found=false;
+ try
+ {
+ Ogre::ConfigFile::SettingsIterator it = mFile.getSettingsIterator(category);
+ while (it.hasMoreElements())
+ {
+ Ogre::ConfigFile::SettingsMultiMap::iterator i = it.current();
+
+ if ((*i).first == setting)
+ {
+ if ((*i).second != value)
+ {
+ mChangedSettings.push_back(std::make_pair(category, setting));
+ (*i).second = value;
+ }
+ found = true;
+ }
+
+ it.getNext();
+ }
+ }
+ catch (Ogre::Exception&)
+ {}
+
+ if (!found)
+ {
+ if (mNewSettings.find(s) != mNewSettings.end())
+ {
+ if (mNewSettings[s] != value)
+ {
+ mChangedSettings.push_back(std::make_pair(category, setting));
+ mNewSettings[s] = value;
+ }
+ }
+ else
+ {
+ if (mDefaultFile.getSetting(setting, category) != value)
+ mChangedSettings.push_back(std::make_pair(category, setting));
+ mNewSettings[s] = value;
+ }
+ }
+}
+
+void Manager::setInt (const std::string& setting, const std::string& category, const int value)
+{
+ setString(setting, category, Ogre::StringConverter::toString(value));
+}
+
+void Manager::setFloat (const std::string& setting, const std::string& category, const float value)
+{
+ setString(setting, category, Ogre::StringConverter::toString(value));
+}
+
+void Manager::setBool (const std::string& setting, const std::string& category, const bool value)
+{
+ setString(setting, category, Ogre::StringConverter::toString(value));
+}
+
+const CategorySettingVector Manager::apply()
+{
+ CategorySettingVector vec = mChangedSettings;
+ mChangedSettings.clear();
+ return vec;
+}
diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp
new file mode 100644
index 0000000000..e9858eb945
--- /dev/null
+++ b/components/settings/settings.hpp
@@ -0,0 +1,52 @@
+#ifndef _COMPONENTS_SETTINGS_H
+#define _COMPONENTS_SETTINGS_H
+
+#include <OgreConfigFile.h>
+
+namespace Settings
+{
+ typedef std::pair < std::string, std::string > CategorySetting;
+ typedef std::vector< std::pair<std::string, std::string> > CategorySettingVector;
+ typedef std::map < CategorySetting, std::string > CategorySettingValueMap;
+
+ ///
+ /// \brief Settings management (can change during runtime)
+ ///
+ class Manager
+ {
+ public:
+ static Ogre::ConfigFile mFile;
+ static Ogre::ConfigFile mDefaultFile;
+
+ static CategorySettingVector mChangedSettings;
+ ///< tracks all the settings that were changed since the last apply() call
+
+ static CategorySettingValueMap mNewSettings;
+ ///< tracks all the settings that are in the default file, but not in user file yet
+
+ void loadDefault (const std::string& file);
+ ///< load file as the default settings (can be overridden by user settings)
+
+ void loadUser (const std::string& file);
+ ///< load file as user settings
+
+ void saveUser (const std::string& file);
+ ///< save user settings to file
+
+ static const CategorySettingVector apply();
+ ///< returns the list of changed settings and then clears it
+
+ static const int getInt (const std::string& setting, const std::string& category);
+ static const float getFloat (const std::string& setting, const std::string& category);
+ static const std::string getString (const std::string& setting, const std::string& category);
+ static const bool getBool (const std::string& setting, const std::string& category);
+
+ static void setInt (const std::string& setting, const std::string& category, const int value);
+ static void setFloat (const std::string& setting, const std::string& category, const float value);
+ static void setString (const std::string& setting, const std::string& category, const std::string& value);
+ static void setBool (const std::string& setting, const std::string& category, const bool value);
+ };
+
+}
+
+#endif // _COMPONENTS_SETTINGS_H
diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp
new file mode 100644
index 0000000000..ce2118cdbd
--- /dev/null
+++ b/components/terrain/chunk.cpp
@@ -0,0 +1,169 @@
+#include "chunk.hpp"
+
+#include <OgreSceneNode.h>
+#include <OgreHardwareBufferManager.h>
+
+#include "quadtreenode.hpp"
+#include "world.hpp"
+#include "storage.hpp"
+
+namespace Terrain
+{
+
+ Chunk::Chunk(QuadTreeNode* node, short lodLevel)
+ : mNode(node)
+ , mVertexLod(lodLevel)
+ , mAdditionalLod(0)
+ {
+ mVertexData = OGRE_NEW Ogre::VertexData;
+ mVertexData->vertexStart = 0;
+
+ // Set the total number of vertices
+ size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1);
+ numVertsOneSide /= 1 << lodLevel;
+ numVertsOneSide += 1;
+ assert((int)numVertsOneSide == ESM::Land::LAND_SIZE);
+ mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
+
+ // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
+ Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
+
+ Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
+ size_t nextBuffer = 0;
+
+ // Positions
+ vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
+ mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
+ mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
+ // Normals
+ vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
+ mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
+ mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
+
+ // UV texture coordinates
+ vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
+ Ogre::VES_TEXTURE_COORDINATES, 0);
+ Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide);
+
+ // Colours
+ vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
+ mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
+ mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
+
+ mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(),
+ mVertexBuffer, mNormalBuffer, mColourBuffer);
+
+ mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
+ mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);
+ mVertexData->vertexBufferBinding->setBinding(2, uvBuf);
+ mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer);
+
+ mIndexData = OGRE_NEW Ogre::IndexData();
+ mIndexData->indexStart = 0;
+ }
+
+
+
+ void Chunk::updateIndexBuffer()
+ {
+ // Fetch a suitable index buffer (which may be shared)
+ size_t ourLod = mVertexLod + mAdditionalLod;
+
+ int flags = 0;
+
+ for (int i=0; i<4; ++i)
+ {
+ QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i);
+
+ // If the neighbour isn't currently rendering itself,
+ // go up until we find one. NOTE: We don't need to go down,
+ // because in that case neighbour's detail would be higher than
+ // our detail and the neighbour would handle stitching by itself.
+ while (neighbour && !neighbour->hasChunk())
+ neighbour = neighbour->getParent();
+
+ size_t lod = 0;
+ if (neighbour)
+ lod = neighbour->getActualLodLevel();
+
+ if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
+ lod = 0; // neighbours with more detail will do the stitching themselves
+
+ // Use 4 bits for each LOD delta
+ if (lod > 0)
+ {
+ assert (lod - ourLod < (1 << 4));
+ flags |= int(lod - ourLod) << (4*i);
+ }
+ }
+
+ flags |= ((int)mAdditionalLod) << (4*4);
+
+ size_t numIndices;
+ mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices);
+ mIndexData->indexCount = numIndices;
+ mIndexData->indexBuffer = mIndexBuffer;
+ }
+
+ Chunk::~Chunk()
+ {
+ OGRE_DELETE mVertexData;
+ OGRE_DELETE mIndexData;
+ }
+
+ void Chunk::setMaterial(const Ogre::MaterialPtr &material)
+ {
+ mMaterial = material;
+ }
+
+ const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
+ {
+ return mNode->getBoundingBox();
+ }
+
+ Ogre::Real Chunk::getBoundingRadius(void) const
+ {
+ return mNode->getBoundingBox().getHalfSize().length();
+ }
+
+ void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
+ {
+ queue->addRenderable(this, mRenderQueueID);
+ }
+
+ void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
+ bool debugRenderables)
+ {
+ visitor->visit(this, 0, false);
+ }
+
+ const Ogre::MaterialPtr& Chunk::getMaterial(void) const
+ {
+ return mMaterial;
+ }
+
+ void Chunk::getRenderOperation(Ogre::RenderOperation& op)
+ {
+ assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!");
+ op.useIndexes = true;
+ op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
+ op.vertexData = mVertexData;
+ op.indexData = mIndexData;
+ }
+
+ void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
+ {
+ *xform = getParentSceneNode()->_getFullTransform();
+ }
+
+ Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
+ {
+ return getParentSceneNode()->getSquaredViewDepth(cam);
+ }
+
+ const Ogre::LightList& Chunk::getLights(void) const
+ {
+ return queryLights();
+ }
+
+}
diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp
new file mode 100644
index 0000000000..d74c65ba6d
--- /dev/null
+++ b/components/terrain/chunk.hpp
@@ -0,0 +1,63 @@
+#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
+#define COMPONENTS_TERRAIN_TERRAINBATCH_H
+
+#include <OgreRenderable.h>
+#include <OgreMovableObject.h>
+
+namespace Terrain
+{
+
+ class QuadTreeNode;
+
+ /**
+ * @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
+ */
+ class Chunk : public Ogre::Renderable, public Ogre::MovableObject
+ {
+ public:
+ /// @param lodLevel LOD level for the vertex buffer.
+ Chunk (QuadTreeNode* node, short lodLevel);
+ virtual ~Chunk();
+
+ void setMaterial (const Ogre::MaterialPtr& material);
+
+ /// Set additional LOD applied on top of vertex LOD. \n
+ /// This is achieved by changing the index buffer to omit vertices.
+ void setAdditionalLod (size_t lod) { mAdditionalLod = lod; }
+ size_t getAdditionalLod() { return mAdditionalLod; }
+
+ void updateIndexBuffer();
+
+ // Inherited from MovableObject
+ virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
+ virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
+ virtual Ogre::Real getBoundingRadius(void) const;
+ virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
+ virtual void visitRenderables(Renderable::Visitor* visitor,
+ bool debugRenderables = false);
+
+ // Inherited from Renderable
+ virtual const Ogre::MaterialPtr& getMaterial(void) const;
+ virtual void getRenderOperation(Ogre::RenderOperation& op);
+ virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
+ virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
+ virtual const Ogre::LightList& getLights(void) const;
+
+ private:
+ QuadTreeNode* mNode;
+ Ogre::MaterialPtr mMaterial;
+
+ size_t mVertexLod;
+ size_t mAdditionalLod;
+
+ Ogre::VertexData* mVertexData;
+ Ogre::IndexData* mIndexData;
+ Ogre::HardwareVertexBufferSharedPtr mVertexBuffer;
+ Ogre::HardwareVertexBufferSharedPtr mNormalBuffer;
+ Ogre::HardwareVertexBufferSharedPtr mColourBuffer;
+ Ogre::HardwareIndexBufferSharedPtr mIndexBuffer;
+ };
+
+}
+
+#endif
diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp
new file mode 100644
index 0000000000..ebf6046ff6
--- /dev/null
+++ b/components/terrain/material.cpp
@@ -0,0 +1,300 @@
+#include "material.hpp"
+
+#include <OgreMaterialManager.h>
+#include <OgreTechnique.h>
+#include <OgrePass.h>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+namespace
+{
+
+int getBlendmapIndexForLayer (int layerIndex)
+{
+ return std::floor((layerIndex-1)/4.f);
+}
+
+std::string getBlendmapComponentForLayer (int layerIndex)
+{
+ int n = (layerIndex-1)%4;
+ if (n == 0)
+ return "x";
+ if (n == 1)
+ return "y";
+ if (n == 2)
+ return "z";
+ else
+ return "w";
+}
+
+}
+
+namespace Terrain
+{
+
+ MaterialGenerator::MaterialGenerator(bool shaders)
+ : mShaders(shaders)
+ , mShadows(false)
+ , mSplitShadows(false)
+ {
+
+ }
+
+ Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat)
+ {
+ return create(mat, false, false);
+ }
+
+ Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat)
+ {
+ return create(mat, true, false);
+ }
+
+ Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap(Ogre::MaterialPtr mat)
+ {
+ return create(mat, false, true);
+ }
+
+ Ogre::MaterialPtr MaterialGenerator::create(Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap)
+ {
+ assert(!renderCompositeMap || !displayCompositeMap);
+ if (!mat.isNull())
+ {
+ sh::Factory::getInstance().destroyMaterialInstance(mat->getName());
+ Ogre::MaterialManager::getSingleton().remove(mat->getName());
+ }
+
+ static int count = 0;
+ std::stringstream name;
+ name << "terrain/mat" << count++;
+
+ if (!mShaders)
+ {
+ mat = Ogre::MaterialManager::getSingleton().create(name.str(),
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+ Ogre::Technique* technique = mat->getTechnique(0);
+ technique->removeAllPasses();
+
+ if (displayCompositeMap)
+ {
+ Ogre::Pass* pass = technique->createPass();
+ pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
+ pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
+ }
+ else
+ {
+ 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)
+ {
+ Ogre::Pass* pass = technique->createPass();
+ pass->setLightingEnabled(false);
+ pass->setVertexColourTracking(Ogre::TVC_NONE);
+ // TODO: How to handle fog?
+ pass->setFog(true, Ogre::FOG_NONE);
+
+ bool first = (layer == mLayerList.begin());
+
+ Ogre::TextureUnitState* tus;
+
+ if (!first)
+ {
+ pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
+ pass->setDepthFunction(Ogre::CMPF_EQUAL);
+
+ tus = pass->createTextureUnitState((*blend)->getName());
+ tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
+ Ogre::LBS_TEXTURE,
+ Ogre::LBS_TEXTURE);
+ tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
+ Ogre::LBS_TEXTURE,
+ Ogre::LBS_TEXTURE);
+ tus->setIsAlpha(true);
+ tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
+
+ float scale = (16/(16.f+1.f));
+ tus->setTextureScale(1.f/scale,1.f/scale);
+ }
+
+ // Add the actual layer texture on top of the alpha map.
+ tus = pass->createTextureUnitState("textures\\" + *layer);
+ if (!first)
+ tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
+ Ogre::LBS_TEXTURE,
+ Ogre::LBS_CURRENT);
+
+ tus->setTextureScale(1/16.f,1/16.f);
+
+ if (!first)
+ ++blend;
+ }
+
+ if (!renderCompositeMap)
+ {
+ Ogre::Pass* lightingPass = technique->createPass();
+ lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
+ lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
+ lightingPass->setFog(true, Ogre::FOG_NONE);
+ }
+ }
+
+ return mat;
+ }
+ else
+ {
+ sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str());
+ material->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
+
+ if (displayCompositeMap)
+ {
+ sh::MaterialInstancePass* p = material->createPass ();
+
+ p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
+ p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
+ p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true)));
+ p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false)));
+ 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")));
+
+ sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
+ tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
+ tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
+
+ // shadow. TODO: repeated, put in function
+ if (mShadows)
+ {
+ for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
+ {
+ sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
+ shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
+ }
+ }
+ p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
+ Ogre::StringConverter::toString(1))));
+
+ p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0)));
+ }
+ else
+ {
+
+ bool shadows = mShadows && !renderCompositeMap;
+
+ int layerOffset = 0;
+ while (layerOffset < (int)mLayerList.size())
+ {
+ int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
+
+ // Check how many layers we can fit in this pass
+ int numLayersInThisPass = 0;
+ int numBlendTextures = 0;
+ std::vector<std::string> blendTextures;
+ int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS;
+ if (shadows)
+ remainingTextureUnits -= (mSplitShadows ? 3 : 1);
+ while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size())
+ {
+ int layerIndex = numLayersInThisPass + layerOffset;
+
+ int neededTextureUnits=0;
+ int neededBlendTextures=0;
+
+ if (layerIndex != 0)
+ {
+ std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName();
+ if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
+ {
+ blendTextures.push_back(blendTextureName);
+ ++neededBlendTextures;
+ ++neededTextureUnits; // blend texture
+ }
+ }
+ ++neededTextureUnits; // layer texture
+ if (neededTextureUnits <= remainingTextureUnits)
+ {
+ // We can fit another!
+ remainingTextureUnits -= neededTextureUnits;
+ numBlendTextures += neededBlendTextures;
+ ++numLayersInThisPass;
+ }
+ else
+ break; // We're full
+ }
+
+
+ sh::MaterialInstancePass* p = material->createPass ();
+
+ p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
+ p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
+ if (layerOffset != 0)
+ {
+ p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
+ // Only write if depth is equal to the depth value written by the previous pass.
+ p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
+ }
+
+ p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
+ p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
+
+ 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))));
+
+ // blend maps
+ // the index of the first blend map used in this pass
+ int blendmapStart;
+ if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
+ blendmapStart = 0;
+ else
+ blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
+ for (int i = 0; i < numBlendTextures; ++i)
+ {
+ sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
+ blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
+ blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
+ }
+
+ // layer maps
+ for (int i = 0; i < numLayersInThisPass; ++i)
+ {
+ sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
+ diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i])));
+
+ 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)));
+ }
+ else
+ {
+ // just to make it shut up about blendmap_component_0 not existing in the first pass.
+ // it might be retrieved, but will never survive the preprocessing step.
+ p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
+ sh::makeProperty (new sh::StringValue("")));
+ }
+ }
+
+ // shadow
+ if (shadows)
+ {
+ for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
+ {
+ sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
+ shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
+ }
+ }
+ p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
+ Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
+
+ // 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)));
+
+ layerOffset += numLayersInThisPass;
+ }
+ }
+ }
+ return Ogre::MaterialManager::getSingleton().getByName(name.str());
+ }
+
+}
diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp
new file mode 100644
index 0000000000..330ed3d147
--- /dev/null
+++ b/components/terrain/material.hpp
@@ -0,0 +1,56 @@
+#ifndef COMPONENTS_TERRAIN_MATERIAL_H
+#define COMPONENTS_TERRAIN_MATERIAL_H
+
+#include <OgreMaterial.h>
+
+namespace Terrain
+{
+
+ class MaterialGenerator
+ {
+ public:
+ /// @param layerList layer textures
+ /// @param blendmapList blend textures
+ /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one),
+ /// 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; }
+ 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 enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
+
+ /// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
+ /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
+ /// a new material is created.
+ Ogre::MaterialPtr generate (Ogre::MaterialPtr mat);
+
+ /// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map.
+ /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
+ /// a new material is created.
+ Ogre::MaterialPtr generateForCompositeMap (Ogre::MaterialPtr mat);
+
+ /// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures
+ /// into one. The main difference compared to a normal material is that no shading is applied at this point.
+ /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
+ /// a new material is created.
+ Ogre::MaterialPtr generateForCompositeMapRTT (Ogre::MaterialPtr mat);
+
+ private:
+ Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap);
+
+ std::vector<std::string> mLayerList;
+ std::vector<Ogre::TexturePtr> mBlendmapList;
+ std::string mCompositeMap;
+ bool mShaders;
+ bool mShadows;
+ bool mSplitShadows;
+ };
+
+}
+
+#endif
diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp
new file mode 100644
index 0000000000..ef2c610138
--- /dev/null
+++ b/components/terrain/quadtreenode.cpp
@@ -0,0 +1,512 @@
+#include "quadtreenode.hpp"
+
+#include <OgreSceneManager.h>
+#include <OgreManualObject.h>
+
+#include "world.hpp"
+#include "chunk.hpp"
+#include "storage.hpp"
+
+#include "material.hpp"
+
+using namespace Terrain;
+
+namespace
+{
+ int Log2( int n )
+ {
+ assert(n > 0);
+ int targetlevel = 0;
+ while (n >>= 1) ++targetlevel;
+ return targetlevel;
+ }
+
+ // Utility functions for neighbour finding algorithm
+ ChildDirection reflect(ChildDirection dir, Direction dir2)
+ {
+ assert(dir != Root);
+
+ const int lookupTable[4][4] =
+ {
+ // NW NE SW SE
+ { SW, SE, NW, NE }, // N
+ { NE, NW, SE, SW }, // E
+ { SW, SE, NW, NE }, // S
+ { NE, NW, SE, SW } // W
+ };
+ return (ChildDirection)lookupTable[dir2][dir];
+ }
+
+ bool adjacent(ChildDirection dir, Direction dir2)
+ {
+ assert(dir != Root);
+ const bool lookupTable[4][4] =
+ {
+ // NW NE SW SE
+ { true, true, false, false }, // N
+ { false, true, false, true }, // E
+ { false, false, true, true }, // S
+ { true, false, true, false } // W
+ };
+ return lookupTable[dir2][dir];
+ }
+
+ // Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
+ // http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
+ QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir)
+ {
+ if (!currentNode->getParent())
+ return NULL; // Arrived at root node, the root node does not have neighbours
+
+ QuadTreeNode* nextNode;
+ if (adjacent(currentNode->getDirection(), dir))
+ nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
+ else
+ nextNode = currentNode->getParent();
+
+ if (nextNode && nextNode->hasChildren())
+ return nextNode->getChild(reflect(currentNode->getDirection(), dir));
+ else
+ return NULL;
+ }
+
+
+ // Ogre::AxisAlignedBox::distance is broken in 1.8.
+ Ogre::Real distance(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& v)
+ {
+
+ if (box.contains(v))
+ return 0;
+ else
+ {
+ Ogre::Vector3 maxDist(0,0,0);
+ const Ogre::Vector3& minimum = box.getMinimum();
+ const Ogre::Vector3& maximum = box.getMaximum();
+
+ if (v.x < minimum.x)
+ maxDist.x = minimum.x - v.x;
+ else if (v.x > maximum.x)
+ maxDist.x = v.x - maximum.x;
+
+ if (v.y < minimum.y)
+ maxDist.y = minimum.y - v.y;
+ else if (v.y > maximum.y)
+ maxDist.y = v.y - maximum.y;
+
+ if (v.z < minimum.z)
+ maxDist.z = minimum.z - v.z;
+ else if (v.z > maximum.z)
+ maxDist.z = v.z - maximum.z;
+
+ return maxDist.length();
+ }
+ }
+
+ // Create a 2D quad
+ void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
+ {
+ Ogre::ManualObject* manual = sceneMgr->createManualObject();
+
+ // Use identity view/projection matrices to get a 2d quad
+ manual->setUseIdentityProjection(true);
+ manual->setUseIdentityView(true);
+
+ manual->begin(material->getName());
+
+ float normLeft = left*2-1;
+ float normTop = top*2-1;
+ float normRight = right*2-1;
+ float normBottom = bottom*2-1;
+
+ manual->position(normLeft, normTop, 0.0);
+ manual->textureCoord(0, 1);
+ manual->position(normRight, normTop, 0.0);
+ manual->textureCoord(1, 1);
+ manual->position(normRight, normBottom, 0.0);
+ manual->textureCoord(1, 0);
+ manual->position(normLeft, normBottom, 0.0);
+ manual->textureCoord(0, 0);
+
+ manual->quad(0,1,2,3);
+
+ manual->end();
+
+ Ogre::AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+ manual->setBoundingBox(aabInf);
+
+ sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
+ }
+}
+
+QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
+ : mSize(size)
+ , mCenter(center)
+ , mParent(parent)
+ , mDirection(dir)
+ , mIsDummy(false)
+ , mSceneNode(NULL)
+ , 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)
+ mChildren[i] = NULL;
+ for (int i=0; i<4; ++i)
+ mNeighbours[i] = NULL;
+
+ if (mDirection == Root)
+ mSceneNode = mTerrain->getRootSceneNode();
+ else
+ mSceneNode = mTerrain->getSceneManager()->createSceneNode();
+ Ogre::Vector2 pos (0,0);
+ if (mParent)
+ pos = mParent->getCenter();
+ pos = mCenter - pos;
+ mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0));
+
+ mLodLevel = Log2(mSize);
+
+ mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
+}
+
+void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 &center)
+{
+ mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
+}
+
+QuadTreeNode::~QuadTreeNode()
+{
+ for (int i=0; i<4; ++i)
+ delete mChildren[i];
+ delete mChunk;
+ delete mMaterialGenerator;
+}
+
+QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
+{
+ return mNeighbours[static_cast<int>(dir)];
+}
+
+void QuadTreeNode::initNeighbours()
+{
+ for (int i=0; i<4; ++i)
+ mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
+
+ if (hasChildren())
+ for (int i=0; i<4; ++i)
+ mChildren[i]->initNeighbours();
+}
+
+void QuadTreeNode::initAabb()
+{
+ if (hasChildren())
+ {
+ for (int i=0; i<4; ++i)
+ {
+ mChildren[i]->initAabb();
+ mBounds.merge(mChildren[i]->getBoundingBox());
+ }
+ mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z),
+ Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z));
+ }
+ mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0),
+ mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
+}
+
+void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
+{
+ mBounds = box;
+}
+
+const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
+{
+ return mBounds;
+}
+
+void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loadingListener)
+{
+ const Ogre::AxisAlignedBox& bounds = getBoundingBox();
+ if (bounds.isNull())
+ return;
+
+ float dist = distance(mWorldBounds, cameraPos);
+
+ bool distantLand = mTerrain->getDistantLandEnabled();
+
+ // Make sure our scene node is attached
+ if (!mSceneNode->isInSceneGraph())
+ {
+ mParent->getSceneNode()->addChild(mSceneNode);
+ }
+
+ /// \todo implement error metrics or some other means of not using arbitrary values
+ /// (general quality needs to be user configurable as well)
+ size_t wantedLod = 0;
+ if (dist > 8192*1)
+ wantedLod = 1;
+ if (dist > 8192*2)
+ wantedLod = 2;
+ if (dist > 8192*5)
+ wantedLod = 3;
+ if (dist > 8192*12)
+ wantedLod = 4;
+ if (dist > 8192*32)
+ wantedLod = 5;
+ if (dist > 8192*64)
+ wantedLod = 6;
+
+ bool hadChunk = hasChunk();
+
+ if (loadingListener)
+ loadingListener->indicateProgress();
+
+ if (!distantLand && dist > 8192*2)
+ {
+ if (mIsActive)
+ {
+ destroyChunks(true);
+ mIsActive = false;
+ }
+ return;
+ }
+
+ mIsActive = true;
+
+ if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
+ {
+ // Wanted LOD is small enough to render this node in one chunk
+ if (!mChunk)
+ {
+ mChunk = new Chunk(this, mLodLevel);
+ mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
+ mChunk->setCastShadows(true);
+ mSceneNode->attachObject(mChunk);
+
+ mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
+ mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
+
+ if (mSize == 1)
+ {
+ ensureLayerInfo();
+ mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
+ }
+ else
+ {
+ ensureCompositeMap();
+ mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
+ mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
+ }
+ }
+
+ // Additional (index buffer) LOD is currently disabled.
+ // This is due to a problem with the LOD selection when a node splits.
+ // After splitting, the distance is measured from the children's bounding boxes, which are possibly
+ // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
+ // than the original node.
+ // In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
+ // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
+ // node hasn't split yet, and has a higher LOD than our node's child:
+ // ----- ----- ------------
+ // | LOD | LOD | |
+ // | 1 | 1 | |
+ // |-----|-----| LOD 0 |
+ // | LOD | LOD | |
+ // | 0 | 0 | |
+ // ----- ----- ------------
+ // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
+ // doing here.
+ // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
+ //mChunk->setAdditionalLod(wantedLod - mLodLevel);
+
+ mChunk->setVisible(true);
+
+ if (!hadChunk && hasChildren())
+ {
+ // Make sure child scene nodes are detached
+ mSceneNode->removeAllChildren();
+
+ // If distant land is enabled, keep the chunks around in case we need them again,
+ // otherwise, prefer low memory usage
+ if (!distantLand)
+ for (int i=0; i<4; ++i)
+ mChildren[i]->destroyChunks(true);
+ }
+ }
+ else
+ {
+ // Wanted LOD is too detailed to be rendered in one chunk,
+ // so split it up by delegating to child nodes
+ if (hadChunk)
+ {
+ // If distant land is enabled, keep the chunks around in case we need them again,
+ // otherwise, prefer low memory usage
+ if (!distantLand)
+ destroyChunks(false);
+ else if (mChunk)
+ mChunk->setVisible(false);
+ }
+ assert(hasChildren() && "Leaf node's LOD needs to be 0");
+ for (int i=0; i<4; ++i)
+ mChildren[i]->update(cameraPos, loadingListener);
+ }
+}
+
+void QuadTreeNode::destroyChunks(bool children)
+{
+ if (mChunk)
+ {
+ Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName());
+ mSceneNode->detachObject(mChunk);
+
+ delete mChunk;
+ mChunk = NULL;
+ // destroy blendmaps
+ if (mMaterialGenerator)
+ {
+ const std::vector<Ogre::TexturePtr>& list = mMaterialGenerator->getBlendmapList();
+ 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->setCompositeMap("");
+ }
+
+ if (!mCompositeMap.isNull())
+ {
+ Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
+ mCompositeMap.setNull();
+ }
+ }
+ else if (children && hasChildren())
+ for (int i=0; i<4; ++i)
+ mChildren[i]->destroyChunks(true);
+}
+
+void QuadTreeNode::updateIndexBuffers()
+{
+ if (hasChunk())
+ mChunk->updateIndexBuffer();
+ else if (hasChildren())
+ {
+ for (int i=0; i<4; ++i)
+ mChildren[i]->updateIndexBuffers();
+ }
+}
+
+bool QuadTreeNode::hasChunk()
+{
+ return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible();
+}
+
+size_t QuadTreeNode::getActualLodLevel()
+{
+ assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
+ return mLodLevel + mChunk->getAdditionalLod();
+}
+
+void QuadTreeNode::ensureLayerInfo()
+{
+ if (mMaterialGenerator->hasLayers())
+ return;
+
+ std::vector<Ogre::TexturePtr> blendmaps;
+ std::vector<std::string> layerList;
+ mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
+
+ mMaterialGenerator->setLayerList(layerList);
+ mMaterialGenerator->setBlendmapList(blendmaps);
+}
+
+void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
+{
+ Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
+
+ 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");
+ matGen.setLayerList(layer);
+ makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
+ return;
+ }
+ if (mSize > 1)
+ {
+ assert(hasChildren());
+
+ // 0,0 -------- 1,0
+ // | | |
+ // |-----|------|
+ // | | |
+ // 0,1 -------- 1,1
+
+ float halfW = area.width()/2.f;
+ float halfH = area.height()/2.f;
+ mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
+ mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
+ mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
+ mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
+ }
+ else
+ {
+ ensureLayerInfo();
+
+ Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr());
+ makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
+ }
+}
+
+void QuadTreeNode::ensureCompositeMap()
+{
+ if (!mCompositeMap.isNull())
+ return;
+
+ static int i=0;
+ std::stringstream name;
+ name << "terrain/comp" << i++;
+
+ const int size = 128;
+ mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
+ name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
+
+ // Create quads for each cell
+ prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
+
+ mTerrain->renderCompositeMap(mCompositeMap);
+
+ mTerrain->clearCompositeMapSceneManager();
+
+}
+
+void QuadTreeNode::applyMaterials()
+{
+ if (mChunk)
+ {
+ mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
+ mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
+ if (mSize <= 1)
+ mChunk->setMaterial(mMaterialGenerator->generate(Ogre::MaterialPtr()));
+ else
+ mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(Ogre::MaterialPtr()));
+ }
+ if (hasChildren())
+ for (int i=0; i<4; ++i)
+ mChildren[i]->applyMaterials();
+}
+
+void QuadTreeNode::setVisible(bool visible)
+{
+ if (!visible && mChunk)
+ mChunk->setVisible(false);
+
+ if (hasChildren())
+ for (int i=0; i<4; ++i)
+ mChildren[i]->setVisible(visible);
+}
diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp
new file mode 100644
index 0000000000..9f6fb5d242
--- /dev/null
+++ b/components/terrain/quadtreenode.hpp
@@ -0,0 +1,161 @@
+#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
+#define COMPONENTS_TERRAIN_QUADTREENODE_H
+
+#include <OgreAxisAlignedBox.h>
+#include <OgreVector2.h>
+#include <OgreTexture.h>
+
+#include <components/loadinglistener/loadinglistener.hpp>
+
+namespace Ogre
+{
+ class Rectangle2D;
+}
+
+namespace Terrain
+{
+ class World;
+ class Chunk;
+ class MaterialGenerator;
+
+ enum Direction
+ {
+ North = 0,
+ East = 1,
+ South = 2,
+ West = 3
+ };
+
+ enum ChildDirection
+ {
+ NW = 0,
+ NE = 1,
+ SW = 2,
+ SE = 3,
+ Root
+ };
+
+ /**
+ * @brief A node in the quad tree for our terrain. Depending on LOD,
+ * a node can either choose to render itself in one batch (merging its children),
+ * or delegate the render process to its children, rendering each child in at least one batch.
+ */
+ class QuadTreeNode
+ {
+ public:
+ /// @param terrain
+ /// @param dir relative to parent, or Root if we are the root node
+ /// @param size size (in *cell* units!)
+ /// @param center center (in *cell* units!)
+ /// @param parent parent node
+ QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
+ ~QuadTreeNode();
+
+ void setVisible(bool visible);
+
+ /// Rebuild all materials
+ void applyMaterials();
+
+ /// Initialize neighbours - do this after the quadtree is built
+ void initNeighbours();
+ /// Initialize bounding boxes of non-leafs by merging children bounding boxes.
+ /// Do this after the quadtree is built - note that leaf bounding boxes
+ /// need to be set first via setBoundingBox!
+ void initAabb();
+
+ /// @note takes ownership of \a child
+ void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
+
+ /// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
+ /// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
+ /// end up with empty nodes that don't actually render anything.
+ void markAsDummy() { mIsDummy = true; }
+ bool isDummy() { return mIsDummy; }
+
+ QuadTreeNode* getParent() { return mParent; }
+
+ Ogre::SceneNode* getSceneNode() { return mSceneNode; }
+
+ int getSize() { return mSize; }
+ Ogre::Vector2 getCenter() { return mCenter; }
+
+ bool hasChildren() { return mChildren[0] != 0; }
+ QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
+
+ /// Get neighbour node in this direction
+ QuadTreeNode* getNeighbour (Direction dir);
+
+ /// Returns our direction relative to the parent node, or Root if we are the root node.
+ ChildDirection getDirection() { return mDirection; }
+
+ /// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
+ /// Other nodes can merge AABB of child nodes.
+ void setBoundingBox (const Ogre::AxisAlignedBox& box);
+
+ /// Get bounding box in local coordinates
+ const Ogre::AxisAlignedBox& getBoundingBox();
+
+ World* getTerrain() { return mTerrain; }
+
+ /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
+ void update (const Ogre::Vector3& cameraPos, Loading::Listener* loadingListener);
+
+ /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
+ /// Call after QuadTreeNode::update!
+ void updateIndexBuffers();
+
+ /// Destroy chunks rendered by this node *and* its children (if param is true)
+ void destroyChunks(bool children);
+
+ /// Get the effective LOD level if this node was rendered in one chunk
+ /// with ESM::Land::LAND_SIZE^2 vertices
+ size_t getNativeLodLevel() { return mLodLevel; }
+
+ /// Get the effective current LOD level used by the chunk rendering this node
+ size_t getActualLodLevel();
+
+ /// Is this node currently configured to render itself?
+ bool hasChunk();
+
+ /// Add a textured quad to a specific 2d area in the composite map scenemanager.
+ /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
+ /// call this method on their children.
+ /// @param area area in image space to put the quad
+ /// @param quads collect quads here so they can be deleted later
+ void prepareForCompositeMap(Ogre::TRect<float> area);
+
+ private:
+ // Stored here for convenience in case we need layer list again
+ MaterialGenerator* mMaterialGenerator;
+
+ /// Is this node (or any of its child nodes) currently configured to render itself?
+ /// (only relevant when distant land is disabled, otherwise whole terrain is always rendered)
+ bool mIsActive;
+
+ bool mIsDummy;
+ float mSize;
+ size_t mLodLevel; // LOD if we were to render this node in one chunk
+ Ogre::AxisAlignedBox mBounds;
+ Ogre::AxisAlignedBox mWorldBounds;
+ ChildDirection mDirection;
+ Ogre::Vector2 mCenter;
+
+ Ogre::SceneNode* mSceneNode;
+
+ QuadTreeNode* mParent;
+ QuadTreeNode* mChildren[4];
+ QuadTreeNode* mNeighbours[4];
+
+ Chunk* mChunk;
+
+ World* mTerrain;
+
+ Ogre::TexturePtr mCompositeMap;
+
+ void ensureLayerInfo();
+ void ensureCompositeMap();
+ };
+
+}
+
+#endif
diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp
new file mode 100644
index 0000000000..f00677e97f
--- /dev/null
+++ b/components/terrain/storage.cpp
@@ -0,0 +1,470 @@
+#include "storage.hpp"
+
+#include <OgreVector2.h>
+#include <OgreTextureManager.h>
+#include <OgreStringConverter.h>
+#include <OgreRenderSystem.h>
+#include <OgreRoot.h>
+
+#include <boost/multi_array.hpp>
+
+namespace Terrain
+{
+
+ struct VertexElement
+ {
+ Ogre::Vector3 pos;
+ Ogre::Vector3 normal;
+ Ogre::ColourValue colour;
+ };
+
+ bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
+ {
+ assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
+
+ /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
+
+ Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
+
+ assert(origin.x == (int) origin.x);
+ assert(origin.y == (int) origin.y);
+
+ int cellX = origin.x;
+ int cellY = origin.y;
+
+ const ESM::Land* land = getLand(cellX, cellY);
+ if (!land)
+ return false;
+
+ min = std::numeric_limits<float>().max();
+ max = -std::numeric_limits<float>().max();
+ for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
+ {
+ for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
+ {
+ float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
+ if (h > max)
+ max = h;
+ if (h < min)
+ min = h;
+ }
+ }
+ return true;
+ }
+
+ void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
+ {
+ while (col >= ESM::Land::LAND_SIZE-1)
+ {
+ ++cellY;
+ col -= ESM::Land::LAND_SIZE-1;
+ }
+ while (row >= ESM::Land::LAND_SIZE-1)
+ {
+ ++cellX;
+ row -= ESM::Land::LAND_SIZE-1;
+ }
+ while (col < 0)
+ {
+ --cellY;
+ col += ESM::Land::LAND_SIZE-1;
+ }
+ while (row < 0)
+ {
+ --cellX;
+ row += ESM::Land::LAND_SIZE-1;
+ }
+ ESM::Land* land = getLand(cellX, cellY);
+ if (land && land->mHasData)
+ {
+ normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
+ normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
+ normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
+ normal.normalise();
+ }
+ else
+ normal = Ogre::Vector3(0,0,1);
+ }
+
+ void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
+ {
+ Ogre::Vector3 n1,n2,n3,n4;
+ fixNormal(n1, cellX, cellY, col+1, row);
+ fixNormal(n2, cellX, cellY, col-1, row);
+ fixNormal(n3, cellX, cellY, col, row+1);
+ fixNormal(n4, cellX, cellY, col, row-1);
+ normal = (n1+n2+n3+n4);
+ normal.normalise();
+ }
+
+ void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
+ {
+ if (col == ESM::Land::LAND_SIZE-1)
+ {
+ ++cellY;
+ col = 0;
+ }
+ if (row == ESM::Land::LAND_SIZE-1)
+ {
+ ++cellX;
+ row = 0;
+ }
+ ESM::Land* land = getLand(cellX, cellY);
+ if (land && land->mLandData->mUsingColours)
+ {
+ color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
+ color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
+ color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
+ }
+ else
+ {
+ color.r = 1;
+ color.g = 1;
+ color.b = 1;
+ }
+
+ }
+
+ void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
+ Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
+ Ogre::HardwareVertexBufferSharedPtr normalBuffer,
+ Ogre::HardwareVertexBufferSharedPtr colourBuffer)
+ {
+ // LOD level n means every 2^n-th vertex is kept
+ size_t increment = 1 << lodLevel;
+
+ Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
+ assert(origin.x == (int) origin.x);
+ assert(origin.y == (int) origin.y);
+
+ int startX = origin.x;
+ int startY = origin.y;
+
+ size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
+
+ std::vector<uint8_t> colors;
+ colors.resize(numVerts*numVerts*4);
+ std::vector<float> positions;
+ positions.resize(numVerts*numVerts*3);
+ std::vector<float> normals;
+ normals.resize(numVerts*numVerts*3);
+
+ Ogre::Vector3 normal;
+ Ogre::ColourValue color;
+
+ float vertY;
+ float vertX;
+
+ float vertY_ = 0; // of current cell corner
+ for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
+ {
+ float vertX_ = 0; // of current cell corner
+ for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
+ {
+ ESM::Land* land = getLand(cellX, cellY);
+ if (land && !land->mHasData)
+ land = NULL;
+ bool hasColors = land && land->mLandData->mUsingColours;
+
+ int rowStart = 0;
+ int colStart = 0;
+ // Skip the first row / column unless we're at a chunk edge,
+ // since this row / column is already contained in a previous cell
+ if (colStart == 0 && vertY_ != 0)
+ colStart += increment;
+ if (rowStart == 0 && vertX_ != 0)
+ rowStart += increment;
+
+ vertY = vertY_;
+ for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
+ {
+ vertX = vertX_;
+ for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
+ {
+ positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
+ positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
+ if (land)
+ positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
+ else
+ positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
+
+ if (land)
+ {
+ normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
+ normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
+ normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
+ normal.normalise();
+ }
+ else
+ normal = Ogre::Vector3(0,0,1);
+
+ // Normals apparently don't connect seamlessly between cells
+ if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
+ fixNormal(normal, cellX, cellY, col, row);
+
+ // some corner normals appear to be complete garbage (z < 0)
+ if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
+ averageNormal(normal, cellX, cellY, col, row);
+
+ assert(normal.z > 0);
+
+ normals[vertX*numVerts*3 + vertY*3] = normal.x;
+ normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
+ normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
+
+ if (hasColors)
+ {
+ color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
+ color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
+ color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
+ }
+ else
+ {
+ color.r = 1;
+ color.g = 1;
+ color.b = 1;
+ }
+
+ // Unlike normals, colors mostly connect seamlessly between cells, but not always...
+ if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
+ fixColour(color, cellX, cellY, col, row);
+
+ color.a = 1;
+ Ogre::uint32 rsColor;
+ Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
+ memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
+
+ ++vertX;
+ }
+ ++vertY;
+ }
+ vertX_ = vertX;
+ }
+ vertY_ = vertY;
+
+ assert(vertX_ == numVerts); // Ensure we covered whole area
+ }
+ assert(vertY_ == numVerts); // Ensure we covered whole area
+
+ vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
+ normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
+ colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
+ }
+
+ Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
+ int x, int y)
+ {
+ // For the first/last row/column, we need to get the texture from the neighbour cell
+ // to get consistent blending at the borders
+ --x;
+ if (x < 0)
+ {
+ --cellX;
+ x += ESM::Land::LAND_TEXTURE_SIZE;
+ }
+ if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
+ {
+ ++cellY;
+ y -= ESM::Land::LAND_TEXTURE_SIZE;
+ }
+
+ assert(x<ESM::Land::LAND_TEXTURE_SIZE);
+ assert(y<ESM::Land::LAND_TEXTURE_SIZE);
+
+ ESM::Land* land = getLand(cellX, cellY);
+ if (land)
+ {
+ if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
+ land->loadData(ESM::Land::DATA_VTEX);
+
+ int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
+ if (tex == 0)
+ return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
+ return std::make_pair(tex, land->mPlugin);
+ }
+ else
+ return std::make_pair(0,0);
+ }
+
+ std::string Storage::getTextureName(UniqueTextureId id)
+ {
+ if (id.first == 0)
+ return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
+
+ // NB: All vtex ids are +1 compared to the ltex ids
+ const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
+
+ std::string texture = ltex->mTexture;
+ //TODO this is needed due to MWs messed up texture handling
+ texture = texture.substr(0, texture.rfind(".")) + ".dds";
+
+ return texture;
+ }
+
+ void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
+ bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<std::string> &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
+ // and interpolate the rest of the cell by hand? :/
+
+ Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
+ int cellX = origin.x;
+ int cellY = origin.y;
+
+ // Save the used texture indices so we know the total number of textures
+ // and number of required blend maps
+ std::set<UniqueTextureId> textureIndices;
+ // Due to the way the blending works, the base layer will always shine through in between
+ // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
+ // To get a consistent look, we need to make sure to use the same base layer in all cells.
+ // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
+ textureIndices.insert(std::make_pair(0,0));
+
+ for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
+ for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
+ {
+ UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
+ textureIndices.insert(id);
+ }
+
+ // Makes sure the indices are sorted, or rather,
+ // retrieved as sorted. This is important to keep the splatting order
+ // consistent across cells.
+ std::map<UniqueTextureId, int> textureIndicesMap;
+ for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
+ {
+ int size = textureIndicesMap.size();
+ textureIndicesMap[*it] = size;
+ layerList.push_back(getTextureName(*it));
+ }
+
+ int numTextures = textureIndices.size();
+ // numTextures-1 since the base layer doesn't need blending
+ int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
+
+ int channels = pack ? 4 : 1;
+
+ // Second iteration - create and fill in the blend maps
+ const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
+ std::vector<Ogre::uchar> data;
+ data.resize(blendmapSize * blendmapSize * channels, 0);
+
+ for (int i=0; i<numBlendmaps; ++i)
+ {
+ Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
+ static int count=0;
+ Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
+ + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format);
+
+ for (int y=0; y<blendmapSize; ++y)
+ {
+ for (int x=0; x<blendmapSize; ++x)
+ {
+ UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
+ int layerIndex = textureIndicesMap.find(id)->second;
+ int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
+ int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
+
+ if (blendIndex == i)
+ data[y*blendmapSize*channels + x*channels + channel] = 255;
+ else
+ data[y*blendmapSize*channels + x*channels + channel] = 0;
+ }
+ }
+
+ // All done, upload to GPU
+ Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
+ map->loadRawData(stream, blendmapSize, blendmapSize, format);
+ blendmaps.push_back(map);
+ }
+ }
+
+ float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
+ {
+ int cellX = std::floor(worldPos.x / 8192.f);
+ int cellY = std::floor(worldPos.y / 8192.f);
+
+ ESM::Land* land = getLand(cellX, cellY);
+ if (!land)
+ return -2048;
+
+ // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
+
+ // Normalized position in the cell
+ float nX = (worldPos.x - (cellX * 8192))/8192.f;
+ float nY = (worldPos.y - (cellY * 8192))/8192.f;
+
+ // get left / bottom points (rounded down)
+ float factor = ESM::Land::LAND_SIZE - 1.0f;
+ float invFactor = 1.0f / factor;
+
+ int startX = static_cast<int>(nX * factor);
+ int startY = static_cast<int>(nY * factor);
+ int endX = startX + 1;
+ int endY = startY + 1;
+
+ assert(endX < ESM::Land::LAND_SIZE);
+ assert(endY < ESM::Land::LAND_SIZE);
+
+ // now get points in terrain space (effectively rounding them to boundaries)
+ float startXTS = startX * invFactor;
+ float startYTS = startY * invFactor;
+ float endXTS = endX * invFactor;
+ float endYTS = endY * invFactor;
+
+ // get parametric from start coord to next point
+ float xParam = (nX - startXTS) * factor;
+ float yParam = (nY - startYTS) * factor;
+
+ /* For even / odd tri strip rows, triangles are this shape:
+ even odd
+ 3---2 3---2
+ | / | | \ |
+ 0---1 0---1
+ */
+
+ // Build all 4 positions in normalized cell space, using point-sampled height
+ Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
+ Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
+ Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
+ Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
+ // define this plane in terrain space
+ Ogre::Plane plane;
+ // (At the moment, all rows have the same triangle alignment)
+ if (true)
+ {
+ // odd row
+ bool secondTri = ((1.0 - yParam) > xParam);
+ if (secondTri)
+ plane.redefine(v0, v1, v3);
+ else
+ plane.redefine(v1, v2, v3);
+ }
+ else
+ {
+ // even row
+ bool secondTri = (yParam > xParam);
+ if (secondTri)
+ plane.redefine(v0, v2, v3);
+ else
+ plane.redefine(v0, v1, v2);
+ }
+
+ // Solve plane equation for z
+ return (-plane.normal.x * nX
+ -plane.normal.y * nY
+ - plane.d) / plane.normal.z * 8192;
+
+ }
+
+ float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
+ {
+ assert(x < ESM::Land::LAND_SIZE);
+ assert(y < ESM::Land::LAND_SIZE);
+ return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
+ }
+
+
+}
diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp
new file mode 100644
index 0000000000..b82f6bbb62
--- /dev/null
+++ b/components/terrain/storage.hpp
@@ -0,0 +1,84 @@
+#ifndef COMPONENTS_TERRAIN_STORAGE_H
+#define COMPONENTS_TERRAIN_STORAGE_H
+
+#include <components/esm/loadland.hpp>
+#include <components/esm/loadltex.hpp>
+
+#include <OgreAxisAlignedBox.h>
+
+#include <OgreHardwareVertexBuffer.h>
+
+namespace Terrain
+{
+
+ /// We keep storage of terrain data abstract here since we need different implementations for game and editor
+ class Storage
+ {
+ public:
+ virtual ~Storage() {}
+ private:
+ virtual ESM::Land* getLand (int cellX, int cellY) = 0;
+ virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
+
+ public:
+ /// Get bounds of the whole terrain in cell units
+ virtual Ogre::AxisAlignedBox getBounds() = 0;
+
+ /// Get the minimum and maximum heights of a terrain chunk.
+ /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
+ /// Larger chunks can simply merge AABB of children.
+ /// @param size size of the chunk in cell units
+ /// @param center center of the chunk in cell units
+ /// @param min min height will be stored here
+ /// @param max max height will be stored here
+ /// @return true if there was data available for this terrain chunk
+ bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
+
+ /// Fill vertex buffers for a terrain chunk.
+ /// @param lodLevel LOD level, 0 = most detailed
+ /// @param size size of the terrain chunk in cell units
+ /// @param center center of the chunk in cell units
+ /// @param vertexBuffer buffer to write vertices
+ /// @param normalBuffer buffer to write vertex normals
+ /// @param colourBuffer buffer to write vertex colours
+ void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
+ Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
+ Ogre::HardwareVertexBufferSharedPtr normalBuffer,
+ Ogre::HardwareVertexBufferSharedPtr colourBuffer);
+
+ /// Create textures holding layer blend values for a terrain chunk.
+ /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
+ /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
+ /// @param chunkSize size of the terrain chunk in cell units
+ /// @param chunkCenter center of the chunk in cell units
+ /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
+ /// otherwise, each texture contains blend values for one layer only. Shader-based rendering
+ /// can utilize packing, FFP can't.
+ /// @param blendmaps created blendmaps will be written here
+ /// @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);
+
+ float getHeightAt (const Ogre::Vector3& worldPos);
+
+ private:
+ void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
+ void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
+ void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
+
+ float getVertexHeight (const ESM::Land* land, int x, int y);
+
+ // Since plugins can define new texture palettes, we need to know the plugin index too
+ // in order to retrieve the correct texture name.
+ // pair <texture id, plugin id>
+ typedef std::pair<short, short> UniqueTextureId;
+
+ UniqueTextureId getVtexIndexAt(int cellX, int cellY,
+ int x, int y);
+ std::string getTextureName (UniqueTextureId id);
+ };
+
+}
+
+#endif
diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp
new file mode 100644
index 0000000000..711ebbc8fa
--- /dev/null
+++ b/components/terrain/world.cpp
@@ -0,0 +1,410 @@
+#include "world.hpp"
+
+#include <OgreAxisAlignedBox.h>
+#include <OgreCamera.h>
+#include <OgreHardwareBufferManager.h>
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRoot.h>
+
+#include <components/esm/loadland.hpp>
+#include <components/loadinglistener/loadinglistener.hpp>
+
+#include "storage.hpp"
+#include "quadtreenode.hpp"
+
+namespace
+{
+
+ bool isPowerOfTwo(int x)
+ {
+ return ( (x > 0) && ((x & (x - 1)) == 0) );
+ }
+
+ int nextPowerOfTwo (int v)
+ {
+ if (isPowerOfTwo(v)) return v;
+ int depth=0;
+ while(v)
+ {
+ v >>= 1;
+ depth++;
+ }
+ return 1 << depth;
+ }
+
+ Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
+ {
+ if (center == node->getCenter())
+ return node;
+
+ if (center.x > node->getCenter().x && center.y > node->getCenter().y)
+ return findNode(center, node->getChild(Terrain::NE));
+ else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
+ return findNode(center, node->getChild(Terrain::SE));
+ else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
+ return findNode(center, node->getChild(Terrain::NW));
+ else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
+ return findNode(center, node->getChild(Terrain::SW));
+ }
+
+}
+
+namespace Terrain
+{
+
+ World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr,
+ Storage* storage, int visibilityFlags, bool distantLand, bool shaders)
+ : mStorage(storage)
+ , mMinBatchSize(1)
+ , mMaxBatchSize(64)
+ , mSceneMgr(sceneMgr)
+ , mVisibilityFlags(visibilityFlags)
+ , mDistantLand(distantLand)
+ , mShaders(shaders)
+ , mVisible(true)
+ , mLoadingListener(loadingListener)
+ {
+ loadingListener->setLabel("Creating terrain");
+ loadingListener->indicateProgress();
+
+ mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
+
+ Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
+ mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
+ "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
+ mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
+ mCompositeMapRenderTarget->setAutoUpdated(false);
+ mCompositeMapRenderTarget->addViewport(compositeMapCam);
+
+ mBounds = storage->getBounds();
+
+ int origSizeX = mBounds.getSize().x;
+ int origSizeY = mBounds.getSize().y;
+
+ // Dividing a quad tree only works well for powers of two, so round up to the nearest one
+ int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
+
+ // Adjust the center according to the new size
+ Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0);
+
+ mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+
+ mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
+ buildQuadTree(mRootNode);
+ loadingListener->indicateProgress();
+ mRootNode->initAabb();
+ loadingListener->indicateProgress();
+ mRootNode->initNeighbours();
+ loadingListener->indicateProgress();
+ }
+
+ World::~World()
+ {
+ delete mRootNode;
+ delete mStorage;
+ }
+
+ void World::buildQuadTree(QuadTreeNode *node)
+ {
+ float halfSize = node->getSize()/2.f;
+
+ if (node->getSize() <= mMinBatchSize)
+ {
+ // We arrived at a leaf
+ float minZ,maxZ;
+ Ogre::Vector2 center = node->getCenter();
+ if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
+ node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ),
+ Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ)));
+ else
+ node->markAsDummy(); // no data available for this node, skip it
+ return;
+ }
+
+ if (node->getCenter().x - halfSize > mBounds.getMaximum().x
+ || node->getCenter().x + halfSize < mBounds.getMinimum().x
+ || node->getCenter().y - halfSize > mBounds.getMaximum().y
+ || node->getCenter().y + halfSize < mBounds.getMinimum().y )
+ // Out of bounds of the actual terrain - this will happen because
+ // we rounded the size up to the next power of two
+ {
+ node->markAsDummy();
+ return;
+ }
+
+ // Not a leaf, create its children
+ node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
+ node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
+ node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
+ node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
+ buildQuadTree(node->getChild(SW));
+ buildQuadTree(node->getChild(SE));
+ buildQuadTree(node->getChild(NW));
+ buildQuadTree(node->getChild(NE));
+
+ // if all children are dummy, we are also dummy
+ for (int i=0; i<4; ++i)
+ {
+ if (!node->getChild((ChildDirection)i)->isDummy())
+ return;
+ }
+ node->markAsDummy();
+ }
+
+ void World::update(const Ogre::Vector3& cameraPos)
+ {
+ if (!mVisible)
+ return;
+ mRootNode->update(cameraPos, mLoadingListener);
+ mRootNode->updateIndexBuffers();
+ }
+
+ Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center)
+ {
+ if (center.x > mBounds.getMaximum().x
+ || center.x < mBounds.getMinimum().x
+ || center.y > mBounds.getMaximum().y
+ || center.y < mBounds.getMinimum().y)
+ return Ogre::AxisAlignedBox::BOX_NULL;
+ QuadTreeNode* node = findNode(center, mRootNode);
+ Ogre::AxisAlignedBox box = node->getBoundingBox();
+ box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192,
+ box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192);
+ return box;
+ }
+
+ Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide)
+ {
+ if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end())
+ {
+ return mUvBufferMap[numVertsOneSide];
+ }
+
+ int vertexCount = numVertsOneSide * numVertsOneSide;
+
+ std::vector<float> uvs;
+ uvs.reserve(vertexCount*2);
+
+ for (int col = 0; col < numVertsOneSide; ++col)
+ {
+ for (int row = 0; row < numVertsOneSide; ++row)
+ {
+ uvs.push_back(col / static_cast<float>(numVertsOneSide-1)); // U
+ uvs.push_back(row / static_cast<float>(numVertsOneSide-1)); // V
+ }
+ }
+
+ Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
+ Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
+ Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
+ vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
+
+ buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
+
+ mUvBufferMap[numVertsOneSide] = buffer;
+ return buffer;
+ }
+
+ Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
+ {
+ if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
+ {
+ numIndices = mIndexBufferMap[flags]->getNumIndexes();
+ return mIndexBufferMap[flags];
+ }
+
+ // LOD level n means every 2^n-th vertex is kept
+ size_t lodLevel = (flags >> (4*4));
+
+ size_t lodDeltas[4];
+ for (int i=0; i<4; ++i)
+ lodDeltas[i] = (flags >> (4*i)) & (0xf);
+
+ bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
+
+ size_t increment = 1 << lodLevel;
+ assert((int)increment < ESM::Land::LAND_SIZE);
+ std::vector<short> indices;
+ indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
+
+ size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
+ // If any edge needs stitching we'll skip all edges at this point,
+ // mainly because stitching one edge would have an effect on corners and on the adjacent edges
+ if (anyDeltas)
+ {
+ colStart += increment;
+ colEnd -= increment;
+ rowEnd -= increment;
+ rowStart += increment;
+ }
+ for (size_t row = rowStart; row < rowEnd; row += increment)
+ {
+ for (size_t col = colStart; col < colEnd; col += increment)
+ {
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
+ indices.push_back(ESM::Land::LAND_SIZE*col+row+increment);
+
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
+ }
+ }
+
+ size_t innerStep = increment;
+ if (anyDeltas)
+ {
+ // Now configure LOD transitions at the edges - this is pretty tedious,
+ // and some very long and boring code, but it works great
+
+ // South
+ size_t row = 0;
+ size_t outerStep = 1 << (lodDeltas[South] + lodLevel);
+ for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
+ {
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
+ // Make sure not to touch the right edge
+ if (col+outerStep == ESM::Land::LAND_SIZE-1)
+ indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep);
+ else
+ indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
+
+ for (size_t i = 0; i < outerStep; i += innerStep)
+ {
+ // Make sure not to touch the left or right edges
+ if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
+ continue;
+ indices.push_back(ESM::Land::LAND_SIZE*(col)+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep);
+ }
+ }
+
+ // North
+ row = ESM::Land::LAND_SIZE-1;
+ outerStep = 1 << (lodDeltas[North] + lodLevel);
+ for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
+ {
+ indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ // Make sure not to touch the left edge
+ if (col == 0)
+ indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
+ else
+ indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
+
+ for (size_t i = 0; i < outerStep; i += innerStep)
+ {
+ // Make sure not to touch the left or right edges
+ if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
+ continue;
+ indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
+ }
+ }
+
+ // West
+ size_t col = 0;
+ outerStep = 1 << (lodDeltas[West] + lodLevel);
+ for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
+ {
+ indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ // Make sure not to touch the top edge
+ if (row+outerStep == ESM::Land::LAND_SIZE-1)
+ indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep);
+ else
+ indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
+
+ for (size_t i = 0; i < outerStep; i += innerStep)
+ {
+ // Make sure not to touch the top or bottom edges
+ if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
+ continue;
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i);
+ indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
+ }
+ }
+
+ // East
+ col = ESM::Land::LAND_SIZE-1;
+ outerStep = 1 << (lodDeltas[East] + lodLevel);
+ for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
+ {
+ indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
+ // Make sure not to touch the bottom edge
+ if (row == 0)
+ indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep);
+ else
+ indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
+
+ for (size_t i = 0; i < outerStep; i += innerStep)
+ {
+ // Make sure not to touch the top or bottom edges
+ if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
+ continue;
+ indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep);
+ indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i);
+ }
+ }
+ }
+
+
+
+ numIndices = indices.size();
+
+ Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
+ Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT,
+ numIndices, Ogre::HardwareBuffer::HBU_STATIC);
+ buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
+ mIndexBufferMap[flags] = buffer;
+ return buffer;
+ }
+
+ void World::renderCompositeMap(Ogre::TexturePtr target)
+ {
+ mCompositeMapRenderTarget->update();
+ target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
+ }
+
+ void World::clearCompositeMapSceneManager()
+ {
+ mCompositeMapSceneMgr->destroyAllManualObjects();
+ mCompositeMapSceneMgr->clearScene();
+ }
+
+ float World::getHeightAt(const Ogre::Vector3 &worldPos)
+ {
+ return mStorage->getHeightAt(worldPos);
+ }
+
+ void World::applyMaterials(bool shadows, bool splitShadows)
+ {
+ mShadows = shadows;
+ mSplitShadows = splitShadows;
+ mRootNode->applyMaterials();
+ }
+
+ void World::setVisible(bool visible)
+ {
+ if (visible && !mVisible)
+ mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
+ else if (!visible && mVisible)
+ mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
+
+ mVisible = visible;
+ }
+
+ bool World::getVisible()
+ {
+ return mVisible;
+ }
+
+
+}
diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp
new file mode 100644
index 0000000000..b8c1b0a7d6
--- /dev/null
+++ b/components/terrain/world.hpp
@@ -0,0 +1,154 @@
+#ifndef COMPONENTS_TERRAIN_H
+#define COMPONENTS_TERRAIN_H
+
+#include <OgreHardwareIndexBuffer.h>
+#include <OgreHardwareVertexBuffer.h>
+#include <OgreAxisAlignedBox.h>
+#include <OgreTexture.h>
+
+namespace Loading
+{
+ class Listener;
+}
+
+namespace Ogre
+{
+ class Camera;
+}
+
+namespace Terrain
+{
+
+ class QuadTreeNode;
+ class Storage;
+
+ /**
+ * @brief A quadtree-based terrain implementation suitable for large data sets. \n
+ * Near cells are rendered with alpha splatting, distant cells are merged
+ * together in batches and have their layers pre-rendered onto a composite map. \n
+ * Cracks at LOD transitions are avoided using stitching.
+ * @note Multiple cameras are not supported yet
+ */
+ class World
+ {
+ public:
+ /// @note takes ownership of \a storage
+ /// @param loadingListener Listener to update with progress
+ /// @param sceneMgr scene manager to use
+ /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
+ /// @param visbilityFlags visibility flags for the created meshes
+ /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera.
+ /// This is a temporary option until it can be streamlined.
+ /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
+ /// faster so this is just here for compatibility.
+ World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr,
+ Storage* storage, int visiblityFlags, bool distantLand, bool shaders);
+ ~World();
+
+ void setLoadingListener(Loading::Listener* loadingListener) { mLoadingListener = loadingListener; }
+
+ bool getDistantLandEnabled() { return mDistantLand; }
+ bool getShadersEnabled() { return mShaders; }
+ bool getShadowsEnabled() { return mShadows; }
+ bool getSplitShadowsEnabled() { return mSplitShadows; }
+
+ float getHeightAt (const Ogre::Vector3& worldPos);
+
+ /// Update chunk LODs according to this camera position
+ /// @note Calling this method might lead to composite textures being rendered, so it is best
+ /// not to call it when render commands are still queued, since that would cause a flush.
+ void update (const Ogre::Vector3& cameraPos);
+
+ /// Get the world bounding box of a chunk of terrain centered at \a center
+ Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
+
+ Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
+
+ Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
+
+ Storage* getStorage() { return mStorage; }
+
+ /// Show or hide the whole terrain
+ /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
+ void setVisible(bool visible);
+ bool getVisible();
+
+ /// Recreate materials used by terrain chunks. This should be called whenever settings of
+ /// the material factory are changed. (Relying on the factory to update those materials is not
+ /// enough, since turning a feature on/off can change the number of texture units available for layer/blend
+ /// textures, and to properly respond to this we may need to change the structure of the material, such as
+ /// adding or removing passes. This can only be achieved by a full rebuild.)
+ void applyMaterials(bool shadows, bool splitShadows);
+
+ int getVisiblityFlags() { return mVisibilityFlags; }
+
+ int getMaxBatchSize() { return mMaxBatchSize; }
+
+ void enableSplattingShader(bool enabled);
+
+ private:
+ bool mDistantLand;
+ bool mShaders;
+ bool mShadows;
+ bool mSplitShadows;
+ bool mVisible;
+
+ Loading::Listener* mLoadingListener;
+
+ QuadTreeNode* mRootNode;
+ Ogre::SceneNode* mRootSceneNode;
+ Storage* mStorage;
+
+ int mVisibilityFlags;
+
+ Ogre::SceneManager* mSceneMgr;
+ Ogre::SceneManager* mCompositeMapSceneMgr;
+
+ /// Bounds in cell units
+ Ogre::AxisAlignedBox mBounds;
+
+ /// Minimum size of a terrain batch along one side (in cell units)
+ float mMinBatchSize;
+ /// Maximum size of a terrain batch along one side (in cell units)
+ float mMaxBatchSize;
+
+ void buildQuadTree(QuadTreeNode* node);
+
+ public:
+ // ----INTERNAL----
+
+ enum IndexBufferFlags
+ {
+ IBF_North = 1 << 0,
+ IBF_East = 1 << 1,
+ IBF_South = 1 << 2,
+ IBF_West = 1 << 3
+ };
+
+ /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each)
+ /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
+ /// @param numIndices number of indices that were used will be written here
+ Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices);
+
+ Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide);
+
+ Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
+
+ // Delete all quads
+ void clearCompositeMapSceneManager();
+ void renderCompositeMap (Ogre::TexturePtr target);
+
+ private:
+ // Index buffers are shared across terrain batches where possible. There is one index buffer for each
+ // combination of LOD deltas and index buffer LOD we may need.
+ std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap;
+
+ std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap;
+
+ Ogre::RenderTarget* mCompositeMapRenderTarget;
+ Ogre::TexturePtr mCompositeMapRenderTexture;
+ };
+
+}
+
+#endif
diff --git a/components/to_utf8/Makefile b/components/to_utf8/Makefile
new file mode 100644
index 0000000000..5234d455ae
--- /dev/null
+++ b/components/to_utf8/Makefile
@@ -0,0 +1,8 @@
+tables_gen.hpp: gen_iconv
+ ./gen_iconv > tables_gen.hpp
+
+gen_iconv: gen_iconv.cpp
+ g++ -Wall $^ -o $@
+
+clean:
+ rm -f ./gen_iconv \ No newline at end of file
diff --git a/components/to_utf8/gen_iconv.cpp b/components/to_utf8/gen_iconv.cpp
new file mode 100644
index 0000000000..8198b305dd
--- /dev/null
+++ b/components/to_utf8/gen_iconv.cpp
@@ -0,0 +1,118 @@
+// This program generates the file tables_gen.hpp
+
+#include <iostream>
+using namespace std;
+
+#include <iconv.h>
+#include <cassert>
+
+void tab() { cout << " "; }
+
+// write one number with a space in front of it and a comma after it
+void num(char i, bool last)
+{
+ // Convert i to its integer value, i.e. -128 to 127. Printing it directly
+ // would result in non-printable characters in the source code, which is bad.
+ cout << " " << static_cast<int>(i);
+ if(!last) cout << ",";
+}
+
+// Write one table entry (UTF8 value), 1-5 bytes
+void writeChar(char *value, int length, bool last, const std::string &comment="")
+{
+ assert(length >= 1 && length <= 5);
+ tab();
+ num(length, false);
+ for(int i=0;i<5;i++)
+ num(value[i], last && i==4);
+
+ if(comment != "")
+ cout << " // " << comment;
+
+ cout << endl;
+}
+
+// What to write on missing characters
+void writeMissing(bool last)
+{
+ // Just write a space character
+ char value[5];
+ value[0] = ' ';
+ for(int i=1; i<5; i++)
+ value[i] = 0;
+ writeChar(value, 1, last, "not part of this charset");
+}
+
+int write_table(const std::string &charset, const std::string &tableName)
+{
+ // Write table header
+ cout << "static signed char " << tableName << "[] =\n{\n";
+
+ // Open conversion system
+ iconv_t cd = iconv_open ("UTF-8", charset.c_str());
+
+ // Convert each character from 0 to 255
+ for(int i=0; i<256; i++)
+ {
+ bool last = (i==255);
+
+ char input = i;
+ char *iptr = &input;
+ size_t ileft = 1;
+
+ char output[5];
+ for(int k=0; k<5; k++) output[k] = 0;
+ char *optr = output;
+ size_t oleft = 5;
+
+ size_t res = iconv(cd, &iptr, &ileft, &optr, &oleft);
+
+ if(res) writeMissing(last);
+ else writeChar(output, 5-oleft, last);
+ }
+
+ iconv_close (cd);
+
+ // Finish table
+ cout << "};\n";
+
+ return 0;
+}
+
+int main()
+{
+ // Write header guard
+ cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n";
+
+ // Write namespace
+ cout << "namespace ToUTF8\n{\n\n";
+
+ // Central European and Eastern European languages that use Latin script, such as
+ // Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian.
+ cout << "\n/// Central European and Eastern European languages that use Latin script,"
+ "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian,"
+ "\n/// Serbian (Latin script), Romanian and Albanian."
+ "\n";
+ write_table("WINDOWS-1250", "windows_1250");
+
+ // Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages
+ cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic"
+ "\n/// and other languages"
+ "\n";
+ write_table("WINDOWS-1251", "windows_1251");
+
+ // English
+ cout << "\n/// Latin alphabet used by English and some other Western languages"
+ "\n";
+ write_table("WINDOWS-1252", "windows_1252");
+
+ write_table("CP437", "cp437");
+
+ // Close namespace
+ cout << "\n}\n\n";
+
+ // Close header guard
+ cout << "#endif\n\n";
+
+ return 0;
+}
diff --git a/components/to_utf8/tables_gen.hpp b/components/to_utf8/tables_gen.hpp
new file mode 100644
index 0000000000..14e66eac17
--- /dev/null
+++ b/components/to_utf8/tables_gen.hpp
@@ -0,0 +1,1056 @@
+#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H
+#define COMPONENTS_TOUTF8_TABLE_GEN_H
+
+namespace ToUTF8
+{
+
+
+/// Central European and Eastern European languages that use Latin script,
+/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian,
+/// Serbian (Latin script), Romanian and Albanian.
+static signed char windows_1250[] =
+{
+ 1, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0,
+ 1, 2, 0, 0, 0, 0,
+ 1, 3, 0, 0, 0, 0,
+ 1, 4, 0, 0, 0, 0,
+ 1, 5, 0, 0, 0, 0,
+ 1, 6, 0, 0, 0, 0,
+ 1, 7, 0, 0, 0, 0,
+ 1, 8, 0, 0, 0, 0,
+ 1, 9, 0, 0, 0, 0,
+ 1, 10, 0, 0, 0, 0,
+ 1, 11, 0, 0, 0, 0,
+ 1, 12, 0, 0, 0, 0,
+ 1, 13, 0, 0, 0, 0,
+ 1, 14, 0, 0, 0, 0,
+ 1, 15, 0, 0, 0, 0,
+ 1, 16, 0, 0, 0, 0,
+ 1, 17, 0, 0, 0, 0,
+ 1, 18, 0, 0, 0, 0,
+ 1, 19, 0, 0, 0, 0,
+ 1, 20, 0, 0, 0, 0,
+ 1, 21, 0, 0, 0, 0,
+ 1, 22, 0, 0, 0, 0,
+ 1, 23, 0, 0, 0, 0,
+ 1, 24, 0, 0, 0, 0,
+ 1, 25, 0, 0, 0, 0,
+ 1, 26, 0, 0, 0, 0,
+ 1, 27, 0, 0, 0, 0,
+ 1, 28, 0, 0, 0, 0,
+ 1, 29, 0, 0, 0, 0,
+ 1, 30, 0, 0, 0, 0,
+ 1, 31, 0, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0,
+ 1, 33, 0, 0, 0, 0,
+ 1, 34, 0, 0, 0, 0,
+ 1, 35, 0, 0, 0, 0,
+ 1, 36, 0, 0, 0, 0,
+ 1, 37, 0, 0, 0, 0,
+ 1, 38, 0, 0, 0, 0,
+ 1, 39, 0, 0, 0, 0,
+ 1, 40, 0, 0, 0, 0,
+ 1, 41, 0, 0, 0, 0,
+ 1, 42, 0, 0, 0, 0,
+ 1, 43, 0, 0, 0, 0,
+ 1, 44, 0, 0, 0, 0,
+ 1, 45, 0, 0, 0, 0,
+ 1, 46, 0, 0, 0, 0,
+ 1, 47, 0, 0, 0, 0,
+ 1, 48, 0, 0, 0, 0,
+ 1, 49, 0, 0, 0, 0,
+ 1, 50, 0, 0, 0, 0,
+ 1, 51, 0, 0, 0, 0,
+ 1, 52, 0, 0, 0, 0,
+ 1, 53, 0, 0, 0, 0,
+ 1, 54, 0, 0, 0, 0,
+ 1, 55, 0, 0, 0, 0,
+ 1, 56, 0, 0, 0, 0,
+ 1, 57, 0, 0, 0, 0,
+ 1, 58, 0, 0, 0, 0,
+ 1, 59, 0, 0, 0, 0,
+ 1, 60, 0, 0, 0, 0,
+ 1, 61, 0, 0, 0, 0,
+ 1, 62, 0, 0, 0, 0,
+ 1, 63, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0,
+ 1, 65, 0, 0, 0, 0,
+ 1, 66, 0, 0, 0, 0,
+ 1, 67, 0, 0, 0, 0,
+ 1, 68, 0, 0, 0, 0,
+ 1, 69, 0, 0, 0, 0,
+ 1, 70, 0, 0, 0, 0,
+ 1, 71, 0, 0, 0, 0,
+ 1, 72, 0, 0, 0, 0,
+ 1, 73, 0, 0, 0, 0,
+ 1, 74, 0, 0, 0, 0,
+ 1, 75, 0, 0, 0, 0,
+ 1, 76, 0, 0, 0, 0,
+ 1, 77, 0, 0, 0, 0,
+ 1, 78, 0, 0, 0, 0,
+ 1, 79, 0, 0, 0, 0,
+ 1, 80, 0, 0, 0, 0,
+ 1, 81, 0, 0, 0, 0,
+ 1, 82, 0, 0, 0, 0,
+ 1, 83, 0, 0, 0, 0,
+ 1, 84, 0, 0, 0, 0,
+ 1, 85, 0, 0, 0, 0,
+ 1, 86, 0, 0, 0, 0,
+ 1, 87, 0, 0, 0, 0,
+ 1, 88, 0, 0, 0, 0,
+ 1, 89, 0, 0, 0, 0,
+ 1, 90, 0, 0, 0, 0,
+ 1, 91, 0, 0, 0, 0,
+ 1, 92, 0, 0, 0, 0,
+ 1, 93, 0, 0, 0, 0,
+ 1, 94, 0, 0, 0, 0,
+ 1, 95, 0, 0, 0, 0,
+ 1, 96, 0, 0, 0, 0,
+ 1, 97, 0, 0, 0, 0,
+ 1, 98, 0, 0, 0, 0,
+ 1, 99, 0, 0, 0, 0,
+ 1, 100, 0, 0, 0, 0,
+ 1, 101, 0, 0, 0, 0,
+ 1, 102, 0, 0, 0, 0,
+ 1, 103, 0, 0, 0, 0,
+ 1, 104, 0, 0, 0, 0,
+ 1, 105, 0, 0, 0, 0,
+ 1, 106, 0, 0, 0, 0,
+ 1, 107, 0, 0, 0, 0,
+ 1, 108, 0, 0, 0, 0,
+ 1, 109, 0, 0, 0, 0,
+ 1, 110, 0, 0, 0, 0,
+ 1, 111, 0, 0, 0, 0,
+ 1, 112, 0, 0, 0, 0,
+ 1, 113, 0, 0, 0, 0,
+ 1, 114, 0, 0, 0, 0,
+ 1, 115, 0, 0, 0, 0,
+ 1, 116, 0, 0, 0, 0,
+ 1, 117, 0, 0, 0, 0,
+ 1, 118, 0, 0, 0, 0,
+ 1, 119, 0, 0, 0, 0,
+ 1, 120, 0, 0, 0, 0,
+ 1, 121, 0, 0, 0, 0,
+ 1, 122, 0, 0, 0, 0,
+ 1, 123, 0, 0, 0, 0,
+ 1, 124, 0, 0, 0, 0,
+ 1, 125, 0, 0, 0, 0,
+ 1, 126, 0, 0, 0, 0,
+ 1, 127, 0, 0, 0, 0,
+ 3, -30, -126, -84, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -102, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -98, 0, 0,
+ 3, -30, -128, -90, 0, 0,
+ 3, -30, -128, -96, 0, 0,
+ 3, -30, -128, -95, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -80, 0, 0,
+ 2, -59, -96, 0, 0, 0,
+ 3, -30, -128, -71, 0, 0,
+ 2, -59, -102, 0, 0, 0,
+ 2, -59, -92, 0, 0, 0,
+ 2, -59, -67, 0, 0, 0,
+ 2, -59, -71, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -104, 0, 0,
+ 3, -30, -128, -103, 0, 0,
+ 3, -30, -128, -100, 0, 0,
+ 3, -30, -128, -99, 0, 0,
+ 3, -30, -128, -94, 0, 0,
+ 3, -30, -128, -109, 0, 0,
+ 3, -30, -128, -108, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -124, -94, 0, 0,
+ 2, -59, -95, 0, 0, 0,
+ 3, -30, -128, -70, 0, 0,
+ 2, -59, -101, 0, 0, 0,
+ 2, -59, -91, 0, 0, 0,
+ 2, -59, -66, 0, 0, 0,
+ 2, -59, -70, 0, 0, 0,
+ 2, -62, -96, 0, 0, 0,
+ 2, -53, -121, 0, 0, 0,
+ 2, -53, -104, 0, 0, 0,
+ 2, -59, -127, 0, 0, 0,
+ 2, -62, -92, 0, 0, 0,
+ 2, -60, -124, 0, 0, 0,
+ 2, -62, -90, 0, 0, 0,
+ 2, -62, -89, 0, 0, 0,
+ 2, -62, -88, 0, 0, 0,
+ 2, -62, -87, 0, 0, 0,
+ 2, -59, -98, 0, 0, 0,
+ 2, -62, -85, 0, 0, 0,
+ 2, -62, -84, 0, 0, 0,
+ 2, -62, -83, 0, 0, 0,
+ 2, -62, -82, 0, 0, 0,
+ 2, -59, -69, 0, 0, 0,
+ 2, -62, -80, 0, 0, 0,
+ 2, -62, -79, 0, 0, 0,
+ 2, -53, -101, 0, 0, 0,
+ 2, -59, -126, 0, 0, 0,
+ 2, -62, -76, 0, 0, 0,
+ 2, -62, -75, 0, 0, 0,
+ 2, -62, -74, 0, 0, 0,
+ 2, -62, -73, 0, 0, 0,
+ 2, -62, -72, 0, 0, 0,
+ 2, -60, -123, 0, 0, 0,
+ 2, -59, -97, 0, 0, 0,
+ 2, -62, -69, 0, 0, 0,
+ 2, -60, -67, 0, 0, 0,
+ 2, -53, -99, 0, 0, 0,
+ 2, -60, -66, 0, 0, 0,
+ 2, -59, -68, 0, 0, 0,
+ 2, -59, -108, 0, 0, 0,
+ 2, -61, -127, 0, 0, 0,
+ 2, -61, -126, 0, 0, 0,
+ 2, -60, -126, 0, 0, 0,
+ 2, -61, -124, 0, 0, 0,
+ 2, -60, -71, 0, 0, 0,
+ 2, -60, -122, 0, 0, 0,
+ 2, -61, -121, 0, 0, 0,
+ 2, -60, -116, 0, 0, 0,
+ 2, -61, -119, 0, 0, 0,
+ 2, -60, -104, 0, 0, 0,
+ 2, -61, -117, 0, 0, 0,
+ 2, -60, -102, 0, 0, 0,
+ 2, -61, -115, 0, 0, 0,
+ 2, -61, -114, 0, 0, 0,
+ 2, -60, -114, 0, 0, 0,
+ 2, -60, -112, 0, 0, 0,
+ 2, -59, -125, 0, 0, 0,
+ 2, -59, -121, 0, 0, 0,
+ 2, -61, -109, 0, 0, 0,
+ 2, -61, -108, 0, 0, 0,
+ 2, -59, -112, 0, 0, 0,
+ 2, -61, -106, 0, 0, 0,
+ 2, -61, -105, 0, 0, 0,
+ 2, -59, -104, 0, 0, 0,
+ 2, -59, -82, 0, 0, 0,
+ 2, -61, -102, 0, 0, 0,
+ 2, -59, -80, 0, 0, 0,
+ 2, -61, -100, 0, 0, 0,
+ 2, -61, -99, 0, 0, 0,
+ 2, -59, -94, 0, 0, 0,
+ 2, -61, -97, 0, 0, 0,
+ 2, -59, -107, 0, 0, 0,
+ 2, -61, -95, 0, 0, 0,
+ 2, -61, -94, 0, 0, 0,
+ 2, -60, -125, 0, 0, 0,
+ 2, -61, -92, 0, 0, 0,
+ 2, -60, -70, 0, 0, 0,
+ 2, -60, -121, 0, 0, 0,
+ 2, -61, -89, 0, 0, 0,
+ 2, -60, -115, 0, 0, 0,
+ 2, -61, -87, 0, 0, 0,
+ 2, -60, -103, 0, 0, 0,
+ 2, -61, -85, 0, 0, 0,
+ 2, -60, -101, 0, 0, 0,
+ 2, -61, -83, 0, 0, 0,
+ 2, -61, -82, 0, 0, 0,
+ 2, -60, -113, 0, 0, 0,
+ 2, -60, -111, 0, 0, 0,
+ 2, -59, -124, 0, 0, 0,
+ 2, -59, -120, 0, 0, 0,
+ 2, -61, -77, 0, 0, 0,
+ 2, -61, -76, 0, 0, 0,
+ 2, -59, -111, 0, 0, 0,
+ 2, -61, -74, 0, 0, 0,
+ 2, -61, -73, 0, 0, 0,
+ 2, -59, -103, 0, 0, 0,
+ 2, -59, -81, 0, 0, 0,
+ 2, -61, -70, 0, 0, 0,
+ 2, -59, -79, 0, 0, 0,
+ 2, -61, -68, 0, 0, 0,
+ 2, -61, -67, 0, 0, 0,
+ 2, -59, -93, 0, 0, 0,
+ 2, -53, -103, 0, 0, 0
+};
+
+/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic
+/// and other languages
+static signed char windows_1251[] =
+{
+ 1, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0,
+ 1, 2, 0, 0, 0, 0,
+ 1, 3, 0, 0, 0, 0,
+ 1, 4, 0, 0, 0, 0,
+ 1, 5, 0, 0, 0, 0,
+ 1, 6, 0, 0, 0, 0,
+ 1, 7, 0, 0, 0, 0,
+ 1, 8, 0, 0, 0, 0,
+ 1, 9, 0, 0, 0, 0,
+ 1, 10, 0, 0, 0, 0,
+ 1, 11, 0, 0, 0, 0,
+ 1, 12, 0, 0, 0, 0,
+ 1, 13, 0, 0, 0, 0,
+ 1, 14, 0, 0, 0, 0,
+ 1, 15, 0, 0, 0, 0,
+ 1, 16, 0, 0, 0, 0,
+ 1, 17, 0, 0, 0, 0,
+ 1, 18, 0, 0, 0, 0,
+ 1, 19, 0, 0, 0, 0,
+ 1, 20, 0, 0, 0, 0,
+ 1, 21, 0, 0, 0, 0,
+ 1, 22, 0, 0, 0, 0,
+ 1, 23, 0, 0, 0, 0,
+ 1, 24, 0, 0, 0, 0,
+ 1, 25, 0, 0, 0, 0,
+ 1, 26, 0, 0, 0, 0,
+ 1, 27, 0, 0, 0, 0,
+ 1, 28, 0, 0, 0, 0,
+ 1, 29, 0, 0, 0, 0,
+ 1, 30, 0, 0, 0, 0,
+ 1, 31, 0, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0,
+ 1, 33, 0, 0, 0, 0,
+ 1, 34, 0, 0, 0, 0,
+ 1, 35, 0, 0, 0, 0,
+ 1, 36, 0, 0, 0, 0,
+ 1, 37, 0, 0, 0, 0,
+ 1, 38, 0, 0, 0, 0,
+ 1, 39, 0, 0, 0, 0,
+ 1, 40, 0, 0, 0, 0,
+ 1, 41, 0, 0, 0, 0,
+ 1, 42, 0, 0, 0, 0,
+ 1, 43, 0, 0, 0, 0,
+ 1, 44, 0, 0, 0, 0,
+ 1, 45, 0, 0, 0, 0,
+ 1, 46, 0, 0, 0, 0,
+ 1, 47, 0, 0, 0, 0,
+ 1, 48, 0, 0, 0, 0,
+ 1, 49, 0, 0, 0, 0,
+ 1, 50, 0, 0, 0, 0,
+ 1, 51, 0, 0, 0, 0,
+ 1, 52, 0, 0, 0, 0,
+ 1, 53, 0, 0, 0, 0,
+ 1, 54, 0, 0, 0, 0,
+ 1, 55, 0, 0, 0, 0,
+ 1, 56, 0, 0, 0, 0,
+ 1, 57, 0, 0, 0, 0,
+ 1, 58, 0, 0, 0, 0,
+ 1, 59, 0, 0, 0, 0,
+ 1, 60, 0, 0, 0, 0,
+ 1, 61, 0, 0, 0, 0,
+ 1, 62, 0, 0, 0, 0,
+ 1, 63, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0,
+ 1, 65, 0, 0, 0, 0,
+ 1, 66, 0, 0, 0, 0,
+ 1, 67, 0, 0, 0, 0,
+ 1, 68, 0, 0, 0, 0,
+ 1, 69, 0, 0, 0, 0,
+ 1, 70, 0, 0, 0, 0,
+ 1, 71, 0, 0, 0, 0,
+ 1, 72, 0, 0, 0, 0,
+ 1, 73, 0, 0, 0, 0,
+ 1, 74, 0, 0, 0, 0,
+ 1, 75, 0, 0, 0, 0,
+ 1, 76, 0, 0, 0, 0,
+ 1, 77, 0, 0, 0, 0,
+ 1, 78, 0, 0, 0, 0,
+ 1, 79, 0, 0, 0, 0,
+ 1, 80, 0, 0, 0, 0,
+ 1, 81, 0, 0, 0, 0,
+ 1, 82, 0, 0, 0, 0,
+ 1, 83, 0, 0, 0, 0,
+ 1, 84, 0, 0, 0, 0,
+ 1, 85, 0, 0, 0, 0,
+ 1, 86, 0, 0, 0, 0,
+ 1, 87, 0, 0, 0, 0,
+ 1, 88, 0, 0, 0, 0,
+ 1, 89, 0, 0, 0, 0,
+ 1, 90, 0, 0, 0, 0,
+ 1, 91, 0, 0, 0, 0,
+ 1, 92, 0, 0, 0, 0,
+ 1, 93, 0, 0, 0, 0,
+ 1, 94, 0, 0, 0, 0,
+ 1, 95, 0, 0, 0, 0,
+ 1, 96, 0, 0, 0, 0,
+ 1, 97, 0, 0, 0, 0,
+ 1, 98, 0, 0, 0, 0,
+ 1, 99, 0, 0, 0, 0,
+ 1, 100, 0, 0, 0, 0,
+ 1, 101, 0, 0, 0, 0,
+ 1, 102, 0, 0, 0, 0,
+ 1, 103, 0, 0, 0, 0,
+ 1, 104, 0, 0, 0, 0,
+ 1, 105, 0, 0, 0, 0,
+ 1, 106, 0, 0, 0, 0,
+ 1, 107, 0, 0, 0, 0,
+ 1, 108, 0, 0, 0, 0,
+ 1, 109, 0, 0, 0, 0,
+ 1, 110, 0, 0, 0, 0,
+ 1, 111, 0, 0, 0, 0,
+ 1, 112, 0, 0, 0, 0,
+ 1, 113, 0, 0, 0, 0,
+ 1, 114, 0, 0, 0, 0,
+ 1, 115, 0, 0, 0, 0,
+ 1, 116, 0, 0, 0, 0,
+ 1, 117, 0, 0, 0, 0,
+ 1, 118, 0, 0, 0, 0,
+ 1, 119, 0, 0, 0, 0,
+ 1, 120, 0, 0, 0, 0,
+ 1, 121, 0, 0, 0, 0,
+ 1, 122, 0, 0, 0, 0,
+ 1, 123, 0, 0, 0, 0,
+ 1, 124, 0, 0, 0, 0,
+ 1, 125, 0, 0, 0, 0,
+ 1, 126, 0, 0, 0, 0,
+ 1, 127, 0, 0, 0, 0,
+ 2, -48, -126, 0, 0, 0,
+ 2, -48, -125, 0, 0, 0,
+ 3, -30, -128, -102, 0, 0,
+ 2, -47, -109, 0, 0, 0,
+ 3, -30, -128, -98, 0, 0,
+ 3, -30, -128, -90, 0, 0,
+ 3, -30, -128, -96, 0, 0,
+ 3, -30, -128, -95, 0, 0,
+ 3, -30, -126, -84, 0, 0,
+ 3, -30, -128, -80, 0, 0,
+ 2, -48, -119, 0, 0, 0,
+ 3, -30, -128, -71, 0, 0,
+ 2, -48, -118, 0, 0, 0,
+ 2, -48, -116, 0, 0, 0,
+ 2, -48, -117, 0, 0, 0,
+ 2, -48, -113, 0, 0, 0,
+ 2, -47, -110, 0, 0, 0,
+ 3, -30, -128, -104, 0, 0,
+ 3, -30, -128, -103, 0, 0,
+ 3, -30, -128, -100, 0, 0,
+ 3, -30, -128, -99, 0, 0,
+ 3, -30, -128, -94, 0, 0,
+ 3, -30, -128, -109, 0, 0,
+ 3, -30, -128, -108, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -124, -94, 0, 0,
+ 2, -47, -103, 0, 0, 0,
+ 3, -30, -128, -70, 0, 0,
+ 2, -47, -102, 0, 0, 0,
+ 2, -47, -100, 0, 0, 0,
+ 2, -47, -101, 0, 0, 0,
+ 2, -47, -97, 0, 0, 0,
+ 2, -62, -96, 0, 0, 0,
+ 2, -48, -114, 0, 0, 0,
+ 2, -47, -98, 0, 0, 0,
+ 2, -48, -120, 0, 0, 0,
+ 2, -62, -92, 0, 0, 0,
+ 2, -46, -112, 0, 0, 0,
+ 2, -62, -90, 0, 0, 0,
+ 2, -62, -89, 0, 0, 0,
+ 2, -48, -127, 0, 0, 0,
+ 2, -62, -87, 0, 0, 0,
+ 2, -48, -124, 0, 0, 0,
+ 2, -62, -85, 0, 0, 0,
+ 2, -62, -84, 0, 0, 0,
+ 2, -62, -83, 0, 0, 0,
+ 2, -62, -82, 0, 0, 0,
+ 2, -48, -121, 0, 0, 0,
+ 2, -62, -80, 0, 0, 0,
+ 2, -62, -79, 0, 0, 0,
+ 2, -48, -122, 0, 0, 0,
+ 2, -47, -106, 0, 0, 0,
+ 2, -46, -111, 0, 0, 0,
+ 2, -62, -75, 0, 0, 0,
+ 2, -62, -74, 0, 0, 0,
+ 2, -62, -73, 0, 0, 0,
+ 2, -47, -111, 0, 0, 0,
+ 3, -30, -124, -106, 0, 0,
+ 2, -47, -108, 0, 0, 0,
+ 2, -62, -69, 0, 0, 0,
+ 2, -47, -104, 0, 0, 0,
+ 2, -48, -123, 0, 0, 0,
+ 2, -47, -107, 0, 0, 0,
+ 2, -47, -105, 0, 0, 0,
+ 2, -48, -112, 0, 0, 0,
+ 2, -48, -111, 0, 0, 0,
+ 2, -48, -110, 0, 0, 0,
+ 2, -48, -109, 0, 0, 0,
+ 2, -48, -108, 0, 0, 0,
+ 2, -48, -107, 0, 0, 0,
+ 2, -48, -106, 0, 0, 0,
+ 2, -48, -105, 0, 0, 0,
+ 2, -48, -104, 0, 0, 0,
+ 2, -48, -103, 0, 0, 0,
+ 2, -48, -102, 0, 0, 0,
+ 2, -48, -101, 0, 0, 0,
+ 2, -48, -100, 0, 0, 0,
+ 2, -48, -99, 0, 0, 0,
+ 2, -48, -98, 0, 0, 0,
+ 2, -48, -97, 0, 0, 0,
+ 2, -48, -96, 0, 0, 0,
+ 2, -48, -95, 0, 0, 0,
+ 2, -48, -94, 0, 0, 0,
+ 2, -48, -93, 0, 0, 0,
+ 2, -48, -92, 0, 0, 0,
+ 2, -48, -91, 0, 0, 0,
+ 2, -48, -90, 0, 0, 0,
+ 2, -48, -89, 0, 0, 0,
+ 2, -48, -88, 0, 0, 0,
+ 2, -48, -87, 0, 0, 0,
+ 2, -48, -86, 0, 0, 0,
+ 2, -48, -85, 0, 0, 0,
+ 2, -48, -84, 0, 0, 0,
+ 2, -48, -83, 0, 0, 0,
+ 2, -48, -82, 0, 0, 0,
+ 2, -48, -81, 0, 0, 0,
+ 2, -48, -80, 0, 0, 0,
+ 2, -48, -79, 0, 0, 0,
+ 2, -48, -78, 0, 0, 0,
+ 2, -48, -77, 0, 0, 0,
+ 2, -48, -76, 0, 0, 0,
+ 2, -48, -75, 0, 0, 0,
+ 2, -48, -74, 0, 0, 0,
+ 2, -48, -73, 0, 0, 0,
+ 2, -48, -72, 0, 0, 0,
+ 2, -48, -71, 0, 0, 0,
+ 2, -48, -70, 0, 0, 0,
+ 2, -48, -69, 0, 0, 0,
+ 2, -48, -68, 0, 0, 0,
+ 2, -48, -67, 0, 0, 0,
+ 2, -48, -66, 0, 0, 0,
+ 2, -48, -65, 0, 0, 0,
+ 2, -47, -128, 0, 0, 0,
+ 2, -47, -127, 0, 0, 0,
+ 2, -47, -126, 0, 0, 0,
+ 2, -47, -125, 0, 0, 0,
+ 2, -47, -124, 0, 0, 0,
+ 2, -47, -123, 0, 0, 0,
+ 2, -47, -122, 0, 0, 0,
+ 2, -47, -121, 0, 0, 0,
+ 2, -47, -120, 0, 0, 0,
+ 2, -47, -119, 0, 0, 0,
+ 2, -47, -118, 0, 0, 0,
+ 2, -47, -117, 0, 0, 0,
+ 2, -47, -116, 0, 0, 0,
+ 2, -47, -115, 0, 0, 0,
+ 2, -47, -114, 0, 0, 0,
+ 2, -47, -113, 0, 0, 0
+};
+
+/// Latin alphabet used by English and some other Western languages
+static signed char windows_1252[] =
+{
+ 1, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0,
+ 1, 2, 0, 0, 0, 0,
+ 1, 3, 0, 0, 0, 0,
+ 1, 4, 0, 0, 0, 0,
+ 1, 5, 0, 0, 0, 0,
+ 1, 6, 0, 0, 0, 0,
+ 1, 7, 0, 0, 0, 0,
+ 1, 8, 0, 0, 0, 0,
+ 1, 9, 0, 0, 0, 0,
+ 1, 10, 0, 0, 0, 0,
+ 1, 11, 0, 0, 0, 0,
+ 1, 12, 0, 0, 0, 0,
+ 1, 13, 0, 0, 0, 0,
+ 1, 14, 0, 0, 0, 0,
+ 1, 15, 0, 0, 0, 0,
+ 1, 16, 0, 0, 0, 0,
+ 1, 17, 0, 0, 0, 0,
+ 1, 18, 0, 0, 0, 0,
+ 1, 19, 0, 0, 0, 0,
+ 1, 20, 0, 0, 0, 0,
+ 1, 21, 0, 0, 0, 0,
+ 1, 22, 0, 0, 0, 0,
+ 1, 23, 0, 0, 0, 0,
+ 1, 24, 0, 0, 0, 0,
+ 1, 25, 0, 0, 0, 0,
+ 1, 26, 0, 0, 0, 0,
+ 1, 27, 0, 0, 0, 0,
+ 1, 28, 0, 0, 0, 0,
+ 1, 29, 0, 0, 0, 0,
+ 1, 30, 0, 0, 0, 0,
+ 1, 31, 0, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0,
+ 1, 33, 0, 0, 0, 0,
+ 1, 34, 0, 0, 0, 0,
+ 1, 35, 0, 0, 0, 0,
+ 1, 36, 0, 0, 0, 0,
+ 1, 37, 0, 0, 0, 0,
+ 1, 38, 0, 0, 0, 0,
+ 1, 39, 0, 0, 0, 0,
+ 1, 40, 0, 0, 0, 0,
+ 1, 41, 0, 0, 0, 0,
+ 1, 42, 0, 0, 0, 0,
+ 1, 43, 0, 0, 0, 0,
+ 1, 44, 0, 0, 0, 0,
+ 1, 45, 0, 0, 0, 0,
+ 1, 46, 0, 0, 0, 0,
+ 1, 47, 0, 0, 0, 0,
+ 1, 48, 0, 0, 0, 0,
+ 1, 49, 0, 0, 0, 0,
+ 1, 50, 0, 0, 0, 0,
+ 1, 51, 0, 0, 0, 0,
+ 1, 52, 0, 0, 0, 0,
+ 1, 53, 0, 0, 0, 0,
+ 1, 54, 0, 0, 0, 0,
+ 1, 55, 0, 0, 0, 0,
+ 1, 56, 0, 0, 0, 0,
+ 1, 57, 0, 0, 0, 0,
+ 1, 58, 0, 0, 0, 0,
+ 1, 59, 0, 0, 0, 0,
+ 1, 60, 0, 0, 0, 0,
+ 1, 61, 0, 0, 0, 0,
+ 1, 62, 0, 0, 0, 0,
+ 1, 63, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0,
+ 1, 65, 0, 0, 0, 0,
+ 1, 66, 0, 0, 0, 0,
+ 1, 67, 0, 0, 0, 0,
+ 1, 68, 0, 0, 0, 0,
+ 1, 69, 0, 0, 0, 0,
+ 1, 70, 0, 0, 0, 0,
+ 1, 71, 0, 0, 0, 0,
+ 1, 72, 0, 0, 0, 0,
+ 1, 73, 0, 0, 0, 0,
+ 1, 74, 0, 0, 0, 0,
+ 1, 75, 0, 0, 0, 0,
+ 1, 76, 0, 0, 0, 0,
+ 1, 77, 0, 0, 0, 0,
+ 1, 78, 0, 0, 0, 0,
+ 1, 79, 0, 0, 0, 0,
+ 1, 80, 0, 0, 0, 0,
+ 1, 81, 0, 0, 0, 0,
+ 1, 82, 0, 0, 0, 0,
+ 1, 83, 0, 0, 0, 0,
+ 1, 84, 0, 0, 0, 0,
+ 1, 85, 0, 0, 0, 0,
+ 1, 86, 0, 0, 0, 0,
+ 1, 87, 0, 0, 0, 0,
+ 1, 88, 0, 0, 0, 0,
+ 1, 89, 0, 0, 0, 0,
+ 1, 90, 0, 0, 0, 0,
+ 1, 91, 0, 0, 0, 0,
+ 1, 92, 0, 0, 0, 0,
+ 1, 93, 0, 0, 0, 0,
+ 1, 94, 0, 0, 0, 0,
+ 1, 95, 0, 0, 0, 0,
+ 1, 96, 0, 0, 0, 0,
+ 1, 97, 0, 0, 0, 0,
+ 1, 98, 0, 0, 0, 0,
+ 1, 99, 0, 0, 0, 0,
+ 1, 100, 0, 0, 0, 0,
+ 1, 101, 0, 0, 0, 0,
+ 1, 102, 0, 0, 0, 0,
+ 1, 103, 0, 0, 0, 0,
+ 1, 104, 0, 0, 0, 0,
+ 1, 105, 0, 0, 0, 0,
+ 1, 106, 0, 0, 0, 0,
+ 1, 107, 0, 0, 0, 0,
+ 1, 108, 0, 0, 0, 0,
+ 1, 109, 0, 0, 0, 0,
+ 1, 110, 0, 0, 0, 0,
+ 1, 111, 0, 0, 0, 0,
+ 1, 112, 0, 0, 0, 0,
+ 1, 113, 0, 0, 0, 0,
+ 1, 114, 0, 0, 0, 0,
+ 1, 115, 0, 0, 0, 0,
+ 1, 116, 0, 0, 0, 0,
+ 1, 117, 0, 0, 0, 0,
+ 1, 118, 0, 0, 0, 0,
+ 1, 119, 0, 0, 0, 0,
+ 1, 120, 0, 0, 0, 0,
+ 1, 121, 0, 0, 0, 0,
+ 1, 122, 0, 0, 0, 0,
+ 1, 123, 0, 0, 0, 0,
+ 1, 124, 0, 0, 0, 0,
+ 1, 125, 0, 0, 0, 0,
+ 1, 126, 0, 0, 0, 0,
+ 1, 127, 0, 0, 0, 0,
+ 3, -30, -126, -84, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -102, 0, 0,
+ 2, -58, -110, 0, 0, 0,
+ 3, -30, -128, -98, 0, 0,
+ 3, -30, -128, -90, 0, 0,
+ 3, -30, -128, -96, 0, 0,
+ 3, -30, -128, -95, 0, 0,
+ 2, -53, -122, 0, 0, 0,
+ 3, -30, -128, -80, 0, 0,
+ 2, -59, -96, 0, 0, 0,
+ 3, -30, -128, -71, 0, 0,
+ 2, -59, -110, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 2, -59, -67, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 3, -30, -128, -104, 0, 0,
+ 3, -30, -128, -103, 0, 0,
+ 3, -30, -128, -100, 0, 0,
+ 3, -30, -128, -99, 0, 0,
+ 3, -30, -128, -94, 0, 0,
+ 3, -30, -128, -109, 0, 0,
+ 3, -30, -128, -108, 0, 0,
+ 2, -53, -100, 0, 0, 0,
+ 3, -30, -124, -94, 0, 0,
+ 2, -59, -95, 0, 0, 0,
+ 3, -30, -128, -70, 0, 0,
+ 2, -59, -109, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0, // not part of this charset
+ 2, -59, -66, 0, 0, 0,
+ 2, -59, -72, 0, 0, 0,
+ 2, -62, -96, 0, 0, 0,
+ 2, -62, -95, 0, 0, 0,
+ 2, -62, -94, 0, 0, 0,
+ 2, -62, -93, 0, 0, 0,
+ 2, -62, -92, 0, 0, 0,
+ 2, -62, -91, 0, 0, 0,
+ 2, -62, -90, 0, 0, 0,
+ 2, -62, -89, 0, 0, 0,
+ 2, -62, -88, 0, 0, 0,
+ 2, -62, -87, 0, 0, 0,
+ 2, -62, -86, 0, 0, 0,
+ 2, -62, -85, 0, 0, 0,
+ 2, -62, -84, 0, 0, 0,
+ 2, -62, -83, 0, 0, 0,
+ 2, -62, -82, 0, 0, 0,
+ 2, -62, -81, 0, 0, 0,
+ 2, -62, -80, 0, 0, 0,
+ 2, -62, -79, 0, 0, 0,
+ 2, -62, -78, 0, 0, 0,
+ 2, -62, -77, 0, 0, 0,
+ 2, -62, -76, 0, 0, 0,
+ 2, -62, -75, 0, 0, 0,
+ 2, -62, -74, 0, 0, 0,
+ 2, -62, -73, 0, 0, 0,
+ 2, -62, -72, 0, 0, 0,
+ 2, -62, -71, 0, 0, 0,
+ 2, -62, -70, 0, 0, 0,
+ 2, -62, -69, 0, 0, 0,
+ 2, -62, -68, 0, 0, 0,
+ 2, -62, -67, 0, 0, 0,
+ 2, -62, -66, 0, 0, 0,
+ 2, -62, -65, 0, 0, 0,
+ 2, -61, -128, 0, 0, 0,
+ 2, -61, -127, 0, 0, 0,
+ 2, -61, -126, 0, 0, 0,
+ 2, -61, -125, 0, 0, 0,
+ 2, -61, -124, 0, 0, 0,
+ 2, -61, -123, 0, 0, 0,
+ 2, -61, -122, 0, 0, 0,
+ 2, -61, -121, 0, 0, 0,
+ 2, -61, -120, 0, 0, 0,
+ 2, -61, -119, 0, 0, 0,
+ 2, -61, -118, 0, 0, 0,
+ 2, -61, -117, 0, 0, 0,
+ 2, -61, -116, 0, 0, 0,
+ 2, -61, -115, 0, 0, 0,
+ 2, -61, -114, 0, 0, 0,
+ 2, -61, -113, 0, 0, 0,
+ 2, -61, -112, 0, 0, 0,
+ 2, -61, -111, 0, 0, 0,
+ 2, -61, -110, 0, 0, 0,
+ 2, -61, -109, 0, 0, 0,
+ 2, -61, -108, 0, 0, 0,
+ 2, -61, -107, 0, 0, 0,
+ 2, -61, -106, 0, 0, 0,
+ 2, -61, -105, 0, 0, 0,
+ 2, -61, -104, 0, 0, 0,
+ 2, -61, -103, 0, 0, 0,
+ 2, -61, -102, 0, 0, 0,
+ 2, -61, -101, 0, 0, 0,
+ 2, -61, -100, 0, 0, 0,
+ 2, -61, -99, 0, 0, 0,
+ 2, -61, -98, 0, 0, 0,
+ 2, -61, -97, 0, 0, 0,
+ 2, -61, -96, 0, 0, 0,
+ 2, -61, -95, 0, 0, 0,
+ 2, -61, -94, 0, 0, 0,
+ 2, -61, -93, 0, 0, 0,
+ 2, -61, -92, 0, 0, 0,
+ 2, -61, -91, 0, 0, 0,
+ 2, -61, -90, 0, 0, 0,
+ 2, -61, -89, 0, 0, 0,
+ 2, -61, -88, 0, 0, 0,
+ 2, -61, -87, 0, 0, 0,
+ 2, -61, -86, 0, 0, 0,
+ 2, -61, -85, 0, 0, 0,
+ 2, -61, -84, 0, 0, 0,
+ 2, -61, -83, 0, 0, 0,
+ 2, -61, -82, 0, 0, 0,
+ 2, -61, -81, 0, 0, 0,
+ 2, -61, -80, 0, 0, 0,
+ 2, -61, -79, 0, 0, 0,
+ 2, -61, -78, 0, 0, 0,
+ 2, -61, -77, 0, 0, 0,
+ 2, -61, -76, 0, 0, 0,
+ 2, -61, -75, 0, 0, 0,
+ 2, -61, -74, 0, 0, 0,
+ 2, -61, -73, 0, 0, 0,
+ 2, -61, -72, 0, 0, 0,
+ 2, -61, -71, 0, 0, 0,
+ 2, -61, -70, 0, 0, 0,
+ 2, -61, -69, 0, 0, 0,
+ 2, -61, -68, 0, 0, 0,
+ 2, -61, -67, 0, 0, 0,
+ 2, -61, -66, 0, 0, 0,
+ 2, -61, -65, 0, 0, 0
+};
+static signed char cp437[] =
+{
+ 1, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0,
+ 1, 2, 0, 0, 0, 0,
+ 1, 3, 0, 0, 0, 0,
+ 1, 4, 0, 0, 0, 0,
+ 1, 5, 0, 0, 0, 0,
+ 1, 6, 0, 0, 0, 0,
+ 1, 7, 0, 0, 0, 0,
+ 1, 8, 0, 0, 0, 0,
+ 1, 9, 0, 0, 0, 0,
+ 1, 10, 0, 0, 0, 0,
+ 1, 11, 0, 0, 0, 0,
+ 1, 12, 0, 0, 0, 0,
+ 1, 13, 0, 0, 0, 0,
+ 1, 14, 0, 0, 0, 0,
+ 1, 15, 0, 0, 0, 0,
+ 1, 16, 0, 0, 0, 0,
+ 1, 17, 0, 0, 0, 0,
+ 1, 18, 0, 0, 0, 0,
+ 1, 19, 0, 0, 0, 0,
+ 1, 20, 0, 0, 0, 0,
+ 1, 21, 0, 0, 0, 0,
+ 1, 22, 0, 0, 0, 0,
+ 1, 23, 0, 0, 0, 0,
+ 1, 24, 0, 0, 0, 0,
+ 1, 25, 0, 0, 0, 0,
+ 1, 26, 0, 0, 0, 0,
+ 1, 27, 0, 0, 0, 0,
+ 1, 28, 0, 0, 0, 0,
+ 1, 29, 0, 0, 0, 0,
+ 1, 30, 0, 0, 0, 0,
+ 1, 31, 0, 0, 0, 0,
+ 1, 32, 0, 0, 0, 0,
+ 1, 33, 0, 0, 0, 0,
+ 1, 34, 0, 0, 0, 0,
+ 1, 35, 0, 0, 0, 0,
+ 1, 36, 0, 0, 0, 0,
+ 1, 37, 0, 0, 0, 0,
+ 1, 38, 0, 0, 0, 0,
+ 1, 39, 0, 0, 0, 0,
+ 1, 40, 0, 0, 0, 0,
+ 1, 41, 0, 0, 0, 0,
+ 1, 42, 0, 0, 0, 0,
+ 1, 43, 0, 0, 0, 0,
+ 1, 44, 0, 0, 0, 0,
+ 1, 45, 0, 0, 0, 0,
+ 1, 46, 0, 0, 0, 0,
+ 1, 47, 0, 0, 0, 0,
+ 1, 48, 0, 0, 0, 0,
+ 1, 49, 0, 0, 0, 0,
+ 1, 50, 0, 0, 0, 0,
+ 1, 51, 0, 0, 0, 0,
+ 1, 52, 0, 0, 0, 0,
+ 1, 53, 0, 0, 0, 0,
+ 1, 54, 0, 0, 0, 0,
+ 1, 55, 0, 0, 0, 0,
+ 1, 56, 0, 0, 0, 0,
+ 1, 57, 0, 0, 0, 0,
+ 1, 58, 0, 0, 0, 0,
+ 1, 59, 0, 0, 0, 0,
+ 1, 60, 0, 0, 0, 0,
+ 1, 61, 0, 0, 0, 0,
+ 1, 62, 0, 0, 0, 0,
+ 1, 63, 0, 0, 0, 0,
+ 1, 64, 0, 0, 0, 0,
+ 1, 65, 0, 0, 0, 0,
+ 1, 66, 0, 0, 0, 0,
+ 1, 67, 0, 0, 0, 0,
+ 1, 68, 0, 0, 0, 0,
+ 1, 69, 0, 0, 0, 0,
+ 1, 70, 0, 0, 0, 0,
+ 1, 71, 0, 0, 0, 0,
+ 1, 72, 0, 0, 0, 0,
+ 1, 73, 0, 0, 0, 0,
+ 1, 74, 0, 0, 0, 0,
+ 1, 75, 0, 0, 0, 0,
+ 1, 76, 0, 0, 0, 0,
+ 1, 77, 0, 0, 0, 0,
+ 1, 78, 0, 0, 0, 0,
+ 1, 79, 0, 0, 0, 0,
+ 1, 80, 0, 0, 0, 0,
+ 1, 81, 0, 0, 0, 0,
+ 1, 82, 0, 0, 0, 0,
+ 1, 83, 0, 0, 0, 0,
+ 1, 84, 0, 0, 0, 0,
+ 1, 85, 0, 0, 0, 0,
+ 1, 86, 0, 0, 0, 0,
+ 1, 87, 0, 0, 0, 0,
+ 1, 88, 0, 0, 0, 0,
+ 1, 89, 0, 0, 0, 0,
+ 1, 90, 0, 0, 0, 0,
+ 1, 91, 0, 0, 0, 0,
+ 1, 92, 0, 0, 0, 0,
+ 1, 93, 0, 0, 0, 0,
+ 1, 94, 0, 0, 0, 0,
+ 1, 95, 0, 0, 0, 0,
+ 1, 96, 0, 0, 0, 0,
+ 1, 97, 0, 0, 0, 0,
+ 1, 98, 0, 0, 0, 0,
+ 1, 99, 0, 0, 0, 0,
+ 1, 100, 0, 0, 0, 0,
+ 1, 101, 0, 0, 0, 0,
+ 1, 102, 0, 0, 0, 0,
+ 1, 103, 0, 0, 0, 0,
+ 1, 104, 0, 0, 0, 0,
+ 1, 105, 0, 0, 0, 0,
+ 1, 106, 0, 0, 0, 0,
+ 1, 107, 0, 0, 0, 0,
+ 1, 108, 0, 0, 0, 0,
+ 1, 109, 0, 0, 0, 0,
+ 1, 110, 0, 0, 0, 0,
+ 1, 111, 0, 0, 0, 0,
+ 1, 112, 0, 0, 0, 0,
+ 1, 113, 0, 0, 0, 0,
+ 1, 114, 0, 0, 0, 0,
+ 1, 115, 0, 0, 0, 0,
+ 1, 116, 0, 0, 0, 0,
+ 1, 117, 0, 0, 0, 0,
+ 1, 118, 0, 0, 0, 0,
+ 1, 119, 0, 0, 0, 0,
+ 1, 120, 0, 0, 0, 0,
+ 1, 121, 0, 0, 0, 0,
+ 1, 122, 0, 0, 0, 0,
+ 1, 123, 0, 0, 0, 0,
+ 1, 124, 0, 0, 0, 0,
+ 1, 125, 0, 0, 0, 0,
+ 1, 126, 0, 0, 0, 0,
+ 1, 127, 0, 0, 0, 0,
+ 2, -61, -121, 0, 0, 0,
+ 2, -61, -68, 0, 0, 0,
+ 2, -61, -87, 0, 0, 0,
+ 2, -61, -94, 0, 0, 0,
+ 2, -61, -92, 0, 0, 0,
+ 2, -61, -96, 0, 0, 0,
+ 2, -61, -91, 0, 0, 0,
+ 2, -61, -89, 0, 0, 0,
+ 2, -61, -86, 0, 0, 0,
+ 2, -61, -85, 0, 0, 0,
+ 2, -61, -88, 0, 0, 0,
+ 2, -61, -81, 0, 0, 0,
+ 2, -61, -82, 0, 0, 0,
+ 2, -61, -84, 0, 0, 0,
+ 2, -61, -124, 0, 0, 0,
+ 2, -61, -123, 0, 0, 0,
+ 2, -61, -119, 0, 0, 0,
+ 2, -61, -90, 0, 0, 0,
+ 2, -61, -122, 0, 0, 0,
+ 2, -61, -76, 0, 0, 0,
+ 2, -61, -74, 0, 0, 0,
+ 2, -61, -78, 0, 0, 0,
+ 2, -61, -69, 0, 0, 0,
+ 2, -61, -71, 0, 0, 0,
+ 2, -61, -65, 0, 0, 0,
+ 2, -61, -106, 0, 0, 0,
+ 2, -61, -100, 0, 0, 0,
+ 2, -62, -94, 0, 0, 0,
+ 2, -62, -93, 0, 0, 0,
+ 2, -62, -91, 0, 0, 0,
+ 3, -30, -126, -89, 0, 0,
+ 2, -58, -110, 0, 0, 0,
+ 2, -61, -95, 0, 0, 0,
+ 2, -61, -83, 0, 0, 0,
+ 2, -61, -77, 0, 0, 0,
+ 2, -61, -70, 0, 0, 0,
+ 2, -61, -79, 0, 0, 0,
+ 2, -61, -111, 0, 0, 0,
+ 2, -62, -86, 0, 0, 0,
+ 2, -62, -70, 0, 0, 0,
+ 2, -62, -65, 0, 0, 0,
+ 3, -30, -116, -112, 0, 0,
+ 2, -62, -84, 0, 0, 0,
+ 2, -62, -67, 0, 0, 0,
+ 2, -62, -68, 0, 0, 0,
+ 2, -62, -95, 0, 0, 0,
+ 2, -62, -85, 0, 0, 0,
+ 2, -62, -69, 0, 0, 0,
+ 3, -30, -106, -111, 0, 0,
+ 3, -30, -106, -110, 0, 0,
+ 3, -30, -106, -109, 0, 0,
+ 3, -30, -108, -126, 0, 0,
+ 3, -30, -108, -92, 0, 0,
+ 3, -30, -107, -95, 0, 0,
+ 3, -30, -107, -94, 0, 0,
+ 3, -30, -107, -106, 0, 0,
+ 3, -30, -107, -107, 0, 0,
+ 3, -30, -107, -93, 0, 0,
+ 3, -30, -107, -111, 0, 0,
+ 3, -30, -107, -105, 0, 0,
+ 3, -30, -107, -99, 0, 0,
+ 3, -30, -107, -100, 0, 0,
+ 3, -30, -107, -101, 0, 0,
+ 3, -30, -108, -112, 0, 0,
+ 3, -30, -108, -108, 0, 0,
+ 3, -30, -108, -76, 0, 0,
+ 3, -30, -108, -84, 0, 0,
+ 3, -30, -108, -100, 0, 0,
+ 3, -30, -108, -128, 0, 0,
+ 3, -30, -108, -68, 0, 0,
+ 3, -30, -107, -98, 0, 0,
+ 3, -30, -107, -97, 0, 0,
+ 3, -30, -107, -102, 0, 0,
+ 3, -30, -107, -108, 0, 0,
+ 3, -30, -107, -87, 0, 0,
+ 3, -30, -107, -90, 0, 0,
+ 3, -30, -107, -96, 0, 0,
+ 3, -30, -107, -112, 0, 0,
+ 3, -30, -107, -84, 0, 0,
+ 3, -30, -107, -89, 0, 0,
+ 3, -30, -107, -88, 0, 0,
+ 3, -30, -107, -92, 0, 0,
+ 3, -30, -107, -91, 0, 0,
+ 3, -30, -107, -103, 0, 0,
+ 3, -30, -107, -104, 0, 0,
+ 3, -30, -107, -110, 0, 0,
+ 3, -30, -107, -109, 0, 0,
+ 3, -30, -107, -85, 0, 0,
+ 3, -30, -107, -86, 0, 0,
+ 3, -30, -108, -104, 0, 0,
+ 3, -30, -108, -116, 0, 0,
+ 3, -30, -106, -120, 0, 0,
+ 3, -30, -106, -124, 0, 0,
+ 3, -30, -106, -116, 0, 0,
+ 3, -30, -106, -112, 0, 0,
+ 3, -30, -106, -128, 0, 0,
+ 2, -50, -79, 0, 0, 0,
+ 2, -61, -97, 0, 0, 0,
+ 2, -50, -109, 0, 0, 0,
+ 2, -49, -128, 0, 0, 0,
+ 2, -50, -93, 0, 0, 0,
+ 2, -49, -125, 0, 0, 0,
+ 2, -62, -75, 0, 0, 0,
+ 2, -49, -124, 0, 0, 0,
+ 2, -50, -90, 0, 0, 0,
+ 2, -50, -104, 0, 0, 0,
+ 2, -50, -87, 0, 0, 0,
+ 2, -50, -76, 0, 0, 0,
+ 3, -30, -120, -98, 0, 0,
+ 2, -49, -122, 0, 0, 0,
+ 2, -50, -75, 0, 0, 0,
+ 3, -30, -120, -87, 0, 0,
+ 3, -30, -119, -95, 0, 0,
+ 2, -62, -79, 0, 0, 0,
+ 3, -30, -119, -91, 0, 0,
+ 3, -30, -119, -92, 0, 0,
+ 3, -30, -116, -96, 0, 0,
+ 3, -30, -116, -95, 0, 0,
+ 2, -61, -73, 0, 0, 0,
+ 3, -30, -119, -120, 0, 0,
+ 2, -62, -80, 0, 0, 0,
+ 3, -30, -120, -103, 0, 0,
+ 2, -62, -73, 0, 0, 0,
+ 3, -30, -120, -102, 0, 0,
+ 3, -30, -127, -65, 0, 0,
+ 2, -62, -78, 0, 0, 0,
+ 3, -30, -106, -96, 0, 0,
+ 2, -62, -96, 0, 0, 0
+};
+
+}
+
+#endif
+
diff --git a/components/to_utf8/tests/output/to_utf8_test.out b/components/to_utf8/tests/output/to_utf8_test.out
new file mode 100644
index 0000000000..dcb32359ab
--- /dev/null
+++ b/components/to_utf8/tests/output/to_utf8_test.out
@@ -0,0 +1,4 @@
+original: Без вопроÑов отдаете ему рулет, знаÑ, что позже вы Ñможете привеÑти Ñ Ñобой Ñвоих друзей и тогда он получит по заÑлугам?
+converted: Без вопроÑов отдаете ему рулет, знаÑ, что позже вы Ñможете привеÑти Ñ Ñобой Ñвоих друзей и тогда он получит по заÑлугам?
+original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger.
+converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger.
diff --git a/components/to_utf8/tests/test.sh b/components/to_utf8/tests/test.sh
new file mode 100755
index 0000000000..2d07708adc
--- /dev/null
+++ b/components/to_utf8/tests/test.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+make || exit
+
+mkdir -p output
+
+PROGS=*_test
+
+for a in $PROGS; do
+ if [ -f "output/$a.out" ]; then
+ echo "Running $a:"
+ ./$a | diff output/$a.out -
+ else
+ echo "Creating $a.out"
+ ./$a > "output/$a.out"
+ git add "output/$a.out"
+ fi
+done
diff --git a/components/to_utf8/tests/test_data/french-utf8.txt b/components/to_utf8/tests/test_data/french-utf8.txt
new file mode 100644
index 0000000000..aaaccac737
--- /dev/null
+++ b/components/to_utf8/tests/test_data/french-utf8.txt
@@ -0,0 +1 @@
+Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. \ No newline at end of file
diff --git a/components/to_utf8/tests/test_data/french-win1252.txt b/components/to_utf8/tests/test_data/french-win1252.txt
new file mode 100644
index 0000000000..1de4593e94
--- /dev/null
+++ b/components/to_utf8/tests/test_data/french-win1252.txt
@@ -0,0 +1 @@
+Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. \ No newline at end of file
diff --git a/components/to_utf8/tests/test_data/russian-utf8.txt b/components/to_utf8/tests/test_data/russian-utf8.txt
new file mode 100644
index 0000000000..eb20b32dd6
--- /dev/null
+++ b/components/to_utf8/tests/test_data/russian-utf8.txt
@@ -0,0 +1 @@
+Без вопроÑов отдаете ему рулет, знаÑ, что позже вы Ñможете привеÑти Ñ Ñобой Ñвоих друзей и тогда он получит по заÑлугам? \ No newline at end of file
diff --git a/components/to_utf8/tests/test_data/russian-win1251.txt b/components/to_utf8/tests/test_data/russian-win1251.txt
new file mode 100644
index 0000000000..086e57edd6
--- /dev/null
+++ b/components/to_utf8/tests/test_data/russian-win1251.txt
@@ -0,0 +1 @@
+Áåç âîïðîñîâ îòäàåòå åìó ðóëåò, çíàÿ, ÷òî ïîçæå âû ñìîæåòå ïðèâåñòè ñ ñîáîé ñâîèõ äðóçåé è òîãäà îí ïîëó÷èò ïî çàñëóãàì? \ No newline at end of file
diff --git a/components/to_utf8/tests/to_utf8_test.cpp b/components/to_utf8/tests/to_utf8_test.cpp
new file mode 100644
index 0000000000..3fcddd1581
--- /dev/null
+++ b/components/to_utf8/tests/to_utf8_test.cpp
@@ -0,0 +1,59 @@
+#include <iostream>
+#include <fstream>
+#include <cassert>
+#include <stdexcept>
+
+#include "../to_utf8.hpp"
+
+std::string getFirstLine(const std::string &filename);
+void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile,
+ const std::string &utf8File);
+
+/// Test character encoding conversion to and from UTF-8
+void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile,
+ const std::string &utf8File)
+{
+ // get some test data
+ std::string legacyEncLine = getFirstLine(legacyEncFile);
+ std::string utf8Line = getFirstLine(utf8File);
+
+ // create an encoder for specified character encoding
+ ToUTF8::Utf8Encoder encoder (encoding);
+
+ // convert text to UTF-8
+ std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine);
+
+ std::cout << "original: " << utf8Line << std::endl;
+ std::cout << "converted: " << convertedUtf8Line << std::endl;
+
+ // check correctness
+ assert(convertedUtf8Line == utf8Line);
+
+ // convert UTF-8 text to legacy encoding
+ std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line);
+ // check correctness
+ assert(convertedLegacyEncLine == legacyEncLine);
+}
+
+std::string getFirstLine(const std::string &filename)
+{
+ std::string line;
+ std::ifstream text (filename.c_str());
+
+ if (!text.is_open())
+ {
+ throw std::runtime_error("Unable to open file " + filename);
+ }
+
+ std::getline(text, line);
+ text.close();
+
+ return line;
+}
+
+int main()
+{
+ testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt");
+ testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt");
+ return 0;
+}
diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp
new file mode 100644
index 0000000000..59a9aff80f
--- /dev/null
+++ b/components/to_utf8/to_utf8.cpp
@@ -0,0 +1,344 @@
+#include "to_utf8.hpp"
+
+#include <vector>
+#include <cassert>
+#include <iostream>
+#include <iomanip>
+
+/* This file contains the code to translate from WINDOWS-1252 (native
+ charset used in English version of Morrowind) to UTF-8. The library
+ is designed to be extened to support more source encodings later,
+ which means that we may add support for Russian, Polish and Chinese
+ files and so on.
+
+ The code does not depend on any external library at
+ runtime. Instead, it uses a pregenerated table made with iconv (see
+ gen_iconv.cpp and the Makefile) which is located in tables_gen.hpp.
+
+ This is both faster and uses less dependencies. The tables would
+ only need to be regenerated if we are adding support more input
+ encodings. As such, there is no need to make the generator code
+ platform independent.
+
+ The library is optimized for the case of pure ASCII input strings,
+ which is the vast majority of cases at least for the English
+ version. A test of my version of Morrowind.esm got 130 non-ASCII vs
+ 236195 ASCII strings, or less than 0.06% of strings containing
+ non-ASCII characters.
+
+ To optmize for this, ff the first pass of the string does not find
+ any non-ASCII characters, the entire string is passed along without
+ any modification.
+
+ Most of the non-ASCII strings are books, and are quite large. (The
+ non-ASCII characters are typically starting and ending quotation
+ marks.) Within these, almost all the characters are ASCII. For this
+ purpose, the library is also optimized for mostly-ASCII contents
+ even in the cases where some conversion is necessary.
+ */
+
+
+// Generated tables
+#include "tables_gen.hpp"
+
+using namespace ToUTF8;
+
+Utf8Encoder::Utf8Encoder(const FromType sourceEncoding):
+ mOutput(50*1024)
+{
+ switch (sourceEncoding)
+ {
+ case ToUTF8::WINDOWS_1252:
+ {
+ translationArray = ToUTF8::windows_1252;
+ break;
+ }
+ case ToUTF8::WINDOWS_1250:
+ {
+ translationArray = ToUTF8::windows_1250;
+ break;
+ }
+ case ToUTF8::WINDOWS_1251:
+ {
+ translationArray = ToUTF8::windows_1251;
+ break;
+ }
+ case ToUTF8::CP437:
+ {
+ translationArray = ToUTF8::cp437;
+ break;
+ }
+
+ default:
+ {
+ assert(0);
+ }
+ }
+}
+
+std::string Utf8Encoder::getUtf8(const char* input, size_t size)
+{
+ // Double check that the input string stops at some point (it might
+ // contain zero terminators before this, inside its own data, which
+ // is also ok.)
+ assert(input[size] == 0);
+
+ // TODO: The rest of this function is designed for single-character
+ // input encodings only. It also assumes that the input the input
+ // encoding shares its first 128 values (0-127) with ASCII. These
+ // conditions must be checked again if you add more input encodings
+ // later.
+
+ // Compute output length, and check for pure ascii input at the same
+ // time.
+ bool ascii;
+ size_t outlen = getLength(input, ascii);
+
+ // If we're pure ascii, then don't bother converting anything.
+ if(ascii)
+ return std::string(input, outlen);
+
+ // Make sure the output is large enough
+ resize(outlen);
+ char *out = &mOutput[0];
+
+ // Translate
+ while (*input)
+ copyFromArray(*(input++), out);
+
+ // Make sure that we wrote the correct number of bytes
+ assert((out-&mOutput[0]) == (int)outlen);
+
+ // And make extra sure the output is null terminated
+ assert(mOutput.size() > outlen);
+ assert(mOutput[outlen] == 0);
+
+ // Return a string
+ return std::string(&mOutput[0], outlen);
+}
+
+std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size)
+{
+ // Double check that the input string stops at some point (it might
+ // contain zero terminators before this, inside its own data, which
+ // is also ok.)
+ assert(input[size] == 0);
+
+ // TODO: The rest of this function is designed for single-character
+ // input encodings only. It also assumes that the input the input
+ // encoding shares its first 128 values (0-127) with ASCII. These
+ // conditions must be checked again if you add more input encodings
+ // later.
+
+ // Compute output length, and check for pure ascii input at the same
+ // time.
+ bool ascii;
+ size_t outlen = getLength2(input, ascii);
+
+ // If we're pure ascii, then don't bother converting anything.
+ if(ascii)
+ return std::string(input, outlen);
+
+ // Make sure the output is large enough
+ resize(outlen);
+ char *out = &mOutput[0];
+
+ // Translate
+ while(*input)
+ copyFromArray2(input, out);
+
+ // Make sure that we wrote the correct number of bytes
+ assert((out-&mOutput[0]) == (int)outlen);
+
+ // And make extra sure the output is null terminated
+ assert(mOutput.size() > outlen);
+ assert(mOutput[outlen] == 0);
+
+ // Return a string
+ return std::string(&mOutput[0], outlen);
+}
+
+// Make sure the output vector is large enough for 'size' bytes,
+// including a terminating zero after it.
+void Utf8Encoder::resize(size_t size)
+{
+ if (mOutput.size() <= size)
+ // Add some extra padding to reduce the chance of having to resize
+ // again later.
+ mOutput.resize(3*size);
+
+ // And make sure the string is zero terminated
+ mOutput[size] = 0;
+}
+
+/** Get the total length length needed to decode the given string with
+ the given translation array. The arrays are encoded with 6 bytes
+ per character, with the first giving the length and the next 5 the
+ actual data.
+
+ The function serves a dual purpose for optimization reasons: it
+ checks if the input is pure ascii (all values are <= 127). If this
+ is the case, then the ascii parameter is set to true, and the
+ caller can optimize for this case.
+ */
+size_t Utf8Encoder::getLength(const char* input, bool &ascii)
+{
+ ascii = true;
+ size_t len = 0;
+ const char* ptr = input;
+ unsigned char inp = *ptr;
+
+ // Do away with the ascii part of the string first (this is almost
+ // always the entire string.)
+ while (inp && inp < 128)
+ inp = *(++ptr);
+ len += (ptr-input);
+
+ // If we're not at the null terminator at this point, then there
+ // were some non-ascii characters to deal with. Go to slow-mode for
+ // the rest of the string.
+ if (inp)
+ {
+ ascii = false;
+ while (inp)
+ {
+ // Find the translated length of this character in the
+ // lookup table.
+ len += translationArray[inp*6];
+ inp = *(++ptr);
+ }
+ }
+ return len;
+}
+
+// Translate one character 'ch' using the translation array 'arr', and
+// advance the output pointer accordingly.
+void Utf8Encoder::copyFromArray(unsigned char ch, char* &out)
+{
+ // Optimize for ASCII values
+ if (ch < 128)
+ {
+ *(out++) = ch;
+ return;
+ }
+
+ const signed char *in = translationArray + ch*6;
+ int len = *(in++);
+ for (int i=0; i<len; i++)
+ *(out++) = *(in++);
+}
+
+size_t Utf8Encoder::getLength2(const char* input, bool &ascii)
+{
+ ascii = true;
+ size_t len = 0;
+ const char* ptr = input;
+ unsigned char inp = *ptr;
+
+ // Do away with the ascii part of the string first (this is almost
+ // always the entire string.)
+ while (inp && inp < 128)
+ inp = *(++ptr);
+ len += (ptr-input);
+
+ // If we're not at the null terminator at this point, then there
+ // were some non-ascii characters to deal with. Go to slow-mode for
+ // the rest of the string.
+ if (inp)
+ {
+ ascii = false;
+ while(inp)
+ {
+ len += 1;
+ // Find the translated length of this character in the
+ // lookup table.
+ switch(inp)
+ {
+ case 0xe2: len -= 2; break;
+ case 0xc2:
+ case 0xcb:
+ case 0xc4:
+ case 0xc6:
+ case 0xc3:
+ case 0xd0:
+ case 0xd1:
+ case 0xd2:
+ case 0xc5: len -= 1; break;
+ }
+
+ inp = *(++ptr);
+ }
+ }
+ return len;
+}
+
+void Utf8Encoder::copyFromArray2(const char*& chp, char* &out)
+{
+ unsigned char ch = *(chp++);
+ // Optimize for ASCII values
+ if (ch < 128)
+ {
+ *(out++) = ch;
+ return;
+ }
+
+ int len = 1;
+ switch (ch)
+ {
+ case 0xe2: len = 3; break;
+ case 0xc2:
+ case 0xcb:
+ case 0xc4:
+ case 0xc6:
+ case 0xc3:
+ case 0xd0:
+ case 0xd1:
+ case 0xd2:
+ case 0xc5: len = 2; break;
+ }
+
+ if (len == 1) // There is no 1 length utf-8 glyph that is not 0x20 (empty space)
+ {
+ *(out++) = ch;
+ return;
+ }
+
+ unsigned char ch2 = *(chp++);
+ unsigned char ch3 = '\0';
+ if (len == 3)
+ ch3 = *(chp++);
+
+ for (int i = 128; i < 256; i++)
+ {
+ unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3];
+ if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3))
+ {
+ *(out++) = (char)i;
+ return;
+ }
+ }
+
+ std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl;
+
+ *(out++) = ch; // Could not find glyph, just put whatever
+}
+
+ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName)
+{
+ if (encodingName == "win1250")
+ return ToUTF8::WINDOWS_1250;
+ else if (encodingName == "win1251")
+ return ToUTF8::WINDOWS_1251;
+ else
+ return ToUTF8::WINDOWS_1252;
+}
+
+std::string ToUTF8::encodingUsingMessage(const std::string& encodingName)
+{
+ if (encodingName == "win1250")
+ return "Using Central and Eastern European font encoding.";
+ else if (encodingName == "win1251")
+ return "Using Cyrillic font encoding.";
+ else
+ return "Using default (English) font encoding.";
+}
diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp
new file mode 100644
index 0000000000..3f20a51f86
--- /dev/null
+++ b/components/to_utf8/to_utf8.hpp
@@ -0,0 +1,55 @@
+#ifndef COMPONENTS_TOUTF8_H
+#define COMPONENTS_TOUTF8_H
+
+#include <string>
+#include <cstring>
+#include <vector>
+
+namespace ToUTF8
+{
+ // These are all the currently supported code pages
+ enum FromType
+ {
+ WINDOWS_1250, // Central ane Eastern European languages
+ WINDOWS_1251, // Cyrillic languages
+ WINDOWS_1252, // Used by English version of Morrowind (and
+ // probably others)
+ CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files.
+ };
+
+ FromType calculateEncoding(const std::string& encodingName);
+ std::string encodingUsingMessage(const std::string& encodingName);
+
+ // class
+
+ class Utf8Encoder
+ {
+ public:
+ Utf8Encoder(FromType sourceEncoding);
+
+ // Convert to UTF8 from the previously given code page.
+ std::string getUtf8(const char *input, size_t size);
+ inline std::string getUtf8(const std::string &str)
+ {
+ return getUtf8(str.c_str(), str.size());
+ }
+
+ std::string getLegacyEnc(const char *input, size_t size);
+ inline std::string getLegacyEnc(const std::string &str)
+ {
+ return getLegacyEnc(str.c_str(), str.size());
+ }
+
+ private:
+ void resize(size_t size);
+ size_t getLength(const char* input, bool &ascii);
+ void copyFromArray(unsigned char chp, char* &out);
+ size_t getLength2(const char* input, bool &ascii);
+ void copyFromArray2(const char*& chp, char* &out);
+
+ std::vector<char> mOutput;
+ signed char* translationArray;
+ };
+}
+
+#endif
diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp
new file mode 100644
index 0000000000..d0ea4b7fb6
--- /dev/null
+++ b/components/translation/translation.cpp
@@ -0,0 +1,115 @@
+#include "translation.hpp"
+#include <components/misc/stringops.hpp>
+
+#include <fstream>
+
+namespace Translation
+{
+ void Storage::loadTranslationData(const Files::Collections& dataFileCollections,
+ const std::string& esmFileName)
+ {
+ std::string esmNameNoExtension(Misc::StringUtils::lowerCase(esmFileName));
+ //changing the extension
+ size_t dotPos = esmNameNoExtension.rfind('.');
+ if (dotPos != std::string::npos)
+ esmNameNoExtension.resize(dotPos);
+
+ loadData(mCellNamesTranslations, esmNameNoExtension, ".cel", dataFileCollections);
+ loadData(mPhraseForms, esmNameNoExtension, ".top", dataFileCollections);
+ loadData(mTopicIDs, esmNameNoExtension, ".mrk", dataFileCollections);
+ }
+
+ void Storage::loadData(ContainerType& container,
+ const std::string& fileNameNoExtension,
+ const std::string& extension,
+ const Files::Collections& dataFileCollections)
+ {
+ std::string fileName = fileNameNoExtension + extension;
+
+ if (dataFileCollections.getCollection (extension).doesExist (fileName))
+ {
+ std::string path = dataFileCollections.getCollection (extension).getPath (fileName).string();
+
+ std::ifstream stream (path.c_str());
+
+ if (!stream.is_open())
+ throw std::runtime_error ("failed to open translation file: " + fileName);
+
+ loadDataFromStream(container, stream);
+ }
+ }
+
+ void Storage::loadDataFromStream(ContainerType& container, std::istream& stream)
+ {
+ std::string line;
+ while (!stream.eof())
+ {
+ std::getline( stream, line );
+ if (!line.empty() && *line.rbegin() == '\r')
+ line.resize(line.size() - 1);
+
+ if (!line.empty())
+ {
+ line = mEncoder->getUtf8(line);
+
+ size_t tab_pos = line.find('\t');
+ if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1)
+ {
+ std::string key = line.substr(0, tab_pos);
+ std::string value = line.substr(tab_pos + 1);
+
+ if (!key.empty() && !value.empty())
+ container.insert(std::make_pair(key, value));
+ }
+ }
+ }
+ }
+
+ std::string Storage::translateCellName(const std::string& cellName) const
+ {
+ std::map<std::string, std::string>::const_iterator entry =
+ mCellNamesTranslations.find(cellName);
+
+ if (entry == mCellNamesTranslations.end())
+ return cellName;
+
+ return entry->second;
+ }
+
+ std::string Storage::topicID(const std::string& phrase) const
+ {
+ std::string result = topicStandardForm(phrase);
+
+ //seeking for the topic ID
+ std::map<std::string, std::string>::const_iterator topicIDIterator =
+ mTopicIDs.find(result);
+
+ if (topicIDIterator != mTopicIDs.end())
+ result = topicIDIterator->second;
+
+ return result;
+ }
+
+ std::string Storage::topicStandardForm(const std::string& phrase) const
+ {
+ std::map<std::string, std::string>::const_iterator phraseFormsIterator =
+ mPhraseForms.find(phrase);
+
+ if (phraseFormsIterator != mPhraseForms.end())
+ return phraseFormsIterator->second;
+ else
+ return phrase;
+ }
+
+ void Storage::setEncoder(ToUTF8::Utf8Encoder* encoder)
+ {
+ mEncoder = encoder;
+ }
+
+ bool Storage::hasTranslation() const
+ {
+ return !mCellNamesTranslations.empty() ||
+ !mTopicIDs.empty() ||
+ !mPhraseForms.empty();
+ }
+}
diff --git a/components/translation/translation.hpp b/components/translation/translation.hpp
new file mode 100644
index 0000000000..bca9ea255c
--- /dev/null
+++ b/components/translation/translation.hpp
@@ -0,0 +1,42 @@
+#ifndef COMPONENTS_TRANSLATION_DATA_H
+#define COMPONENTS_TRANSLATION_DATA_H
+
+#include <components/to_utf8/to_utf8.hpp>
+#include <components/files/collections.hpp>
+
+namespace Translation
+{
+ class Storage
+ {
+ public:
+
+ void loadTranslationData(const Files::Collections& dataFileCollections,
+ const std::string& esmFileName);
+
+ std::string translateCellName(const std::string& cellName) const;
+ std::string topicID(const std::string& phrase) const;
+
+ // Standard form usually means nominative case
+ std::string topicStandardForm(const std::string& phrase) const;
+
+ void setEncoder(ToUTF8::Utf8Encoder* encoder);
+
+ bool hasTranslation() const;
+
+ private:
+ typedef std::map<std::string, std::string> ContainerType;
+
+ void loadData(ContainerType& container,
+ const std::string& fileNameNoExtension,
+ const std::string& extension,
+ const Files::Collections& dataFileCollections);
+
+ void loadDataFromStream(ContainerType& container, std::istream& stream);
+
+
+ ToUTF8::Utf8Encoder* mEncoder;
+ ContainerType mCellNamesTranslations, mTopicIDs, mPhraseForms;
+ };
+}
+
+#endif
diff --git a/credits.txt b/credits.txt
new file mode 100644
index 0000000000..9a84c5327d
--- /dev/null
+++ b/credits.txt
@@ -0,0 +1,156 @@
+Contributors
+
+The OpenMW project was started in 2008 by Nicolay Korslund.
+In the course of years many people have contributed to the project.
+
+If you feel your name is missing from this list,
+please notify a developer.
+
+
+Programmers:
+Marc Zinnschlag (Zini) - Lead Programmer/Project Manager
+
+Adam Hogan (aurix)
+Aleksandar Jovanov
+Alex McKibben (WeirdSexy)
+Alexander Nadeau (wareya)
+Alexander Olofsson (Ace)
+Artem Kotsynyak (greye)
+athile
+Britt Mathis (galdor557)
+BrotherBrick
+Chris Robinson (KittyCat)
+Cory F. Cohen (cfcohen)
+Cris Mihalache (Mirceam)
+darkf
+Douglas Diniz (Dgdiniz)
+Douglas Mencken (dougmencken)
+Edmondo Tommasina (edmondo)
+Eduard Cot (trombonecot)
+Eli2
+Emanuel Guével (potatoesmaster)
+gugus/gus
+Jacob Essex (Yacoby)
+Jannik Heller (scrawl)
+Jason Hooks (jhooks)
+Joel Graff (graffy)
+John Blomberg (fstp)
+Jordan Milne
+Julien Voisin (jvoisin/ap0)
+Karl-Felix Glatzer (k1ll)
+Lars Söderberg (Lazaroth)
+lazydev
+Leon Saunders (emoose)
+Lukasz Gromanowski (lgro)
+Manuel Edelmann (vorenon)
+Marc Bouvier (CramitDeFrog)
+Marcin Hulist (Gohan)
+Mark Siewert (mark76)
+Mateusz Kołaczek (PL_kolek)
+Michael Mc Donnell
+Michael Papageorgiou (werdanith)
+Michał Bień (Glorf)
+Nathan Jeffords (blunted2night)
+Nikolay Kasyanov (corristo)
+Nolan Poe (nopoe)
+Paul McElroy (Greendogo)
+Pieter van der Kloet (pvdk)
+Radu-Marius Popovici (rpopovici)
+Roman Melnik (Kromgart)
+Roman Proskuryakov (humbug)
+Sandy Carter (bwrsandman)
+Sebastian Wick (swick)
+Sergey Shambir
+Sylvain Thesnieres (Garvek)
+Tom Mason (wheybags)
+Torben Leif Carrington (TorbenC)
+
+Packagers:
+Alexander Olofsson (Ace) - Windows
+BrotherBrick - Ubuntu Linux
+Edmondo Tommasina (edmondo) - Gentoo Linux
+Julian Ospald (hasufell) - Gentoo Linux
+Karl-Felix Glatzer (k1ll) - Linux Binaries
+Kenny Armstrong (artorius) - Fedora Linux
+Nikolay Kasyanov (corristo) - Mac OS X
+Sandy Carter (bwrsandman) - Arch Linux
+
+
+Public Relations and Translations:
+Artem Kotsynyak (greye) - Russian News Writer
+Julien Voisin (jvoisin/ap0) - French News Writer
+Mickey Lyle (raevol) - Release Manager
+Pithorn - Chinese News Writer
+sir_herrbatka - English/Polish News Writer
+Alex McKibben (WeirdSexy) - Podcaster
+
+
+Website:
+Lukasz Gromanowski (lgro) - Website Administrator
+Ryan Sardonic (Wry) - Wiki Editor
+sir_herrbatka - Forum Administrator
+
+
+Formula Research:
+Epsilon
+fragonard
+Greendogo
+HiPhish
+modred11
+Myckel
+natirips
+Sadler
+
+
+Artwork:
+Necrod - OpenMW Logo
+Mickey Lyle (raevol) - Wordpress Theme
+Okulo - OpenMW Editor Icons
+
+Inactive Contributors:
+Ardekantur
+Armin Preiml
+Carl Maxwell
+Diggory Hardy
+Dmitry Marakasov (AMDmi3)
+ElderTroll
+guidoj
+Jan-Peter Nilsson (peppe)
+Jan Borsodi
+Josua Grawitter
+juanmnzsk8
+Kingpix
+Lordrea
+Michal Sciubidlo
+Nicolay Korslund
+pchan3
+penguinroad
+psi29a
+sergoz
+spyboot
+Star-Demon
+Thoronador
+Yuri Krupenin
+
+
+Additional Credits:
+In this section we would like to thank people not part of OpenMW for their work.
+
+Thanks to Maxim Nikolaev,
+for allowing us to use his excellent Morrowind fan-art on our website and in other places.
+
+Thanks to DokterDume,
+for kindly providing us with the Moon and Star logo,
+used as the application icon and project logo.
+
+Thanks to Kevin Ryan,
+for creating the icon used for the Data Files tab of the OpenMW Launcher.
+
+Thanks to Georg Duffner,
+for his EB Garamond fontface, see OFL.txt for his license terms.
+
+Thanks to Dongle,
+for his Daedric fontface, see Daedric Font License.txt for his license terms.
+
+Thanks to DejaVu team,
+for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms.
diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt
new file mode 100644
index 0000000000..7c14387a4b
--- /dev/null
+++ b/extern/oics/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(OICS_LIBRARY "oics")
+
+# Sources
+
+set(OICS_SOURCE_FILES
+ ICSChannel.cpp
+ ICSControl.cpp
+ ICSInputControlSystem.cpp
+ ICSInputControlSystem_keyboard.cpp
+ ICSInputControlSystem_mouse.cpp
+ ICSInputControlSystem_joystick.cpp
+ tinyxml.cpp
+ tinyxmlparser.cpp
+ tinyxmlerror.cpp
+ tinystr.cpp
+)
+
+add_library(${OICS_LIBRARY} STATIC ${OICS_SOURCE_FILES})
+
+link_directories(${CMAKE_CURRENT_BINARY_DIR})
diff --git a/extern/oics/ICSChannel.cpp b/extern/oics/ICSChannel.cpp
new file mode 100644
index 0000000000..703f2207c9
--- /dev/null
+++ b/extern/oics/ICSChannel.cpp
@@ -0,0 +1,258 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+#define B1(t) (t*t)
+#define B2(t) (2*t*(1-t))
+#define B3(t) ((1-t)*(1-t))
+
+namespace ICS
+{
+ Channel::Channel(int number, float initialValue
+ , float bezierMidPointY, float bezierMidPointX, float symmetricAt, float bezierStep)
+ : mNumber(number)
+ , mValue(initialValue)
+ , mSymmetricAt(symmetricAt)
+ , mBezierStep(bezierStep)
+ {
+ mBezierMidPoint.x = bezierMidPointX;
+ mBezierMidPoint.y = bezierMidPointY;
+
+ setBezierFunction(bezierMidPointY, bezierMidPointX, symmetricAt, bezierStep);
+ }
+
+ float Channel::getValue()
+ {
+ if(mValue == 0 || mValue == 1)
+ {
+ return mValue;
+ }
+
+ BezierFunction::iterator it = mBezierFunction.begin();
+ //size_t size_minus_1 = mBezierFunction.size() - 1;
+ BezierFunction::iterator last = mBezierFunction.end();
+ last--;
+ for ( ; it != last ; )
+ {
+ BezierPoint left = (*it);
+ BezierPoint right = (*(++it));
+
+ if( (left.x <= mValue) && (right.x > mValue) )
+ {
+ float val = left.y - (left.x - mValue) * (left.y - right.y) / (left.x - right.x);
+
+ return std::max<float>(0.0,std::min<float>(1.0, val));
+ }
+ }
+
+ return -1;
+ }
+
+ void Channel::setValue(float value)
+ {
+ float previousValue = this->getValue();
+
+ mValue = value;
+
+ if(previousValue != value)
+ {
+ notifyListeners(previousValue);
+ }
+ }
+
+ void Channel::notifyListeners(float previousValue)
+ {
+ std::list<ChannelListener*>::iterator pos = mListeners.begin();
+ while (pos != mListeners.end())
+ {
+ ((ChannelListener* )(*pos))->channelChanged((Channel*)this, this->getValue(), previousValue);
+ ++pos;
+ }
+ }
+
+ void Channel::addControl(Control* control, Channel::ChannelDirection dir, float percentage)
+ {
+ ControlChannelBinderItem ccBinderItem;
+ ccBinderItem.control = control;
+ ccBinderItem.direction = dir;
+ ccBinderItem.percentage = percentage;
+
+ mAttachedControls.push_back(ccBinderItem);
+ }
+
+ Channel::ControlChannelBinderItem Channel::getAttachedControlBinding(Control* control)
+ {
+ for(std::vector<ControlChannelBinderItem>::iterator it = mAttachedControls.begin() ;
+ it != mAttachedControls.end() ; it++)
+ {
+ if((*it).control == control)
+ {
+ return (*it);
+ }
+ }
+
+ ControlChannelBinderItem nullBinderItem;
+ nullBinderItem.control = NULL;
+ nullBinderItem.direction = Channel/*::ChannelDirection*/::DIRECT;
+ nullBinderItem.percentage = 0;
+ return nullBinderItem;
+ }
+
+ void Channel::update()
+ {
+ if(this->getControlsCount() == 1)
+ {
+ ControlChannelBinderItem ccBinderItem = mAttachedControls.back();
+ float diff = ccBinderItem.control->getValue() - ccBinderItem.control->getInitialValue();
+
+ if(ccBinderItem.direction == ICS::Channel::DIRECT)
+ {
+ this->setValue(ccBinderItem.control->getInitialValue() + (ccBinderItem.percentage * diff));
+ }
+ else
+ {
+ this->setValue(ccBinderItem.control->getInitialValue() - (ccBinderItem.percentage * diff));
+ }
+ }
+ else
+ {
+ float val = 0;
+ std::vector<ControlChannelBinderItem>::const_iterator it;
+ for(it=mAttachedControls.begin(); it!=mAttachedControls.end(); ++it)
+ {
+ ControlChannelBinderItem ccBinderItem = (*it);
+ float diff = ccBinderItem.control->getValue() - ccBinderItem.control->getInitialValue();
+
+ if(ccBinderItem.direction == ICS::Channel::DIRECT)
+ {
+ val += (ccBinderItem.percentage * diff);
+ }
+ else
+ {
+ val -= (ccBinderItem.percentage * diff);
+ }
+ }
+
+ if(mAttachedControls.size() > 0)
+ {
+ this->setValue(mAttachedControls.begin()->control->getInitialValue() + val);
+ }
+ }
+ }
+
+ void Channel::setBezierFunction(float bezierMidPointY, float bezierMidPointX, float symmetricAt, float bezierStep)
+ {
+ mBezierMidPoint.x = bezierMidPointX;
+ mBezierMidPoint.y = bezierMidPointY;
+ mBezierStep = bezierStep;
+ mSymmetricAt = symmetricAt;
+
+ mBezierFunction.clear();
+
+ BezierPoint start;
+ start.x = 0;
+ start.y = 0;
+
+ BezierPoint end;
+ end.x = 1;
+ end.y = 1;
+ mBezierFunction.push_front(end);
+
+ FilterInterval interval;
+ interval.startX = start.x;
+ interval.startY = start.y;
+ interval.midX = mBezierMidPoint.x;
+ interval.midY = mBezierMidPoint.y;
+ interval.endX = end.x;
+ interval.endY = end.y;
+ interval.step = bezierStep;
+ mIntervals.push_back(interval);
+
+ if(!(mBezierMidPoint.x == 0.5 && mBezierMidPoint.y == 0.5))
+ {
+ float t = mBezierStep;
+ while(t < 1)
+ {
+ BezierPoint p;
+ p.x = start.x * B1(t) + mBezierMidPoint.x * B2(t) + end.x * B3(t);
+ p.y = start.y * B1(t) + mBezierMidPoint.y * B2(t) + end.y * B3(t);
+ mBezierFunction.push_front(p);
+
+ t += mBezierStep;
+ }
+ }
+
+ mBezierFunction.push_front(start);
+ }
+
+ void Channel::addBezierInterval(float startX, float startY, float midX, float midY
+ , float endX, float endY, float step)
+ {
+ FilterInterval interval;
+ interval.startX = startX;
+ interval.startY = startY;
+ interval.midX = midX;
+ interval.midY = midY;
+ interval.endX = endX;
+ interval.endY = endY;
+ interval.step = step;
+ mIntervals.push_back(interval);
+
+ float t = 0;
+ while(t <= 1)
+ {
+ BezierPoint p;
+ p.x = startX * B1(t) + midX * B2(t) + endX * B3(t);
+ p.y = startY * B1(t) + midY * B2(t) + endY * B3(t);
+
+ BezierFunction::iterator it = mBezierFunction.begin();
+ while( it != mBezierFunction.end() )
+ {
+ BezierPoint left = (*it);
+ BezierPoint right;
+ ++it;
+ if( it != mBezierFunction.end() )
+ {
+ right = (*it);
+ }
+ else
+ {
+ right.x = endX;
+ right.y = endY;
+ }
+
+ if(p.x > left.x && p.x < right.x)
+ {
+ mBezierFunction.insert(it, p);
+ break;
+ }
+ }
+
+ t += 1.0f / ((endX-startX)/step);
+ }
+ }
+} \ No newline at end of file
diff --git a/extern/oics/ICSChannel.h b/extern/oics/ICSChannel.h
new file mode 100644
index 0000000000..f98f0d94d3
--- /dev/null
+++ b/extern/oics/ICSChannel.h
@@ -0,0 +1,122 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#ifndef _Channel_H_
+#define _Channel_H_
+
+#include "ICSPrerequisites.h"
+
+#include "ICSChannelListener.h"
+
+namespace ICS
+{
+ struct FilterInterval{
+ //std::string type; //! @todo uncomment when more types implemented
+ float startX;
+ float startY;
+ float midX;
+ float midY;
+ float endX;
+ float endY;
+ float step;
+ };
+
+ typedef std::list<FilterInterval> IntervalList;
+
+ class DllExport Channel
+ {
+ public:
+ enum ChannelDirection
+ {
+ INVERSE = -1, DIRECT = 1
+ };
+
+ typedef struct {
+ ChannelDirection direction;
+ float percentage;
+ Control* control;
+ } ControlChannelBinderItem;
+
+
+ Channel(int number, float initialValue = 0.5
+ , float bezierMidPointY = 0.5, float bezierMidPointX = 0.5
+ , float symmetricAt = 0, float bezierStep = 0.2); //! @todo implement symetry
+ ~Channel(){};
+
+ void setValue(float value);
+ float getValue();
+
+ inline int getNumber(){ return mNumber; };
+
+ void addControl(Control* control, Channel::ChannelDirection dir, float percentage);
+ inline size_t getControlsCount(){ return mAttachedControls.size(); };
+ std::vector<ControlChannelBinderItem> getAttachedControls(){ return mAttachedControls; };
+ ControlChannelBinderItem getAttachedControlBinding(Control* control);
+
+ void addListener(ChannelListener* ob){ mListeners.push_back(ob); };
+ void removeListener(ChannelListener* ob){ mListeners.remove(ob); };
+
+ void update();
+
+ void setBezierFunction(float bezierMidPointY, float bezierMidPointX = 0.5
+ , float symmetricAt = 0, float bezierStep = 0.2);
+
+ void addBezierInterval(float startX, float startY, float midX, float midY
+ , float endX, float endY, float step = 0.1);
+
+ IntervalList& getIntervals(){ return mIntervals; };
+
+ protected:
+
+ int mNumber;
+ float mValue;
+
+ struct BezierPoint{
+ float x;
+ float y;
+ bool operator < (const BezierPoint& other){ return x < other.x; }
+ };
+
+ typedef std::list<BezierPoint> BezierFunction;
+
+ BezierPoint mBezierMidPoint;
+ BezierFunction mBezierFunction;
+ float mSymmetricAt;
+ float mBezierStep;
+
+ IntervalList mIntervals;
+
+ std::vector<ControlChannelBinderItem> mAttachedControls;
+
+ std::list<ChannelListener* > mListeners;
+ void notifyListeners(float previousValue);
+
+ };
+
+}
+
+
+#endif \ No newline at end of file
diff --git a/extern/oics/ICSChannelListener.h b/extern/oics/ICSChannelListener.h
new file mode 100644
index 0000000000..d520b3bceb
--- /dev/null
+++ b/extern/oics/ICSChannelListener.h
@@ -0,0 +1,46 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#ifndef _ChannelListener_H_
+#define _ChannelListener_H_
+
+#include "ICSPrerequisites.h"
+
+#include "ICSChannel.h"
+
+namespace ICS
+{
+
+ class DllExport ChannelListener
+ {
+ public:
+ virtual void channelChanged(Channel* channel, float currentValue, float previousValue) = 0;
+ };
+
+}
+
+
+#endif \ No newline at end of file
diff --git a/extern/oics/ICSControl.cpp b/extern/oics/ICSControl.cpp
new file mode 100644
index 0000000000..934c661c93
--- /dev/null
+++ b/extern/oics/ICSControl.cpp
@@ -0,0 +1,161 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+#include "ICSControl.h"
+
+namespace ICS
+{
+ Control::Control(const std::string& name, bool autoChangeDirectionOnLimitsAfterStop, bool autoReverseToInitialValue
+ , float initialValue, float stepSize, float stepsPerSeconds, bool axisBindable)
+ : mName(name)
+ , mValue(initialValue)
+ , mInitialValue(initialValue)
+ , mStepSize(stepSize)
+ , mStepsPerSeconds(stepsPerSeconds)
+ , mAutoReverseToInitialValue(autoReverseToInitialValue)
+ , mIgnoreAutoReverse(false)
+ , mAutoChangeDirectionOnLimitsAfterStop(autoChangeDirectionOnLimitsAfterStop)
+ , mAxisBindable(axisBindable)
+ , currentChangingDirection(STOP)
+ {
+
+ }
+
+ Control::~Control()
+ {
+ mAttachedChannels.clear();
+ }
+
+ void Control::setValue(float value)
+ {
+ float previousValue = mValue;
+
+ mValue = std::max<float>(0.0,std::min<float>(1.0,value));
+
+ if(mValue != previousValue)
+ {
+ updateChannels();
+
+ notifyListeners(previousValue);
+ }
+ }
+
+ void Control::attachChannel(Channel* channel, Channel::ChannelDirection direction, float percentage)
+ {
+ mAttachedChannels.push_back(channel);
+ channel->addControl(this, direction, percentage);
+ }
+
+ void Control::updateChannels()
+ {
+ std::list<Channel*>::iterator pos = mAttachedChannels.begin();
+ while (pos != mAttachedChannels.end())
+ {
+ ((Channel* )(*pos))->update();
+ ++pos;
+ }
+ }
+
+ void Control::notifyListeners(float previousValue)
+ {
+ std::list<ControlListener*>::iterator pos = mListeners.begin();
+ while (pos != mListeners.end())
+ {
+ ((ControlListener* )(*pos))->controlChanged((Control*)this, this->getValue(), previousValue);
+ ++pos;
+ }
+ }
+
+ void Control::setChangingDirection(ControlChangingDirection direction)
+ {
+ currentChangingDirection = direction;
+ mPendingActions.push_back(direction);
+ }
+
+ void Control::update(float timeSinceLastFrame)
+ {
+ if(mPendingActions.size() > 0)
+ {
+ size_t timedActionsCount = 0;
+
+ std::list<Control::ControlChangingDirection>::iterator cached_end = mPendingActions.end();
+ for(std::list<Control::ControlChangingDirection>::iterator it = mPendingActions.begin() ;
+ it != cached_end ; it++)
+ {
+ if( (*it) != Control::STOP )
+ {
+ timedActionsCount++;
+ }
+ }
+
+ float timeSinceLastFramePart = timeSinceLastFrame / std::max<size_t>(1, timedActionsCount);
+ for(std::list<Control::ControlChangingDirection>::iterator it = mPendingActions.begin() ;
+ it != cached_end ; it++)
+ {
+ if( (*it) != Control::STOP )
+ {
+ this->setValue(mValue +
+ (((int)(*it)) * mStepSize * mStepsPerSeconds * (timeSinceLastFramePart)));
+ }
+ else if(mAutoReverseToInitialValue && !mIgnoreAutoReverse && mValue != mInitialValue )
+ {
+
+ if(mValue > mInitialValue)
+ {
+ this->setValue( std::max<float>( mInitialValue,
+ mValue - (mStepSize * mStepsPerSeconds * (timeSinceLastFramePart))));
+ }
+ else if(mValue < mInitialValue)
+ {
+ this->setValue( std::min<float>( mInitialValue,
+ mValue + (mStepSize * mStepsPerSeconds * (timeSinceLastFramePart))));
+ }
+ }
+ }
+ mPendingActions.clear();
+ }
+ else if( currentChangingDirection != Control::STOP )
+ {
+ this->setValue(mValue +
+ (((int)currentChangingDirection) * mStepSize * mStepsPerSeconds * (timeSinceLastFrame)));
+ }
+ else if(mAutoReverseToInitialValue && !mIgnoreAutoReverse && mValue != mInitialValue )
+ {
+ if(mValue > mInitialValue)
+ {
+ this->setValue( std::max<float>( mInitialValue,
+ mValue - (mStepSize * mStepsPerSeconds * (timeSinceLastFrame))));
+ }
+ else if(mValue < mInitialValue)
+ {
+ this->setValue( std::min<float>( mInitialValue,
+ mValue + (mStepSize * mStepsPerSeconds * (timeSinceLastFrame))));
+ }
+ }
+ }
+}
diff --git a/extern/oics/ICSControl.h b/extern/oics/ICSControl.h
new file mode 100644
index 0000000000..7939c86b95
--- /dev/null
+++ b/extern/oics/ICSControl.h
@@ -0,0 +1,107 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#ifndef _Control_H_
+#define _Control_H_
+
+#include "ICSPrerequisites.h"
+
+#include "ICSChannel.h"
+#include "ICSControlListener.h"
+
+namespace ICS
+{
+
+ class DllExport Control
+ {
+ public:
+
+ enum ControlChangingDirection
+ {
+ DECREASE = -1, STOP = 0, INCREASE = 1
+ };
+
+ Control(const std::string& name, bool autoChangeDirectionOnLimitsAfterStop = false, bool autoReverseToInitialValue = false, float initialValue = 0.5, float stepSize = 0.1, float stepsPerSeconds = 2.0, bool axisBindable = true);
+ ~Control();
+
+ void setChangingDirection(ControlChangingDirection direction);
+ inline ControlChangingDirection getChangingDirection(){ return currentChangingDirection; };
+
+ void setValue(float value);
+ inline float getValue(){ return mValue; };
+ inline float getInitialValue(){ return mInitialValue; };
+
+ void attachChannel(Channel* channel, Channel::ChannelDirection direction, float percentage = 1.0);
+ std::list<Channel*> getAttachedChannels(){ return mAttachedChannels; };
+
+ inline float getStepSize(){ return mStepSize; };
+ inline float getStepsPerSeconds(){ return mStepsPerSeconds; };
+
+ inline void setIgnoreAutoReverse(bool value){ mIgnoreAutoReverse = value; }; // mouse disable autoreverse
+ inline bool isAutoReverseIgnored(){ return mIgnoreAutoReverse; };
+ inline bool getAutoReverse(){ return mAutoReverseToInitialValue; };
+
+ inline bool getAutoChangeDirectionOnLimitsAfterStop(){ return mAutoChangeDirectionOnLimitsAfterStop; };
+
+ inline std::string getName(){ return mName; };
+
+ inline bool isAxisBindable(){ return mAxisBindable; };
+ inline void setAxisBindable(bool value){ mAxisBindable = value; };
+
+ inline void addListener(ControlListener* ob){ mListeners.push_back(ob); };
+ inline void removeListener(ControlListener* ob){ mListeners.remove(ob); };
+
+ void update(float timeSinceLastFrame);
+
+ protected:
+ float mValue;
+ float mInitialValue;
+ std::string mName;
+ float mStepSize;
+ float mStepsPerSeconds;
+ bool mAutoReverseToInitialValue;
+ bool mIgnoreAutoReverse;
+ bool mAutoChangeDirectionOnLimitsAfterStop;
+ bool mAxisBindable;
+
+ Control::ControlChangingDirection currentChangingDirection;
+ std::list<Channel*> mAttachedChannels;
+
+ std::list<ControlListener*> mListeners;
+
+ std::list<Control::ControlChangingDirection> mPendingActions;
+
+ protected:
+
+ void updateChannels();
+ void notifyListeners(float previousValue);
+
+ };
+
+}
+
+
+#endif
diff --git a/extern/oics/ICSControlListener.h b/extern/oics/ICSControlListener.h
new file mode 100644
index 0000000000..067b2d6f24
--- /dev/null
+++ b/extern/oics/ICSControlListener.h
@@ -0,0 +1,46 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#ifndef _ControlListener_H_
+#define _ControlListener_H_
+
+#include "ICSPrerequisites.h"
+
+#include "ICSControl.h"
+
+namespace ICS
+{
+
+ class DllExport ControlListener
+ {
+ public:
+ virtual void controlChanged(Control* control, float currentValue, float previousValue) = 0;
+ };
+
+}
+
+
+#endif \ No newline at end of file
diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp
new file mode 100644
index 0000000000..cdf8fbfe2f
--- /dev/null
+++ b/extern/oics/ICSInputControlSystem.cpp
@@ -0,0 +1,933 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+namespace ICS
+{
+ InputControlSystem::InputControlSystem(std::string file, bool active
+ , DetectingBindingListener* detectingBindingListener
+ , InputControlSystemLog* log, size_t channelCount)
+ : mFileName(file)
+ , mDetectingBindingListener(detectingBindingListener)
+ , mDetectingBindingControl(NULL)
+ , mLog(log)
+ , mXmouseAxisBinded(false), mYmouseAxisBinded(false)
+ {
+ ICS_LOG(" - Creating InputControlSystem - ");
+
+ this->mActive = active;
+
+ this->fillSDLKeysMap();
+
+ ICS_LOG("Channel count = " + ToString<size_t>(channelCount) );
+ for(size_t i=0;i<channelCount;i++)
+ {
+ mChannels.push_back(new Channel((int)i));
+ }
+
+ if(file != "")
+ {
+ TiXmlDocument* xmlDoc;
+ TiXmlElement* xmlRoot;
+
+ ICS_LOG("Loading file \""+file+"\"");
+
+ xmlDoc = new TiXmlDocument(file.c_str());
+ xmlDoc->LoadFile();
+
+ if(xmlDoc->Error())
+ {
+ std::ostringstream message;
+ message << "TinyXml reported an error reading \""+ file + "\". Row " <<
+ (int)xmlDoc->ErrorRow() << ", Col " << (int)xmlDoc->ErrorCol() << ": " <<
+ xmlDoc->ErrorDesc() ;
+ ICS_LOG(message.str());
+
+ delete xmlDoc;
+ return;
+ }
+
+ xmlRoot = xmlDoc->RootElement();
+ if(std::string(xmlRoot->Value()) != "Controller") {
+ ICS_LOG("Error: Invalid Controller file. Missing <Controller> element.");
+ delete xmlDoc;
+ return;
+ }
+
+ TiXmlElement* xmlControl = xmlRoot->FirstChildElement("Control");
+
+ size_t controlChannelCount = 0;
+ while(xmlControl)
+ {
+ TiXmlElement* xmlChannel = xmlControl->FirstChildElement("Channel");
+ while(xmlChannel)
+ {
+ controlChannelCount = std::max(channelCount, FromString<size_t>(xmlChannel->Attribute("number")));
+
+ xmlChannel = xmlChannel->NextSiblingElement("Channel");
+ }
+
+ xmlControl = xmlControl->NextSiblingElement("Control");
+ }
+
+ if(controlChannelCount > channelCount)
+ {
+ size_t dif = controlChannelCount - channelCount;
+ ICS_LOG("Warning: default channel count exceeded. Adding " + ToString<size_t>(dif) + " channels" );
+ for(size_t i = channelCount ; i < controlChannelCount ; i++)
+ {
+ mChannels.push_back(new Channel((int)i));
+ }
+ }
+
+ ICS_LOG("Applying filters to channels");
+ //<ChannelFilter number="0">
+ // <interval type="bezier" startX="0.0" startY="0.0" midX="0.25" midY="0.5" endX="0.5" endY="0.5" step="0.1" />
+ // <interval type="bezier" startX="0.5" startY="0.5" midX="0.75" midY="0.5" endX="1.0" endY="1.0" step="0.1" />
+ //</ChannelFilter>
+
+ TiXmlElement* xmlChannelFilter = xmlRoot->FirstChildElement("ChannelFilter");
+ while(xmlChannelFilter)
+ {
+ int ch = FromString<int>(xmlChannelFilter->Attribute("number"));
+
+ TiXmlElement* xmlInterval = xmlChannelFilter->FirstChildElement("Interval");
+ while(xmlInterval)
+ {
+ std::string type = xmlInterval->Attribute("type");
+
+ if(type == "bezier")
+ {
+ float step = 0.1;
+
+ float startX = FromString<float>(xmlInterval->Attribute("startX"));
+ float startY = FromString<float>(xmlInterval->Attribute("startY"));
+ float midX = FromString<float>(xmlInterval->Attribute("midX"));
+ float midY = FromString<float>(xmlInterval->Attribute("midY"));
+ float endX = FromString<float>(xmlInterval->Attribute("endX"));
+ float endY = FromString<float>(xmlInterval->Attribute("endY"));
+
+ step = FromString<float>(xmlInterval->Attribute("step"));
+
+ ICS_LOG("Applying Bezier filter to channel [number="
+ + ToString<int>(ch) + ", startX="
+ + ToString<float>(startX) + ", startY="
+ + ToString<float>(startY) + ", midX="
+ + ToString<float>(midX) + ", midY="
+ + ToString<float>(midY) + ", endX="
+ + ToString<float>(endX) + ", endY="
+ + ToString<float>(endY) + ", step="
+ + ToString<float>(step) + "]");
+
+ mChannels.at(ch)->addBezierInterval(startX, startY, midX, midY, endX, endY, step);
+ }
+
+ xmlInterval = xmlInterval->NextSiblingElement("Interval");
+ }
+
+
+ xmlChannelFilter = xmlChannelFilter->NextSiblingElement("ChannelFilter");
+ }
+
+ xmlControl = xmlRoot->FirstChildElement("Control");
+ while(xmlControl)
+ {
+ bool axisBindable = true;
+ if(xmlControl->Attribute("axisBindable"))
+ {
+ axisBindable = (std::string( xmlControl->Attribute("axisBindable") ) == "true");
+ }
+
+ ICS_LOG("Adding Control [name="
+ + std::string( xmlControl->Attribute("name") ) + ", autoChangeDirectionOnLimitsAfterStop="
+ + std::string( xmlControl->Attribute("autoChangeDirectionOnLimitsAfterStop") ) + ", autoReverseToInitialValue="
+ + std::string( xmlControl->Attribute("autoReverseToInitialValue") ) + ", initialValue="
+ + std::string( xmlControl->Attribute("initialValue") ) + ", stepSize="
+ + std::string( xmlControl->Attribute("stepSize") ) + ", stepsPerSeconds="
+ + std::string( xmlControl->Attribute("stepsPerSeconds") ) + ", axisBindable="
+ + std::string( (axisBindable)? "true" : "false" ) + "]");
+
+ float _stepSize = 0;
+ if(xmlControl->Attribute("stepSize"))
+ {
+ std::string value(xmlControl->Attribute("stepSize"));
+ if(value == "MAX")
+ {
+ _stepSize = ICS_MAX;
+ }
+ else
+ {
+ _stepSize = FromString<float>(value.c_str());
+ }
+ }
+ else
+ {
+ ICS_LOG("Warning: no stepSize value found. Default value is 0.");
+ }
+
+ float _stepsPerSeconds = 0;
+ if(xmlControl->Attribute("stepsPerSeconds"))
+ {
+ std::string value(xmlControl->Attribute("stepsPerSeconds"));
+ if(value == "MAX")
+ {
+ _stepsPerSeconds = ICS_MAX;
+ }
+ else
+ {
+ _stepsPerSeconds = FromString<float>(value.c_str());
+ }
+ }
+ else
+ {
+ ICS_LOG("Warning: no stepSize value found. Default value is 0.");
+ }
+
+ addControl( new Control(xmlControl->Attribute("name")
+ , std::string( xmlControl->Attribute("autoChangeDirectionOnLimitsAfterStop") ) == "true"
+ , std::string( xmlControl->Attribute("autoReverseToInitialValue") ) == "true"
+ , FromString<float>(xmlControl->Attribute("initialValue"))
+ , _stepSize
+ , _stepsPerSeconds
+ , axisBindable) );
+
+ loadKeyBinders(xmlControl);
+
+ loadMouseAxisBinders(xmlControl);
+
+ loadMouseButtonBinders(xmlControl);
+
+ loadJoystickAxisBinders(xmlControl);
+
+ loadJoystickButtonBinders(xmlControl);
+
+ loadJoystickPOVBinders(xmlControl);
+
+ loadJoystickSliderBinders(xmlControl);
+
+ // Attach controls to channels
+ TiXmlElement* xmlChannel = xmlControl->FirstChildElement("Channel");
+ while(xmlChannel)
+ {
+ ICS_LOG("\tAttaching control to channel [number="
+ + std::string( xmlChannel->Attribute("number") ) + ", direction="
+ + std::string( xmlChannel->Attribute("direction") ) + "]");
+
+ float percentage = 1;
+ if(xmlChannel->Attribute("percentage"))
+ {
+ if(StringIsNumber<float>(xmlChannel->Attribute("percentage")))
+ {
+ float val = FromString<float>(xmlChannel->Attribute("percentage"));
+ if(val > 1 || val < 0)
+ {
+ ICS_LOG("ERROR: attaching percentage value range is [0,1]");
+ }
+ else
+ {
+ percentage = val;
+ }
+ }
+ else
+ {
+ ICS_LOG("ERROR: attaching percentage value range is [0,1]");
+ }
+ }
+
+ int chNumber = FromString<int>(xmlChannel->Attribute("number"));
+ if(std::string(xmlChannel->Attribute("direction")) == "DIRECT")
+ {
+ mControls.back()->attachChannel(mChannels[ chNumber ],Channel::DIRECT, percentage);
+ }
+ else if(std::string(xmlChannel->Attribute("direction")) == "INVERSE")
+ {
+ mControls.back()->attachChannel(mChannels[ chNumber ],Channel::INVERSE, percentage);
+ }
+
+ xmlChannel = xmlChannel->NextSiblingElement("Channel");
+ }
+
+ xmlControl = xmlControl->NextSiblingElement("Control");
+ }
+
+ std::vector<Channel *>::const_iterator o;
+ for(o = mChannels.begin(); o != mChannels.end(); ++o)
+ {
+ (*o)->update();
+ }
+
+ delete xmlDoc;
+ }
+
+ ICS_LOG(" - InputControlSystem Created - ");
+ }
+
+ InputControlSystem::~InputControlSystem()
+ {
+ ICS_LOG(" - Deleting InputControlSystem (" + mFileName + ") - ");
+
+ mJoystickIDList.clear();
+
+ std::vector<Channel *>::const_iterator o;
+ for(o = mChannels.begin(); o != mChannels.end(); ++o)
+ {
+ delete (*o);
+ }
+ mChannels.clear();
+
+ std::vector<Control *>::const_iterator o2;
+ for(o2 = mControls.begin(); o2 != mControls.end(); ++o2)
+ {
+ delete (*o2);
+ }
+ mControls.clear();
+
+ mControlsKeyBinderMap.clear();
+ mControlsMouseButtonBinderMap.clear();
+ mControlsJoystickButtonBinderMap.clear();
+
+ mKeys.clear();
+ mKeyCodes.clear();
+
+ ICS_LOG(" - InputControlSystem deleted - ");
+ }
+
+ std::string InputControlSystem::getBaseFileName()
+ {
+ size_t found = mFileName.find_last_of("/\\");
+ std::string file = mFileName.substr(found+1);
+
+ return file.substr(0, file.find_last_of("."));
+ }
+
+ bool InputControlSystem::save(std::string fileName)
+ {
+ if(fileName != "")
+ {
+ mFileName = fileName;
+ }
+
+ TiXmlDocument doc( mFileName.c_str() );
+
+ TiXmlDeclaration dec;
+ dec.Parse( "<?xml version='1.0' encoding='utf-8'?>", 0, TIXML_ENCODING_UNKNOWN );
+ doc.InsertEndChild(dec);
+
+ TiXmlElement Controller( "Controller" );
+
+ for(std::vector<Channel*>::const_iterator o = mChannels.begin() ; o != mChannels.end(); o++)
+ {
+ ICS::IntervalList intervals = (*o)->getIntervals();
+
+ if(intervals.size() > 1) // all channels have a default linear filter
+ {
+ TiXmlElement ChannelFilter( "ChannelFilter" );
+
+ ChannelFilter.SetAttribute("number", ToString<int>((*o)->getNumber()).c_str());
+
+ ICS::IntervalList::const_iterator interval = intervals.begin();
+ while( interval != intervals.end() )
+ {
+ // if not default linear filter
+ if(!( interval->step == 0.2f
+ && interval->startX == 0.0f
+ && interval->startY == 0.0f
+ && interval->midX == 0.5f
+ && interval->midY == 0.5f
+ && interval->endX == 1.0f
+ && interval->endY == 1.0f ))
+ {
+ TiXmlElement XMLInterval( "Interval" );
+
+ XMLInterval.SetAttribute("type", "bezier");
+ XMLInterval.SetAttribute("step", ToString<float>(interval->step).c_str());
+
+ XMLInterval.SetAttribute("startX", ToString<float>(interval->startX).c_str());
+ XMLInterval.SetAttribute("startY", ToString<float>(interval->startY).c_str());
+ XMLInterval.SetAttribute("midX", ToString<float>(interval->midX).c_str());
+ XMLInterval.SetAttribute("midY", ToString<float>(interval->midY).c_str());
+ XMLInterval.SetAttribute("endX", ToString<float>(interval->endX).c_str());
+ XMLInterval.SetAttribute("endY", ToString<float>(interval->endY).c_str());
+
+ ChannelFilter.InsertEndChild(XMLInterval);
+ }
+
+ interval++;
+ }
+
+ Controller.InsertEndChild(ChannelFilter);
+ }
+ }
+
+ for(std::vector<Control*>::const_iterator o = mControls.begin() ; o != mControls.end(); o++)
+ {
+ TiXmlElement control( "Control" );
+
+ control.SetAttribute( "name", (*o)->getName().c_str() );
+ if((*o)->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ control.SetAttribute( "autoChangeDirectionOnLimitsAfterStop", "true" );
+ }
+ else
+ {
+ control.SetAttribute( "autoChangeDirectionOnLimitsAfterStop", "false" );
+ }
+ if((*o)->getAutoReverse())
+ {
+ control.SetAttribute( "autoReverseToInitialValue", "true" );
+ }
+ else
+ {
+ control.SetAttribute( "autoReverseToInitialValue", "false" );
+ }
+ control.SetAttribute( "initialValue", ToString<float>((*o)->getInitialValue()).c_str() );
+
+ if((*o)->getStepSize() == ICS_MAX)
+ {
+ control.SetAttribute( "stepSize", "MAX" );
+ }
+ else
+ {
+ control.SetAttribute( "stepSize", ToString<float>((*o)->getStepSize()).c_str() );
+ }
+
+ if((*o)->getStepsPerSeconds() == ICS_MAX)
+ {
+ control.SetAttribute( "stepsPerSeconds", "MAX" );
+ }
+ else
+ {
+ control.SetAttribute( "stepsPerSeconds", ToString<float>((*o)->getStepsPerSeconds()).c_str() );
+ }
+
+ if(!(*o)->isAxisBindable())
+ {
+ control.SetAttribute( "axisBindable", "false" );
+ }
+
+ if(getKeyBinding(*o, Control/*::ControlChangingDirection*/::INCREASE) != SDLK_UNKNOWN)
+ {
+ TiXmlElement keyBinder( "KeyBinder" );
+
+ keyBinder.SetAttribute( "key", keyCodeToString(
+ getKeyBinding(*o, Control/*::ControlChangingDirection*/::INCREASE)).c_str() );
+ keyBinder.SetAttribute( "direction", "INCREASE" );
+ control.InsertEndChild(keyBinder);
+ }
+
+ if(getKeyBinding(*o, Control/*::ControlChangingDirection*/::DECREASE) != SDLK_UNKNOWN)
+ {
+ TiXmlElement keyBinder( "KeyBinder" );
+
+ keyBinder.SetAttribute( "key", keyCodeToString(
+ getKeyBinding(*o, Control/*::ControlChangingDirection*/::DECREASE)).c_str() );
+ keyBinder.SetAttribute( "direction", "DECREASE" );
+ control.InsertEndChild(keyBinder);
+ }
+
+ if(getMouseAxisBinding(*o, Control/*::ControlChangingDirection*/::INCREASE)
+ != InputControlSystem/*::NamedAxis*/::UNASSIGNED)
+ {
+ TiXmlElement binder( "MouseBinder" );
+
+ InputControlSystem::NamedAxis axis =
+ getMouseAxisBinding(*o, Control/*::ControlChangingDirection*/::INCREASE);
+ if(axis == InputControlSystem/*::NamedAxis*/::X)
+ {
+ binder.SetAttribute( "axis", "X" );
+ }
+ else if(axis == InputControlSystem/*::NamedAxis*/::Y)
+ {
+ binder.SetAttribute( "axis", "Y" );
+ }
+ else if(axis == InputControlSystem/*::NamedAxis*/::Z)
+ {
+ binder.SetAttribute( "axis", "Z" );
+ }
+
+ binder.SetAttribute( "direction", "INCREASE" );
+ control.InsertEndChild(binder);
+ }
+
+ if(getMouseAxisBinding(*o, Control/*::ControlChangingDirection*/::DECREASE)
+ != InputControlSystem/*::NamedAxis*/::UNASSIGNED)
+ {
+ TiXmlElement binder( "MouseBinder" );
+
+ InputControlSystem::NamedAxis axis =
+ getMouseAxisBinding(*o, Control/*::ControlChangingDirection*/::DECREASE);
+ if(axis == InputControlSystem/*::NamedAxis*/::X)
+ {
+ binder.SetAttribute( "axis", "X" );
+ }
+ else if(axis == InputControlSystem/*::NamedAxis*/::Y)
+ {
+ binder.SetAttribute( "axis", "Y" );
+ }
+ else if(axis == InputControlSystem/*::NamedAxis*/::Z)
+ {
+ binder.SetAttribute( "axis", "Z" );
+ }
+
+ binder.SetAttribute( "direction", "DECREASE" );
+ control.InsertEndChild(binder);
+ }
+
+ if(getMouseButtonBinding(*o, Control/*::ControlChangingDirection*/::INCREASE)
+ != ICS_MAX_DEVICE_BUTTONS)
+ {
+ TiXmlElement binder( "MouseButtonBinder" );
+
+ unsigned int button = getMouseButtonBinding(*o, Control/*::ControlChangingDirection*/::INCREASE);
+ if(button == SDL_BUTTON_LEFT)
+ {
+ binder.SetAttribute( "button", "LEFT" );
+ }
+ else if(button == SDL_BUTTON_MIDDLE)
+ {
+ binder.SetAttribute( "button", "MIDDLE" );
+ }
+ else if(button == SDL_BUTTON_RIGHT)
+ {
+ binder.SetAttribute( "button", "RIGHT" );
+ }
+ else
+ {
+ binder.SetAttribute( "button", ToString<unsigned int>(button).c_str() );
+ }
+ binder.SetAttribute( "direction", "INCREASE" );
+ control.InsertEndChild(binder);
+ }
+
+ if(getMouseButtonBinding(*o, Control/*::ControlChangingDirection*/::DECREASE)
+ != ICS_MAX_DEVICE_BUTTONS)
+ {
+ TiXmlElement binder( "MouseButtonBinder" );
+
+ unsigned int button = getMouseButtonBinding(*o, Control/*::ControlChangingDirection*/::DECREASE);
+ if(button == SDL_BUTTON_LEFT)
+ {
+ binder.SetAttribute( "button", "LEFT" );
+ }
+ else if(button == SDL_BUTTON_MIDDLE)
+ {
+ binder.SetAttribute( "button", "MIDDLE" );
+ }
+ else if(button == SDL_BUTTON_RIGHT)
+ {
+ binder.SetAttribute( "button", "RIGHT" );
+ }
+ else
+ {
+ binder.SetAttribute( "button", ToString<unsigned int>(button).c_str() );
+ }
+ binder.SetAttribute( "direction", "DECREASE" );
+ control.InsertEndChild(binder);
+ }
+
+ JoystickIDList::const_iterator it = mJoystickIDList.begin();
+ while(it != mJoystickIDList.end())
+ {
+ int deviceId = *it;
+
+ if(getJoystickAxisBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)
+ != /*NamedAxis::*/UNASSIGNED)
+ {
+ TiXmlElement binder( "JoystickAxisBinder" );
+
+ binder.SetAttribute( "axis", ToString<int>(
+ getJoystickAxisBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "INCREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickAxisBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE)
+ != /*NamedAxis::*/UNASSIGNED)
+ {
+ TiXmlElement binder( "JoystickAxisBinder" );
+
+ binder.SetAttribute( "axis", ToString<int>(
+ getJoystickAxisBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "DECREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickButtonBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)
+ != ICS_MAX_DEVICE_BUTTONS)
+ {
+ TiXmlElement binder( "JoystickButtonBinder" );
+
+ binder.SetAttribute( "button", ToString<unsigned int>(
+ getJoystickButtonBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "INCREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickButtonBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE)
+ != ICS_MAX_DEVICE_BUTTONS)
+ {
+ TiXmlElement binder( "JoystickButtonBinder" );
+
+ binder.SetAttribute( "button", ToString<unsigned int>(
+ getJoystickButtonBinding(*o, *it, Control/*::ControlChangingDirection*/::DECREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "DECREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickPOVBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE).index >= 0)
+ {
+ TiXmlElement binder( "JoystickPOVBinder" );
+
+ POVBindingPair POVPair = getJoystickPOVBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE);
+
+ binder.SetAttribute( "pov", ToString<int>(POVPair.index).c_str() );
+
+ binder.SetAttribute( "direction", "INCREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ if(POVPair.axis == ICS::InputControlSystem::EastWest)
+ {
+ binder.SetAttribute( "axis", "EastWest" );
+ }
+ else
+ {
+ binder.SetAttribute( "axis", "NorthSouth" );
+ }
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickPOVBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE).index >= 0)
+ {
+ TiXmlElement binder( "JoystickPOVBinder" );
+
+ POVBindingPair POVPair = getJoystickPOVBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE);
+
+ binder.SetAttribute( "pov", ToString<int>(POVPair.index).c_str() );
+
+ binder.SetAttribute( "direction", "DECREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ if(POVPair.axis == ICS::InputControlSystem::EastWest)
+ {
+ binder.SetAttribute( "axis", "EastWest" );
+ }
+ else
+ {
+ binder.SetAttribute( "axis", "NorthSouth" );
+ }
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickSliderBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)
+ != /*NamedAxis::*/UNASSIGNED)
+ {
+ TiXmlElement binder( "JoystickSliderBinder" );
+
+ binder.SetAttribute( "slider", ToString<int>(
+ getJoystickSliderBinding(*o, deviceId, Control/*::ControlChangingDirection*/::INCREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "INCREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ if(getJoystickSliderBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE)
+ != /*NamedAxis::*/UNASSIGNED)
+ {
+ TiXmlElement binder( "JoystickSliderBinder" );
+
+ binder.SetAttribute( "slider", ToString<int>(
+ getJoystickSliderBinding(*o, deviceId, Control/*::ControlChangingDirection*/::DECREASE)).c_str() );
+
+ binder.SetAttribute( "direction", "DECREASE" );
+
+ binder.SetAttribute( "deviceId", ToString<int>(deviceId).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ it++;
+ }
+
+
+ std::list<Channel*> channels = (*o)->getAttachedChannels();
+ for(std::list<Channel*>::iterator it = channels.begin() ;
+ it != channels.end() ; it++)
+ {
+ TiXmlElement binder( "Channel" );
+
+ binder.SetAttribute( "number", ToString<int>((*it)->getNumber()).c_str() );
+
+ Channel::ChannelDirection direction = (*it)->getAttachedControlBinding(*o).direction;
+ if(direction == Channel/*::ChannelDirection*/::DIRECT)
+ {
+ binder.SetAttribute( "direction", "DIRECT" );
+ }
+ else
+ {
+ binder.SetAttribute( "direction", "INVERSE" );
+ }
+
+ float percentage = (*it)->getAttachedControlBinding(*o).percentage;
+ binder.SetAttribute( "percentage", ToString<float>(percentage).c_str() );
+
+ control.InsertEndChild(binder);
+ }
+
+ Controller.InsertEndChild(control);
+ }
+
+ doc.InsertEndChild(Controller);
+ return doc.SaveFile();
+ }
+
+ void InputControlSystem::update(float lTimeSinceLastFrame)
+ {
+ if(mActive)
+ {
+ std::vector<Control *>::const_iterator it;
+ for(it=mControls.begin(); it!=mControls.end(); ++it)
+ {
+ (*it)->update(lTimeSinceLastFrame);
+ }
+ }
+
+ //! @todo Future versions should consider channel exponentials and mixtures, so
+ // after updating Controls, Channels should be updated according to their values
+ }
+
+ float InputControlSystem::getChannelValue(int i)
+ {
+ return std::max<float>(0.0,std::min<float>(1.0,mChannels[i]->getValue()));
+ }
+
+ float InputControlSystem::getControlValue(int i)
+ {
+ return mControls[i]->getValue();
+ }
+
+ void InputControlSystem::addJoystick(int deviceId)
+ {
+ ICS_LOG("Adding joystick (device id: " + ToString<int>(deviceId) + ")");
+
+ for(int j = 0 ; j < ICS_MAX_JOYSTICK_AXIS ; j++)
+ {
+ if(mControlsJoystickAxisBinderMap[deviceId].find(j) == mControlsJoystickAxisBinderMap[deviceId].end())
+ {
+ ControlAxisBinderItem controlJoystickBinderItem;
+ controlJoystickBinderItem.direction = Control::STOP;
+ controlJoystickBinderItem.control = NULL;
+ mControlsJoystickAxisBinderMap[deviceId][j] = controlJoystickBinderItem;
+ }
+ }
+
+ mJoystickIDList.push_back(deviceId);
+ }
+
+ Control* InputControlSystem::findControl(std::string name)
+ {
+ if(mActive)
+ {
+ std::vector<Control *>::const_iterator it;
+ for(it = mControls.begin(); it != mControls.end(); ++it)
+ {
+ if( ((Control*)(*it))->getName() == name)
+ {
+ return (Control*)(*it);
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ void InputControlSystem::enableDetectingBindingState(Control* control
+ , Control::ControlChangingDirection direction)
+ {
+ mDetectingBindingControl = control;
+ mDetectingBindingDirection = direction;
+
+ mMouseAxisBindingInitialValues[0] = ICS_MOUSE_AXIS_BINDING_NULL_VALUE;
+ }
+
+ void InputControlSystem::cancelDetectingBindingState()
+ {
+ mDetectingBindingControl = NULL;
+ }
+
+ void InputControlSystem::fillSDLKeysMap()
+ {
+ mKeys["UNASSIGNED"]= SDLK_UNKNOWN;
+ mKeys["ESCAPE"]= SDLK_ESCAPE;
+ mKeys["1"]= SDLK_1;
+ mKeys["2"]= SDLK_2;
+ mKeys["3"]= SDLK_3;
+ mKeys["4"]= SDLK_4;
+ mKeys["5"]= SDLK_5;
+ mKeys["6"]= SDLK_6;
+ mKeys["7"]= SDLK_7;
+ mKeys["8"]= SDLK_8;
+ mKeys["9"]= SDLK_9;
+ mKeys["0"]= SDLK_0;
+ mKeys["MINUS"]= SDLK_MINUS;
+ mKeys["EQUALS"]= SDLK_EQUALS;
+ mKeys["BACK"]= SDLK_BACKSPACE;
+ mKeys["TAB"]= SDLK_TAB;
+ mKeys["Q"]= SDLK_q;
+ mKeys["W"]= SDLK_w;
+ mKeys["E"]= SDLK_e;
+ mKeys["R"]= SDLK_r;
+ mKeys["T"]= SDLK_t;
+ mKeys["Y"]= SDLK_y;
+ mKeys["U"]= SDLK_u;
+ mKeys["I"]= SDLK_i;
+ mKeys["O"]= SDLK_o;
+ mKeys["P"]= SDLK_p;
+ mKeys["LBRACKET"]= SDLK_LEFTBRACKET;
+ mKeys["RBRACKET"]= SDLK_RIGHTBRACKET;
+ mKeys["RETURN"]= SDLK_RETURN;
+ mKeys["LCONTROL"]= SDLK_LCTRL;
+ mKeys["A"]= SDLK_a;
+ mKeys["S"]= SDLK_s;
+ mKeys["D"]= SDLK_d;
+ mKeys["F"]= SDLK_f;
+ mKeys["G"]= SDLK_g;
+ mKeys["H"]= SDLK_h;
+ mKeys["J"]= SDLK_j;
+ mKeys["K"]= SDLK_k;
+ mKeys["L"]= SDLK_l;
+ mKeys["SEMICOLON"]= SDLK_SEMICOLON;
+ mKeys["APOSTROPHE"]= SDLK_QUOTE;
+ mKeys["GRAVE"]= SDLK_BACKQUOTE;
+ mKeys["LSHIFT"]= SDLK_LSHIFT;
+ mKeys["BACKSLASH"]= SDLK_BACKSLASH;
+ mKeys["Z"]= SDLK_z;
+ mKeys["X"]= SDLK_x;
+ mKeys["C"]= SDLK_c;
+ mKeys["V"]= SDLK_v;
+ mKeys["B"]= SDLK_b;
+ mKeys["N"]= SDLK_n;
+ mKeys["M"]= SDLK_m;
+ mKeys["COMMA"]= SDLK_COMMA;
+ mKeys["PERIOD"]= SDLK_PERIOD;
+ mKeys["SLASH"]= SDLK_SLASH;
+ mKeys["RSHIFT"]= SDLK_RSHIFT;
+ mKeys["MULTIPLY"]= SDLK_ASTERISK;
+ mKeys["LMENU"]= SDLK_LALT;
+ mKeys["SPACE"]= SDLK_SPACE;
+ mKeys["CAPITAL"]= SDLK_CAPSLOCK;
+ mKeys["F1"]= SDLK_F1;
+ mKeys["F2"]= SDLK_F2;
+ mKeys["F3"]= SDLK_F3;
+ mKeys["F4"]= SDLK_F4;
+ mKeys["F5"]= SDLK_F5;
+ mKeys["F6"]= SDLK_F6;
+ mKeys["F7"]= SDLK_F7;
+ mKeys["F8"]= SDLK_F8;
+ mKeys["F9"]= SDLK_F9;
+ mKeys["F10"]= SDLK_F10;
+ mKeys["F11"]= SDLK_F11;
+ mKeys["F12"]= SDLK_F12;
+ mKeys["NUMLOCK"]= SDLK_NUMLOCKCLEAR;
+ mKeys["SCROLL"]= SDLK_SCROLLLOCK;
+ mKeys["NUMPAD7"]= SDLK_KP_7;
+ mKeys["NUMPAD8"]= SDLK_KP_8;
+ mKeys["NUMPAD9"]= SDLK_KP_9;
+ mKeys["SUBTRACT"]= SDLK_KP_MINUS;
+ mKeys["NUMPAD4"]= SDLK_KP_4;
+ mKeys["NUMPAD5"]= SDLK_KP_5;
+ mKeys["NUMPAD6"]= SDLK_KP_6;
+ mKeys["ADD"]= SDLK_KP_PLUS;
+ mKeys["NUMPAD1"]= SDLK_KP_1;
+ mKeys["NUMPAD2"]= SDLK_KP_2;
+ mKeys["NUMPAD3"]= SDLK_KP_3;
+ mKeys["NUMPAD0"]= SDLK_KP_0;
+ mKeys["DECIMAL"]= SDLK_KP_DECIMAL;
+ mKeys["RCONTROL"]= SDLK_RCTRL;
+ mKeys["DIVIDE"]= SDLK_SLASH;
+ mKeys["SYSRQ"]= SDLK_SYSREQ;
+ mKeys["PRNTSCRN"] = SDLK_PRINTSCREEN;
+ mKeys["RMENU"]= SDLK_RALT;
+ mKeys["PAUSE"]= SDLK_PAUSE;
+ mKeys["HOME"]= SDLK_HOME;
+ mKeys["UP"]= SDLK_UP;
+ mKeys["PGUP"]= SDLK_PAGEUP;
+ mKeys["LEFT"]= SDLK_LEFT;
+ mKeys["RIGHT"]= SDLK_RIGHT;
+ mKeys["END"]= SDLK_END;
+ mKeys["DOWN"]= SDLK_DOWN;
+ mKeys["PGDOWN"]= SDLK_PAGEDOWN;
+ mKeys["INSERT"]= SDLK_INSERT;
+ mKeys["DELETE"]= SDLK_DELETE;
+
+ mKeys["NUMPADENTER"]= SDLK_KP_ENTER;
+
+ for(std::map<std::string, SDL_Keycode>::iterator it = mKeys.begin()
+ ; it != mKeys.end() ; it++)
+ {
+ mKeyCodes[ it->second ] = it->first;
+ }
+ }
+
+ std::string InputControlSystem::keyCodeToString(SDL_Keycode key)
+ {
+ return mKeyCodes[key];
+ }
+
+ SDL_Keycode InputControlSystem::stringToKeyCode(std::string key)
+ {
+ return mKeys[key];
+ }
+
+ void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height)
+ {
+ mClientWidth = width;
+ mClientHeight = height;
+ }
+}
diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h
new file mode 100644
index 0000000000..f42f9c0b5f
--- /dev/null
+++ b/extern/oics/ICSInputControlSystem.h
@@ -0,0 +1,264 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#ifndef _InputControlSystem_H_
+#define _InputControlSystem_H_
+
+#include "ICSPrerequisites.h"
+
+#include "ICSControl.h"
+#include "ICSChannel.h"
+
+#include "../sdl4ogre/events.h"
+
+#define ICS_LOG(text) if(mLog) mLog->logMessage( ("ICS: " + std::string(text)).c_str() );
+#define ICS_MAX_JOYSTICK_AXIS 16
+#define ICS_MOUSE_BINDING_MARGIN 30
+#define ICS_JOYSTICK_AXIS_BINDING_MARGIN 10000
+#define ICS_JOYSTICK_SLIDER_BINDING_MARGIN 10000
+#define ICS_MOUSE_AXIS_BINDING_NULL_VALUE std::numeric_limits<int>::max()
+
+namespace ICS
+{
+ class DllExport InputControlSystemLog
+ {
+ public:
+ virtual void logMessage(const char* text) = 0;
+ };
+
+ class DllExport InputControlSystem :
+ public SFO::MouseListener,
+ public SFO::KeyListener,
+ public SFO::JoyListener
+ {
+
+ public:
+
+ enum NamedAxis { X = -1, Y = -2, Z = -3, UNASSIGNED = -4 };
+ enum POVAxis { NorthSouth = 0, EastWest = 1 };
+
+ typedef NamedAxis MouseAxis; // MouseAxis is deprecated. It will be removed in future versions
+
+ typedef std::list<int> JoystickIDList;
+
+ typedef struct
+ {
+ int index;
+ POVAxis axis;
+ } POVBindingPair;
+
+ InputControlSystem(std::string file = "", bool active = true
+ , DetectingBindingListener* detectingBindingListener = NULL
+ , InputControlSystemLog* log = NULL, size_t channelCount = 16);
+ ~InputControlSystem();
+
+ std::string getFileName(){ return mFileName; };
+ std::string getBaseFileName();
+
+ void setDetectingBindingListener(DetectingBindingListener* detectingBindingListener){ mDetectingBindingListener = detectingBindingListener; };
+ DetectingBindingListener* getDetectingBindingListener(){ return mDetectingBindingListener; };
+
+ // in seconds
+ void update(float timeSinceLastFrame);
+
+ inline Channel* getChannel(int i){ return mChannels[i]; };
+ float getChannelValue(int i);
+ inline int getChannelCount(){ return (int)mChannels.size(); };
+
+ inline Control* getControl(int i){ return mControls[i]; };
+ float getControlValue(int i);
+ inline int getControlCount(){ return (int)mControls.size(); };
+ inline void addControl(Control* control){ mControls.push_back(control); };
+
+ Control* findControl(std::string name);
+
+ inline void activate(){ this->mActive = true; };
+ inline void deactivate(){ this->mActive = false; };
+
+ void addJoystick(int deviceId);
+ JoystickIDList& getJoystickIdList(){ return mJoystickIDList; };
+
+ // MouseListener
+ bool mouseMoved(const SFO::MouseMotionEvent &evt);
+ bool mousePressed(const SDL_MouseButtonEvent &evt, Uint8);
+ bool mouseReleased(const SDL_MouseButtonEvent &evt, Uint8);
+
+ // KeyListener
+ bool keyPressed(const SDL_KeyboardEvent &evt);
+ bool keyReleased(const SDL_KeyboardEvent &evt);
+
+ // JoyStickListener
+ bool buttonPressed(const SDL_JoyButtonEvent &evt, int button);
+ bool buttonReleased(const SDL_JoyButtonEvent &evt, int button);
+ bool axisMoved(const SDL_JoyAxisEvent &evt, int axis);
+ bool povMoved(const SDL_JoyHatEvent &evt, int index);
+ //TODO: does this have an SDL equivalent?
+ //bool sliderMoved(const OIS::JoyStickEvent &evt, int index);
+
+ void addKeyBinding(Control* control, SDL_Keycode key, Control::ControlChangingDirection direction);
+ void addMouseAxisBinding(Control* control, NamedAxis axis, Control::ControlChangingDirection direction);
+ void addMouseButtonBinding(Control* control, unsigned int button, Control::ControlChangingDirection direction);
+ void addJoystickAxisBinding(Control* control, int deviceId, int axis, Control::ControlChangingDirection direction);
+ void addJoystickButtonBinding(Control* control, int deviceId, unsigned int button, Control::ControlChangingDirection direction);
+ void addJoystickPOVBinding(Control* control, int deviceId, int index, POVAxis axis, Control::ControlChangingDirection direction);
+ void addJoystickSliderBinding(Control* control, int deviceId, int index, Control::ControlChangingDirection direction);
+ void removeKeyBinding(SDL_Keycode key);
+ void removeMouseAxisBinding(NamedAxis axis);
+ void removeMouseButtonBinding(unsigned int button);
+ void removeJoystickAxisBinding(int deviceId, int axis);
+ void removeJoystickButtonBinding(int deviceId, unsigned int button);
+ void removeJoystickPOVBinding(int deviceId, int index, POVAxis axis);
+ void removeJoystickSliderBinding(int deviceId, int index);
+
+ SDL_Keycode getKeyBinding(Control* control, ICS::Control::ControlChangingDirection direction);
+ NamedAxis getMouseAxisBinding(Control* control, ICS::Control::ControlChangingDirection direction);
+ unsigned int getMouseButtonBinding(Control* control, ICS::Control::ControlChangingDirection direction);
+ int getJoystickAxisBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction);
+ unsigned int getJoystickButtonBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction);
+ POVBindingPair getJoystickPOVBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction);
+ int getJoystickSliderBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction);
+
+ std::string keyCodeToString(SDL_Keycode key);
+ SDL_Keycode stringToKeyCode(std::string key);
+
+ void enableDetectingBindingState(Control* control, Control::ControlChangingDirection direction);
+ void cancelDetectingBindingState();
+
+ bool save(std::string fileName = "");
+
+ void adjustMouseRegion (Uint16 width, Uint16 height);
+
+ protected:
+
+ void loadKeyBinders(TiXmlElement* xmlControlNode);
+ void loadMouseAxisBinders(TiXmlElement* xmlControlNode);
+ void loadMouseButtonBinders(TiXmlElement* xmlControlNode);
+ void loadJoystickAxisBinders(TiXmlElement* xmlControlNode);
+ void loadJoystickButtonBinders(TiXmlElement* xmlControlNode);
+ void loadJoystickPOVBinders(TiXmlElement* xmlControlNode);
+ void loadJoystickSliderBinders(TiXmlElement* xmlControlNode);
+
+ void addMouseAxisBinding_(Control* control, int axis, Control::ControlChangingDirection direction);
+ void removeMouseAxisBinding_(int axis);
+
+ protected:
+
+ typedef struct {
+ Control::ControlChangingDirection direction;
+ Control* control;
+ } ControlKeyBinderItem;
+
+ typedef ControlKeyBinderItem ControlAxisBinderItem;
+ typedef ControlKeyBinderItem ControlButtonBinderItem;
+ typedef ControlKeyBinderItem ControlPOVBinderItem;
+ typedef ControlKeyBinderItem ControlSliderBinderItem;
+
+ typedef struct {
+ Control* control;
+ Control::ControlChangingDirection direction;
+ } PendingActionItem;
+
+ std::list<PendingActionItem> mPendingActions;
+
+ std::string mFileName;
+
+ typedef std::map<SDL_Keycode, ControlKeyBinderItem> ControlsKeyBinderMapType; // <KeyCode, [direction, control]>
+ typedef std::map<int, ControlAxisBinderItem> ControlsAxisBinderMapType; // <axis, [direction, control]>
+ typedef std::map<int, ControlButtonBinderItem> ControlsButtonBinderMapType; // <button, [direction, control]>
+ typedef std::map<int, ControlPOVBinderItem> ControlsPOVBinderMapType; // <index, [direction, control]>
+ typedef std::map<int, ControlSliderBinderItem> ControlsSliderBinderMapType; // <index, [direction, control]>
+
+ typedef std::map<int, ControlsAxisBinderMapType> JoystickAxisBinderMapType; // <joystick_id, <axis, [direction, control]> >
+ typedef std::map<int, ControlsButtonBinderMapType> JoystickButtonBinderMapType; // <joystick_id, <button, [direction, control]> >
+ typedef std::map<int, std::map<int, ControlsPOVBinderMapType> > JoystickPOVBinderMapType; // <joystick_id, <index, <axis, [direction, control]> > >
+ typedef std::map<int, ControlsSliderBinderMapType> JoystickSliderBinderMapType; // <joystick_id, <index, [direction, control]> >
+
+ ControlsAxisBinderMapType mControlsMouseAxisBinderMap; // <axis, [direction, control]>
+ ControlsButtonBinderMapType mControlsMouseButtonBinderMap; // <int, [direction, control]>
+ JoystickAxisBinderMapType mControlsJoystickAxisBinderMap; // <joystick_id, <axis, [direction, control]> >
+ JoystickButtonBinderMapType mControlsJoystickButtonBinderMap; // <joystick_id, <button, [direction, control]> >
+ JoystickPOVBinderMapType mControlsJoystickPOVBinderMap; // <joystick_id, <index, <axis, [direction, control]> > >
+ JoystickSliderBinderMapType mControlsJoystickSliderBinderMap; // <joystick_id, <index, [direction, control]> >
+
+ std::vector<Control *> mControls;
+ std::vector<Channel *> mChannels;
+
+ ControlsKeyBinderMapType mControlsKeyBinderMap;
+ std::map<std::string, SDL_Keycode> mKeys;
+ std::map<SDL_Keycode, std::string> mKeyCodes;
+
+ bool mActive;
+ InputControlSystemLog* mLog;
+
+ DetectingBindingListener* mDetectingBindingListener;
+ Control* mDetectingBindingControl;
+ Control::ControlChangingDirection mDetectingBindingDirection;
+
+ bool mXmouseAxisBinded;
+ bool mYmouseAxisBinded;
+
+ JoystickIDList mJoystickIDList;
+
+ int mMouseAxisBindingInitialValues[3];
+
+ private:
+
+ void fillSDLKeysMap();
+
+ Uint16 mClientWidth;
+ Uint16 mClientHeight;
+ };
+
+ class DllExport DetectingBindingListener
+ {
+ public:
+ virtual void keyBindingDetected(InputControlSystem* ICS, Control* control
+ , SDL_Keycode key, Control::ControlChangingDirection direction);
+
+ virtual void mouseAxisBindingDetected(InputControlSystem* ICS, Control* control
+ , InputControlSystem::NamedAxis axis, Control::ControlChangingDirection direction);
+
+ virtual void mouseButtonBindingDetected(InputControlSystem* ICS, Control* control
+ , unsigned int button, Control::ControlChangingDirection direction);
+
+ virtual void joystickAxisBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int axis, Control::ControlChangingDirection direction);
+
+ virtual void joystickButtonBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, unsigned int button, Control::ControlChangingDirection direction);
+
+ virtual void joystickPOVBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int pov, InputControlSystem::POVAxis axis, Control::ControlChangingDirection direction);
+
+ virtual void joystickSliderBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int slider, Control::ControlChangingDirection direction);
+ };
+
+ static const float ICS_MAX = std::numeric_limits<float>::max();
+}
+
+
+#endif
diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp
new file mode 100644
index 0000000000..8e501d5018
--- /dev/null
+++ b/extern/oics/ICSInputControlSystem_joystick.cpp
@@ -0,0 +1,666 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+#define SDL_JOY_AXIS_MIN -32768
+#define SDL_JOY_AXIS_MAX 32767
+
+namespace ICS
+{
+ // load xml
+ void InputControlSystem::loadJoystickAxisBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlJoystickBinder = xmlControlNode->FirstChildElement("JoystickAxisBinder");
+ while(xmlJoystickBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlJoystickBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlJoystickBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ addJoystickAxisBinding(mControls.back(), FromString<int>(xmlJoystickBinder->Attribute("deviceId"))
+ , FromString<int>(xmlJoystickBinder->Attribute("axis")), dir);
+
+ xmlJoystickBinder = xmlJoystickBinder->NextSiblingElement("JoystickAxisBinder");
+ }
+ }
+
+ void InputControlSystem::loadJoystickButtonBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlJoystickButtonBinder = xmlControlNode->FirstChildElement("JoystickButtonBinder");
+ while(xmlJoystickButtonBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlJoystickButtonBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlJoystickButtonBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ addJoystickButtonBinding(mControls.back(), FromString<int>(xmlJoystickButtonBinder->Attribute("deviceId"))
+ , FromString<int>(xmlJoystickButtonBinder->Attribute("button")), dir);
+
+ xmlJoystickButtonBinder = xmlJoystickButtonBinder->NextSiblingElement("JoystickButtonBinder");
+ }
+ }
+
+ void InputControlSystem::loadJoystickPOVBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlJoystickPOVBinder = xmlControlNode->FirstChildElement("JoystickPOVBinder");
+ while(xmlJoystickPOVBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlJoystickPOVBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlJoystickPOVBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ InputControlSystem::POVAxis axis = /*POVAxis::*/NorthSouth;
+ if(std::string(xmlJoystickPOVBinder->Attribute("axis")) == "EastWest")
+ {
+ axis = /*POVAxis::*/EastWest;
+ }
+
+ addJoystickPOVBinding(mControls.back(), FromString<int>(xmlJoystickPOVBinder->Attribute("deviceId"))
+ , FromString<int>(xmlJoystickPOVBinder->Attribute("pov")), axis, dir);
+
+ xmlJoystickPOVBinder = xmlJoystickPOVBinder->NextSiblingElement("JoystickPOVBinder");
+ }
+ }
+
+ void InputControlSystem::loadJoystickSliderBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlJoystickSliderBinder = xmlControlNode->FirstChildElement("JoystickSliderBinder");
+ while(xmlJoystickSliderBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlJoystickSliderBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlJoystickSliderBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ addJoystickSliderBinding(mControls.back(), FromString<int>(xmlJoystickSliderBinder->Attribute("deviceId"))
+ , FromString<int>(xmlJoystickSliderBinder->Attribute("slider")), dir);
+
+ xmlJoystickSliderBinder = xmlJoystickSliderBinder->NextSiblingElement("JoystickSliderBinder");
+ }
+ }
+
+ // add bindings
+ void InputControlSystem::addJoystickAxisBinding(Control* control, int deviceId, int axis, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding AxisBinder [deviceid="
+ + ToString<int>(deviceId) + ", axis="
+ + ToString<int>(axis) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlAxisBinderItem controlAxisBinderItem;
+ controlAxisBinderItem.control = control;
+ controlAxisBinderItem.direction = direction;
+ mControlsJoystickAxisBinderMap[ deviceId ][ axis ] = controlAxisBinderItem;
+ }
+
+ void InputControlSystem::addJoystickButtonBinding(Control* control, int deviceId, unsigned int button, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding JoystickButtonBinder [deviceId="
+ + ToString<int>(deviceId) + ", button="
+ + ToString<int>(button) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlButtonBinderItem controlJoystickButtonBinderItem;
+ controlJoystickButtonBinderItem.direction = direction;
+ controlJoystickButtonBinderItem.control = control;
+ mControlsJoystickButtonBinderMap[ deviceId ][ button ] = controlJoystickButtonBinderItem;
+ }
+
+ void InputControlSystem::addJoystickPOVBinding(Control* control, int deviceId, int index, InputControlSystem::POVAxis axis, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding JoystickPOVBinder [deviceId="
+ + ToString<int>(deviceId) + ", pov="
+ + ToString<int>(index) + ", axis="
+ + ToString<int>(axis) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlPOVBinderItem ControlPOVBinderItem;
+ ControlPOVBinderItem.direction = direction;
+ ControlPOVBinderItem.control = control;
+ mControlsJoystickPOVBinderMap[ deviceId ][ index ][ axis ] = ControlPOVBinderItem;
+ }
+
+ void InputControlSystem::addJoystickSliderBinding(Control* control, int deviceId, int index, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding JoystickSliderBinder [deviceId="
+ + ToString<int>(deviceId) + ", direction="
+ + ToString<int>(index) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlSliderBinderItem ControlSliderBinderItem;
+ ControlSliderBinderItem.direction = direction;
+ ControlSliderBinderItem.control = control;
+ mControlsJoystickSliderBinderMap[ deviceId ][ index ] = ControlSliderBinderItem;
+ }
+
+ // get bindings
+ int InputControlSystem::getJoystickAxisBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction)
+ {
+ if(mControlsJoystickAxisBinderMap.find(deviceId) != mControlsJoystickAxisBinderMap.end())
+ {
+ ControlsAxisBinderMapType::iterator it = mControlsJoystickAxisBinderMap[deviceId].begin();
+ while(it != mControlsJoystickAxisBinderMap[deviceId].end())
+ {
+ if(it->first >= 0 && it->second.control == control && it->second.direction == direction)
+ {
+ return it->first;
+ }
+ it++;
+ }
+ }
+
+ return /*NamedAxis::*/UNASSIGNED;
+ }
+
+ unsigned int InputControlSystem::getJoystickButtonBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction)
+ {
+ if(mControlsJoystickButtonBinderMap.find(deviceId) != mControlsJoystickButtonBinderMap.end())
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsJoystickButtonBinderMap[deviceId].begin();
+ while(it != mControlsJoystickButtonBinderMap[deviceId].end())
+ {
+ if(it->second.control == control && it->second.direction == direction)
+ {
+ return it->first;
+ }
+ it++;
+ }
+ }
+
+ return ICS_MAX_DEVICE_BUTTONS;
+ }
+
+ InputControlSystem::POVBindingPair InputControlSystem::getJoystickPOVBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction)
+ {
+ POVBindingPair result;
+ result.index = -1;
+
+ if(mControlsJoystickPOVBinderMap.find(deviceId) != mControlsJoystickPOVBinderMap.end())
+ {
+ //ControlsAxisBinderMapType::iterator it = mControlsJoystickPOVBinderMap[deviceId].begin();
+ std::map<int, ControlsPOVBinderMapType>::iterator it = mControlsJoystickPOVBinderMap[deviceId].begin();
+ while(it != mControlsJoystickPOVBinderMap[deviceId].end())
+ {
+ ControlsPOVBinderMapType::const_iterator it2 = it->second.begin();
+ while(it2 != it->second.end())
+ {
+ if(it2->second.control == control && it2->second.direction == direction)
+ {
+ result.index = it->first;
+ result.axis = (POVAxis)it2->first;
+ return result;
+ }
+ it2++;
+ }
+
+ it++;
+ }
+ }
+
+ return result;
+ }
+
+ int InputControlSystem::getJoystickSliderBinding(Control* control, int deviceId, ICS::Control::ControlChangingDirection direction)
+ {
+ if(mControlsJoystickSliderBinderMap.find(deviceId) != mControlsJoystickSliderBinderMap.end())
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsJoystickSliderBinderMap[deviceId].begin();
+ while(it != mControlsJoystickSliderBinderMap[deviceId].end())
+ {
+ if(it->second.control == control && it->second.direction == direction)
+ {
+ return it->first;
+ }
+ it++;
+ }
+ }
+
+ return /*NamedAxis::*/UNASSIGNED;
+ }
+
+ // remove bindings
+ void InputControlSystem::removeJoystickAxisBinding(int deviceId, int axis)
+ {
+ if(mControlsJoystickAxisBinderMap.find(deviceId) != mControlsJoystickAxisBinderMap.end())
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsJoystickAxisBinderMap[deviceId].find(axis);
+ if(it != mControlsJoystickAxisBinderMap[deviceId].end())
+ {
+ mControlsJoystickAxisBinderMap[deviceId].erase(it);
+ }
+ }
+ }
+
+ void InputControlSystem::removeJoystickButtonBinding(int deviceId, unsigned int button)
+ {
+ if(mControlsJoystickButtonBinderMap.find(deviceId) != mControlsJoystickButtonBinderMap.end())
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsJoystickButtonBinderMap[deviceId].find(button);
+ if(it != mControlsJoystickButtonBinderMap[deviceId].end())
+ {
+ mControlsJoystickButtonBinderMap[deviceId].erase(it);
+ }
+ }
+ }
+
+ void InputControlSystem::removeJoystickPOVBinding(int deviceId, int index, POVAxis axis)
+ {
+ if(mControlsJoystickPOVBinderMap.find(deviceId) != mControlsJoystickPOVBinderMap.end())
+ {
+ std::map<int, ControlsPOVBinderMapType>::iterator it = mControlsJoystickPOVBinderMap[deviceId].find(index);
+ if(it != mControlsJoystickPOVBinderMap[deviceId].end())
+ {
+ if(it->second.find(axis) != it->second.end())
+ {
+ mControlsJoystickPOVBinderMap[deviceId].find(index)->second.erase( it->second.find(axis) );
+ }
+ }
+ }
+ }
+
+ void InputControlSystem::removeJoystickSliderBinding(int deviceId, int index)
+ {
+ if(mControlsJoystickSliderBinderMap.find(deviceId) != mControlsJoystickSliderBinderMap.end())
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsJoystickSliderBinderMap[deviceId].find(index);
+ if(it != mControlsJoystickSliderBinderMap[deviceId].end())
+ {
+ mControlsJoystickSliderBinderMap[deviceId].erase(it);
+ }
+ }
+ }
+
+ // joyStick listeners
+ bool InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ if(mControlsJoystickButtonBinderMap.find(evt.which) != mControlsJoystickButtonBinderMap.end())
+ {
+ ControlsButtonBinderMapType::const_iterator it = mControlsJoystickButtonBinderMap[evt.which].find(button);
+ if(it != mControlsJoystickButtonBinderMap[evt.which].end())
+ {
+ it->second.control->setIgnoreAutoReverse(false);
+ if(!it->second.control->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ it->second.control->setChangingDirection(it->second.direction);
+ }
+ else
+ {
+ if(it->second.control->getValue() == 1)
+ {
+ it->second.control->setChangingDirection(Control::DECREASE);
+ }
+ else if(it->second.control->getValue() == 0)
+ {
+ it->second.control->setChangingDirection(Control::INCREASE);
+ }
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ mDetectingBindingListener->joystickButtonBindingDetected(this,
+ mDetectingBindingControl, evt.which, button, mDetectingBindingDirection);
+ }
+ }
+
+ return true;
+ }
+
+ bool InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button)
+ {
+ if(mActive)
+ {
+ if(mControlsJoystickButtonBinderMap.find(evt.which) != mControlsJoystickButtonBinderMap.end())
+ {
+ ControlsButtonBinderMapType::const_iterator it = mControlsJoystickButtonBinderMap[evt.which].find(button);
+ if(it != mControlsJoystickButtonBinderMap[evt.which].end())
+ {
+ it->second.control->setChangingDirection(Control::STOP);
+ }
+ }
+ }
+ return true;
+ }
+
+ bool InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ if(mControlsJoystickAxisBinderMap.find(evt.which) != mControlsJoystickAxisBinderMap.end())
+ {
+ ControlAxisBinderItem joystickBinderItem = mControlsJoystickAxisBinderMap[ evt.which ][ axis ]; // joystic axis start at 0 index
+ Control* ctrl = joystickBinderItem.control;
+ if(ctrl)
+ {
+ ctrl->setIgnoreAutoReverse(true);
+
+ float axisRange = SDL_JOY_AXIS_MAX - SDL_JOY_AXIS_MAX;
+ float valDisplaced = (float)(evt.value - SDL_JOY_AXIS_MIN);
+
+ if(joystickBinderItem.direction == Control::INCREASE)
+ {
+ ctrl->setValue( valDisplaced / axisRange );
+ }
+ else if(joystickBinderItem.direction == Control::DECREASE)
+ {
+ ctrl->setValue( 1 - ( valDisplaced / axisRange ) );
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ //ControlAxisBinderItem joystickBinderItem = mControlsJoystickAxisBinderMap[ evt.which ][ axis ]; // joystic axis start at 0 index
+ //Control* ctrl = joystickBinderItem.control;
+ //if(ctrl && ctrl->isAxisBindable())
+ if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable())
+ {
+ if( abs( evt.value ) > ICS_JOYSTICK_AXIS_BINDING_MARGIN)
+ {
+ mDetectingBindingListener->joystickAxisBindingDetected(this,
+ mDetectingBindingControl, evt.which, axis, mDetectingBindingDirection);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ //Here be dragons, apparently
+ bool InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ if(mControlsJoystickPOVBinderMap.find(evt.which) != mControlsJoystickPOVBinderMap.end())
+ {
+ std::map<int, ControlsPOVBinderMapType>::const_iterator i = mControlsJoystickPOVBinderMap[ evt.which ].find(index);
+ if(i != mControlsJoystickPOVBinderMap[ evt.which ].end())
+ {
+ if(evt.value != SDL_HAT_LEFT
+ && evt.value != SDL_HAT_RIGHT
+ && evt.value != SDL_HAT_CENTERED)
+ {
+ ControlsPOVBinderMapType::const_iterator it = i->second.find( /*POVAxis::*/NorthSouth );
+ if(it != i->second.end())
+ {
+ it->second.control->setIgnoreAutoReverse(false);
+ if(!it->second.control->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ if(evt.value == SDL_HAT_UP
+ || evt.value == SDL_HAT_LEFTUP
+ || evt.value == SDL_HAT_RIGHTUP)
+ {
+ it->second.control->setChangingDirection(it->second.direction);
+ }
+ else
+ {
+ it->second.control->setChangingDirection((Control::ControlChangingDirection)(-1 * it->second.direction));
+ }
+ }
+ else
+ {
+ if(it->second.control->getValue() == 1)
+ {
+ it->second.control->setChangingDirection(Control::DECREASE);
+ }
+ else if(it->second.control->getValue() == 0)
+ {
+ it->second.control->setChangingDirection(Control::INCREASE);
+ }
+ }
+ }
+ }
+
+ if(evt.value != SDL_HAT_UP
+ && evt.value != SDL_HAT_DOWN
+ && evt.value != SDL_HAT_CENTERED)
+ {
+ ControlsPOVBinderMapType::const_iterator it = i->second.find( /*POVAxis::*/EastWest );
+ if(it != i->second.end())
+ {
+ it->second.control->setIgnoreAutoReverse(false);
+ if(!it->second.control->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ if(evt.value == SDL_HAT_RIGHT
+ || evt.value == SDL_HAT_RIGHTUP
+ || evt.value == SDL_HAT_RIGHTDOWN)
+ {
+ it->second.control->setChangingDirection(it->second.direction);
+ }
+ else
+ {
+ it->second.control->setChangingDirection((Control::ControlChangingDirection)(-1 * it->second.direction));
+ }
+ }
+ else
+ {
+ if(it->second.control->getValue() == 1)
+ {
+ it->second.control->setChangingDirection(Control::DECREASE);
+ }
+ else if(it->second.control->getValue() == 0)
+ {
+ it->second.control->setChangingDirection(Control::INCREASE);
+ }
+ }
+ }
+ }
+
+ if(evt.value == SDL_HAT_CENTERED)
+ {
+ ControlsPOVBinderMapType::const_iterator it = i->second.find( /*POVAxis::*/NorthSouth );
+ if(it != i->second.end())
+ {
+ it->second.control->setChangingDirection(Control::STOP);
+ }
+
+ it = i->second.find( /*POVAxis::*/EastWest );
+ if(it != i->second.end())
+ {
+ it->second.control->setChangingDirection(Control::STOP);
+ }
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable())
+ {
+ if(evt.value == SDL_HAT_LEFT
+ || evt.value == SDL_HAT_RIGHT
+ || evt.value == SDL_HAT_UP
+ || evt.value == SDL_HAT_DOWN)
+ {
+ POVAxis povAxis = NorthSouth;
+ if(evt.value == SDL_HAT_LEFT
+ || evt.value == SDL_HAT_RIGHT)
+ {
+ povAxis = EastWest;
+ }
+
+ mDetectingBindingListener->joystickPOVBindingDetected(this,
+ mDetectingBindingControl, evt.which, index, povAxis, mDetectingBindingDirection);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ //TODO: does this have an SDL equivalent?
+ /*
+ bool InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ if(mControlsJoystickSliderBinderMap.find(evt.device->getID()) != mControlsJoystickSliderBinderMap.end())
+ {
+ ControlSliderBinderItem joystickBinderItem = mControlsJoystickSliderBinderMap[ evt.device->getID() ][ index ];
+ Control* ctrl = joystickBinderItem.control;
+ if(ctrl)
+ {
+ ctrl->setIgnoreAutoReverse(true);
+ if(joystickBinderItem.direction == Control::INCREASE)
+ {
+ float axisRange = OIS::JoyStick::MAX_AXIS - OIS::JoyStick::MIN_AXIS;
+ float valDisplaced = (float)( evt.state.mSliders[index].abX - OIS::JoyStick::MIN_AXIS);
+
+ ctrl->setValue( valDisplaced / axisRange );
+ }
+ else if(joystickBinderItem.direction == Control::DECREASE)
+ {
+ float axisRange = OIS::JoyStick::MAX_AXIS - OIS::JoyStick::MIN_AXIS;
+ float valDisplaced = (float)(evt.state.mSliders[index].abX - OIS::JoyStick::MIN_AXIS);
+
+ ctrl->setValue( 1 - ( valDisplaced / axisRange ) );
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable())
+ {
+ if( abs( evt.state.mSliders[index].abX ) > ICS_JOYSTICK_SLIDER_BINDING_MARGIN)
+ {
+ mDetectingBindingListener->joystickSliderBindingDetected(this,
+ mDetectingBindingControl, evt.device->getID(), index, mDetectingBindingDirection);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ */
+
+ // joystick auto bindings
+ void DetectingBindingListener::joystickAxisBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int axis, Control::ControlChangingDirection direction)
+ {
+ // if the joystick axis is used by another control, remove it
+ ICS->removeJoystickAxisBinding(deviceId, axis);
+
+ // if the control has an axis assigned, remove it
+ int oldAxis = ICS->getJoystickAxisBinding(control, deviceId, direction);
+ if(oldAxis != InputControlSystem::UNASSIGNED)
+ {
+ ICS->removeJoystickAxisBinding(deviceId, oldAxis);
+ }
+
+ ICS->addJoystickAxisBinding(control, deviceId, axis, direction);
+ ICS->cancelDetectingBindingState();
+ }
+ void DetectingBindingListener::joystickButtonBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, unsigned int button, Control::ControlChangingDirection direction)
+ {
+ // if the joystick button is used by another control, remove it
+ ICS->removeJoystickButtonBinding(deviceId, button);
+
+ // if the control has a joystick button assigned, remove it
+ unsigned int oldButton = ICS->getJoystickButtonBinding(control, deviceId, direction);
+ if(oldButton != ICS_MAX_DEVICE_BUTTONS)
+ {
+ ICS->removeJoystickButtonBinding(deviceId, oldButton);
+ }
+
+ ICS->addJoystickButtonBinding(control, deviceId, button, direction);
+ ICS->cancelDetectingBindingState();
+ }
+
+
+ void DetectingBindingListener::joystickPOVBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int pov, InputControlSystem::POVAxis axis, Control::ControlChangingDirection direction)
+ {
+ // if the joystick slider is used by another control, remove it
+ ICS->removeJoystickPOVBinding(deviceId, pov, axis);
+
+ // if the control has a joystick button assigned, remove it
+ ICS::InputControlSystem::POVBindingPair oldPOV = ICS->getJoystickPOVBinding(control, deviceId, direction);
+ if(oldPOV.index >= 0 && oldPOV.axis == axis)
+ {
+ ICS->removeJoystickPOVBinding(deviceId, oldPOV.index, oldPOV.axis);
+ }
+
+ ICS->addJoystickPOVBinding(control, deviceId, pov, axis, direction);
+ ICS->cancelDetectingBindingState();
+ }
+
+ void DetectingBindingListener::joystickSliderBindingDetected(InputControlSystem* ICS, Control* control
+ , int deviceId, int slider, Control::ControlChangingDirection direction)
+ {
+ // if the joystick slider is used by another control, remove it
+ ICS->removeJoystickSliderBinding(deviceId, slider);
+
+ // if the control has a joystick slider assigned, remove it
+ int oldSlider = ICS->getJoystickSliderBinding(control, deviceId, direction);
+ if(oldSlider != InputControlSystem::/*NamedAxis::*/UNASSIGNED)
+ {
+ ICS->removeJoystickSliderBinding(deviceId, oldSlider);
+ }
+
+ ICS->addJoystickSliderBinding(control, deviceId, slider, direction);
+ ICS->cancelDetectingBindingState();
+ }
+}
diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp
new file mode 100644
index 0000000000..01d68f7843
--- /dev/null
+++ b/extern/oics/ICSInputControlSystem_keyboard.cpp
@@ -0,0 +1,156 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+namespace ICS
+{
+ void InputControlSystem::loadKeyBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlKeyBinder = xmlControlNode->FirstChildElement("KeyBinder");
+ while(xmlKeyBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlKeyBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlKeyBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ addKeyBinding(mControls.back(), mKeys[xmlKeyBinder->Attribute("key")], dir);
+
+ xmlKeyBinder = xmlKeyBinder->NextSiblingElement("KeyBinder");
+ }
+ }
+
+ void InputControlSystem::addKeyBinding(Control* control, SDL_Keycode key, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding KeyBinder [key="
+ + keyCodeToString(key) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlKeyBinderItem controlKeyBinderItem;
+ controlKeyBinderItem.control = control;
+ controlKeyBinderItem.direction = direction;
+ mControlsKeyBinderMap[ key ] = controlKeyBinderItem;
+ }
+
+ void InputControlSystem::removeKeyBinding(SDL_Keycode key)
+ {
+ ControlsKeyBinderMapType::iterator it = mControlsKeyBinderMap.find(key);
+ if(it != mControlsKeyBinderMap.end())
+ {
+ mControlsKeyBinderMap.erase(it);
+ }
+ }
+
+ SDL_Keycode InputControlSystem::getKeyBinding(Control* control
+ , ICS::Control::ControlChangingDirection direction)
+ {
+ ControlsKeyBinderMapType::iterator it = mControlsKeyBinderMap.begin();
+ while(it != mControlsKeyBinderMap.end())
+ {
+ if(it->second.control == control && it->second.direction == direction)
+ {
+ return it->first;
+ }
+ it++;
+ }
+
+ return SDLK_UNKNOWN;
+ }
+ bool InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ ControlsKeyBinderMapType::const_iterator it = mControlsKeyBinderMap.find(evt.keysym.sym);
+ if(it != mControlsKeyBinderMap.end())
+ {
+ it->second.control->setIgnoreAutoReverse(false);
+ if(!it->second.control->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ it->second.control->setChangingDirection(it->second.direction);
+ }
+ else
+ {
+ if(it->second.control->getValue() == 1)
+ {
+ it->second.control->setChangingDirection(Control::DECREASE);
+ }
+ else if(it->second.control->getValue() == 0)
+ {
+ it->second.control->setChangingDirection(Control::INCREASE);
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ mDetectingBindingListener->keyBindingDetected(this,
+ mDetectingBindingControl, evt.keysym.sym, mDetectingBindingDirection);
+ }
+ }
+
+ return true;
+ }
+
+ bool InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt)
+ {
+ if(mActive)
+ {
+ ControlsKeyBinderMapType::const_iterator it = mControlsKeyBinderMap.find(evt.keysym.sym);
+ if(it != mControlsKeyBinderMap.end())
+ {
+ it->second.control->setChangingDirection(Control::STOP);
+ }
+ }
+
+ return true;
+ }
+
+ void DetectingBindingListener::keyBindingDetected(InputControlSystem* ICS, Control* control
+ , SDL_Keycode key, Control::ControlChangingDirection direction)
+ {
+ // if the key is used by another control, remove it
+ ICS->removeKeyBinding(key);
+
+ // if the control has a key assigned, remove it
+ SDL_Keycode oldKey = ICS->getKeyBinding(control, direction);
+ if(oldKey != SDLK_UNKNOWN)
+ {
+ ICS->removeKeyBinding(oldKey);
+ }
+
+ ICS->addKeyBinding(control, key, direction);
+ ICS->cancelDetectingBindingState();
+ }
+
+}
diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp
new file mode 100644
index 0000000000..52eb894ed5
--- /dev/null
+++ b/extern/oics/ICSInputControlSystem_mouse.cpp
@@ -0,0 +1,397 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSInputControlSystem.h"
+
+namespace ICS
+{
+ // load xml
+ void InputControlSystem::loadMouseAxisBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlMouseBinder = xmlControlNode->FirstChildElement("MouseBinder");
+ while(xmlMouseBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlMouseBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlMouseBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ NamedAxis axis = /*NamedAxis::*/ X;
+ if((*xmlMouseBinder->Attribute("axis")) == 'Y')
+ {
+ axis = /*NamedAxis::*/ Y;
+ }
+ else if((*xmlMouseBinder->Attribute("axis")) == 'Z')
+ {
+ axis = /*NamedAxis::*/ Z;
+ }
+
+ addMouseAxisBinding(mControls.back(), axis, dir);
+
+ xmlMouseBinder = xmlMouseBinder->NextSiblingElement("MouseBinder");
+ }
+ }
+
+ void InputControlSystem::loadMouseButtonBinders(TiXmlElement* xmlControlNode)
+ {
+ TiXmlElement* xmlMouseButtonBinder = xmlControlNode->FirstChildElement("MouseButtonBinder");
+ while(xmlMouseButtonBinder)
+ {
+ Control::ControlChangingDirection dir = Control::STOP;
+ if(std::string(xmlMouseButtonBinder->Attribute("direction")) == "INCREASE")
+ {
+ dir = Control::INCREASE;
+ }
+ else if(std::string(xmlMouseButtonBinder->Attribute("direction")) == "DECREASE")
+ {
+ dir = Control::DECREASE;
+ }
+
+ int button = 0;
+ if(std::string(xmlMouseButtonBinder->Attribute("button")) == "LEFT")
+ {
+ button = SDL_BUTTON_LEFT;
+ }
+ else if(std::string(xmlMouseButtonBinder->Attribute("button")) == "RIGHT")
+ {
+ button = SDL_BUTTON_RIGHT;
+ }
+ else if(std::string(xmlMouseButtonBinder->Attribute("button")) == "MIDDLE")
+ {
+ button = SDL_BUTTON_MIDDLE;
+ }
+ else
+ {
+ button = FromString<int>(xmlMouseButtonBinder->Attribute("button"));
+ }
+
+ addMouseButtonBinding(mControls.back(), button, dir);
+
+ xmlMouseButtonBinder = xmlMouseButtonBinder->NextSiblingElement("MouseButtonBinder");
+ }
+ }
+
+
+ // add bindings
+ void InputControlSystem::addMouseAxisBinding(Control* control, NamedAxis axis, Control::ControlChangingDirection direction)
+ {
+ if(axis == /*NamedAxis::*/X)
+ {
+ mXmouseAxisBinded = true;
+ }
+ else if(axis == /*NamedAxis::*/Y)
+ {
+ mYmouseAxisBinded = true;
+ }
+
+ addMouseAxisBinding_(control, axis, direction);
+ }
+
+ /*protected*/ void InputControlSystem::addMouseAxisBinding_(Control* control, int axis, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding AxisBinder [axis="
+ + ToString<int>(axis) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlAxisBinderItem controlAxisBinderItem;
+ controlAxisBinderItem.control = control;
+ controlAxisBinderItem.direction = direction;
+ mControlsMouseAxisBinderMap[ axis ] = controlAxisBinderItem;
+ }
+
+ void InputControlSystem::addMouseButtonBinding(Control* control, unsigned int button, Control::ControlChangingDirection direction)
+ {
+ ICS_LOG("\tAdding MouseButtonBinder [button="
+ + ToString<int>(button) + ", direction="
+ + ToString<int>(direction) + "]");
+
+ ControlButtonBinderItem controlMouseButtonBinderItem;
+ controlMouseButtonBinderItem.direction = direction;
+ controlMouseButtonBinderItem.control = control;
+ mControlsMouseButtonBinderMap[ button ] = controlMouseButtonBinderItem;
+ }
+
+ // get bindings
+ InputControlSystem::NamedAxis InputControlSystem::getMouseAxisBinding(Control* control, ICS::Control::ControlChangingDirection direction)
+ {
+ ControlsAxisBinderMapType::iterator it = mControlsMouseAxisBinderMap.begin();
+ while(it != mControlsMouseAxisBinderMap.end())
+ {
+ if(it->first < 0 && it->second.control == control && it->second.direction == direction)
+ {
+ return (InputControlSystem::NamedAxis)(it->first);
+ }
+ it++;
+ }
+
+ return /*NamedAxis::*/UNASSIGNED;
+ }
+
+ //int InputControlSystem::getMouseAxisBinding(Control* control, ICS::Control::ControlChangingDirection direction)
+ //{
+ // ControlsAxisBinderMapType::iterator it = mControlsMouseAxisBinderMap.begin();
+ // while(it != mControlsMouseAxisBinderMap.end())
+ // {
+ // if(it->first >= 0 && it->second.control == control && it->second.direction == direction)
+ // {
+ // return it->first;
+ // }
+ // it++;
+ // }
+
+ // return /*NamedAxis::*/UNASSIGNED;
+ //}
+
+ unsigned int InputControlSystem::getMouseButtonBinding(Control* control, ICS::Control::ControlChangingDirection direction)
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsMouseButtonBinderMap.begin();
+ while(it != mControlsMouseButtonBinderMap.end())
+ {
+ if(it->second.control == control && it->second.direction == direction)
+ {
+ return it->first;
+ }
+ it++;
+ }
+
+ return ICS_MAX_DEVICE_BUTTONS;
+ }
+
+ // remove bindings
+ void InputControlSystem::removeMouseAxisBinding(NamedAxis axis)
+ {
+ if(axis == /*NamedAxis::*/X)
+ {
+ mXmouseAxisBinded = false;
+ }
+ else if(axis == /*NamedAxis::*/Y)
+ {
+ mYmouseAxisBinded = false;
+ }
+
+ removeMouseAxisBinding_(axis);
+ }
+ /*protected*/ void InputControlSystem::removeMouseAxisBinding_(int axis)
+ {
+ ControlsAxisBinderMapType::iterator it = mControlsMouseAxisBinderMap.find(axis);
+ if(it != mControlsMouseAxisBinderMap.end())
+ {
+ mControlsMouseAxisBinderMap.erase(it);
+ }
+ }
+
+
+ void InputControlSystem::removeMouseButtonBinding(unsigned int button)
+ {
+ ControlsButtonBinderMapType::iterator it = mControlsMouseButtonBinderMap.find(button);
+ if(it != mControlsMouseButtonBinderMap.end())
+ {
+ mControlsMouseButtonBinderMap.erase(it);
+ }
+ }
+
+ // mouse Listeners
+ bool InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ if(mXmouseAxisBinded && evt.xrel)
+ {
+ ControlAxisBinderItem mouseBinderItem = mControlsMouseAxisBinderMap[ /*NamedAxis::*/X ];
+ Control* ctrl = mouseBinderItem.control;
+ ctrl->setIgnoreAutoReverse(true);
+ if(mouseBinderItem.direction == Control::INCREASE)
+ {
+ ctrl->setValue( float( (evt.x) / float(mClientWidth) ) );
+ }
+ else if(mouseBinderItem.direction == Control::DECREASE)
+ {
+ ctrl->setValue( 1 - float( evt.x / float(mClientWidth) ) );
+ }
+ }
+
+ if(mYmouseAxisBinded && evt.yrel)
+ {
+ ControlAxisBinderItem mouseBinderItem = mControlsMouseAxisBinderMap[ /*NamedAxis::*/Y ];
+ Control* ctrl = mouseBinderItem.control;
+ ctrl->setIgnoreAutoReverse(true);
+ if(mouseBinderItem.direction == Control::INCREASE)
+ {
+ ctrl->setValue( float( (evt.y) / float(mClientHeight) ) );
+ }
+ else if(mouseBinderItem.direction == Control::DECREASE)
+ {
+ ctrl->setValue( 1 - float( evt.y / float(mClientHeight) ) );
+ }
+ }
+
+ //! @todo Whats the range of the Z axis?
+ /*if(evt.state.Z.rel)
+ {
+ ControlAxisBinderItem mouseBinderItem = mControlsAxisBinderMap[ NamedAxis::Z ];
+ Control* ctrl = mouseBinderItem.control;
+ ctrl->setIgnoreAutoReverse(true);
+ if(mouseBinderItem.direction == Control::INCREASE)
+ {
+ ctrl->setValue( float( (evt.state.Z.abs) / float(evt.state.¿width?) ) );
+ }
+ else if(mouseBinderItem.direction == Control::DECREASE)
+ {
+ ctrl->setValue( float( (1 - evt.state.Z.abs) / float(evt.state.¿width?) ) );
+ }
+ }*/
+ }
+ else if(mDetectingBindingListener)
+ {
+ if(mDetectingBindingControl->isAxisBindable())
+ {
+ if(mMouseAxisBindingInitialValues[0] == ICS_MOUSE_AXIS_BINDING_NULL_VALUE)
+ {
+ mMouseAxisBindingInitialValues[0] = 0;
+ mMouseAxisBindingInitialValues[1] = 0;
+ mMouseAxisBindingInitialValues[2] = 0;
+ }
+
+ mMouseAxisBindingInitialValues[0] += evt.xrel;
+ mMouseAxisBindingInitialValues[1] += evt.yrel;
+ mMouseAxisBindingInitialValues[2] += evt.zrel;
+
+ if( abs(mMouseAxisBindingInitialValues[0]) > ICS_MOUSE_BINDING_MARGIN )
+ {
+ mDetectingBindingListener->mouseAxisBindingDetected(this,
+ mDetectingBindingControl, X, mDetectingBindingDirection);
+ }
+ else if( abs(mMouseAxisBindingInitialValues[1]) > ICS_MOUSE_BINDING_MARGIN )
+ {
+ mDetectingBindingListener->mouseAxisBindingDetected(this,
+ mDetectingBindingControl, Y, mDetectingBindingDirection);
+ }
+ else if( abs(mMouseAxisBindingInitialValues[2]) > ICS_MOUSE_BINDING_MARGIN )
+ {
+ mDetectingBindingListener->mouseAxisBindingDetected(this,
+ mDetectingBindingControl, Z, mDetectingBindingDirection);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn)
+ {
+ if(mActive)
+ {
+ if(!mDetectingBindingControl)
+ {
+ ControlsButtonBinderMapType::const_iterator it = mControlsMouseButtonBinderMap.find((int)btn);
+ if(it != mControlsMouseButtonBinderMap.end())
+ {
+ it->second.control->setIgnoreAutoReverse(false);
+ if(!it->second.control->getAutoChangeDirectionOnLimitsAfterStop())
+ {
+ it->second.control->setChangingDirection(it->second.direction);
+ }
+ else
+ {
+ if(it->second.control->getValue() == 1)
+ {
+ it->second.control->setChangingDirection(Control::DECREASE);
+ }
+ else if(it->second.control->getValue() == 0)
+ {
+ it->second.control->setChangingDirection(Control::INCREASE);
+ }
+ }
+ }
+ }
+ else if(mDetectingBindingListener)
+ {
+ mDetectingBindingListener->mouseButtonBindingDetected(this,
+ mDetectingBindingControl, btn, mDetectingBindingDirection);
+ }
+ }
+
+ return true;
+ }
+
+ bool InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn)
+ {
+ if(mActive)
+ {
+ ControlsButtonBinderMapType::const_iterator it = mControlsMouseButtonBinderMap.find((int)btn);
+ if(it != mControlsMouseButtonBinderMap.end())
+ {
+ it->second.control->setChangingDirection(Control::STOP);
+ }
+ }
+
+ return true;
+ }
+
+ // mouse auto bindings
+ void DetectingBindingListener::mouseAxisBindingDetected(InputControlSystem* ICS, Control* control
+ , InputControlSystem::NamedAxis axis, Control::ControlChangingDirection direction)
+ {
+ // if the mouse axis is used by another control, remove it
+ ICS->removeMouseAxisBinding(axis);
+
+ // if the control has an axis assigned, remove it
+ InputControlSystem::NamedAxis oldAxis = ICS->getMouseAxisBinding(control, direction);
+ if(oldAxis != InputControlSystem::UNASSIGNED)
+ {
+ ICS->removeMouseAxisBinding(oldAxis);
+ }
+
+ ICS->addMouseAxisBinding(control, axis, direction);
+ ICS->cancelDetectingBindingState();
+ }
+
+ void DetectingBindingListener::mouseButtonBindingDetected(InputControlSystem* ICS, Control* control
+ , unsigned int button, Control::ControlChangingDirection direction)
+ {
+ // if the mouse button is used by another control, remove it
+ ICS->removeMouseButtonBinding(button);
+
+ // if the control has a mouse button assigned, remove it
+ unsigned int oldButton = ICS->getMouseButtonBinding(control, direction);
+ if(oldButton != ICS_MAX_DEVICE_BUTTONS)
+ {
+ ICS->removeMouseButtonBinding(oldButton);
+ }
+
+ ICS->addMouseButtonBinding(control, button, direction);
+ ICS->cancelDetectingBindingState();
+ }
+
+}
diff --git a/extern/oics/ICSPrerequisites.cpp b/extern/oics/ICSPrerequisites.cpp
new file mode 100644
index 0000000000..2824950ed1
--- /dev/null
+++ b/extern/oics/ICSPrerequisites.cpp
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+#include "ICSPrerequisites.h"
diff --git a/extern/oics/ICSPrerequisites.h b/extern/oics/ICSPrerequisites.h
new file mode 100644
index 0000000000..52daea3f47
--- /dev/null
+++ b/extern/oics/ICSPrerequisites.h
@@ -0,0 +1,109 @@
+/* -------------------------------------------------------
+Copyright (c) 2011 Alberto G. Salguero (alberto.salguero (at) uca.es)
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of
+the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------- */
+
+//! @todo add mouse wheel support
+
+#ifndef _InputControlSystem_Prerequisites_H_
+#define _InputControlSystem_Prerequisites_H_
+
+/// Include external headers
+#include <sstream>
+#include <fstream>
+#include <vector>
+#include <map>
+#include <list>
+#include <limits>
+
+#include "tinyxml.h"
+
+#include "SDL_keyboard.h"
+#include "SDL_mouse.h"
+#include "SDL_joystick.h"
+#include "SDL_events.h"
+
+/// Define the dll export qualifier if compiling for Windows
+
+/*
+#ifdef ICS_PLATFORM_WIN32
+ #ifdef ICS_LIB
+ #define DllExport __declspec (dllexport)
+ #else
+ #define DllExport __declspec (dllimport)
+ #endif
+#else
+ #define DllExport
+#endif
+*/
+#define DllExport
+
+// Define some macros
+#define ICS_DEPRECATED __declspec(deprecated("Deprecated. It will be removed in future versions."))
+
+/// Version defines
+#define ICS_VERSION_MAJOR 0
+#define ICS_VERSION_MINOR 4
+#define ICS_VERSION_PATCH 0
+
+#define ICS_MAX_DEVICE_BUTTONS 30
+
+namespace ICS
+{
+ template <typename T>
+ bool StringIsNumber ( const std::string &Text )
+ {
+ std::stringstream ss(Text);
+ T result;
+ return ss >> result ? true : false;
+ }
+
+ // from http://www.cplusplus.com/forum/articles/9645/
+ template <typename T>
+ std::string ToString ( T value )
+ {
+ std::stringstream ss;
+ ss << value;
+ return ss.str();
+ }
+
+ // from http://www.cplusplus.com/forum/articles/9645/
+ template <typename T>
+ T FromString ( const std::string &Text )//Text not by const reference so that the function can be used with a
+ { //character array as argument
+ std::stringstream ss(Text);
+ T result;
+ return ss >> result ? result : 0;
+ }
+
+ class InputControlSystem;
+ class Channel;
+ class ChannelListener;
+ class Control;
+ class ControlListener;
+ class DetectingBindingListener;
+ class InputControlSystemLog;
+}
+
+#endif
diff --git a/extern/oics/tinystr.cpp b/extern/oics/tinystr.cpp
new file mode 100644
index 0000000000..681250714b
--- /dev/null
+++ b/extern/oics/tinystr.cpp
@@ -0,0 +1,116 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original file by Yves Berquin.
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+/*
+ * THIS FILE WAS ALTERED BY Tyge Løvset, 7. April 2005.
+ */
+
+
+#ifndef TIXML_USE_STL
+
+#include "tinystr.h"
+
+// Error value for find primitive
+const TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1);
+
+
+// Null rep.
+TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\0' } };
+
+
+void TiXmlString::reserve (size_type cap)
+{
+ if (cap > capacity())
+ {
+ TiXmlString tmp;
+ tmp.init(length(), cap);
+ memcpy(tmp.start(), data(), length());
+ swap(tmp);
+ }
+}
+
+
+TiXmlString& TiXmlString::assign(const char* str, size_type len)
+{
+ size_type cap = capacity();
+ if (len > cap || cap > 3*(len + 8))
+ {
+ TiXmlString tmp;
+ tmp.init(len);
+ memcpy(tmp.start(), str, len);
+ swap(tmp);
+ }
+ else
+ {
+ memmove(start(), str, len);
+ set_size(len);
+ }
+ return *this;
+}
+
+
+TiXmlString& TiXmlString::append(const char* str, size_type len)
+{
+ size_type newsize = length() + len;
+ if (newsize > capacity())
+ {
+ reserve (newsize + capacity());
+ }
+ memmove(finish(), str, len);
+ set_size(newsize);
+ return *this;
+}
+
+
+TiXmlString operator + (const TiXmlString & a, const TiXmlString & b)
+{
+ TiXmlString tmp;
+ tmp.reserve(a.length() + b.length());
+ tmp += a;
+ tmp += b;
+ return tmp;
+}
+
+TiXmlString operator + (const TiXmlString & a, const char* b)
+{
+ TiXmlString tmp;
+ TiXmlString::size_type b_len = static_cast<TiXmlString::size_type>( strlen(b) );
+ tmp.reserve(a.length() + b_len);
+ tmp += a;
+ tmp.append(b, b_len);
+ return tmp;
+}
+
+TiXmlString operator + (const char* a, const TiXmlString & b)
+{
+ TiXmlString tmp;
+ TiXmlString::size_type a_len = static_cast<TiXmlString::size_type>( strlen(a) );
+ tmp.reserve(a_len + b.length());
+ tmp.append(a, a_len);
+ tmp += b;
+ return tmp;
+}
+
+
+#endif // TIXML_USE_STL
diff --git a/extern/oics/tinystr.h b/extern/oics/tinystr.h
new file mode 100644
index 0000000000..419e647e16
--- /dev/null
+++ b/extern/oics/tinystr.h
@@ -0,0 +1,319 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original file by Yves Berquin.
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+/*
+ * THIS FILE WAS ALTERED BY Tyge Lovset, 7. April 2005.
+ *
+ * - completely rewritten. compact, clean, and fast implementation.
+ * - sizeof(TiXmlString) = pointer size (4 bytes on 32-bit systems)
+ * - fixed reserve() to work as per specification.
+ * - fixed buggy compares operator==(), operator<(), and operator>()
+ * - fixed operator+=() to take a const ref argument, following spec.
+ * - added "copy" constructor with length, and most compare operators.
+ * - added swap(), clear(), size(), capacity(), operator+().
+ */
+
+#ifndef TIXML_USE_STL
+
+#ifndef TIXML_STRING_INCLUDED
+#define TIXML_STRING_INCLUDED
+
+#include <assert.h>
+#include <string.h>
+
+/* The support for explicit isn't that universal, and it isn't really
+ required - it is used to check that the TiXmlString class isn't incorrectly
+ used. Be nice to old compilers and macro it here:
+*/
+#if defined(_MSC_VER) && (_MSC_VER >= 1200 )
+ // Microsoft visual studio, version 6 and higher.
+ #define TIXML_EXPLICIT explicit
+#elif defined(__GNUC__) && (__GNUC__ >= 3 )
+ // GCC version 3 and higher.s
+ #define TIXML_EXPLICIT explicit
+#else
+ #define TIXML_EXPLICIT
+#endif
+
+
+/*
+ TiXmlString is an emulation of a subset of the std::string template.
+ Its purpose is to allow compiling TinyXML on compilers with no or poor STL support.
+ Only the member functions relevant to the TinyXML project have been implemented.
+ The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase
+ a string and there's no more room, we allocate a buffer twice as big as we need.
+*/
+class TiXmlString
+{
+ public :
+ // The size type used
+ typedef size_t size_type;
+
+ // Error value for find primitive
+ static const size_type npos; // = -1;
+
+
+ // TiXmlString empty constructor
+ TiXmlString () : rep_(&nullrep_)
+ {
+ }
+
+ // TiXmlString copy constructor
+ TiXmlString ( const TiXmlString & copy) : rep_(0)
+ {
+ init(copy.length());
+ memcpy(start(), copy.data(), length());
+ }
+
+ // TiXmlString constructor, based on a string
+ TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0)
+ {
+ init( static_cast<size_type>( strlen(copy) ));
+ memcpy(start(), copy, length());
+ }
+
+ // TiXmlString constructor, based on a string
+ TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0)
+ {
+ init(len);
+ memcpy(start(), str, len);
+ }
+
+ // TiXmlString destructor
+ ~TiXmlString ()
+ {
+ quit();
+ }
+
+ // = operator
+ TiXmlString& operator = (const char * copy)
+ {
+ return assign( copy, (size_type)strlen(copy));
+ }
+
+ // = operator
+ TiXmlString& operator = (const TiXmlString & copy)
+ {
+ return assign(copy.start(), copy.length());
+ }
+
+
+ // += operator. Maps to append
+ TiXmlString& operator += (const char * suffix)
+ {
+ return append(suffix, static_cast<size_type>( strlen(suffix) ));
+ }
+
+ // += operator. Maps to append
+ TiXmlString& operator += (char single)
+ {
+ return append(&single, 1);
+ }
+
+ // += operator. Maps to append
+ TiXmlString& operator += (const TiXmlString & suffix)
+ {
+ return append(suffix.data(), suffix.length());
+ }
+
+
+ // Convert a TiXmlString into a null-terminated char *
+ const char * c_str () const { return rep_->str; }
+
+ // Convert a TiXmlString into a char * (need not be null terminated).
+ const char * data () const { return rep_->str; }
+
+ // Return the length of a TiXmlString
+ size_type length () const { return rep_->size; }
+
+ // Alias for length()
+ size_type size () const { return rep_->size; }
+
+ // Checks if a TiXmlString is empty
+ bool empty () const { return rep_->size == 0; }
+
+ // Return capacity of string
+ size_type capacity () const { return rep_->capacity; }
+
+
+ // single char extraction
+ const char& at (size_type index) const
+ {
+ assert( index < length() );
+ return rep_->str[ index ];
+ }
+
+ // [] operator
+ char& operator [] (size_type index) const
+ {
+ assert( index < length() );
+ return rep_->str[ index ];
+ }
+
+ // find a char in a string. Return TiXmlString::npos if not found
+ size_type find (char lookup) const
+ {
+ return find(lookup, 0);
+ }
+
+ // find a char in a string from an offset. Return TiXmlString::npos if not found
+ size_type find (char tofind, size_type offset) const
+ {
+ if (offset >= length()) return npos;
+
+ for (const char* p = c_str() + offset; *p != '\0'; ++p)
+ {
+ if (*p == tofind) return static_cast< size_type >( p - c_str() );
+ }
+ return npos;
+ }
+
+ void clear ()
+ {
+ //Lee:
+ //The original was just too strange, though correct:
+ // TiXmlString().swap(*this);
+ //Instead use the quit & re-init:
+ quit();
+ init(0,0);
+ }
+
+ /* Function to reserve a big amount of data when we know we'll need it. Be aware that this
+ function DOES NOT clear the content of the TiXmlString if any exists.
+ */
+ void reserve (size_type cap);
+
+ TiXmlString& assign (const char* str, size_type len);
+
+ TiXmlString& append (const char* str, size_type len);
+
+ void swap (TiXmlString& other)
+ {
+ Rep* r = rep_;
+ rep_ = other.rep_;
+ other.rep_ = r;
+ }
+
+ private:
+
+ void init(size_type sz) { init(sz, sz); }
+ void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; }
+ char* start() const { return rep_->str; }
+ char* finish() const { return rep_->str + rep_->size; }
+
+ struct Rep
+ {
+ size_type size, capacity;
+ char str[1];
+ };
+
+ void init(size_type sz, size_type cap)
+ {
+ if (cap)
+ {
+ // Lee: the original form:
+ // rep_ = static_cast<Rep*>(operator new(sizeof(Rep) + cap));
+ // doesn't work in some cases of new being overloaded. Switching
+ // to the normal allocation, although use an 'int' for systems
+ // that are overly picky about structure alignment.
+ const size_type bytesNeeded = sizeof(Rep) + cap;
+ const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int );
+ rep_ = reinterpret_cast<Rep*>( new int[ intsNeeded ] );
+
+ rep_->str[ rep_->size = sz ] = '\0';
+ rep_->capacity = cap;
+ }
+ else
+ {
+ rep_ = &nullrep_;
+ }
+ }
+
+ void quit()
+ {
+ if (rep_ != &nullrep_)
+ {
+ // The rep_ is really an array of ints. (see the allocator, above).
+ // Cast it back before delete, so the compiler won't incorrectly call destructors.
+ delete [] ( reinterpret_cast<int*>( rep_ ) );
+ }
+ }
+
+ Rep * rep_;
+ static Rep nullrep_;
+
+} ;
+
+
+inline bool operator == (const TiXmlString & a, const TiXmlString & b)
+{
+ return ( a.length() == b.length() ) // optimization on some platforms
+ && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare
+}
+inline bool operator < (const TiXmlString & a, const TiXmlString & b)
+{
+ return strcmp(a.c_str(), b.c_str()) < 0;
+}
+
+inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); }
+inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; }
+inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); }
+inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); }
+
+inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; }
+inline bool operator == (const char* a, const TiXmlString & b) { return b == a; }
+inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); }
+inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); }
+
+TiXmlString operator + (const TiXmlString & a, const TiXmlString & b);
+TiXmlString operator + (const TiXmlString & a, const char* b);
+TiXmlString operator + (const char* a, const TiXmlString & b);
+
+
+/*
+ TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString.
+ Only the operators that we need for TinyXML have been developped.
+*/
+class TiXmlOutStream : public TiXmlString
+{
+public :
+
+ // TiXmlOutStream << operator.
+ TiXmlOutStream & operator << (const TiXmlString & in)
+ {
+ *this += in;
+ return *this;
+ }
+
+ // TiXmlOutStream << operator.
+ TiXmlOutStream & operator << (const char * in)
+ {
+ *this += in;
+ return *this;
+ }
+
+} ;
+
+#endif // TIXML_STRING_INCLUDED
+#endif // TIXML_USE_STL
diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp
new file mode 100644
index 0000000000..29a4768aa0
--- /dev/null
+++ b/extern/oics/tinyxml.cpp
@@ -0,0 +1,1888 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include <ctype.h>
+
+#ifdef TIXML_USE_STL
+#include <sstream>
+#include <iostream>
+#endif
+
+#include "tinyxml.h"
+
+
+bool TiXmlBase::condenseWhiteSpace = true;
+
+// Microsoft compiler security
+FILE* TiXmlFOpen( const char* filename, const char* mode )
+{
+ #if defined(_MSC_VER) && (_MSC_VER >= 1400 )
+ FILE* fp = 0;
+ errno_t err = fopen_s( &fp, filename, mode );
+ if ( !err && fp )
+ return fp;
+ return 0;
+ #else
+ return fopen( filename, mode );
+ #endif
+}
+
+void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString )
+{
+ int i=0;
+
+ while( i<(int)str.length() )
+ {
+ unsigned char c = (unsigned char) str[i];
+
+ if ( c == '&'
+ && i < ( (int)str.length() - 2 )
+ && str[i+1] == '#'
+ && str[i+2] == 'x' )
+ {
+ // Hexadecimal character reference.
+ // Pass through unchanged.
+ // &#xA9; -- copyright symbol, for example.
+ //
+ // The -1 is a bug fix from Rob Laveaux. It keeps
+ // an overflow from happening if there is no ';'.
+ // There are actually 2 ways to exit this loop -
+ // while fails (error case) and break (semicolon found).
+ // However, there is no mechanism (currently) for
+ // this function to return an error.
+ while ( i<(int)str.length()-1 )
+ {
+ outString->append( str.c_str() + i, 1 );
+ ++i;
+ if ( str[i] == ';' )
+ break;
+ }
+ }
+ else if ( c == '&' )
+ {
+ outString->append( entity[0].str, entity[0].strLength );
+ ++i;
+ }
+ else if ( c == '<' )
+ {
+ outString->append( entity[1].str, entity[1].strLength );
+ ++i;
+ }
+ else if ( c == '>' )
+ {
+ outString->append( entity[2].str, entity[2].strLength );
+ ++i;
+ }
+ else if ( c == '\"' )
+ {
+ outString->append( entity[3].str, entity[3].strLength );
+ ++i;
+ }
+ else if ( c == '\'' )
+ {
+ outString->append( entity[4].str, entity[4].strLength );
+ ++i;
+ }
+ else if ( c < 32 )
+ {
+ // Easy pass at non-alpha/numeric/symbol
+ // Below 32 is symbolic.
+ char buf[ 32 ];
+
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) );
+ #else
+ sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) );
+ #endif
+
+ //*ME: warning C4267: convert 'size_t' to 'int'
+ //*ME: Int-Cast to make compiler happy ...
+ outString->append( buf, (int)strlen( buf ) );
+ ++i;
+ }
+ else
+ {
+ //char realc = (char) c;
+ //outString->append( &realc, 1 );
+ *outString += (char) c; // somewhat more efficient function call.
+ ++i;
+ }
+ }
+}
+
+
+TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase()
+{
+ parent = 0;
+ type = _type;
+ firstChild = 0;
+ lastChild = 0;
+ prev = 0;
+ next = 0;
+}
+
+
+TiXmlNode::~TiXmlNode()
+{
+ TiXmlNode* node = firstChild;
+ TiXmlNode* temp = 0;
+
+ while ( node )
+ {
+ temp = node;
+ node = node->next;
+ delete temp;
+ }
+}
+
+
+void TiXmlNode::CopyTo( TiXmlNode* target ) const
+{
+ target->SetValue (value.c_str() );
+ target->userData = userData;
+}
+
+
+void TiXmlNode::Clear()
+{
+ TiXmlNode* node = firstChild;
+ TiXmlNode* temp = 0;
+
+ while ( node )
+ {
+ temp = node;
+ node = node->next;
+ delete temp;
+ }
+
+ firstChild = 0;
+ lastChild = 0;
+}
+
+
+TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node )
+{
+ assert( node->parent == 0 || node->parent == this );
+ assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() );
+
+ if ( node->Type() == TiXmlNode::DOCUMENT )
+ {
+ delete node;
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ node->parent = this;
+
+ node->prev = lastChild;
+ node->next = 0;
+
+ if ( lastChild )
+ lastChild->next = node;
+ else
+ firstChild = node; // it was an empty list.
+
+ lastChild = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis )
+{
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+
+ return LinkEndChild( node );
+}
+
+
+TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis )
+{
+ if ( !beforeThis || beforeThis->parent != this ) {
+ return 0;
+ }
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+ node->parent = this;
+
+ node->next = beforeThis;
+ node->prev = beforeThis->prev;
+ if ( beforeThis->prev )
+ {
+ beforeThis->prev->next = node;
+ }
+ else
+ {
+ assert( firstChild == beforeThis );
+ firstChild = node;
+ }
+ beforeThis->prev = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis )
+{
+ if ( !afterThis || afterThis->parent != this ) {
+ return 0;
+ }
+ if ( addThis.Type() == TiXmlNode::DOCUMENT )
+ {
+ if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ TiXmlNode* node = addThis.Clone();
+ if ( !node )
+ return 0;
+ node->parent = this;
+
+ node->prev = afterThis;
+ node->next = afterThis->next;
+ if ( afterThis->next )
+ {
+ afterThis->next->prev = node;
+ }
+ else
+ {
+ assert( lastChild == afterThis );
+ lastChild = node;
+ }
+ afterThis->next = node;
+ return node;
+}
+
+
+TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis )
+{
+ if ( replaceThis->parent != this )
+ return 0;
+
+ TiXmlNode* node = withThis.Clone();
+ if ( !node )
+ return 0;
+
+ node->next = replaceThis->next;
+ node->prev = replaceThis->prev;
+
+ if ( replaceThis->next )
+ replaceThis->next->prev = node;
+ else
+ lastChild = node;
+
+ if ( replaceThis->prev )
+ replaceThis->prev->next = node;
+ else
+ firstChild = node;
+
+ delete replaceThis;
+ node->parent = this;
+ return node;
+}
+
+
+bool TiXmlNode::RemoveChild( TiXmlNode* removeThis )
+{
+ if ( removeThis->parent != this )
+ {
+ assert( 0 );
+ return false;
+ }
+
+ if ( removeThis->next )
+ removeThis->next->prev = removeThis->prev;
+ else
+ lastChild = removeThis->prev;
+
+ if ( removeThis->prev )
+ removeThis->prev->next = removeThis->next;
+ else
+ firstChild = removeThis->next;
+
+ delete removeThis;
+ return true;
+}
+
+const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = firstChild; node; node = node->next )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = lastChild; node; node = node->prev )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const
+{
+ if ( !previous )
+ {
+ return FirstChild();
+ }
+ else
+ {
+ assert( previous->parent == this );
+ return previous->NextSibling();
+ }
+}
+
+
+const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const
+{
+ if ( !previous )
+ {
+ return FirstChild( val );
+ }
+ else
+ {
+ assert( previous->parent == this );
+ return previous->NextSibling( val );
+ }
+}
+
+
+const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = next; node; node = node->next )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const
+{
+ const TiXmlNode* node;
+ for ( node = prev; node; node = node->prev )
+ {
+ if ( strcmp( node->Value(), _value ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+
+void TiXmlElement::RemoveAttribute( const char * name )
+{
+ #ifdef TIXML_USE_STL
+ TIXML_STRING str( name );
+ TiXmlAttribute* node = attributeSet.Find( str );
+ #else
+ TiXmlAttribute* node = attributeSet.Find( name );
+ #endif
+ if ( node )
+ {
+ attributeSet.Remove( node );
+ delete node;
+ }
+}
+
+const TiXmlElement* TiXmlNode::FirstChildElement() const
+{
+ const TiXmlNode* node;
+
+ for ( node = FirstChild();
+ node;
+ node = node->NextSibling() )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const
+{
+ const TiXmlNode* node;
+
+ for ( node = FirstChild( _value );
+ node;
+ node = node->NextSibling( _value ) )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::NextSiblingElement() const
+{
+ const TiXmlNode* node;
+
+ for ( node = NextSibling();
+ node;
+ node = node->NextSibling() )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const
+{
+ const TiXmlNode* node;
+
+ for ( node = NextSibling( _value );
+ node;
+ node = node->NextSibling( _value ) )
+ {
+ if ( node->ToElement() )
+ return node->ToElement();
+ }
+ return 0;
+}
+
+
+const TiXmlDocument* TiXmlNode::GetDocument() const
+{
+ const TiXmlNode* node;
+
+ for( node = this; node; node = node->parent )
+ {
+ if ( node->ToDocument() )
+ return node->ToDocument();
+ }
+ return 0;
+}
+
+
+TiXmlElement::TiXmlElement (const char * _value)
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ value = _value;
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlElement::TiXmlElement( const std::string& _value )
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ value = _value;
+}
+#endif
+
+
+TiXmlElement::TiXmlElement( const TiXmlElement& copy)
+ : TiXmlNode( TiXmlNode::ELEMENT )
+{
+ firstChild = lastChild = 0;
+ copy.CopyTo( this );
+}
+
+
+void TiXmlElement::operator=( const TiXmlElement& base )
+{
+ ClearThis();
+ base.CopyTo( this );
+}
+
+
+TiXmlElement::~TiXmlElement()
+{
+ ClearThis();
+}
+
+
+void TiXmlElement::ClearThis()
+{
+ Clear();
+ while( attributeSet.First() )
+ {
+ TiXmlAttribute* node = attributeSet.First();
+ attributeSet.Remove( node );
+ delete node;
+ }
+}
+
+
+const char* TiXmlElement::Attribute( const char* name ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ return node->Value();
+ return 0;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ return &node->ValueStr();
+ return 0;
+}
+#endif
+
+
+const char* TiXmlElement::Attribute( const char* name, int* i ) const
+{
+ const char* s = Attribute( name );
+ if ( i )
+ {
+ if ( s ) {
+ *i = atoi( s );
+ }
+ else {
+ *i = 0;
+ }
+ }
+ return s;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const
+{
+ const std::string* s = Attribute( name );
+ if ( i )
+ {
+ if ( s ) {
+ *i = atoi( s->c_str() );
+ }
+ else {
+ *i = 0;
+ }
+ }
+ return s;
+}
+#endif
+
+
+const char* TiXmlElement::Attribute( const char* name, double* d ) const
+{
+ const char* s = Attribute( name );
+ if ( d )
+ {
+ if ( s ) {
+ *d = atof( s );
+ }
+ else {
+ *d = 0;
+ }
+ }
+ return s;
+}
+
+
+#ifdef TIXML_USE_STL
+const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const
+{
+ const std::string* s = Attribute( name );
+ if ( d )
+ {
+ if ( s ) {
+ *d = atof( s->c_str() );
+ }
+ else {
+ *d = 0;
+ }
+ }
+ return s;
+}
+#endif
+
+
+int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryIntValue( ival );
+}
+
+
+#ifdef TIXML_USE_STL
+int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryIntValue( ival );
+}
+#endif
+
+
+int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryDoubleValue( dval );
+}
+
+
+#ifdef TIXML_USE_STL
+int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const
+{
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ return node->QueryDoubleValue( dval );
+}
+#endif
+
+
+void TiXmlElement::SetAttribute( const char * name, int val )
+{
+ char buf[64];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%d", val );
+ #else
+ sprintf( buf, "%d", val );
+ #endif
+ SetAttribute( name, buf );
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlElement::SetAttribute( const std::string& name, int val )
+{
+ std::ostringstream oss;
+ oss << val;
+ SetAttribute( name, oss.str() );
+}
+#endif
+
+
+void TiXmlElement::SetDoubleAttribute( const char * name, double val )
+{
+ char buf[256];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%f", val );
+ #else
+ sprintf( buf, "%f", val );
+ #endif
+ SetAttribute( name, buf );
+}
+
+
+void TiXmlElement::SetAttribute( const char * cname, const char * cvalue )
+{
+ #ifdef TIXML_USE_STL
+ TIXML_STRING _name( cname );
+ TIXML_STRING _value( cvalue );
+ #else
+ const char* _name = cname;
+ const char* _value = cvalue;
+ #endif
+
+ TiXmlAttribute* node = attributeSet.Find( _name );
+ if ( node )
+ {
+ node->SetValue( _value );
+ return;
+ }
+
+ TiXmlAttribute* attrib = new TiXmlAttribute( cname, cvalue );
+ if ( attrib )
+ {
+ attributeSet.Add( attrib );
+ }
+ else
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlElement::SetAttribute( const std::string& name, const std::string& _value )
+{
+ TiXmlAttribute* node = attributeSet.Find( name );
+ if ( node )
+ {
+ node->SetValue( _value );
+ return;
+ }
+
+ TiXmlAttribute* attrib = new TiXmlAttribute( name, _value );
+ if ( attrib )
+ {
+ attributeSet.Add( attrib );
+ }
+ else
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+}
+#endif
+
+
+void TiXmlElement::Print( FILE* cfile, int depth ) const
+{
+ int i;
+ assert( cfile );
+ for ( i=0; i<depth; i++ ) {
+ fprintf( cfile, " " );
+ }
+
+ fprintf( cfile, "<%s", value.c_str() );
+
+ const TiXmlAttribute* attrib;
+ for ( attrib = attributeSet.First(); attrib; attrib = attrib->Next() )
+ {
+ fprintf( cfile, " " );
+ attrib->Print( cfile, depth );
+ }
+
+ // There are 3 different formatting approaches:
+ // 1) An element without children is printed as a <foo /> node
+ // 2) An element with only a text child is printed as <foo> text </foo>
+ // 3) An element with children is printed on multiple lines.
+ TiXmlNode* node;
+ if ( !firstChild )
+ {
+ fprintf( cfile, " />" );
+ }
+ else if ( firstChild == lastChild && firstChild->ToText() )
+ {
+ fprintf( cfile, ">" );
+ firstChild->Print( cfile, depth + 1 );
+ fprintf( cfile, "</%s>", value.c_str() );
+ }
+ else
+ {
+ fprintf( cfile, ">" );
+
+ for ( node = firstChild; node; node=node->NextSibling() )
+ {
+ if ( !node->ToText() )
+ {
+ fprintf( cfile, "\n" );
+ }
+ node->Print( cfile, depth+1 );
+ }
+ fprintf( cfile, "\n" );
+ for( i=0; i<depth; ++i ) {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "</%s>", value.c_str() );
+ }
+}
+
+
+void TiXmlElement::CopyTo( TiXmlElement* target ) const
+{
+ // superclass:
+ TiXmlNode::CopyTo( target );
+
+ // Element class:
+ // Clone the attributes, then clone the children.
+ const TiXmlAttribute* attribute = 0;
+ for( attribute = attributeSet.First();
+ attribute;
+ attribute = attribute->Next() )
+ {
+ target->SetAttribute( attribute->Name(), attribute->Value() );
+ }
+
+ TiXmlNode* node = 0;
+ for ( node = firstChild; node; node = node->NextSibling() )
+ {
+ target->LinkEndChild( node->Clone() );
+ }
+}
+
+bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const
+{
+ if ( visitor->VisitEnter( *this, attributeSet.First() ) )
+ {
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ if ( !node->Accept( visitor ) )
+ break;
+ }
+ }
+ return visitor->VisitExit( *this );
+}
+
+
+TiXmlNode* TiXmlElement::Clone() const
+{
+ TiXmlElement* clone = new TiXmlElement( Value() );
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+const char* TiXmlElement::GetText() const
+{
+ const TiXmlNode* child = this->FirstChild();
+ if ( child ) {
+ const TiXmlText* childText = child->ToText();
+ if ( childText ) {
+ return childText->Value();
+ }
+ }
+ return 0;
+}
+
+
+TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ ClearError();
+}
+
+TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ value = documentName;
+ ClearError();
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ tabsize = 4;
+ useMicrosoftBOM = false;
+ value = documentName;
+ ClearError();
+}
+#endif
+
+
+TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDocument::operator=( const TiXmlDocument& copy )
+{
+ Clear();
+ copy.CopyTo( this );
+}
+
+
+bool TiXmlDocument::LoadFile( TiXmlEncoding encoding )
+{
+ // See STL_STRING_BUG below.
+ //StringToBuffer buf( value );
+
+ return LoadFile( Value(), encoding );
+}
+
+
+bool TiXmlDocument::SaveFile() const
+{
+ // See STL_STRING_BUG below.
+// StringToBuffer buf( value );
+//
+// if ( buf.buffer && SaveFile( buf.buffer ) )
+// return true;
+//
+// return false;
+ return SaveFile( Value() );
+}
+
+bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
+{
+ // There was a really terrifying little bug here. The code:
+ // value = filename
+ // in the STL case, cause the assignment method of the std::string to
+ // be called. What is strange, is that the std::string had the same
+ // address as it's c_str() method, and so bad things happen. Looks
+ // like a bug in the Microsoft STL implementation.
+ // Add an extra string to avoid the crash.
+ TIXML_STRING filename( _filename );
+ value = filename;
+
+ // reading in binary mode so that tinyxml can normalize the EOL
+ FILE* file = TiXmlFOpen( value.c_str (), "rb" );
+
+ if ( file )
+ {
+ bool result = LoadFile( file, encoding );
+ fclose( file );
+ return result;
+ }
+ else
+ {
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+}
+
+bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
+{
+ if ( !file )
+ {
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ // Delete the existing data:
+ Clear();
+ location.Clear();
+
+ // Get the file size, so we can pre-allocate the string. HUGE speed impact.
+ long length = 0;
+ fseek( file, 0, SEEK_END );
+ length = ftell( file );
+ fseek( file, 0, SEEK_SET );
+
+ // Strange case, but good to handle up front.
+ if ( length <= 0 )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ // If we have a file, assume it is all one big XML file, and read it in.
+ // The document parser may decide the document ends sooner than the entire file, however.
+ TIXML_STRING data;
+ data.reserve( length );
+
+ // Subtle bug here. TinyXml did use fgets. But from the XML spec:
+ // 2.11 End-of-Line Handling
+ // <snip>
+ // <quote>
+ // ...the XML processor MUST behave as if it normalized all line breaks in external
+ // parsed entities (including the document entity) on input, before parsing, by translating
+ // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to
+ // a single #xA character.
+ // </quote>
+ //
+ // It is not clear fgets does that, and certainly isn't clear it works cross platform.
+ // Generally, you expect fgets to translate from the convention of the OS to the c/unix
+ // convention, and not work generally.
+
+ /*
+ while( fgets( buf, sizeof(buf), file ) )
+ {
+ data += buf;
+ }
+ */
+
+ char* buf = new char[ length+1 ];
+ buf[0] = 0;
+
+ if ( fread( buf, length, 1, file ) != 1 ) {
+ delete [] buf;
+ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return false;
+ }
+
+ const char* lastPos = buf;
+ const char* p = buf;
+
+ buf[length] = 0;
+ while( *p ) {
+ assert( p < (buf+length) );
+ if ( *p == 0xa ) {
+ // Newline character. No special rules for this. Append all the characters
+ // since the last string, and include the newline.
+ data.append( lastPos, (p-lastPos+1) ); // append, include the newline
+ ++p; // move past the newline
+ lastPos = p; // and point to the new buffer (may be 0)
+ assert( p <= (buf+length) );
+ }
+ else if ( *p == 0xd ) {
+ // Carriage return. Append what we have so far, then
+ // handle moving forward in the buffer.
+ if ( (p-lastPos) > 0 ) {
+ data.append( lastPos, p-lastPos ); // do not add the CR
+ }
+ data += (char)0xa; // a proper newline
+
+ if ( *(p+1) == 0xa ) {
+ // Carriage return - new line sequence
+ p += 2;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ else {
+ // it was followed by something else...that is presumably characters again.
+ ++p;
+ lastPos = p;
+ assert( p <= (buf+length) );
+ }
+ }
+ else {
+ ++p;
+ }
+ }
+ // Handle any left over characters.
+ if ( p-lastPos ) {
+ data.append( lastPos, p-lastPos );
+ }
+ delete [] buf;
+ buf = 0;
+
+ Parse( data.c_str(), 0, encoding );
+
+ if ( Error() )
+ return false;
+ else
+ return true;
+}
+
+
+bool TiXmlDocument::SaveFile( const char * filename ) const
+{
+ // The old c stuff lives on...
+ FILE* fp = TiXmlFOpen( filename, "w" );
+ if ( fp )
+ {
+ bool result = SaveFile( fp );
+ fclose( fp );
+ return result;
+ }
+ return false;
+}
+
+
+bool TiXmlDocument::SaveFile( FILE* fp ) const
+{
+ if ( useMicrosoftBOM )
+ {
+ const unsigned char TIXML_UTF_LEAD_0 = 0xefU;
+ const unsigned char TIXML_UTF_LEAD_1 = 0xbbU;
+ const unsigned char TIXML_UTF_LEAD_2 = 0xbfU;
+
+ fputc( TIXML_UTF_LEAD_0, fp );
+ fputc( TIXML_UTF_LEAD_1, fp );
+ fputc( TIXML_UTF_LEAD_2, fp );
+ }
+ Print( fp, 0 );
+ return (ferror(fp) == 0);
+}
+
+
+void TiXmlDocument::CopyTo( TiXmlDocument* target ) const
+{
+ TiXmlNode::CopyTo( target );
+
+ target->error = error;
+ target->errorId = errorId;
+ target->errorDesc = errorDesc;
+ target->tabsize = tabsize;
+ target->errorLocation = errorLocation;
+ target->useMicrosoftBOM = useMicrosoftBOM;
+
+ TiXmlNode* node = 0;
+ for ( node = firstChild; node; node = node->NextSibling() )
+ {
+ target->LinkEndChild( node->Clone() );
+ }
+}
+
+
+TiXmlNode* TiXmlDocument::Clone() const
+{
+ TiXmlDocument* clone = new TiXmlDocument();
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlDocument::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ node->Print( cfile, depth );
+ fprintf( cfile, "\n" );
+ }
+}
+
+
+bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const
+{
+ if ( visitor->VisitEnter( *this ) )
+ {
+ for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() )
+ {
+ if ( !node->Accept( visitor ) )
+ break;
+ }
+ }
+ return visitor->VisitExit( *this );
+}
+
+
+const TiXmlAttribute* TiXmlAttribute::Next() const
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( next->value.empty() && next->name.empty() )
+ return 0;
+ return next;
+}
+
+/*
+TiXmlAttribute* TiXmlAttribute::Next()
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( next->value.empty() && next->name.empty() )
+ return 0;
+ return next;
+}
+*/
+
+const TiXmlAttribute* TiXmlAttribute::Previous() const
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( prev->value.empty() && prev->name.empty() )
+ return 0;
+ return prev;
+}
+
+/*
+TiXmlAttribute* TiXmlAttribute::Previous()
+{
+ // We are using knowledge of the sentinel. The sentinel
+ // have a value or name.
+ if ( prev->value.empty() && prev->name.empty() )
+ return 0;
+ return prev;
+}
+*/
+
+void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const
+{
+ TIXML_STRING n, v;
+
+ EncodeString( name, &n );
+ EncodeString( value, &v );
+
+ if (value.find ('\"') == TIXML_STRING::npos) {
+ if ( cfile ) {
+ fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() );
+ }
+ if ( str ) {
+ (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\"";
+ }
+ }
+ else {
+ if ( cfile ) {
+ fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() );
+ }
+ if ( str ) {
+ (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'";
+ }
+ }
+}
+
+
+int TiXmlAttribute::QueryIntValue( int* ival ) const
+{
+ if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+}
+
+int TiXmlAttribute::QueryDoubleValue( double* dval ) const
+{
+ if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+}
+
+void TiXmlAttribute::SetIntValue( int _value )
+{
+ char buf [64];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value);
+ #else
+ sprintf (buf, "%d", _value);
+ #endif
+ SetValue (buf);
+}
+
+void TiXmlAttribute::SetDoubleValue( double _value )
+{
+ char buf [256];
+ #if defined(TIXML_SNPRINTF)
+ TIXML_SNPRINTF( buf, sizeof(buf), "%f", _value);
+ #else
+ sprintf (buf, "%f", _value);
+ #endif
+ SetValue (buf);
+}
+
+int TiXmlAttribute::IntValue() const
+{
+ return atoi (value.c_str ());
+}
+
+double TiXmlAttribute::DoubleValue() const
+{
+ return atof (value.c_str ());
+}
+
+
+TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlComment::operator=( const TiXmlComment& base )
+{
+ Clear();
+ base.CopyTo( this );
+}
+
+
+void TiXmlComment::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ for ( int i=0; i<depth; i++ )
+ {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "<!--%s-->", value.c_str() );
+}
+
+
+void TiXmlComment::CopyTo( TiXmlComment* target ) const
+{
+ TiXmlNode::CopyTo( target );
+}
+
+
+bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlComment::Clone() const
+{
+ TiXmlComment* clone = new TiXmlComment();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlText::Print( FILE* cfile, int depth ) const
+{
+ assert( cfile );
+ if ( cdata )
+ {
+ int i;
+ fprintf( cfile, "\n" );
+ for ( i=0; i<depth; i++ ) {
+ fprintf( cfile, " " );
+ }
+ fprintf( cfile, "<![CDATA[%s]]>\n", value.c_str() ); // unformatted output
+ }
+ else
+ {
+ TIXML_STRING buffer;
+ EncodeString( value, &buffer );
+ fprintf( cfile, "%s", buffer.c_str() );
+ }
+}
+
+
+void TiXmlText::CopyTo( TiXmlText* target ) const
+{
+ TiXmlNode::CopyTo( target );
+ target->cdata = cdata;
+}
+
+
+bool TiXmlText::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlText::Clone() const
+{
+ TiXmlText* clone = 0;
+ clone = new TiXmlText( "" );
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+TiXmlDeclaration::TiXmlDeclaration( const char * _version,
+ const char * _encoding,
+ const char * _standalone )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ version = _version;
+ encoding = _encoding;
+ standalone = _standalone;
+}
+
+
+#ifdef TIXML_USE_STL
+TiXmlDeclaration::TiXmlDeclaration( const std::string& _version,
+ const std::string& _encoding,
+ const std::string& _standalone )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ version = _version;
+ encoding = _encoding;
+ standalone = _standalone;
+}
+#endif
+
+
+TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy )
+ : TiXmlNode( TiXmlNode::DECLARATION )
+{
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy )
+{
+ Clear();
+ copy.CopyTo( this );
+}
+
+
+void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const
+{
+ if ( cfile ) fprintf( cfile, "<?xml " );
+ if ( str ) (*str) += "<?xml ";
+
+ if ( !version.empty() ) {
+ if ( cfile ) fprintf (cfile, "version=\"%s\" ", version.c_str ());
+ if ( str ) { (*str) += "version=\""; (*str) += version; (*str) += "\" "; }
+ }
+ if ( !encoding.empty() ) {
+ if ( cfile ) fprintf (cfile, "encoding=\"%s\" ", encoding.c_str ());
+ if ( str ) { (*str) += "encoding=\""; (*str) += encoding; (*str) += "\" "; }
+ }
+ if ( !standalone.empty() ) {
+ if ( cfile ) fprintf (cfile, "standalone=\"%s\" ", standalone.c_str ());
+ if ( str ) { (*str) += "standalone=\""; (*str) += standalone; (*str) += "\" "; }
+ }
+ if ( cfile ) fprintf( cfile, "?>" );
+ if ( str ) (*str) += "?>";
+}
+
+
+void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const
+{
+ TiXmlNode::CopyTo( target );
+
+ target->version = version;
+ target->encoding = encoding;
+ target->standalone = standalone;
+}
+
+
+bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlDeclaration::Clone() const
+{
+ TiXmlDeclaration* clone = new TiXmlDeclaration();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+void TiXmlUnknown::Print( FILE* cfile, int depth ) const
+{
+ for ( int i=0; i<depth; i++ )
+ fprintf( cfile, " " );
+ fprintf( cfile, "<%s>", value.c_str() );
+}
+
+
+void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const
+{
+ TiXmlNode::CopyTo( target );
+}
+
+
+bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const
+{
+ return visitor->Visit( *this );
+}
+
+
+TiXmlNode* TiXmlUnknown::Clone() const
+{
+ TiXmlUnknown* clone = new TiXmlUnknown();
+
+ if ( !clone )
+ return 0;
+
+ CopyTo( clone );
+ return clone;
+}
+
+
+TiXmlAttributeSet::TiXmlAttributeSet()
+{
+ sentinel.next = &sentinel;
+ sentinel.prev = &sentinel;
+}
+
+
+TiXmlAttributeSet::~TiXmlAttributeSet()
+{
+ assert( sentinel.next == &sentinel );
+ assert( sentinel.prev == &sentinel );
+}
+
+
+void TiXmlAttributeSet::Add( TiXmlAttribute* addMe )
+{
+ #ifdef TIXML_USE_STL
+ assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set.
+ #else
+ assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set.
+ #endif
+
+ addMe->next = &sentinel;
+ addMe->prev = sentinel.prev;
+
+ sentinel.prev->next = addMe;
+ sentinel.prev = addMe;
+}
+
+void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe )
+{
+ TiXmlAttribute* node;
+
+ for( node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node == removeMe )
+ {
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->next = 0;
+ node->prev = 0;
+ return;
+ }
+ }
+ assert( 0 ); // we tried to remove a non-linked attribute.
+}
+
+
+#ifdef TIXML_USE_STL
+const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const
+{
+ for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node->name == name )
+ return node;
+ }
+ return 0;
+}
+
+/*
+TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name )
+{
+ for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( node->name == name )
+ return node;
+ }
+ return 0;
+}
+*/
+#endif
+
+
+const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const
+{
+ for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( strcmp( node->name.c_str(), name ) == 0 )
+ return node;
+ }
+ return 0;
+}
+
+/*
+TiXmlAttribute* TiXmlAttributeSet::Find( const char* name )
+{
+ for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next )
+ {
+ if ( strcmp( node->name.c_str(), name ) == 0 )
+ return node;
+ }
+ return 0;
+}
+*/
+
+#ifdef TIXML_USE_STL
+std::istream& operator>> (std::istream & in, TiXmlNode & base)
+{
+ TIXML_STRING tag;
+ tag.reserve( 8 * 1000 );
+ base.StreamIn( &in, &tag );
+
+ base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING );
+ return in;
+}
+#endif
+
+
+#ifdef TIXML_USE_STL
+std::ostream& operator<< (std::ostream & out, const TiXmlNode & base)
+{
+ TiXmlPrinter printer;
+ printer.SetStreamPrinting();
+ base.Accept( &printer );
+ out << printer.Str();
+
+ return out;
+}
+
+
+std::string& operator<< (std::string& out, const TiXmlNode& base )
+{
+ TiXmlPrinter printer;
+ printer.SetStreamPrinting();
+ base.Accept( &printer );
+ out.append( printer.Str() );
+
+ return out;
+}
+#endif
+
+
+TiXmlHandle TiXmlHandle::FirstChild() const
+{
+ if ( node )
+ {
+ TiXmlNode* child = node->FirstChild();
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const
+{
+ if ( node )
+ {
+ TiXmlNode* child = node->FirstChild( value );
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChildElement() const
+{
+ if ( node )
+ {
+ TiXmlElement* child = node->FirstChildElement();
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const
+{
+ if ( node )
+ {
+ TiXmlElement* child = node->FirstChildElement( value );
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::Child( int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlNode* child = node->FirstChild();
+ for ( i=0;
+ child && i<count;
+ child = child->NextSibling(), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlNode* child = node->FirstChild( value );
+ for ( i=0;
+ child && i<count;
+ child = child->NextSibling( value ), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::ChildElement( int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlElement* child = node->FirstChildElement();
+ for ( i=0;
+ child && i<count;
+ child = child->NextSiblingElement(), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const
+{
+ if ( node )
+ {
+ int i;
+ TiXmlElement* child = node->FirstChildElement( value );
+ for ( i=0;
+ child && i<count;
+ child = child->NextSiblingElement( value ), ++i )
+ {
+ // nothing
+ }
+ if ( child )
+ return TiXmlHandle( child );
+ }
+ return TiXmlHandle( 0 );
+}
+
+
+bool TiXmlPrinter::VisitEnter( const TiXmlDocument& )
+{
+ return true;
+}
+
+bool TiXmlPrinter::VisitExit( const TiXmlDocument& )
+{
+ return true;
+}
+
+bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
+{
+ DoIndent();
+ buffer += "<";
+ buffer += element.Value();
+
+ for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() )
+ {
+ buffer += " ";
+ attrib->Print( 0, 0, &buffer );
+ }
+
+ if ( !element.FirstChild() )
+ {
+ buffer += " />";
+ DoLineBreak();
+ }
+ else
+ {
+ buffer += ">";
+ if ( element.FirstChild()->ToText()
+ && element.LastChild() == element.FirstChild()
+ && element.FirstChild()->ToText()->CDATA() == false )
+ {
+ simpleTextPrint = true;
+ // no DoLineBreak()!
+ }
+ else
+ {
+ DoLineBreak();
+ }
+ }
+ ++depth;
+ return true;
+}
+
+
+bool TiXmlPrinter::VisitExit( const TiXmlElement& element )
+{
+ --depth;
+ if ( !element.FirstChild() )
+ {
+ // nothing.
+ }
+ else
+ {
+ if ( simpleTextPrint )
+ {
+ simpleTextPrint = false;
+ }
+ else
+ {
+ DoIndent();
+ }
+ buffer += "</";
+ buffer += element.Value();
+ buffer += ">";
+ DoLineBreak();
+ }
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlText& text )
+{
+ if ( text.CDATA() )
+ {
+ DoIndent();
+ buffer += "<![CDATA[";
+ buffer += text.Value();
+ buffer += "]]>";
+ DoLineBreak();
+ }
+ else if ( simpleTextPrint )
+ {
+ TIXML_STRING str;
+ TiXmlBase::EncodeString( text.ValueTStr(), &str );
+ buffer += str;
+ }
+ else
+ {
+ DoIndent();
+ TIXML_STRING str;
+ TiXmlBase::EncodeString( text.ValueTStr(), &str );
+ buffer += str;
+ DoLineBreak();
+ }
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration )
+{
+ DoIndent();
+ declaration.Print( 0, 0, &buffer );
+ DoLineBreak();
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlComment& comment )
+{
+ DoIndent();
+ buffer += "<!--";
+ buffer += comment.Value();
+ buffer += "-->";
+ DoLineBreak();
+ return true;
+}
+
+
+bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown )
+{
+ DoIndent();
+ buffer += "<";
+ buffer += unknown.Value();
+ buffer += ">";
+ DoLineBreak();
+ return true;
+}
+
diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h
new file mode 100644
index 0000000000..e69913b59c
--- /dev/null
+++ b/extern/oics/tinyxml.h
@@ -0,0 +1,1802 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+//#define TIXML_USE_STL
+
+#ifndef TINYXML_INCLUDED
+#define TINYXML_INCLUDED
+
+#ifdef _MSC_VER
+#pragma warning( push )
+#pragma warning( disable : 4530 )
+#pragma warning( disable : 4786 )
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+// Help out windows:
+#if defined( _DEBUG ) && !defined( DEBUG )
+#define DEBUG
+#endif
+
+#ifdef TIXML_USE_STL
+ #include <string>
+ #include <iostream>
+ #include <sstream>
+ #define TIXML_STRING std::string
+#else
+ #include "tinystr.h"
+ #define TIXML_STRING TiXmlString
+#endif
+
+// Deprecated library function hell. Compilers want to use the
+// new safe versions. This probably doesn't fully address the problem,
+// but it gets closer. There are too many compilers for me to fully
+// test. If you get compilation troubles, undefine TIXML_SAFE
+#define TIXML_SAFE
+
+#ifdef TIXML_SAFE
+ #if defined(_MSC_VER) && (_MSC_VER >= 1400 )
+ // Microsoft visual studio, version 2005 and higher.
+ #define TIXML_SNPRINTF _snprintf_s
+ #define TIXML_SNSCANF _snscanf_s
+ #define TIXML_SSCANF sscanf_s
+ #elif defined(_MSC_VER) && (_MSC_VER >= 1200 )
+ // Microsoft visual studio, version 6 and higher.
+ //#pragma message( "Using _sn* functions." )
+ #define TIXML_SNPRINTF _snprintf
+ #define TIXML_SNSCANF _snscanf
+ #define TIXML_SSCANF sscanf
+ #elif defined(__GNUC__) && (__GNUC__ >= 3 )
+ // GCC version 3 and higher.s
+ //#warning( "Using sn* functions." )
+ #define TIXML_SNPRINTF snprintf
+ #define TIXML_SNSCANF snscanf
+ #define TIXML_SSCANF sscanf
+ #else
+ #define TIXML_SSCANF sscanf
+ #endif
+#endif
+
+class TiXmlDocument;
+class TiXmlElement;
+class TiXmlComment;
+class TiXmlUnknown;
+class TiXmlAttribute;
+class TiXmlText;
+class TiXmlDeclaration;
+class TiXmlParsingData;
+
+const int TIXML_MAJOR_VERSION = 2;
+const int TIXML_MINOR_VERSION = 5;
+const int TIXML_PATCH_VERSION = 3;
+
+/* Internal structure for tracking location of items
+ in the XML file.
+*/
+struct TiXmlCursor
+{
+ TiXmlCursor() { Clear(); }
+ void Clear() { row = col = -1; }
+
+ int row; // 0 based.
+ int col; // 0 based.
+};
+
+
+/**
+ If you call the Accept() method, it requires being passed a TiXmlVisitor
+ class to handle callbacks. For nodes that contain other nodes (Document, Element)
+ you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves
+ are simple called with Visit().
+
+ If you return 'true' from a Visit method, recursive parsing will continue. If you return
+ false, <b>no children of this node or its sibilings</b> will be Visited.
+
+ All flavors of Visit methods have a default implementation that returns 'true' (continue
+ visiting). You need to only override methods that are interesting to you.
+
+ Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting.
+
+ You should never change the document from a callback.
+
+ @sa TiXmlNode::Accept()
+*/
+class TiXmlVisitor
+{
+public:
+ virtual ~TiXmlVisitor() {}
+
+ /// Visit a document.
+ virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; }
+ /// Visit a document.
+ virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; }
+
+ /// Visit an element.
+ virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; }
+ /// Visit an element.
+ virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; }
+
+ /// Visit a declaration
+ virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; }
+ /// Visit a text node
+ virtual bool Visit( const TiXmlText& /*text*/ ) { return true; }
+ /// Visit a comment node
+ virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; }
+ /// Visit an unknow node
+ virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; }
+};
+
+// Only used by Attribute::Query functions
+enum
+{
+ TIXML_SUCCESS,
+ TIXML_NO_ATTRIBUTE,
+ TIXML_WRONG_TYPE
+};
+
+
+// Used by the parsing routines.
+enum TiXmlEncoding
+{
+ TIXML_ENCODING_UNKNOWN,
+ TIXML_ENCODING_UTF8,
+ TIXML_ENCODING_LEGACY
+};
+
+const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN;
+
+/** TiXmlBase is a base class for every class in TinyXml.
+ It does little except to establish that TinyXml classes
+ can be printed and provide some utility functions.
+
+ In XML, the document and elements can contain
+ other elements and other types of nodes.
+
+ @verbatim
+ A Document can contain: Element (container or leaf)
+ Comment (leaf)
+ Unknown (leaf)
+ Declaration( leaf )
+
+ An Element can contain: Element (container or leaf)
+ Text (leaf)
+ Attributes (not on tree)
+ Comment (leaf)
+ Unknown (leaf)
+
+ A Decleration contains: Attributes (not on tree)
+ @endverbatim
+*/
+class TiXmlBase
+{
+ friend class TiXmlNode;
+ friend class TiXmlElement;
+ friend class TiXmlDocument;
+
+public:
+ TiXmlBase() : userData(0) {}
+ virtual ~TiXmlBase() {}
+
+ /** All TinyXml classes can print themselves to a filestream
+ or the string class (TiXmlString in non-STL mode, std::string
+ in STL mode.) Either or both cfile and str can be null.
+
+ This is a formatted print, and will insert
+ tabs and newlines.
+
+ (For an unformatted stream, use the << operator.)
+ */
+ virtual void Print( FILE* cfile, int depth ) const = 0;
+
+ /** The world does not agree on whether white space should be kept or
+ not. In order to make everyone happy, these global, static functions
+ are provided to set whether or not TinyXml will condense all white space
+ into a single space or not. The default is to condense. Note changing this
+ value is not thread safe.
+ */
+ static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; }
+
+ /// Return the current white space setting.
+ static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; }
+
+ /** Return the position, in the original source file, of this node or attribute.
+ The row and column are 1-based. (That is the first row and first column is
+ 1,1). If the returns values are 0 or less, then the parser does not have
+ a row and column value.
+
+ Generally, the row and column value will be set when the TiXmlDocument::Load(),
+ TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set
+ when the DOM was created from operator>>.
+
+ The values reflect the initial load. Once the DOM is modified programmatically
+ (by adding or changing nodes and attributes) the new values will NOT update to
+ reflect changes in the document.
+
+ There is a minor performance cost to computing the row and column. Computation
+ can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value.
+
+ @sa TiXmlDocument::SetTabSize()
+ */
+ int Row() const { return location.row + 1; }
+ int Column() const { return location.col + 1; } ///< See Row()
+
+ void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data.
+ void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data.
+ const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data.
+
+ // Table that returs, for a given lead byte, the total number of bytes
+ // in the UTF-8 sequence.
+ static const int utf8ByteTable[256];
+
+ virtual const char* Parse( const char* p,
+ TiXmlParsingData* data,
+ TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0;
+
+ /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc,
+ or they will be transformed into entities!
+ */
+ static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out );
+
+ enum
+ {
+ TIXML_NO_ERROR = 0,
+ TIXML_ERROR,
+ TIXML_ERROR_OPENING_FILE,
+ TIXML_ERROR_OUT_OF_MEMORY,
+ TIXML_ERROR_PARSING_ELEMENT,
+ TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME,
+ TIXML_ERROR_READING_ELEMENT_VALUE,
+ TIXML_ERROR_READING_ATTRIBUTES,
+ TIXML_ERROR_PARSING_EMPTY,
+ TIXML_ERROR_READING_END_TAG,
+ TIXML_ERROR_PARSING_UNKNOWN,
+ TIXML_ERROR_PARSING_COMMENT,
+ TIXML_ERROR_PARSING_DECLARATION,
+ TIXML_ERROR_DOCUMENT_EMPTY,
+ TIXML_ERROR_EMBEDDED_NULL,
+ TIXML_ERROR_PARSING_CDATA,
+ TIXML_ERROR_DOCUMENT_TOP_ONLY,
+
+ TIXML_ERROR_STRING_COUNT
+ };
+
+protected:
+
+ static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding );
+ inline static bool IsWhiteSpace( char c )
+ {
+ return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' );
+ }
+ inline static bool IsWhiteSpace( int c )
+ {
+ if ( c < 256 )
+ return IsWhiteSpace( (char) c );
+ return false; // Again, only truly correct for English/Latin...but usually works.
+ }
+
+ #ifdef TIXML_USE_STL
+ static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag );
+ static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag );
+ #endif
+
+ /* Reads an XML name into the string provided. Returns
+ a pointer just past the last character of the name,
+ or 0 if the function has an error.
+ */
+ static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding );
+
+ /* Reads text. Returns a pointer past the given end tag.
+ Wickedly complex options, but it keeps the (sensitive) code in one place.
+ */
+ static const char* ReadText( const char* in, // where to start
+ TIXML_STRING* text, // the string read
+ bool ignoreWhiteSpace, // whether to keep the white space
+ const char* endTag, // what ends this text
+ bool ignoreCase, // whether to ignore case in the end tag
+ TiXmlEncoding encoding ); // the current encoding
+
+ // If an entity has been found, transform it into a character.
+ static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding );
+
+ // Get a character, while interpreting entities.
+ // The length can be from 0 to 4 bytes.
+ inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding )
+ {
+ assert( p );
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ *length = utf8ByteTable[ *((const unsigned char*)p) ];
+ assert( *length >= 0 && *length < 5 );
+ }
+ else
+ {
+ *length = 1;
+ }
+
+ if ( *length == 1 )
+ {
+ if ( *p == '&' )
+ return GetEntity( p, _value, length, encoding );
+ *_value = *p;
+ return p+1;
+ }
+ else if ( *length )
+ {
+ //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 ) {
+ _value[i] = p[i];
+ }
+ return p + (*length);
+ }
+ else
+ {
+ // Not valid text.
+ return 0;
+ }
+ }
+
+ // Return true if the next characters in the stream are any of the endTag sequences.
+ // Ignore case only works for english, and should only be relied on when comparing
+ // to English words: StringEqual( p, "version", true ) is fine.
+ static bool StringEqual( const char* p,
+ const char* endTag,
+ bool ignoreCase,
+ TiXmlEncoding encoding );
+
+ static const char* errorString[ TIXML_ERROR_STRING_COUNT ];
+
+ TiXmlCursor location;
+
+ /// Field containing a generic user pointer
+ void* userData;
+
+ // None of these methods are reliable for any language except English.
+ // Good for approximation, not great for accuracy.
+ static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding );
+ static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding );
+ inline static int ToLower( int v, TiXmlEncoding encoding )
+ {
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ if ( v < 128 ) return tolower( v );
+ return v;
+ }
+ else
+ {
+ return tolower( v );
+ }
+ }
+ static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length );
+
+private:
+ TiXmlBase( const TiXmlBase& ); // not implemented.
+ void operator=( const TiXmlBase& base ); // not allowed.
+
+ struct Entity
+ {
+ const char* str;
+ unsigned int strLength;
+ char chr;
+ };
+ enum
+ {
+ NUM_ENTITY = 5,
+ MAX_ENTITY_LENGTH = 6
+
+ };
+ static Entity entity[ NUM_ENTITY ];
+ static bool condenseWhiteSpace;
+};
+
+
+/** The parent class for everything in the Document Object Model.
+ (Except for attributes).
+ Nodes have siblings, a parent, and children. A node can be
+ in a document, or stand on its own. The type of a TiXmlNode
+ can be queried, and it can be cast to its more defined type.
+*/
+class TiXmlNode : public TiXmlBase
+{
+ friend class TiXmlDocument;
+ friend class TiXmlElement;
+
+public:
+ #ifdef TIXML_USE_STL
+
+ /** An input stream operator, for every class. Tolerant of newlines and
+ formatting, but doesn't expect them.
+ */
+ friend std::istream& operator >> (std::istream& in, TiXmlNode& base);
+
+ /** An output stream operator, for every class. Note that this outputs
+ without any newlines or formatting, as opposed to Print(), which
+ includes tabs and new lines.
+
+ The operator<< and operator>> are not completely symmetric. Writing
+ a node to a stream is very well defined. You'll get a nice stream
+ of output, without any extra whitespace or newlines.
+
+ But reading is not as well defined. (As it always is.) If you create
+ a TiXmlElement (for example) and read that from an input stream,
+ the text needs to define an element or junk will result. This is
+ true of all input streams, but it's worth keeping in mind.
+
+ A TiXmlDocument will read nodes until it reads a root element, and
+ all the children of that root element.
+ */
+ friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base);
+
+ /// Appends the XML node or attribute to a std::string.
+ friend std::string& operator<< (std::string& out, const TiXmlNode& base );
+
+ #endif
+
+ /** The types of XML nodes supported by TinyXml. (All the
+ unsupported types are picked up by UNKNOWN.)
+ */
+ enum NodeType
+ {
+ DOCUMENT,
+ ELEMENT,
+ COMMENT,
+ UNKNOWN,
+ TEXT,
+ DECLARATION,
+ TYPECOUNT
+ };
+
+ virtual ~TiXmlNode();
+
+ /** The meaning of 'value' changes for the specific type of
+ TiXmlNode.
+ @verbatim
+ Document: filename of the xml file
+ Element: name of the element
+ Comment: the comment text
+ Unknown: the tag contents
+ Text: the text string
+ @endverbatim
+
+ The subclasses will wrap this function.
+ */
+ const char *Value() const { return value.c_str (); }
+
+ #ifdef TIXML_USE_STL
+ /** Return Value() as a std::string. If you only use STL,
+ this is more efficient than calling Value().
+ Only available in STL mode.
+ */
+ const std::string& ValueStr() const { return value; }
+ #endif
+
+ const TIXML_STRING& ValueTStr() const { return value; }
+
+ /** Changes the value of the node. Defined as:
+ @verbatim
+ Document: filename of the xml file
+ Element: name of the element
+ Comment: the comment text
+ Unknown: the tag contents
+ Text: the text string
+ @endverbatim
+ */
+ void SetValue(const char * _value) { value = _value;}
+
+ #ifdef TIXML_USE_STL
+ /// STL std::string form.
+ void SetValue( const std::string& _value ) { value = _value; }
+ #endif
+
+ /// Delete all the children of this node. Does not affect 'this'.
+ void Clear();
+
+ /// One step up the DOM.
+ TiXmlNode* Parent() { return parent; }
+ const TiXmlNode* Parent() const { return parent; }
+
+ const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children.
+ TiXmlNode* FirstChild() { return firstChild; }
+ const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found.
+ /// The first child of this node with the matching 'value'. Will be null if none found.
+ TiXmlNode* FirstChild( const char * _value ) {
+ // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe)
+ // call the method, cast the return back to non-const.
+ return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value ));
+ }
+ const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children.
+ TiXmlNode* LastChild() { return lastChild; }
+
+ const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children.
+ TiXmlNode* LastChild( const char * _value ) {
+ return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value ));
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form.
+ const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /** An alternate way to walk the children of a node.
+ One way to iterate over nodes is:
+ @verbatim
+ for( child = parent->FirstChild(); child; child = child->NextSibling() )
+ @endverbatim
+
+ IterateChildren does the same thing with the syntax:
+ @verbatim
+ child = 0;
+ while( child = parent->IterateChildren( child ) )
+ @endverbatim
+
+ IterateChildren takes the previous child as input and finds
+ the next one. If the previous child is null, it returns the
+ first. IterateChildren will return null when done.
+ */
+ const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const;
+ TiXmlNode* IterateChildren( const TiXmlNode* previous ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) );
+ }
+
+ /// This flavor of IterateChildren searches for children with a particular 'value'
+ const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const;
+ TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form.
+ TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form.
+ #endif
+
+ /** Add a new node related to this. Adds a child past the LastChild.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertEndChild( const TiXmlNode& addThis );
+
+
+ /** Add a new node related to this. Adds a child past the LastChild.
+
+ NOTE: the node to be added is passed by pointer, and will be
+ henceforth owned (and deleted) by tinyXml. This method is efficient
+ and avoids an extra copy, but should be used with care as it
+ uses a different memory model than the other insert functions.
+
+ @sa InsertEndChild
+ */
+ TiXmlNode* LinkEndChild( TiXmlNode* addThis );
+
+ /** Add a new node related to this. Adds a child before the specified child.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis );
+
+ /** Add a new node related to this. Adds a child after the specified child.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis );
+
+ /** Replace a child of this node.
+ Returns a pointer to the new object or NULL if an error occured.
+ */
+ TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis );
+
+ /// Delete a child of this node.
+ bool RemoveChild( TiXmlNode* removeThis );
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* PreviousSibling() const { return prev; }
+ TiXmlNode* PreviousSibling() { return prev; }
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* PreviousSibling( const char * ) const;
+ TiXmlNode* PreviousSibling( const char *_prev ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form.
+ const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form.
+ TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /// Navigate to a sibling node.
+ const TiXmlNode* NextSibling() const { return next; }
+ TiXmlNode* NextSibling() { return next; }
+
+ /// Navigate to a sibling node with the given 'value'.
+ const TiXmlNode* NextSibling( const char * ) const;
+ TiXmlNode* NextSibling( const char* _next ) {
+ return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) );
+ }
+
+ /** Convenience function to get through elements.
+ Calls NextSibling and ToElement. Will skip all non-Element
+ nodes. Returns 0 if there is not another element.
+ */
+ const TiXmlElement* NextSiblingElement() const;
+ TiXmlElement* NextSiblingElement() {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() );
+ }
+
+ /** Convenience function to get through elements.
+ Calls NextSibling and ToElement. Will skip all non-Element
+ nodes. Returns 0 if there is not another element.
+ */
+ const TiXmlElement* NextSiblingElement( const char * ) const;
+ TiXmlElement* NextSiblingElement( const char *_next ) {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form.
+ TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /// Convenience function to get through elements.
+ const TiXmlElement* FirstChildElement() const;
+ TiXmlElement* FirstChildElement() {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() );
+ }
+
+ /// Convenience function to get through elements.
+ const TiXmlElement* FirstChildElement( const char * _value ) const;
+ TiXmlElement* FirstChildElement( const char * _value ) {
+ return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) );
+ }
+
+ #ifdef TIXML_USE_STL
+ const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form.
+ TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form.
+ #endif
+
+ /** Query the type (as an enumerated value, above) of this node.
+ The possible types are: DOCUMENT, ELEMENT, COMMENT,
+ UNKNOWN, TEXT, and DECLARATION.
+ */
+ int Type() const { return type; }
+
+ /** Return a pointer to the Document this node lives in.
+ Returns null if not in a document.
+ */
+ const TiXmlDocument* GetDocument() const;
+ TiXmlDocument* GetDocument() {
+ return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() );
+ }
+
+ /// Returns true if this node has no children.
+ bool NoChildren() const { return !firstChild; }
+
+ virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+
+ virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+ virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type.
+
+ /** Create an exact duplicate of this node and return it. The memory must be deleted
+ by the caller.
+ */
+ virtual TiXmlNode* Clone() const = 0;
+
+ /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the
+ XML tree will be conditionally visited and the host will be called back
+ via the TiXmlVisitor interface.
+
+ This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse
+ the XML for the callbacks, so the performance of TinyXML is unchanged by using this
+ interface versus any other.)
+
+ The interface has been based on ideas from:
+
+ - http://www.saxproject.org/
+ - http://c2.com/cgi/wiki?HierarchicalVisitorPattern
+
+ Which are both good references for "visiting".
+
+ An example of using Accept():
+ @verbatim
+ TiXmlPrinter printer;
+ tinyxmlDoc.Accept( &printer );
+ const char* xmlcstr = printer.CStr();
+ @endverbatim
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const = 0;
+
+protected:
+ TiXmlNode( NodeType _type );
+
+ // Copy to the allocated object. Shared functionality between Clone, Copy constructor,
+ // and the assignment operator.
+ void CopyTo( TiXmlNode* target ) const;
+
+ #ifdef TIXML_USE_STL
+ // The real work of the input operator.
+ virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0;
+ #endif
+
+ // Figure out what is at *p, and parse it. Returns null if it is not an xml node.
+ TiXmlNode* Identify( const char* start, TiXmlEncoding encoding );
+
+ TiXmlNode* parent;
+ NodeType type;
+
+ TiXmlNode* firstChild;
+ TiXmlNode* lastChild;
+
+ TIXML_STRING value;
+
+ TiXmlNode* prev;
+ TiXmlNode* next;
+
+private:
+ TiXmlNode( const TiXmlNode& ); // not implemented.
+ void operator=( const TiXmlNode& base ); // not allowed.
+};
+
+
+/** An attribute is a name-value pair. Elements have an arbitrary
+ number of attributes, each with a unique name.
+
+ @note The attributes are not TiXmlNodes, since they are not
+ part of the tinyXML document object model. There are other
+ suggested ways to look at this problem.
+*/
+class TiXmlAttribute : public TiXmlBase
+{
+ friend class TiXmlAttributeSet;
+
+public:
+ /// Construct an empty attribute.
+ TiXmlAttribute() : TiXmlBase()
+ {
+ document = 0;
+ prev = next = 0;
+ }
+
+ #ifdef TIXML_USE_STL
+ /// std::string constructor.
+ TiXmlAttribute( const std::string& _name, const std::string& _value )
+ {
+ name = _name;
+ value = _value;
+ document = 0;
+ prev = next = 0;
+ }
+ #endif
+
+ /// Construct an attribute with a name and value.
+ TiXmlAttribute( const char * _name, const char * _value )
+ {
+ name = _name;
+ value = _value;
+ document = 0;
+ prev = next = 0;
+ }
+
+ const char* Name() const { return name.c_str(); } ///< Return the name of this attribute.
+ const char* Value() const { return value.c_str(); } ///< Return the value of this attribute.
+ #ifdef TIXML_USE_STL
+ const std::string& ValueStr() const { return value; } ///< Return the value of this attribute.
+ #endif
+ int IntValue() const; ///< Return the value of this attribute, converted to an integer.
+ double DoubleValue() const; ///< Return the value of this attribute, converted to a double.
+
+ // Get the tinyxml string representation
+ const TIXML_STRING& NameTStr() const { return name; }
+
+ /** QueryIntValue examines the value string. It is an alternative to the
+ IntValue() method with richer error checking.
+ If the value is an integer, it is stored in 'value' and
+ the call returns TIXML_SUCCESS. If it is not
+ an integer, it returns TIXML_WRONG_TYPE.
+
+ A specialized but useful call. Note that for success it returns 0,
+ which is the opposite of almost all other TinyXml calls.
+ */
+ int QueryIntValue( int* _value ) const;
+ /// QueryDoubleValue examines the value string. See QueryIntValue().
+ int QueryDoubleValue( double* _value ) const;
+
+ void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute.
+ void SetValue( const char* _value ) { value = _value; } ///< Set the value.
+
+ void SetIntValue( int _value ); ///< Set the value from an integer.
+ void SetDoubleValue( double _value ); ///< Set the value from a double.
+
+ #ifdef TIXML_USE_STL
+ /// STL std::string form.
+ void SetName( const std::string& _name ) { name = _name; }
+ /// STL std::string form.
+ void SetValue( const std::string& _value ) { value = _value; }
+ #endif
+
+ /// Get the next sibling attribute in the DOM. Returns null at end.
+ const TiXmlAttribute* Next() const;
+ TiXmlAttribute* Next() {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() );
+ }
+
+ /// Get the previous sibling attribute in the DOM. Returns null at beginning.
+ const TiXmlAttribute* Previous() const;
+ TiXmlAttribute* Previous() {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() );
+ }
+
+ bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; }
+ bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; }
+ bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; }
+
+ /* Attribute parsing starts: first letter of the name
+ returns: the next char after the value end quote
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ // Prints this Attribute to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const {
+ Print( cfile, depth, 0 );
+ }
+ void Print( FILE* cfile, int depth, TIXML_STRING* str ) const;
+
+ // [internal use]
+ // Set the document pointer so the attribute can report errors.
+ void SetDocument( TiXmlDocument* doc ) { document = doc; }
+
+private:
+ TiXmlAttribute( const TiXmlAttribute& ); // not implemented.
+ void operator=( const TiXmlAttribute& base ); // not allowed.
+
+ TiXmlDocument* document; // A pointer back to a document, for error reporting.
+ TIXML_STRING name;
+ TIXML_STRING value;
+ TiXmlAttribute* prev;
+ TiXmlAttribute* next;
+};
+
+
+/* A class used to manage a group of attributes.
+ It is only used internally, both by the ELEMENT and the DECLARATION.
+
+ The set can be changed transparent to the Element and Declaration
+ classes that use it, but NOT transparent to the Attribute
+ which has to implement a next() and previous() method. Which makes
+ it a bit problematic and prevents the use of STL.
+
+ This version is implemented with circular lists because:
+ - I like circular lists
+ - it demonstrates some independence from the (typical) doubly linked list.
+*/
+class TiXmlAttributeSet
+{
+public:
+ TiXmlAttributeSet();
+ ~TiXmlAttributeSet();
+
+ void Add( TiXmlAttribute* attribute );
+ void Remove( TiXmlAttribute* attribute );
+
+ const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }
+ TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; }
+ const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }
+ TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; }
+
+ const TiXmlAttribute* Find( const char* _name ) const;
+ TiXmlAttribute* Find( const char* _name ) {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) );
+ }
+ #ifdef TIXML_USE_STL
+ const TiXmlAttribute* Find( const std::string& _name ) const;
+ TiXmlAttribute* Find( const std::string& _name ) {
+ return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) );
+ }
+
+ #endif
+
+private:
+ //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element),
+ //*ME: this class must be also use a hidden/disabled copy-constructor !!!
+ TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed
+ void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute)
+
+ TiXmlAttribute sentinel;
+};
+
+
+/** The element is a container class. It has a value, the element name,
+ and can contain other elements, text, comments, and unknowns.
+ Elements also contain an arbitrary number of attributes.
+*/
+class TiXmlElement : public TiXmlNode
+{
+public:
+ /// Construct an element.
+ TiXmlElement (const char * in_value);
+
+ #ifdef TIXML_USE_STL
+ /// std::string constructor.
+ TiXmlElement( const std::string& _value );
+ #endif
+
+ TiXmlElement( const TiXmlElement& );
+
+ void operator=( const TiXmlElement& base );
+
+ virtual ~TiXmlElement();
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ */
+ const char* Attribute( const char* name ) const;
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ If the attribute exists and can be converted to an integer,
+ the integer value will be put in the return 'i', if 'i'
+ is non-null.
+ */
+ const char* Attribute( const char* name, int* i ) const;
+
+ /** Given an attribute name, Attribute() returns the value
+ for the attribute of that name, or null if none exists.
+ If the attribute exists and can be converted to an double,
+ the double value will be put in the return 'd', if 'd'
+ is non-null.
+ */
+ const char* Attribute( const char* name, double* d ) const;
+
+ /** QueryIntAttribute examines the attribute - it is an alternative to the
+ Attribute() method with richer error checking.
+ If the attribute is an integer, it is stored in 'value' and
+ the call returns TIXML_SUCCESS. If it is not
+ an integer, it returns TIXML_WRONG_TYPE. If the attribute
+ does not exist, then TIXML_NO_ATTRIBUTE is returned.
+ */
+ int QueryIntAttribute( const char* name, int* _value ) const;
+ /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute().
+ int QueryDoubleAttribute( const char* name, double* _value ) const;
+ /// QueryFloatAttribute examines the attribute - see QueryIntAttribute().
+ int QueryFloatAttribute( const char* name, float* _value ) const {
+ double d;
+ int result = QueryDoubleAttribute( name, &d );
+ if ( result == TIXML_SUCCESS ) {
+ *_value = (float)d;
+ }
+ return result;
+ }
+
+ #ifdef TIXML_USE_STL
+ /** Template form of the attribute query which will try to read the
+ attribute into the specified type. Very easy, very powerful, but
+ be careful to make sure to call this with the correct type.
+
+ NOTE: This method doesn't work correctly for 'string' types.
+
+ @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE
+ */
+ template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const
+ {
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+
+ std::stringstream sstream( node->ValueStr() );
+ sstream >> *outValue;
+ if ( !sstream.fail() )
+ return TIXML_SUCCESS;
+ return TIXML_WRONG_TYPE;
+ }
+ /*
+ This is - in theory - a bug fix for "QueryValueAtribute returns truncated std::string"
+ but template specialization is hard to get working cross-compiler. Leaving the bug for now.
+
+ // The above will fail for std::string because the space character is used as a seperator.
+ // Specialize for strings. Bug [ 1695429 ] QueryValueAtribute returns truncated std::string
+ template<> int QueryValueAttribute( const std::string& name, std::string* outValue ) const
+ {
+ const TiXmlAttribute* node = attributeSet.Find( name );
+ if ( !node )
+ return TIXML_NO_ATTRIBUTE;
+ *outValue = node->ValueStr();
+ return TIXML_SUCCESS;
+ }
+ */
+ #endif
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetAttribute( const char* name, const char * _value );
+
+ #ifdef TIXML_USE_STL
+ const std::string* Attribute( const std::string& name ) const;
+ const std::string* Attribute( const std::string& name, int* i ) const;
+ const std::string* Attribute( const std::string& name, double* d ) const;
+ int QueryIntAttribute( const std::string& name, int* _value ) const;
+ int QueryDoubleAttribute( const std::string& name, double* _value ) const;
+
+ /// STL std::string form.
+ void SetAttribute( const std::string& name, const std::string& _value );
+ ///< STL std::string form.
+ void SetAttribute( const std::string& name, int _value );
+ #endif
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetAttribute( const char * name, int value );
+
+ /** Sets an attribute of name to a given value. The attribute
+ will be created if it does not exist, or changed if it does.
+ */
+ void SetDoubleAttribute( const char * name, double value );
+
+ /** Deletes an attribute with the given name.
+ */
+ void RemoveAttribute( const char * name );
+ #ifdef TIXML_USE_STL
+ void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form.
+ #endif
+
+ const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element.
+ TiXmlAttribute* FirstAttribute() { return attributeSet.First(); }
+ const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element.
+ TiXmlAttribute* LastAttribute() { return attributeSet.Last(); }
+
+ /** Convenience function for easy access to the text inside an element. Although easy
+ and concise, GetText() is limited compared to getting the TiXmlText child
+ and accessing it directly.
+
+ If the first child of 'this' is a TiXmlText, the GetText()
+ returns the character string of the Text node, else null is returned.
+
+ This is a convenient method for getting the text of simple contained text:
+ @verbatim
+ <foo>This is text</foo>
+ const char* str = fooElement->GetText();
+ @endverbatim
+
+ 'str' will be a pointer to "This is text".
+
+ Note that this function can be misleading. If the element foo was created from
+ this XML:
+ @verbatim
+ <foo><b>This is text</b></foo>
+ @endverbatim
+
+ then the value of str would be null. The first child node isn't a text node, it is
+ another element. From this XML:
+ @verbatim
+ <foo>This is <b>text</b></foo>
+ @endverbatim
+ GetText() will return "This is ".
+
+ WARNING: GetText() accesses a child node - don't become confused with the
+ similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are
+ safe type casts on the referenced node.
+ */
+ const char* GetText() const;
+
+ /// Creates a new Element and returns it - the returned element is a copy.
+ virtual TiXmlNode* Clone() const;
+ // Print the Element to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /* Attribtue parsing starts: next char past '<'
+ returns: next char past '>'
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+
+ void CopyTo( TiXmlElement* target ) const;
+ void ClearThis(); // like clear, but initializes 'this' object as well
+
+ // Used to be public [internal use]
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+ /* [internal use]
+ Reads the "value" of the element -- another element, or text.
+ This should terminate with the current end tag.
+ */
+ const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding );
+
+private:
+
+ TiXmlAttributeSet attributeSet;
+};
+
+
+/** An XML comment.
+*/
+class TiXmlComment : public TiXmlNode
+{
+public:
+ /// Constructs an empty comment.
+ TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {}
+ /// Construct a comment from text.
+ TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::COMMENT ) {
+ SetValue( _value );
+ }
+ TiXmlComment( const TiXmlComment& );
+ void operator=( const TiXmlComment& base );
+
+ virtual ~TiXmlComment() {}
+
+ /// Returns a copy of this Comment.
+ virtual TiXmlNode* Clone() const;
+ // Write this Comment to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /* Attribtue parsing starts: at the ! of the !--
+ returns: next char past '>'
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+ void CopyTo( TiXmlComment* target ) const;
+
+ // used to be public
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+// virtual void StreamOut( TIXML_OSTREAM * out ) const;
+
+private:
+
+};
+
+
+/** XML text. A text node can have 2 ways to output the next. "normal" output
+ and CDATA. It will default to the mode it was parsed from the XML file and
+ you generally want to leave it alone, but you can change the output mode with
+ SetCDATA() and query it with CDATA().
+*/
+class TiXmlText : public TiXmlNode
+{
+ friend class TiXmlElement;
+public:
+ /** Constructor for text element. By default, it is treated as
+ normal, encoded text. If you want it be output as a CDATA text
+ element, set the parameter _cdata to 'true'
+ */
+ TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TEXT)
+ {
+ SetValue( initValue );
+ cdata = false;
+ }
+ virtual ~TiXmlText() {}
+
+ #ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT)
+ {
+ SetValue( initValue );
+ cdata = false;
+ }
+ #endif
+
+ TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); }
+ void operator=( const TiXmlText& base ) { base.CopyTo( this ); }
+
+ // Write this text object to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ /// Queries whether this represents text using a CDATA section.
+ bool CDATA() const { return cdata; }
+ /// Turns on or off a CDATA representation of text.
+ void SetCDATA( bool _cdata ) { cdata = _cdata; }
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected :
+ /// [internal use] Creates a new Element and returns it.
+ virtual TiXmlNode* Clone() const;
+ void CopyTo( TiXmlText* target ) const;
+
+ bool Blank() const; // returns true if all white space and new lines
+ // [internal use]
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+ bool cdata; // true if this should be input and output as a CDATA style text element
+};
+
+
+/** In correct XML the declaration is the first entry in the file.
+ @verbatim
+ <?xml version="1.0" standalone="yes"?>
+ @endverbatim
+
+ TinyXml will happily read or write files without a declaration,
+ however. There are 3 possible attributes to the declaration:
+ version, encoding, and standalone.
+
+ Note: In this version of the code, the attributes are
+ handled as special cases, not generic attributes, simply
+ because there can only be at most 3 and they are always the same.
+*/
+class TiXmlDeclaration : public TiXmlNode
+{
+public:
+ /// Construct an empty declaration.
+ TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {}
+
+#ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlDeclaration( const std::string& _version,
+ const std::string& _encoding,
+ const std::string& _standalone );
+#endif
+
+ /// Construct.
+ TiXmlDeclaration( const char* _version,
+ const char* _encoding,
+ const char* _standalone );
+
+ TiXmlDeclaration( const TiXmlDeclaration& copy );
+ void operator=( const TiXmlDeclaration& copy );
+
+ virtual ~TiXmlDeclaration() {}
+
+ /// Version. Will return an empty string if none was found.
+ const char *Version() const { return version.c_str (); }
+ /// Encoding. Will return an empty string if none was found.
+ const char *Encoding() const { return encoding.c_str (); }
+ /// Is this a standalone document?
+ const char *Standalone() const { return standalone.c_str (); }
+
+ /// Creates a copy of this Declaration and returns it.
+ virtual TiXmlNode* Clone() const;
+ // Print this declaration to a FILE stream.
+ virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const;
+ virtual void Print( FILE* cfile, int depth ) const {
+ Print( cfile, depth, 0 );
+ }
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* visitor ) const;
+
+protected:
+ void CopyTo( TiXmlDeclaration* target ) const;
+ // used to be public
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+
+ TIXML_STRING version;
+ TIXML_STRING encoding;
+ TIXML_STRING standalone;
+};
+
+
+/** Any tag that tinyXml doesn't recognize is saved as an
+ unknown. It is a tag of text, but should not be modified.
+ It will be written back to the XML, unchanged, when the file
+ is saved.
+
+ DTD tags get thrown into TiXmlUnknowns.
+*/
+class TiXmlUnknown : public TiXmlNode
+{
+public:
+ TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {}
+ virtual ~TiXmlUnknown() {}
+
+ TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); }
+ void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); }
+
+ /// Creates a copy of this Unknown and returns it.
+ virtual TiXmlNode* Clone() const;
+ // Print this Unknown to a FILE stream.
+ virtual void Print( FILE* cfile, int depth ) const;
+
+ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding );
+
+ virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected:
+ void CopyTo( TiXmlUnknown* target ) const;
+
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+
+};
+
+
+/** Always the top level node. A document binds together all the
+ XML pieces. It can be saved, loaded, and printed to the screen.
+ The 'value' of a document node is the xml file name.
+*/
+class TiXmlDocument : public TiXmlNode
+{
+public:
+ /// Create an empty document, that has no name.
+ TiXmlDocument();
+ /// Create a document with a name. The name of the document is also the filename of the xml.
+ TiXmlDocument( const char * documentName );
+
+ #ifdef TIXML_USE_STL
+ /// Constructor.
+ TiXmlDocument( const std::string& documentName );
+ #endif
+
+ TiXmlDocument( const TiXmlDocument& copy );
+ void operator=( const TiXmlDocument& copy );
+
+ virtual ~TiXmlDocument() {}
+
+ /** Load a file using the current document value.
+ Returns true if successful. Will delete any existing
+ document data before loading.
+ */
+ bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the current document value. Returns true if successful.
+ bool SaveFile() const;
+ /// Load a file using the given filename. Returns true if successful.
+ bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the given filename. Returns true if successful.
+ bool SaveFile( const char * filename ) const;
+ /** Load a file using the given FILE*. Returns true if successful. Note that this method
+ doesn't stream - the entire object pointed at by the FILE*
+ will be interpreted as an XML file. TinyXML doesn't stream in XML from the current
+ file location. Streaming may be added in the future.
+ */
+ bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+ /// Save a file using the given FILE*. Returns true if successful.
+ bool SaveFile( FILE* ) const;
+
+ #ifdef TIXML_USE_STL
+ bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version.
+ {
+// StringToBuffer f( filename );
+// return ( f.buffer && LoadFile( f.buffer, encoding ));
+ return LoadFile( filename.c_str(), encoding );
+ }
+ bool SaveFile( const std::string& filename ) const ///< STL std::string version.
+ {
+// StringToBuffer f( filename );
+// return ( f.buffer && SaveFile( f.buffer ));
+ return SaveFile( filename.c_str() );
+ }
+ #endif
+
+ /** Parse the given null terminated block of xml data. Passing in an encoding to this
+ method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml
+ to use that encoding, regardless of what TinyXml might otherwise try to detect.
+ */
+ virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING );
+
+ /** Get the root element -- the only top level element -- of the document.
+ In well formed XML, there should only be one. TinyXml is tolerant of
+ multiple elements at the document level.
+ */
+ const TiXmlElement* RootElement() const { return FirstChildElement(); }
+ TiXmlElement* RootElement() { return FirstChildElement(); }
+
+ /** If an error occurs, Error will be set to true. Also,
+ - The ErrorId() will contain the integer identifier of the error (not generally useful)
+ - The ErrorDesc() method will return the name of the error. (very useful)
+ - The ErrorRow() and ErrorCol() will return the location of the error (if known)
+ */
+ bool Error() const { return error; }
+
+ /// Contains a textual (english) description of the error if one occurs.
+ const char * ErrorDesc() const { return errorDesc.c_str (); }
+
+ /** Generally, you probably want the error string ( ErrorDesc() ). But if you
+ prefer the ErrorId, this function will fetch it.
+ */
+ int ErrorId() const { return errorId; }
+
+ /** Returns the location (if known) of the error. The first column is column 1,
+ and the first row is row 1. A value of 0 means the row and column wasn't applicable
+ (memory errors, for example, have no row/column) or the parser lost the error. (An
+ error in the error reporting, in that case.)
+
+ @sa SetTabSize, Row, Column
+ */
+ int ErrorRow() const { return errorLocation.row+1; }
+ int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow()
+
+ /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol())
+ to report the correct values for row and column. It does not change the output
+ or input in any way.
+
+ By calling this method, with a tab size
+ greater than 0, the row and column of each node and attribute is stored
+ when the file is loaded. Very useful for tracking the DOM back in to
+ the source file.
+
+ The tab size is required for calculating the location of nodes. If not
+ set, the default of 4 is used. The tabsize is set per document. Setting
+ the tabsize to 0 disables row/column tracking.
+
+ Note that row and column tracking is not supported when using operator>>.
+
+ The tab size needs to be enabled before the parse or load. Correct usage:
+ @verbatim
+ TiXmlDocument doc;
+ doc.SetTabSize( 8 );
+ doc.Load( "myfile.xml" );
+ @endverbatim
+
+ @sa Row, Column
+ */
+ void SetTabSize( int _tabsize ) { tabsize = _tabsize; }
+
+ int TabSize() const { return tabsize; }
+
+ /** If you have handled the error, it can be reset with this call. The error
+ state is automatically cleared if you Parse a new XML block.
+ */
+ void ClearError() { error = false;
+ errorId = 0;
+ errorDesc = "";
+ errorLocation.row = errorLocation.col = 0;
+ //errorLocation.last = 0;
+ }
+
+ /** Write the document to standard out using formatted printing ("pretty print"). */
+ void Print() const { Print( stdout, 0 ); }
+
+ /* Write the document to a string using formatted printing ("pretty print"). This
+ will allocate a character array (new char[]) and return it as a pointer. The
+ calling code pust call delete[] on the return char* to avoid a memory leak.
+ */
+ //char* PrintToMemory() const;
+
+ /// Print this Document to a FILE stream.
+ virtual void Print( FILE* cfile, int depth = 0 ) const;
+ // [internal use]
+ void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding );
+
+ virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+ virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type.
+
+ /** Walk the XML tree visiting this node and all of its children.
+ */
+ virtual bool Accept( TiXmlVisitor* content ) const;
+
+protected :
+ // [internal use]
+ virtual TiXmlNode* Clone() const;
+ #ifdef TIXML_USE_STL
+ virtual void StreamIn( std::istream * in, TIXML_STRING * tag );
+ #endif
+
+private:
+ void CopyTo( TiXmlDocument* target ) const;
+
+ bool error;
+ int errorId;
+ TIXML_STRING errorDesc;
+ int tabsize;
+ TiXmlCursor errorLocation;
+ bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write.
+};
+
+
+/**
+ A TiXmlHandle is a class that wraps a node pointer with null checks; this is
+ an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml
+ DOM structure. It is a separate utility class.
+
+ Take an example:
+ @verbatim
+ <Document>
+ <Element attributeA = "valueA">
+ <Child attributeB = "value1" />
+ <Child attributeB = "value2" />
+ </Element>
+ <Document>
+ @endverbatim
+
+ Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very
+ easy to write a *lot* of code that looks like:
+
+ @verbatim
+ TiXmlElement* root = document.FirstChildElement( "Document" );
+ if ( root )
+ {
+ TiXmlElement* element = root->FirstChildElement( "Element" );
+ if ( element )
+ {
+ TiXmlElement* child = element->FirstChildElement( "Child" );
+ if ( child )
+ {
+ TiXmlElement* child2 = child->NextSiblingElement( "Child" );
+ if ( child2 )
+ {
+ // Finally do something useful.
+ @endverbatim
+
+ And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity
+ of such code. A TiXmlHandle checks for null pointers so it is perfectly safe
+ and correct to use:
+
+ @verbatim
+ TiXmlHandle docHandle( &document );
+ TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
+ if ( child2 )
+ {
+ // do something useful
+ @endverbatim
+
+ Which is MUCH more concise and useful.
+
+ It is also safe to copy handles - internally they are nothing more than node pointers.
+ @verbatim
+ TiXmlHandle handleCopy = handle;
+ @endverbatim
+
+ What they should not be used for is iteration:
+
+ @verbatim
+ int i=0;
+ while ( true )
+ {
+ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement();
+ if ( !child )
+ break;
+ // do something
+ ++i;
+ }
+ @endverbatim
+
+ It seems reasonable, but it is in fact two embedded while loops. The Child method is
+ a linear walk to find the element, so this code would iterate much more than it needs
+ to. Instead, prefer:
+
+ @verbatim
+ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement();
+
+ for( child; child; child=child->NextSiblingElement() )
+ {
+ // do something
+ }
+ @endverbatim
+*/
+class TiXmlHandle
+{
+public:
+ /// Create a handle from any node (at any depth of the tree.) This can be a null pointer.
+ TiXmlHandle( TiXmlNode* _node ) { this->node = _node; }
+ /// Copy constructor
+ TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; }
+ TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; }
+
+ /// Return a handle to the first child node.
+ TiXmlHandle FirstChild() const;
+ /// Return a handle to the first child node with the given name.
+ TiXmlHandle FirstChild( const char * value ) const;
+ /// Return a handle to the first child element.
+ TiXmlHandle FirstChildElement() const;
+ /// Return a handle to the first child element with the given name.
+ TiXmlHandle FirstChildElement( const char * value ) const;
+
+ /** Return a handle to the "index" child with the given name.
+ The first child is 0, the second 1, etc.
+ */
+ TiXmlHandle Child( const char* value, int index ) const;
+ /** Return a handle to the "index" child.
+ The first child is 0, the second 1, etc.
+ */
+ TiXmlHandle Child( int index ) const;
+ /** Return a handle to the "index" child element with the given name.
+ The first child element is 0, the second 1, etc. Note that only TiXmlElements
+ are indexed: other types are not counted.
+ */
+ TiXmlHandle ChildElement( const char* value, int index ) const;
+ /** Return a handle to the "index" child element.
+ The first child element is 0, the second 1, etc. Note that only TiXmlElements
+ are indexed: other types are not counted.
+ */
+ TiXmlHandle ChildElement( int index ) const;
+
+ #ifdef TIXML_USE_STL
+ TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); }
+ TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); }
+
+ TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); }
+ TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); }
+ #endif
+
+ /** Return the handle as a TiXmlNode. This may return null.
+ */
+ TiXmlNode* ToNode() const { return node; }
+ /** Return the handle as a TiXmlElement. This may return null.
+ */
+ TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); }
+ /** Return the handle as a TiXmlText. This may return null.
+ */
+ TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); }
+ /** Return the handle as a TiXmlUnknown. This may return null.
+ */
+ TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); }
+
+ /** @deprecated use ToNode.
+ Return the handle as a TiXmlNode. This may return null.
+ */
+ TiXmlNode* Node() const { return ToNode(); }
+ /** @deprecated use ToElement.
+ Return the handle as a TiXmlElement. This may return null.
+ */
+ TiXmlElement* Element() const { return ToElement(); }
+ /** @deprecated use ToText()
+ Return the handle as a TiXmlText. This may return null.
+ */
+ TiXmlText* Text() const { return ToText(); }
+ /** @deprecated use ToUnknown()
+ Return the handle as a TiXmlUnknown. This may return null.
+ */
+ TiXmlUnknown* Unknown() const { return ToUnknown(); }
+
+private:
+ TiXmlNode* node;
+};
+
+
+/** Print to memory functionality. The TiXmlPrinter is useful when you need to:
+
+ -# Print to memory (especially in non-STL mode)
+ -# Control formatting (line endings, etc.)
+
+ When constructed, the TiXmlPrinter is in its default "pretty printing" mode.
+ Before calling Accept() you can call methods to control the printing
+ of the XML document. After TiXmlNode::Accept() is called, the printed document can
+ be accessed via the CStr(), Str(), and Size() methods.
+
+ TiXmlPrinter uses the Visitor API.
+ @verbatim
+ TiXmlPrinter printer;
+ printer.SetIndent( "\t" );
+
+ doc.Accept( &printer );
+ fprintf( stdout, "%s", printer.CStr() );
+ @endverbatim
+*/
+class TiXmlPrinter : public TiXmlVisitor
+{
+public:
+ TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ),
+ buffer(), indent( " " ), lineBreak( "\n" ) {}
+
+ virtual bool VisitEnter( const TiXmlDocument& doc );
+ virtual bool VisitExit( const TiXmlDocument& doc );
+
+ virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute );
+ virtual bool VisitExit( const TiXmlElement& element );
+
+ virtual bool Visit( const TiXmlDeclaration& declaration );
+ virtual bool Visit( const TiXmlText& text );
+ virtual bool Visit( const TiXmlComment& comment );
+ virtual bool Visit( const TiXmlUnknown& unknown );
+
+ /** Set the indent characters for printing. By default 4 spaces
+ but tab (\t) is also useful, or null/empty string for no indentation.
+ */
+ void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; }
+ /// Query the indention string.
+ const char* Indent() { return indent.c_str(); }
+ /** Set the line breaking string. By default set to newline (\n).
+ Some operating systems prefer other characters, or can be
+ set to the null/empty string for no indenation.
+ */
+ void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; }
+ /// Query the current line breaking string.
+ const char* LineBreak() { return lineBreak.c_str(); }
+
+ /** Switch over to "stream printing" which is the most dense formatting without
+ linebreaks. Common when the XML is needed for network transmission.
+ */
+ void SetStreamPrinting() { indent = "";
+ lineBreak = "";
+ }
+ /// Return the result.
+ const char* CStr() { return buffer.c_str(); }
+ /// Return the length of the result string.
+ size_t Size() { return buffer.size(); }
+
+ #ifdef TIXML_USE_STL
+ /// Return the result.
+ const std::string& Str() { return buffer; }
+ #endif
+
+private:
+ void DoIndent() {
+ for( int i=0; i<depth; ++i )
+ buffer += indent;
+ }
+ void DoLineBreak() {
+ buffer += lineBreak;
+ }
+
+ int depth;
+ bool simpleTextPrint;
+ TIXML_STRING buffer;
+ TIXML_STRING indent;
+ TIXML_STRING lineBreak;
+};
+
+
+#ifdef _MSC_VER
+#pragma warning( pop )
+#endif
+
+#endif
+
diff --git a/extern/oics/tinyxmlerror.cpp b/extern/oics/tinyxmlerror.cpp
new file mode 100644
index 0000000000..d24f63b2e5
--- /dev/null
+++ b/extern/oics/tinyxmlerror.cpp
@@ -0,0 +1,53 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include "tinyxml.h"
+
+// The goal of the seperate error file is to make the first
+// step towards localization. tinyxml (currently) only supports
+// english error messages, but the could now be translated.
+//
+// It also cleans up the code a bit.
+//
+
+const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] =
+{
+ "No error",
+ "Error",
+ "Failed to open file",
+ "Memory allocation failed.",
+ "Error parsing Element.",
+ "Failed to read Element name",
+ "Error reading Element value.",
+ "Error reading Attributes.",
+ "Error: empty tag.",
+ "Error reading end tag.",
+ "Error parsing Unknown.",
+ "Error parsing Comment.",
+ "Error parsing Declaration.",
+ "Error document empty.",
+ "Error null (0) or unexpected EOF found in input stream.",
+ "Error parsing CDATA.",
+ "Error when TiXmlDocument added to document, because TiXmlDocument can only be at the root.",
+};
diff --git a/extern/oics/tinyxmlparser.cpp b/extern/oics/tinyxmlparser.cpp
new file mode 100644
index 0000000000..253cd93ff0
--- /dev/null
+++ b/extern/oics/tinyxmlparser.cpp
@@ -0,0 +1,1638 @@
+/*
+www.sourceforge.net/projects/tinyxml
+Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#include <ctype.h>
+#include <stddef.h>
+
+#include "tinyxml.h"
+
+//#define DEBUG_PARSER
+#if defined( DEBUG_PARSER )
+# if defined( DEBUG ) && defined( _MSC_VER )
+# include <windows.h>
+# define TIXML_LOG OutputDebugString
+# else
+# define TIXML_LOG printf
+# endif
+#endif
+
+// Note tha "PutString" hardcodes the same list. This
+// is less flexible than it appears. Changing the entries
+// or order will break putstring.
+TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] =
+{
+ { "&amp;", 5, '&' },
+ { "&lt;", 4, '<' },
+ { "&gt;", 4, '>' },
+ { "&quot;", 6, '\"' },
+ { "&apos;", 6, '\'' }
+};
+
+// Bunch of unicode info at:
+// http://www.unicode.org/faq/utf_bom.html
+// Including the basic of this table, which determines the #bytes in the
+// sequence from the lead byte. 1 placed for invalid sequences --
+// although the result will be junk, pass it through as much as possible.
+// Beware of the non-characters in UTF-8:
+// ef bb bf (Microsoft "lead bytes")
+// ef bf be
+// ef bf bf
+
+const unsigned char TIXML_UTF_LEAD_0 = 0xefU;
+const unsigned char TIXML_UTF_LEAD_1 = 0xbbU;
+const unsigned char TIXML_UTF_LEAD_2 = 0xbfU;
+
+const int TiXmlBase::utf8ByteTable[256] =
+{
+ // 0 1 2 3 4 5 6 7 8 9 a b c d e f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
+ 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte
+ 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid
+};
+
+
+void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length )
+{
+ const unsigned long BYTE_MASK = 0xBF;
+ const unsigned long BYTE_MARK = 0x80;
+ const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+ if (input < 0x80)
+ *length = 1;
+ else if ( input < 0x800 )
+ *length = 2;
+ else if ( input < 0x10000 )
+ *length = 3;
+ else if ( input < 0x200000 )
+ *length = 4;
+ else
+ { *length = 0; return; } // This code won't covert this correctly anyway.
+
+ output += *length;
+
+ // Scary scary fall throughs.
+ switch (*length)
+ {
+ case 4:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 3:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 2:
+ --output;
+ *output = (char)((input | BYTE_MARK) & BYTE_MASK);
+ input >>= 6;
+ case 1:
+ --output;
+ *output = (char)(input | FIRST_BYTE_MARK[*length]);
+ }
+}
+
+
+/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ )
+{
+ // This will only work for low-ascii, everything else is assumed to be a valid
+ // letter. I'm not sure this is the best approach, but it is quite tricky trying
+ // to figure out alhabetical vs. not across encoding. So take a very
+ // conservative approach.
+
+// if ( encoding == TIXML_ENCODING_UTF8 )
+// {
+ if ( anyByte < 127 )
+ return isalpha( anyByte );
+ else
+ return 1; // What else to do? The unicode set is huge...get the english ones right.
+// }
+// else
+// {
+// return isalpha( anyByte );
+// }
+}
+
+
+/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ )
+{
+ // This will only work for low-ascii, everything else is assumed to be a valid
+ // letter. I'm not sure this is the best approach, but it is quite tricky trying
+ // to figure out alhabetical vs. not across encoding. So take a very
+ // conservative approach.
+
+// if ( encoding == TIXML_ENCODING_UTF8 )
+// {
+ if ( anyByte < 127 )
+ return isalnum( anyByte );
+ else
+ return 1; // What else to do? The unicode set is huge...get the english ones right.
+// }
+// else
+// {
+// return isalnum( anyByte );
+// }
+}
+
+
+class TiXmlParsingData
+{
+ friend class TiXmlDocument;
+ public:
+ void Stamp( const char* now, TiXmlEncoding encoding );
+
+ const TiXmlCursor& Cursor() { return cursor; }
+
+ private:
+ // Only used by the document!
+ TiXmlParsingData( const char* start, int _tabsize, int row, int col )
+ {
+ assert( start );
+ stamp = start;
+ tabsize = _tabsize;
+ cursor.row = row;
+ cursor.col = col;
+ }
+
+ TiXmlCursor cursor;
+ const char* stamp;
+ int tabsize;
+};
+
+
+void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding )
+{
+ assert( now );
+
+ // Do nothing if the tabsize is 0.
+ if ( tabsize < 1 )
+ {
+ return;
+ }
+
+ // Get the current row, column.
+ int row = cursor.row;
+ int col = cursor.col;
+ const char* p = stamp;
+ assert( p );
+
+ while ( p < now )
+ {
+ // Treat p as unsigned, so we have a happy compiler.
+ const unsigned char* pU = (const unsigned char*)p;
+
+ // Code contributed by Fletcher Dunn: (modified by lee)
+ switch (*pU) {
+ case 0:
+ // We *should* never get here, but in case we do, don't
+ // advance past the terminating null character, ever
+ return;
+
+ case '\r':
+ // bump down to the next line
+ ++row;
+ col = 0;
+ // Eat the character
+ ++p;
+
+ // Check for \r\n sequence, and treat this as a single character
+ if (*p == '\n') {
+ ++p;
+ }
+ break;
+
+ case '\n':
+ // bump down to the next line
+ ++row;
+ col = 0;
+
+ // Eat the character
+ ++p;
+
+ // Check for \n\r sequence, and treat this as a single
+ // character. (Yes, this bizarre thing does occur still
+ // on some arcane platforms...)
+ if (*p == '\r') {
+ ++p;
+ }
+ break;
+
+ case '\t':
+ // Eat the character
+ ++p;
+
+ // Skip to next tab stop
+ col = (col / tabsize + 1) * tabsize;
+ break;
+
+ case TIXML_UTF_LEAD_0:
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ if ( *(p+1) && *(p+2) )
+ {
+ // In these cases, don't advance the column. These are
+ // 0-width spaces.
+ if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 )
+ p += 3;
+ else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU )
+ p += 3;
+ else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU )
+ p += 3;
+ else
+ { p +=3; ++col; } // A normal character.
+ }
+ }
+ else
+ {
+ ++p;
+ ++col;
+ }
+ break;
+
+ default:
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ // Eat the 1 to 4 byte utf8 character.
+ int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)];
+ if ( step == 0 )
+ step = 1; // Error case from bad encoding, but handle gracefully.
+ p += step;
+
+ // Just advance one column, of course.
+ ++col;
+ }
+ else
+ {
+ ++p;
+ ++col;
+ }
+ break;
+ }
+ }
+ cursor.row = row;
+ cursor.col = col;
+ assert( cursor.row >= -1 );
+ assert( cursor.col >= -1 );
+ stamp = p;
+ assert( stamp );
+}
+
+
+const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding )
+{
+ if ( !p || !*p )
+ {
+ return 0;
+ }
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ while ( *p )
+ {
+ const unsigned char* pU = (const unsigned char*)p;
+
+ // Skip the stupid Microsoft UTF-8 Byte order marks
+ if ( *(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==TIXML_UTF_LEAD_1
+ && *(pU+2)==TIXML_UTF_LEAD_2 )
+ {
+ p += 3;
+ continue;
+ }
+ else if(*(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==0xbfU
+ && *(pU+2)==0xbeU )
+ {
+ p += 3;
+ continue;
+ }
+ else if(*(pU+0)==TIXML_UTF_LEAD_0
+ && *(pU+1)==0xbfU
+ && *(pU+2)==0xbfU )
+ {
+ p += 3;
+ continue;
+ }
+
+ if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space.
+ ++p;
+ else
+ break;
+ }
+ }
+ else
+ {
+ while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' )
+ ++p;
+ }
+
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag )
+{
+ for( ;; )
+ {
+ if ( !in->good() ) return false;
+
+ int c = in->peek();
+ // At this scope, we can't get to a document. So fail silently.
+ if ( !IsWhiteSpace( c ) || c <= 0 )
+ return true;
+
+ *tag += (char) in->get();
+ }
+}
+
+/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag )
+{
+ //assert( character > 0 && character < 128 ); // else it won't work in utf-8
+ while ( in->good() )
+ {
+ int c = in->peek();
+ if ( c == character )
+ return true;
+ if ( c <= 0 ) // Silent failure: can't get document at this scope
+ return false;
+
+ in->get();
+ *tag += (char) c;
+ }
+ return false;
+}
+#endif
+
+// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The
+// "assign" optimization removes over 10% of the execution time.
+//
+const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding )
+{
+ // Oddly, not supported on some comilers,
+ //name->clear();
+ // So use this:
+ *name = "";
+ assert( p );
+
+ // Names start with letters or underscores.
+ // Of course, in unicode, tinyxml has no idea what a letter *is*. The
+ // algorithm is generous.
+ //
+ // After that, they can be letters, underscores, numbers,
+ // hyphens, or colons. (Colons are valid ony for namespaces,
+ // but tinyxml can't tell namespaces from names.)
+ if ( p && *p
+ && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) )
+ {
+ const char* start = p;
+ while( p && *p
+ && ( IsAlphaNum( (unsigned char ) *p, encoding )
+ || *p == '_'
+ || *p == '-'
+ || *p == '.'
+ || *p == ':' ) )
+ {
+ //(*name) += *p; // expensive
+ ++p;
+ }
+ if ( p-start > 0 ) {
+ name->assign( start, p-start );
+ }
+ return p;
+ }
+ return 0;
+}
+
+const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding )
+{
+ // Presume an entity, and pull it out.
+ TIXML_STRING ent;
+ int i;
+ *length = 0;
+
+ if ( *(p+1) && *(p+1) == '#' && *(p+2) )
+ {
+ unsigned long ucs = 0;
+ ptrdiff_t delta = 0;
+ unsigned mult = 1;
+
+ if ( *(p+2) == 'x' )
+ {
+ // Hexadecimal.
+ if ( !*(p+3) ) return 0;
+
+ const char* q = p+3;
+ q = strchr( q, ';' );
+
+ if ( !q || !*q ) return 0;
+
+ delta = q-p;
+ --q;
+
+ while ( *q != 'x' )
+ {
+ if ( *q >= '0' && *q <= '9' )
+ ucs += mult * (*q - '0');
+ else if ( *q >= 'a' && *q <= 'f' )
+ ucs += mult * (*q - 'a' + 10);
+ else if ( *q >= 'A' && *q <= 'F' )
+ ucs += mult * (*q - 'A' + 10 );
+ else
+ return 0;
+ mult *= 16;
+ --q;
+ }
+ }
+ else
+ {
+ // Decimal.
+ if ( !*(p+2) ) return 0;
+
+ const char* q = p+2;
+ q = strchr( q, ';' );
+
+ if ( !q || !*q ) return 0;
+
+ delta = q-p;
+ --q;
+
+ while ( *q != '#' )
+ {
+ if ( *q >= '0' && *q <= '9' )
+ ucs += mult * (*q - '0');
+ else
+ return 0;
+ mult *= 10;
+ --q;
+ }
+ }
+ if ( encoding == TIXML_ENCODING_UTF8 )
+ {
+ // convert the UCS to UTF-8
+ ConvertUTF32ToUTF8( ucs, value, length );
+ }
+ else
+ {
+ *value = (char)ucs;
+ *length = 1;
+ }
+ return p + delta + 1;
+ }
+
+ // Now try to match it.
+ for( i=0; i<NUM_ENTITY; ++i )
+ {
+ if ( strncmp( entity[i].str, p, entity[i].strLength ) == 0 )
+ {
+ assert( strlen( entity[i].str ) == entity[i].strLength );
+ *value = entity[i].chr;
+ *length = 1;
+ return ( p + entity[i].strLength );
+ }
+ }
+
+ // So it wasn't an entity, its unrecognized, or something like that.
+ *value = *p; // Don't put back the last one, since we return it!
+ //*length = 1; // Leave unrecognized entities - this doesn't really work.
+ // Just writes strange XML.
+ return p+1;
+}
+
+
+bool TiXmlBase::StringEqual( const char* p,
+ const char* tag,
+ bool ignoreCase,
+ TiXmlEncoding encoding )
+{
+ assert( p );
+ assert( tag );
+ if ( !p || !*p )
+ {
+ assert( 0 );
+ return false;
+ }
+
+ const char* q = p;
+
+ if ( ignoreCase )
+ {
+ while ( *q && *tag && ToLower( *q, encoding ) == ToLower( *tag, encoding ) )
+ {
+ ++q;
+ ++tag;
+ }
+
+ if ( *tag == 0 )
+ return true;
+ }
+ else
+ {
+ while ( *q && *tag && *q == *tag )
+ {
+ ++q;
+ ++tag;
+ }
+
+ if ( *tag == 0 ) // Have we found the end of the tag, and everything equal?
+ return true;
+ }
+ return false;
+}
+
+const char* TiXmlBase::ReadText( const char* p,
+ TIXML_STRING * text,
+ bool trimWhiteSpace,
+ const char* endTag,
+ bool caseInsensitive,
+ TiXmlEncoding encoding )
+{
+ *text = "";
+ if ( !trimWhiteSpace // certain tags always keep whitespace
+ || !condenseWhiteSpace ) // if true, whitespace is always kept
+ {
+ // Keep all the white space.
+ while ( p && *p
+ && !StringEqual( p, endTag, caseInsensitive, encoding )
+ )
+ {
+ int len;
+ char cArr[4] = { 0, 0, 0, 0 };
+ p = GetChar( p, cArr, &len, encoding );
+ text->append( cArr, len );
+ }
+ }
+ else
+ {
+ bool whitespace = false;
+
+ // Remove leading white space:
+ p = SkipWhiteSpace( p, encoding );
+ while ( p && *p
+ && !StringEqual( p, endTag, caseInsensitive, encoding ) )
+ {
+ if ( *p == '\r' || *p == '\n' )
+ {
+ whitespace = true;
+ ++p;
+ }
+ else if ( IsWhiteSpace( *p ) )
+ {
+ whitespace = true;
+ ++p;
+ }
+ else
+ {
+ // If we've found whitespace, add it before the
+ // new character. Any whitespace just becomes a space.
+ if ( whitespace )
+ {
+ (*text) += ' ';
+ whitespace = false;
+ }
+ int len;
+ char cArr[4] = { 0, 0, 0, 0 };
+ p = GetChar( p, cArr, &len, encoding );
+ if ( len == 1 )
+ (*text) += cArr[0]; // more efficient
+ else
+ text->append( cArr, len );
+ }
+ }
+ }
+ if ( p )
+ p += strlen( endTag );
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+
+void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ // The basic issue with a document is that we don't know what we're
+ // streaming. Read something presumed to be a tag (and hope), then
+ // identify it, and call the appropriate stream method on the tag.
+ //
+ // This "pre-streaming" will never read the closing ">" so the
+ // sub-tag can orient itself.
+
+ if ( !StreamTo( in, '<', tag ) )
+ {
+ SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ while ( in->good() )
+ {
+ int tagIndex = (int) tag->length();
+ while ( in->good() && in->peek() != '>' )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ break;
+ }
+ (*tag) += (char) c;
+ }
+
+ if ( in->good() )
+ {
+ // We now have something we presume to be a node of
+ // some sort. Identify it, and call the node to
+ // continue streaming.
+ TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING );
+
+ if ( node )
+ {
+ node->StreamIn( in, tag );
+ bool isElement = node->ToElement() != 0;
+ delete node;
+ node = 0;
+
+ // If this is the root element, we're done. Parsing will be
+ // done by the >> operator.
+ if ( isElement )
+ {
+ return;
+ }
+ }
+ else
+ {
+ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ }
+ }
+ // We should have returned sooner.
+ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN );
+}
+
+#endif
+
+const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding )
+{
+ ClearError();
+
+ // Parse away, at the document level. Since a document
+ // contains nothing but other tags, most of what happens
+ // here is skipping white space.
+ if ( !p || !*p )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ // Note that, for a document, this needs to come
+ // before the while space skip, so that parsing
+ // starts from the pointer we are given.
+ location.Clear();
+ if ( prevData )
+ {
+ location.row = prevData->cursor.row;
+ location.col = prevData->cursor.col;
+ }
+ else
+ {
+ location.row = 0;
+ location.col = 0;
+ }
+ TiXmlParsingData data( p, TabSize(), location.row, location.col );
+ location = data.Cursor();
+
+ if ( encoding == TIXML_ENCODING_UNKNOWN )
+ {
+ // Check for the Microsoft UTF-8 lead bytes.
+ const unsigned char* pU = (const unsigned char*)p;
+ if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0
+ && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1
+ && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 )
+ {
+ encoding = TIXML_ENCODING_UTF8;
+ useMicrosoftBOM = true;
+ }
+ }
+
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p )
+ {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return 0;
+ }
+
+ while ( p && *p )
+ {
+ TiXmlNode* node = Identify( p, encoding );
+ if ( node )
+ {
+ p = node->Parse( p, &data, encoding );
+ LinkEndChild( node );
+ }
+ else
+ {
+ break;
+ }
+
+ // Did we get encoding info?
+ if ( encoding == TIXML_ENCODING_UNKNOWN
+ && node->ToDeclaration() )
+ {
+ TiXmlDeclaration* dec = node->ToDeclaration();
+ const char* enc = dec->Encoding();
+ assert( enc );
+
+ if ( *enc == 0 )
+ encoding = TIXML_ENCODING_UTF8;
+ else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) )
+ encoding = TIXML_ENCODING_UTF8;
+ else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) )
+ encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice
+ else
+ encoding = TIXML_ENCODING_LEGACY;
+ }
+
+ p = SkipWhiteSpace( p, encoding );
+ }
+
+ // Was this empty?
+ if ( !firstChild ) {
+ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding );
+ return 0;
+ }
+
+ // All is well.
+ return p;
+}
+
+void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ // The first error in a chain is more accurate - don't set again!
+ if ( error )
+ return;
+
+ assert( err > 0 && err < TIXML_ERROR_STRING_COUNT );
+ error = true;
+ errorId = err;
+ errorDesc = errorString[ errorId ];
+
+ errorLocation.Clear();
+ if ( pError && data )
+ {
+ data->Stamp( pError, encoding );
+ errorLocation = data->Cursor();
+ }
+}
+
+
+TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding )
+{
+ TiXmlNode* returnNode = 0;
+
+ p = SkipWhiteSpace( p, encoding );
+ if( !p || !*p || *p != '<' )
+ {
+ return 0;
+ }
+
+ TiXmlDocument* doc = GetDocument();
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( !p || !*p )
+ {
+ return 0;
+ }
+
+ // What is this thing?
+ // - Elements start with a letter or underscore, but xml is reserved.
+ // - Comments: <!--
+ // - Decleration: <?xml
+ // - Everthing else is unknown to tinyxml.
+ //
+
+ const char* xmlHeader = { "<?xml" };
+ const char* commentHeader = { "<!--" };
+ const char* dtdHeader = { "<!" };
+ const char* cdataHeader = { "<![CDATA[" };
+
+ if ( StringEqual( p, xmlHeader, true, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Declaration\n" );
+ #endif
+ returnNode = new TiXmlDeclaration();
+ }
+ else if ( StringEqual( p, commentHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Comment\n" );
+ #endif
+ returnNode = new TiXmlComment();
+ }
+ else if ( StringEqual( p, cdataHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing CDATA\n" );
+ #endif
+ TiXmlText* text = new TiXmlText( "" );
+ text->SetCDATA( true );
+ returnNode = text;
+ }
+ else if ( StringEqual( p, dtdHeader, false, encoding ) )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Unknown(1)\n" );
+ #endif
+ returnNode = new TiXmlUnknown();
+ }
+ else if ( IsAlpha( *(p+1), encoding )
+ || *(p+1) == '_' )
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Element\n" );
+ #endif
+ returnNode = new TiXmlElement( "" );
+ }
+ else
+ {
+ #ifdef DEBUG_PARSER
+ TIXML_LOG( "XML parsing Unknown(2)\n" );
+ #endif
+ returnNode = new TiXmlUnknown();
+ }
+
+ if ( returnNode )
+ {
+ // Set the parent, so it can report errors
+ returnNode->parent = this;
+ }
+ else
+ {
+ if ( doc )
+ doc->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN );
+ }
+ return returnNode;
+}
+
+#ifdef TIXML_USE_STL
+
+void TiXmlElement::StreamIn (std::istream * in, TIXML_STRING * tag)
+{
+ // We're called with some amount of pre-parsing. That is, some of "this"
+ // element is in "tag". Go ahead and stream to the closing ">"
+ while( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c ;
+
+ if ( c == '>' )
+ break;
+ }
+
+ if ( tag->length() < 3 ) return;
+
+ // Okay...if we are a "/>" tag, then we're done. We've read a complete tag.
+ // If not, identify and stream.
+
+ if ( tag->at( tag->length() - 1 ) == '>'
+ && tag->at( tag->length() - 2 ) == '/' )
+ {
+ // All good!
+ return;
+ }
+ else if ( tag->at( tag->length() - 1 ) == '>' )
+ {
+ // There is more. Could be:
+ // text
+ // cdata text (which looks like another node)
+ // closing tag
+ // another node.
+ for ( ;; )
+ {
+ StreamWhiteSpace( in, tag );
+
+ // Do we have text?
+ if ( in->good() && in->peek() != '<' )
+ {
+ // Yep, text.
+ TiXmlText text( "" );
+ text.StreamIn( in, tag );
+
+ // What follows text is a closing tag or another node.
+ // Go around again and figure it out.
+ continue;
+ }
+
+ // We now have either a closing tag...or another node.
+ // We should be at a "<", regardless.
+ if ( !in->good() ) return;
+ assert( in->peek() == '<' );
+ int tagIndex = (int) tag->length();
+
+ bool closingTag = false;
+ bool firstCharFound = false;
+
+ for( ;; )
+ {
+ if ( !in->good() )
+ return;
+
+ int c = in->peek();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ if ( c == '>' )
+ break;
+
+ *tag += (char) c;
+ in->get();
+
+ // Early out if we find the CDATA id.
+ if ( c == '[' && tag->size() >= 9 )
+ {
+ size_t len = tag->size();
+ const char* start = tag->c_str() + len - 9;
+ if ( strcmp( start, "<![CDATA[" ) == 0 ) {
+ assert( !closingTag );
+ break;
+ }
+ }
+
+ if ( !firstCharFound && c != '<' && !IsWhiteSpace( c ) )
+ {
+ firstCharFound = true;
+ if ( c == '/' )
+ closingTag = true;
+ }
+ }
+ // If it was a closing tag, then read in the closing '>' to clean up the input stream.
+ // If it was not, the streaming will be done by the tag.
+ if ( closingTag )
+ {
+ if ( !in->good() )
+ return;
+
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ assert( c == '>' );
+ *tag += (char) c;
+
+ // We are done, once we've found our closing tag.
+ return;
+ }
+ else
+ {
+ // If not a closing tag, id it, and stream.
+ const char* tagloc = tag->c_str() + tagIndex;
+ TiXmlNode* node = Identify( tagloc, TIXML_DEFAULT_ENCODING );
+ if ( !node )
+ return;
+ node->StreamIn( in, tag );
+ delete node;
+ node = 0;
+
+ // No return: go around from the beginning: text, closing tag, or node.
+ }
+ }
+ }
+}
+#endif
+
+const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ p = SkipWhiteSpace( p, encoding );
+ TiXmlDocument* document = GetDocument();
+
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, 0, 0, encoding );
+ return 0;
+ }
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+
+ if ( *p != '<' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, p, data, encoding );
+ return 0;
+ }
+
+ p = SkipWhiteSpace( p+1, encoding );
+
+ // Read the name.
+ const char* pErr = p;
+
+ p = ReadName( p, &value, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, pErr, data, encoding );
+ return 0;
+ }
+
+ TIXML_STRING endTag ("</");
+ endTag += value;
+ endTag += ">";
+
+ // Check for and read attributes. Also look for an empty
+ // tag or an end tag.
+ while ( p && *p )
+ {
+ pErr = p;
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );
+ return 0;
+ }
+ if ( *p == '/' )
+ {
+ ++p;
+ // Empty tag.
+ if ( *p != '>' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_EMPTY, p, data, encoding );
+ return 0;
+ }
+ return (p+1);
+ }
+ else if ( *p == '>' )
+ {
+ // Done with attributes (if there were any.)
+ // Read the value -- which can include other
+ // elements -- read the end tag, and return.
+ ++p;
+ p = ReadValue( p, data, encoding ); // Note this is an Element method, and will set the error if one happens.
+ if ( !p || !*p ) {
+ // We were looking for the end tag, but found nothing.
+ // Fix for [ 1663758 ] Failure to report error on bad XML
+ if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
+ return 0;
+ }
+
+ // We should find the end tag now
+ if ( StringEqual( p, endTag.c_str(), false, encoding ) )
+ {
+ p += endTag.length();
+ return p;
+ }
+ else
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
+ return 0;
+ }
+ }
+ else
+ {
+ // Try to read an attribute:
+ TiXmlAttribute* attrib = new TiXmlAttribute();
+ if ( !attrib )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, pErr, data, encoding );
+ return 0;
+ }
+
+ attrib->SetDocument( document );
+ pErr = p;
+ p = attrib->Parse( p, data, encoding );
+
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );
+ delete attrib;
+ return 0;
+ }
+
+ // Handle the strange case of double attributes:
+ #ifdef TIXML_USE_STL
+ TiXmlAttribute* node = attributeSet.Find( attrib->NameTStr() );
+ #else
+ TiXmlAttribute* node = attributeSet.Find( attrib->Name() );
+ #endif
+ if ( node )
+ {
+ node->SetValue( attrib->Value() );
+ delete attrib;
+ return 0;
+ }
+
+ attributeSet.Add( attrib );
+ }
+ }
+ return p;
+}
+
+
+const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+
+ // Read in text and elements in any order.
+ const char* pWithWhiteSpace = p;
+ p = SkipWhiteSpace( p, encoding );
+
+ while ( p && *p )
+ {
+ if ( *p != '<' )
+ {
+ // Take what we have, make a text element.
+ TiXmlText* textNode = new TiXmlText( "" );
+
+ if ( !textNode )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, encoding );
+ return 0;
+ }
+
+ if ( TiXmlBase::IsWhiteSpaceCondensed() )
+ {
+ p = textNode->Parse( p, data, encoding );
+ }
+ else
+ {
+ // Special case: we want to keep the white space
+ // so that leading spaces aren't removed.
+ p = textNode->Parse( pWithWhiteSpace, data, encoding );
+ }
+
+ if ( !textNode->Blank() )
+ LinkEndChild( textNode );
+ else
+ delete textNode;
+ }
+ else
+ {
+ // We hit a '<'
+ // Have we hit a new element or an end tag? This could also be
+ // a TiXmlText in the "CDATA" style.
+ if ( StringEqual( p, "</", false, encoding ) )
+ {
+ return p;
+ }
+ else
+ {
+ TiXmlNode* node = Identify( p, encoding );
+ if ( node )
+ {
+ p = node->Parse( p, data, encoding );
+ LinkEndChild( node );
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ pWithWhiteSpace = p;
+ p = SkipWhiteSpace( p, encoding );
+ }
+
+ if ( !p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ELEMENT_VALUE, 0, 0, encoding );
+ }
+ return p;
+}
+
+
+#ifdef TIXML_USE_STL
+void TiXmlUnknown::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c;
+
+ if ( c == '>' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+
+const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ if ( !p || !*p || *p != '<' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, p, data, encoding );
+ return 0;
+ }
+ ++p;
+ value = "";
+
+ while ( p && *p && *p != '>' )
+ {
+ value += *p;
+ ++p;
+ }
+
+ if ( !p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding );
+ }
+ if ( *p == '>' )
+ return p+1;
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlComment::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ (*tag) += (char) c;
+
+ if ( c == '>'
+ && tag->at( tag->length() - 2 ) == '-'
+ && tag->at( tag->length() - 3 ) == '-' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+
+const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ TiXmlDocument* document = GetDocument();
+ value = "";
+
+ p = SkipWhiteSpace( p, encoding );
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ const char* startTag = "<!--";
+ const char* endTag = "-->";
+
+ if ( !StringEqual( p, startTag, false, encoding ) )
+ {
+ document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding );
+ return 0;
+ }
+ p += strlen( startTag );
+
+ // [ 1475201 ] TinyXML parses entities in comments
+ // Oops - ReadText doesn't work, because we don't want to parse the entities.
+ // p = ReadText( p, &value, false, endTag, false, encoding );
+ //
+ // from the XML spec:
+ /*
+ [Definition: Comments may appear anywhere in a document outside other markup; in addition,
+ they may appear within the document type declaration at places allowed by the grammar.
+ They are not part of the document's character data; an XML processor MAY, but need not,
+ make it possible for an application to retrieve the text of comments. For compatibility,
+ the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity
+ references MUST NOT be recognized within comments.
+
+ An example of a comment:
+
+ <!-- declarations for <head> & <body> -->
+ */
+
+ value = "";
+ // Keep all the white space.
+ while ( p && *p && !StringEqual( p, endTag, false, encoding ) )
+ {
+ value.append( p, 1 );
+ ++p;
+ }
+ if ( p )
+ p += strlen( endTag );
+
+ return p;
+}
+
+
+const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p ) return 0;
+
+// int tabsize = 4;
+// if ( document )
+// tabsize = document->TabSize();
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+ // Read the name, the '=' and the value.
+ const char* pErr = p;
+ p = ReadName( p, &name, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );
+ return 0;
+ }
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p || *p != '=' )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+
+ ++p; // skip '='
+ p = SkipWhiteSpace( p, encoding );
+ if ( !p || !*p )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+
+ const char* end;
+ const char SINGLE_QUOTE = '\'';
+ const char DOUBLE_QUOTE = '\"';
+
+ if ( *p == SINGLE_QUOTE )
+ {
+ ++p;
+ end = "\'"; // single quote in string
+ p = ReadText( p, &value, false, end, false, encoding );
+ }
+ else if ( *p == DOUBLE_QUOTE )
+ {
+ ++p;
+ end = "\""; // double quote in string
+ p = ReadText( p, &value, false, end, false, encoding );
+ }
+ else
+ {
+ // All attribute values should be in single or double quotes.
+ // But this is such a common error that the parser will try
+ // its best, even without them.
+ value = "";
+ while ( p && *p // existence
+ && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace
+ && *p != '/' && *p != '>' ) // tag end
+ {
+ if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) {
+ // [ 1451649 ] Attribute values with trailing quotes not handled correctly
+ // We did not have an opening quote but seem to have a
+ // closing one. Give up and throw an error.
+ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding );
+ return 0;
+ }
+ value += *p;
+ ++p;
+ }
+ }
+ return p;
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->peek();
+ if ( !cdata && (c == '<' ) )
+ {
+ return;
+ }
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+
+ (*tag) += (char) c;
+ in->get(); // "commits" the peek made above
+
+ if ( cdata && c == '>' && tag->size() >= 3 ) {
+ size_t len = tag->size();
+ if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) {
+ // terminator of cdata.
+ return;
+ }
+ }
+ }
+}
+#endif
+
+const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
+{
+ value = "";
+ TiXmlDocument* document = GetDocument();
+
+ if ( data )
+ {
+ data->Stamp( p, encoding );
+ location = data->Cursor();
+ }
+
+ const char* const startTag = "<![CDATA[";
+ const char* const endTag = "]]>";
+
+ if ( cdata || StringEqual( p, startTag, false, encoding ) )
+ {
+ cdata = true;
+
+ if ( !StringEqual( p, startTag, false, encoding ) )
+ {
+ document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding );
+ return 0;
+ }
+ p += strlen( startTag );
+
+ // Keep all the white space, ignore the encoding, etc.
+ while ( p && *p
+ && !StringEqual( p, endTag, false, encoding )
+ )
+ {
+ value += *p;
+ ++p;
+ }
+
+ TIXML_STRING dummy;
+ p = ReadText( p, &dummy, false, endTag, false, encoding );
+ return p;
+ }
+ else
+ {
+ bool ignoreWhite = true;
+
+ const char* end = "<";
+ p = ReadText( p, &value, ignoreWhite, end, false, encoding );
+ if ( p )
+ return p-1; // don't truncate the '<'
+ return 0;
+ }
+}
+
+#ifdef TIXML_USE_STL
+void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag )
+{
+ while ( in->good() )
+ {
+ int c = in->get();
+ if ( c <= 0 )
+ {
+ TiXmlDocument* document = GetDocument();
+ if ( document )
+ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN );
+ return;
+ }
+ (*tag) += (char) c;
+
+ if ( c == '>' )
+ {
+ // All is well.
+ return;
+ }
+ }
+}
+#endif
+
+const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding )
+{
+ p = SkipWhiteSpace( p, _encoding );
+ // Find the beginning, find the end, and look for
+ // the stuff in-between.
+ TiXmlDocument* document = GetDocument();
+ if ( !p || !*p || !StringEqual( p, "<?xml", true, _encoding ) )
+ {
+ if ( document ) document->SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding );
+ return 0;
+ }
+ if ( data )
+ {
+ data->Stamp( p, _encoding );
+ location = data->Cursor();
+ }
+ p += 5;
+
+ version = "";
+ encoding = "";
+ standalone = "";
+
+ while ( p && *p )
+ {
+ if ( *p == '>' )
+ {
+ ++p;
+ return p;
+ }
+
+ p = SkipWhiteSpace( p, _encoding );
+ if ( StringEqual( p, "version", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ version = attrib.Value();
+ }
+ else if ( StringEqual( p, "encoding", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ encoding = attrib.Value();
+ }
+ else if ( StringEqual( p, "standalone", true, _encoding ) )
+ {
+ TiXmlAttribute attrib;
+ p = attrib.Parse( p, data, _encoding );
+ standalone = attrib.Value();
+ }
+ else
+ {
+ // Read over whatever it is.
+ while( p && *p && *p != '>' && !IsWhiteSpace( *p ) )
+ ++p;
+ }
+ }
+ return 0;
+}
+
+bool TiXmlText::Blank() const
+{
+ for ( unsigned i=0; i<value.length(); i++ )
+ if ( !IsWhiteSpace( value[i] ) )
+ return false;
+ return true;
+}
+
diff --git a/extern/sdl4ogre/CMakeLists.txt b/extern/sdl4ogre/CMakeLists.txt
new file mode 100644
index 0000000000..fb0832f712
--- /dev/null
+++ b/extern/sdl4ogre/CMakeLists.txt
@@ -0,0 +1,25 @@
+set(SDL4OGRE_LIBRARY "sdl4ogre")
+
+# Sources
+
+set(SDL4OGRE_SOURCE_FILES
+ sdlinputwrapper.cpp
+ sdlcursormanager.cpp
+ sdlwindowhelper.cpp
+)
+
+if (APPLE)
+ set(SDL4OGRE_SOURCE_FILES ${SDL4OGRE_SOURCE_FILES} osx_utils.mm)
+endif ()
+
+set(SDL4OGRE_HEADER_FILES
+ OISCompat.h
+ cursormanager.hpp
+)
+
+add_library(${SDL4OGRE_LIBRARY} STATIC ${SDL4OGRE_SOURCE_FILES} ${SDL4OGRE_HEADER_FILES})
+
+link_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+
+target_link_libraries(${SDL4OGRE_LIBRARY} ${SDL2_LIBRARY})
diff --git a/extern/sdl4ogre/OISCompat.h b/extern/sdl4ogre/OISCompat.h
new file mode 100644
index 0000000000..3cffa143db
--- /dev/null
+++ b/extern/sdl4ogre/OISCompat.h
@@ -0,0 +1,159 @@
+#ifndef _OIS_SDL_COMPAT_H
+#define _OIS_SDL_COMPAT_H
+
+#include <SDL_events.h>
+#include <SDL_types.h>
+
+namespace OIS
+{
+//! Keyboard scan codes
+enum KeyCode
+{
+ KC_UNASSIGNED = 0x00,
+ KC_ESCAPE = 0x01,
+ KC_1 = 0x02,
+ KC_2 = 0x03,
+ KC_3 = 0x04,
+ KC_4 = 0x05,
+ KC_5 = 0x06,
+ KC_6 = 0x07,
+ KC_7 = 0x08,
+ KC_8 = 0x09,
+ KC_9 = 0x0A,
+ KC_0 = 0x0B,
+ KC_MINUS = 0x0C, // - on main keyboard
+ KC_EQUALS = 0x0D,
+ KC_BACK = 0x0E, // backspace
+ KC_TAB = 0x0F,
+ KC_Q = 0x10,
+ KC_W = 0x11,
+ KC_E = 0x12,
+ KC_R = 0x13,
+ KC_T = 0x14,
+ KC_Y = 0x15,
+ KC_U = 0x16,
+ KC_I = 0x17,
+ KC_O = 0x18,
+ KC_P = 0x19,
+ KC_LBRACKET = 0x1A,
+ KC_RBRACKET = 0x1B,
+ KC_RETURN = 0x1C, // Enter on main keyboard
+ KC_LCONTROL = 0x1D,
+ KC_A = 0x1E,
+ KC_S = 0x1F,
+ KC_D = 0x20,
+ KC_F = 0x21,
+ KC_G = 0x22,
+ KC_H = 0x23,
+ KC_J = 0x24,
+ KC_K = 0x25,
+ KC_L = 0x26,
+ KC_SEMICOLON = 0x27,
+ KC_APOSTROPHE = 0x28,
+ KC_GRAVE = 0x29, // accent
+ KC_LSHIFT = 0x2A,
+ KC_BACKSLASH = 0x2B,
+ KC_Z = 0x2C,
+ KC_X = 0x2D,
+ KC_C = 0x2E,
+ KC_V = 0x2F,
+ KC_B = 0x30,
+ KC_N = 0x31,
+ KC_M = 0x32,
+ KC_COMMA = 0x33,
+ KC_PERIOD = 0x34, // . on main keyboard
+ KC_SLASH = 0x35, // / on main keyboard
+ KC_RSHIFT = 0x36,
+ KC_MULTIPLY = 0x37, // * on numeric keypad
+ KC_LMENU = 0x38, // left Alt
+ KC_SPACE = 0x39,
+ KC_CAPITAL = 0x3A,
+ KC_F1 = 0x3B,
+ KC_F2 = 0x3C,
+ KC_F3 = 0x3D,
+ KC_F4 = 0x3E,
+ KC_F5 = 0x3F,
+ KC_F6 = 0x40,
+ KC_F7 = 0x41,
+ KC_F8 = 0x42,
+ KC_F9 = 0x43,
+ KC_F10 = 0x44,
+ KC_NUMLOCK = 0x45,
+ KC_SCROLL = 0x46, // Scroll Lock
+ KC_NUMPAD7 = 0x47,
+ KC_NUMPAD8 = 0x48,
+ KC_NUMPAD9 = 0x49,
+ KC_SUBTRACT = 0x4A, // - on numeric keypad
+ KC_NUMPAD4 = 0x4B,
+ KC_NUMPAD5 = 0x4C,
+ KC_NUMPAD6 = 0x4D,
+ KC_ADD = 0x4E, // + on numeric keypad
+ KC_NUMPAD1 = 0x4F,
+ KC_NUMPAD2 = 0x50,
+ KC_NUMPAD3 = 0x51,
+ KC_NUMPAD0 = 0x52,
+ KC_DECIMAL = 0x53, // . on numeric keypad
+ KC_OEM_102 = 0x56, // < > | on UK/Germany keyboards
+ KC_F11 = 0x57,
+ KC_F12 = 0x58,
+ KC_F13 = 0x64, // (NEC PC98)
+ KC_F14 = 0x65, // (NEC PC98)
+ KC_F15 = 0x66, // (NEC PC98)
+ KC_KANA = 0x70, // (Japanese keyboard)
+ KC_ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
+ KC_CONVERT = 0x79, // (Japanese keyboard)
+ KC_NOCONVERT = 0x7B, // (Japanese keyboard)
+ KC_YEN = 0x7D, // (Japanese keyboard)
+ KC_ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
+ KC_NUMPADEQUALS= 0x8D, // = on numeric keypad (NEC PC98)
+ KC_PREVTRACK = 0x90, // Previous Track (KC_CIRCUMFLEX on Japanese keyboard)
+ KC_AT = 0x91, // (NEC PC98)
+ KC_COLON = 0x92, // (NEC PC98)
+ KC_UNDERLINE = 0x93, // (NEC PC98)
+ KC_KANJI = 0x94, // (Japanese keyboard)
+ KC_STOP = 0x95, // (NEC PC98)
+ KC_AX = 0x96, // (Japan AX)
+ KC_UNLABELED = 0x97, // (J3100)
+ KC_NEXTTRACK = 0x99, // Next Track
+ KC_NUMPADENTER = 0x9C, // Enter on numeric keypad
+ KC_RCONTROL = 0x9D,
+ KC_MUTE = 0xA0, // Mute
+ KC_CALCULATOR = 0xA1, // Calculator
+ KC_PLAYPAUSE = 0xA2, // Play / Pause
+ KC_MEDIASTOP = 0xA4, // Media Stop
+ KC_VOLUMEDOWN = 0xAE, // Volume -
+ KC_VOLUMEUP = 0xB0, // Volume +
+ KC_WEBHOME = 0xB2, // Web home
+ KC_NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
+ KC_DIVIDE = 0xB5, // / on numeric keypad
+ KC_SYSRQ = 0xB7,
+ KC_RMENU = 0xB8, // right Alt
+ KC_PAUSE = 0xC5, // Pause
+ KC_HOME = 0xC7, // Home on arrow keypad
+ KC_UP = 0xC8, // UpArrow on arrow keypad
+ KC_PGUP = 0xC9, // PgUp on arrow keypad
+ KC_LEFT = 0xCB, // LeftArrow on arrow keypad
+ KC_RIGHT = 0xCD, // RightArrow on arrow keypad
+ KC_END = 0xCF, // End on arrow keypad
+ KC_DOWN = 0xD0, // DownArrow on arrow keypad
+ KC_PGDOWN = 0xD1, // PgDn on arrow keypad
+ KC_INSERT = 0xD2, // Insert on arrow keypad
+ KC_DELETE = 0xD3, // Delete on arrow keypad
+ KC_LWIN = 0xDB, // Left Windows key
+ KC_RWIN = 0xDC, // Right Windows key
+ KC_APPS = 0xDD, // AppMenu key
+ KC_POWER = 0xDE, // System Power
+ KC_SLEEP = 0xDF, // System Sleep
+ KC_WAKE = 0xE3, // System Wake
+ KC_WEBSEARCH = 0xE5, // Web Search
+ KC_WEBFAVORITES= 0xE6, // Web Favorites
+ KC_WEBREFRESH = 0xE7, // Web Refresh
+ KC_WEBSTOP = 0xE8, // Web Stop
+ KC_WEBFORWARD = 0xE9, // Web Forward
+ KC_WEBBACK = 0xEA, // Web Back
+ KC_MYCOMPUTER = 0xEB, // My Computer
+ KC_MAIL = 0xEC, // Mail
+ KC_MEDIASELECT = 0xED // Media Select
+};
+}
+#endif
diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp
new file mode 100644
index 0000000000..f45c5cdc24
--- /dev/null
+++ b/extern/sdl4ogre/cursormanager.hpp
@@ -0,0 +1,33 @@
+#ifndef _SDL4OGRE_CURSOR_MANAGER_H
+#define _SDL4OGRE_CURSOR_MANAGER_H
+
+#include <SDL_types.h>
+#include <string>
+
+#include <OgreTexture.h>
+#include <OgrePrerequisites.h>
+
+namespace SFO
+{
+class CursorManager
+{
+public:
+ virtual ~CursorManager(){}
+
+ /// \brief Tell the manager that the cursor has changed, giving the
+ /// name of the cursor we changed to ("arrow", "ibeam", etc)
+ /// \return Whether the manager is interested in more information about the cursor
+ virtual bool cursorChanged(const std::string &name) = 0;
+
+ /// \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;
+};
+}
+
+#endif
diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h
new file mode 100644
index 0000000000..e6e8434cb9
--- /dev/null
+++ b/extern/sdl4ogre/events.h
@@ -0,0 +1,78 @@
+#ifndef _SFO_EVENTS_H
+#define _SFO_EVENTS_H
+
+#include <SDL.h>
+
+
+////////////
+// Events //
+////////////
+
+namespace SFO {
+
+/** Extended mouse event struct where we treat the wheel like an axis, like everyone expects */
+struct MouseMotionEvent : SDL_MouseMotionEvent {
+
+ Sint32 zrel;
+ Sint32 z;
+};
+
+
+///////////////
+// Listeners //
+///////////////
+
+class MouseListener
+{
+public:
+ virtual ~MouseListener() {}
+ virtual bool mouseMoved( const MouseMotionEvent &arg ) = 0;
+ virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0;
+ virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0;
+};
+
+class KeyListener
+{
+public:
+ virtual ~KeyListener() {}
+ virtual void textInput (const SDL_TextInputEvent& arg) {}
+ virtual bool keyPressed(const SDL_KeyboardEvent &arg) = 0;
+ virtual bool keyReleased(const SDL_KeyboardEvent &arg) = 0;
+};
+
+class JoyListener
+{
+public:
+ virtual ~JoyListener() {}
+ /** @remarks Joystick button down event */
+ virtual bool buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0;
+
+ /** @remarks Joystick button up event */
+ virtual bool buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0;
+
+ /** @remarks Joystick axis moved event */
+ virtual bool axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0;
+
+ //-- Not so common control events, so are not required --//
+
+ //! Joystick Event, and povID
+ virtual bool povMoved( const SDL_JoyHatEvent &arg, int index) {return true;}
+};
+
+class WindowListener
+{
+public:
+ virtual ~WindowListener() {}
+
+ /** @remarks The window's visibility changed */
+ virtual void windowVisibilityChange( bool visible ) {};
+
+ /** @remarks The window got / lost input focus */
+ virtual void windowFocusChange( bool have_focus ) {}
+
+ virtual void windowResized (int x, int y) {}
+};
+
+}
+
+#endif
diff --git a/extern/sdl4ogre/osx_utils.h b/extern/sdl4ogre/osx_utils.h
new file mode 100644
index 0000000000..48149827ab
--- /dev/null
+++ b/extern/sdl4ogre/osx_utils.h
@@ -0,0 +1,12 @@
+#ifndef SDL4OGRE_OSX_UTILS_H
+#define SDL4OGRE_OSX_UTILS_H
+
+#include <SDL_syswm.h>
+
+namespace SFO {
+
+extern unsigned long WindowContentViewHandle(SDL_SysWMinfo &info);
+
+}
+
+#endif // SDL4OGRE_OSX_UTILS_H
diff --git a/extern/sdl4ogre/osx_utils.mm b/extern/sdl4ogre/osx_utils.mm
new file mode 100644
index 0000000000..4069959cbd
--- /dev/null
+++ b/extern/sdl4ogre/osx_utils.mm
@@ -0,0 +1,15 @@
+#include "osx_utils.h"
+#import <AppKit/NSWindow.h>
+
+
+namespace SFO {
+
+unsigned long WindowContentViewHandle(SDL_SysWMinfo &info)
+{
+ NSWindow *window = info.info.cocoa.window;
+ NSView *view = [window contentView];
+
+ return (unsigned long)view;
+}
+
+}
diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp
new file mode 100644
index 0000000000..d14a9ffa09
--- /dev/null
+++ b/extern/sdl4ogre/sdlcursormanager.cpp
@@ -0,0 +1,177 @@
+#include "sdlcursormanager.hpp"
+
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRoot.h>
+
+#include <openengine/ogre/imagerotate.hpp>
+
+namespace SFO
+{
+
+ SDLCursorManager::SDLCursorManager() :
+ mEnabled(false),
+ mCursorVisible(false),
+ mInitialized(false)
+ {
+ }
+
+ SDLCursorManager::~SDLCursorManager()
+ {
+ CursorMap::const_iterator curs_iter = mCursorMap.begin();
+
+ while(curs_iter != mCursorMap.end())
+ {
+ SDL_FreeCursor(curs_iter->second);
+ ++curs_iter;
+ }
+
+ mCursorMap.clear();
+ }
+
+ void SDLCursorManager::setEnabled(bool enabled)
+ {
+ if(mInitialized && enabled == mEnabled)
+ return;
+
+ mInitialized = true;
+ mEnabled = enabled;
+
+ //turn on hardware cursors
+ if(enabled)
+ {
+ _setGUICursor(mCurrentCursor);
+ }
+ //turn off hardware cursors
+ else
+ {
+ SDL_ShowCursor(SDL_FALSE);
+ }
+ }
+
+ bool SDLCursorManager::cursorChanged(const std::string &name)
+ {
+ mCurrentCursor = name;
+
+ CursorMap::const_iterator curs_iter = mCursorMap.find(name);
+
+ //we have this cursor
+ if(curs_iter != mCursorMap.end())
+ {
+ _setGUICursor(name);
+
+ return false;
+ }
+ else
+ {
+ //they should get back to us with more info
+ return true;
+ }
+ }
+
+ 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);
+ }
+
+ void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y)
+ {
+ _createCursorFromResource(name, rotDegrees, tex, size_x, size_y, hotspot_x, hotspot_y);
+ }
+
+ /// \brief creates an SDL cursor from an Ogre texture
+ void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y)
+ {
+ if (mCursorMap.find(name) != mCursorMap.end())
+ return;
+
+ std::string tempName = tex->getName() + "_rotated";
+
+ // we use a render target to uncompress the DDS texture
+ // just blitting doesn't seem to work on D3D9
+ OEngine::Render::ImageRotate::rotate(tex->getName(), tempName, -rotDegrees);
+
+ Ogre::TexturePtr resultTexture = Ogre::TextureManager::getSingleton().getByName(tempName);
+
+ // now blit to memory
+ Ogre::Image destImage;
+ resultTexture->convertToImage(destImage);
+
+ SDL_Surface* surf = SDL_CreateRGBSurface(0,size_x,size_y,32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF);
+
+
+ //copy the Ogre texture to an SDL surface
+ for(size_t x = 0; x < size_x; ++x)
+ {
+ for(size_t y = 0; y < size_y; ++y)
+ {
+ Ogre::ColourValue clr = destImage.getColourAt(x, y, 0);
+
+ //set the pixel on the SDL surface to the same value as the Ogre texture's
+ _putPixel(surf, x, y, SDL_MapRGBA(surf->format, clr.r*255, clr.g*255, clr.b*255, clr.a*255));
+ }
+ }
+
+ //set the cursor and store it for later
+ SDL_Cursor* curs = SDL_CreateColorCursor(surf, hotspot_x, hotspot_y);
+ mCursorMap.insert(CursorMap::value_type(std::string(name), curs));
+
+ //clean up
+ SDL_FreeSurface(surf);
+ Ogre::TextureManager::getSingleton().remove(tempName);
+
+ _setGUICursor(name);
+ }
+
+ void SDLCursorManager::_putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
+ {
+ int bpp = surface->format->BytesPerPixel;
+ /* Here p is the address to the pixel we want to set */
+ Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
+
+ switch(bpp) {
+ case 1:
+ *p = pixel;
+ break;
+
+ case 2:
+ *(Uint16 *)p = pixel;
+ break;
+
+ case 3:
+ if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
+ p[0] = (pixel >> 16) & 0xff;
+ p[1] = (pixel >> 8) & 0xff;
+ p[2] = pixel & 0xff;
+ } else {
+ p[0] = pixel & 0xff;
+ p[1] = (pixel >> 8) & 0xff;
+ p[2] = (pixel >> 16) & 0xff;
+ }
+ break;
+
+ case 4:
+ *(Uint32 *)p = pixel;
+ break;
+ }
+ }
+}
diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp
new file mode 100644
index 0000000000..8940220d41
--- /dev/null
+++ b/extern/sdl4ogre/sdlcursormanager.hpp
@@ -0,0 +1,41 @@
+#ifndef _SDL4OGRE_CURSORMANAGER_H
+#define _SDL4OGRE_CURSORMANAGER_H
+
+#include <SDL.h>
+
+#include "cursormanager.hpp"
+#include <map>
+
+namespace SFO
+{
+ class SDLCursorManager :
+ public CursorManager
+ {
+ public:
+ SDLCursorManager();
+ virtual ~SDLCursorManager();
+
+ virtual void setEnabled(bool enabled);
+
+ 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;
+
+ std::string mCurrentCursor;
+ bool mEnabled;
+ bool mInitialized;
+ bool mCursorVisible;
+ };
+}
+
+#endif
diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp
new file mode 100644
index 0000000000..931d6aca30
--- /dev/null
+++ b/extern/sdl4ogre/sdlinputwrapper.cpp
@@ -0,0 +1,418 @@
+#include "sdlinputwrapper.hpp"
+#include <SDL_syswm.h>
+
+#include <OgrePlatform.h>
+#include <OgreRoot.h>
+
+
+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) :
+ mSDLWindow(window),
+ mOgreWindow(ogreWindow),
+ mWarpCompensate(false),
+ mMouseRelative(false),
+ mGrabPointer(false),
+ mWrapPointer(false),
+ mMouseZ(0),
+ mMouseY(0),
+ mMouseX(0),
+ mMouseInWindow(true),
+ mJoyListener(NULL),
+ mKeyboardListener(NULL),
+ mMouseListener(NULL),
+ mWindowListener(NULL)
+ {
+ _setupOISKeys();
+ }
+
+ InputWrapper::~InputWrapper()
+ {
+ if(mSDLWindow != NULL)
+ SDL_DestroyWindow(mSDLWindow);
+ mSDLWindow = NULL;
+ }
+
+ void InputWrapper::capture(bool windowEventsOnly)
+ {
+ SDL_PumpEvents();
+
+ SDL_Event evt;
+
+ if (windowEventsOnly)
+ {
+ // During loading, just handle window events, and keep others for later
+ while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT))
+ handleWindowEvent(evt);
+ return;
+ }
+
+ while(SDL_PollEvent(&evt))
+ {
+ switch(evt.type)
+ {
+ case SDL_MOUSEMOTION:
+ //ignore this if it happened due to a warp
+ if(!_handleWarpMotion(evt.motion))
+ {
+ mMouseListener->mouseMoved(_packageMouseMotion(evt));
+
+ //try to keep the mouse inside the window
+ _wrapMousePointer(evt.motion);
+ }
+ break;
+ case SDL_MOUSEWHEEL:
+ mMouseListener->mouseMoved(_packageMouseMotion(evt));
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ mMouseListener->mousePressed(evt.button, evt.button.button);
+ break;
+ case SDL_MOUSEBUTTONUP:
+ mMouseListener->mouseReleased(evt.button, evt.button.button);
+ break;
+ case SDL_KEYDOWN:
+ if (!evt.key.repeat)
+ mKeyboardListener->keyPressed(evt.key);
+ break;
+ case SDL_KEYUP:
+ if (!evt.key.repeat)
+ mKeyboardListener->keyReleased(evt.key);
+ break;
+ case SDL_TEXTINPUT:
+ mKeyboardListener->textInput(evt.text);
+ break;
+ case SDL_JOYAXISMOTION:
+ if (mJoyListener)
+ mJoyListener->axisMoved(evt.jaxis, evt.jaxis.axis);
+ break;
+ case SDL_JOYBUTTONDOWN:
+ if (mJoyListener)
+ mJoyListener->buttonPressed(evt.jbutton, evt.jbutton.button);
+ break;
+ case SDL_JOYBUTTONUP:
+ if (mJoyListener)
+ mJoyListener->buttonReleased(evt.jbutton, evt.jbutton.button);
+ break;
+ case SDL_JOYDEVICEADDED:
+ //SDL_JoystickOpen(evt.jdevice.which);
+ //std::cout << "Detected a new joystick: " << SDL_JoystickNameForIndex(evt.jdevice.which) << std::endl;
+ break;
+ case SDL_JOYDEVICEREMOVED:
+ //std::cout << "A joystick has been removed" << std::endl;
+ break;
+ case SDL_WINDOWEVENT:
+ handleWindowEvent(evt);
+ break;
+ case SDL_QUIT:
+ Ogre::Root::getSingleton().queueEndRendering();
+ break;
+ default:
+ std::cerr << "Unhandled SDL event of type " << evt.type << std::endl;
+ break;
+ }
+ }
+ }
+
+ void InputWrapper::handleWindowEvent(const SDL_Event& evt)
+ {
+ switch (evt.window.event) {
+ case SDL_WINDOWEVENT_ENTER:
+ mMouseInWindow = true;
+ break;
+ case SDL_WINDOWEVENT_LEAVE:
+ mMouseInWindow = false;
+ SDL_SetWindowGrab(mSDLWindow, SDL_FALSE);
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ int w,h;
+ SDL_GetWindowSize(mSDLWindow, &w, &h);
+ // TODO: Fix Ogre to handle this more consistently
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+ mOgreWindow->resize(w, h);
+#else
+ mOgreWindow->windowMovedOrResized();
+#endif
+ if (mWindowListener)
+ mWindowListener->windowResized(w, h);
+ break;
+
+ case SDL_WINDOWEVENT_RESIZED:
+ // TODO: Fix Ogre to handle this more consistently
+#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
+ mOgreWindow->resize(evt.window.data1, evt.window.data2);
+#else
+ mOgreWindow->windowMovedOrResized();
+#endif
+ if (mWindowListener)
+ mWindowListener->windowResized(evt.window.data1, evt.window.data2);
+ break;
+
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ if (mWindowListener)
+ mWindowListener->windowFocusChange(true);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ if (mWindowListener)
+ mWindowListener->windowFocusChange(false);
+ break;
+ case SDL_WINDOWEVENT_CLOSE:
+ break;
+ case SDL_WINDOWEVENT_SHOWN:
+ mOgreWindow->setVisible(true);
+ if (mWindowListener)
+ mWindowListener->windowVisibilityChange(true);
+ break;
+ case SDL_WINDOWEVENT_HIDDEN:
+ mOgreWindow->setVisible(false);
+ if (mWindowListener)
+ mWindowListener->windowVisibilityChange(false);
+ break;
+ }
+ }
+
+ bool InputWrapper::isModifierHeld(SDL_Keymod mod)
+ {
+ return SDL_GetModState() & mod;
+ }
+
+ bool InputWrapper::isKeyDown(SDL_Scancode key)
+ {
+ return SDL_GetKeyboardState(NULL)[key];
+ }
+
+ /// \brief Moves the mouse to the specified point within the viewport
+ void InputWrapper::warpMouse(int x, int y)
+ {
+ SDL_WarpMouseInWindow(mSDLWindow, x, y);
+ mWarpCompensate = true;
+ mWarpX = x;
+ mWarpY = y;
+ }
+
+ /// \brief Locks the pointer to the window
+ void InputWrapper::setGrabPointer(bool grab)
+ {
+ mGrabPointer = grab && mMouseInWindow;
+ SDL_SetWindowGrab(mSDLWindow, grab && mMouseInWindow ? SDL_TRUE : SDL_FALSE);
+ }
+
+ /// \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)
+ return;
+
+ mMouseRelative = relative && mMouseInWindow;
+
+ 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)
+ mWrapPointer = true;
+
+ //now remove all mouse events using the old setting from the queue
+ SDL_PumpEvents();
+ SDL_FlushEvent(SDL_MOUSEMOTION);
+ }
+
+ /// \brief Internal method for ignoring relative motions as a side effect
+ /// of warpMouse()
+ bool InputWrapper::_handleWarpMotion(const SDL_MouseMotionEvent& evt)
+ {
+ if(!mWarpCompensate)
+ return false;
+
+ //this was a warp event, signal the caller to eat it.
+ if(evt.x == mWarpX && evt.y == mWarpY)
+ {
+ mWarpCompensate = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// \brief Wrap the mouse to the viewport
+ void InputWrapper::_wrapMousePointer(const SDL_MouseMotionEvent& evt)
+ {
+ //don't wrap if we don't want relative movements, support relative
+ //movements natively, or aren't grabbing anyways
+ if(!mMouseRelative || !mWrapPointer || !mGrabPointer)
+ return;
+
+ int width = 0;
+ int height = 0;
+
+ SDL_GetWindowSize(mSDLWindow, &width, &height);
+
+ const int FUDGE_FACTOR_X = width;
+ const int FUDGE_FACTOR_Y = height;
+
+ //warp the mouse if it's about to go outside the window
+ if(evt.x - FUDGE_FACTOR_X < 0 || evt.x + FUDGE_FACTOR_X > width
+ || evt.y - FUDGE_FACTOR_Y < 0 || evt.y + FUDGE_FACTOR_Y > height)
+ {
+ warpMouse(width / 2, height / 2);
+ }
+ }
+
+ /// \brief Package mouse and mousewheel motions into a single event
+ MouseMotionEvent InputWrapper::_packageMouseMotion(const SDL_Event &evt)
+ {
+ MouseMotionEvent pack_evt;
+ pack_evt.x = mMouseX;
+ pack_evt.xrel = 0;
+ pack_evt.y = mMouseY;
+ pack_evt.yrel = 0;
+ pack_evt.z = mMouseZ;
+ pack_evt.zrel = 0;
+
+ if(evt.type == SDL_MOUSEMOTION)
+ {
+ pack_evt.x = mMouseX = evt.motion.x;
+ pack_evt.y = mMouseY = evt.motion.y;
+ pack_evt.xrel = evt.motion.xrel;
+ pack_evt.yrel = evt.motion.yrel;
+ }
+ else if(evt.type == SDL_MOUSEWHEEL)
+ {
+ mMouseZ += pack_evt.zrel = (evt.wheel.y * 120);
+ pack_evt.z = mMouseZ;
+ }
+ else
+ {
+ throw new std::runtime_error("Tried to package non-motion event!");
+ }
+
+ return pack_evt;
+ }
+
+ OIS::KeyCode InputWrapper::sdl2OISKeyCode(SDL_Keycode code)
+ {
+ OIS::KeyCode kc = OIS::KC_UNASSIGNED;
+
+ KeyMap::const_iterator ois_equiv = mKeyMap.find(code);
+
+ if(ois_equiv != mKeyMap.end())
+ kc = ois_equiv->second;
+
+ return kc;
+ }
+
+ void InputWrapper::_setupOISKeys()
+ {
+ //lifted from OIS's SDLKeyboard.cpp
+
+ //TODO: Consider switching to scancodes so we
+ //can properly support international keyboards
+ //look at SDL_GetKeyFromScancode and SDL_GetKeyName
+ mKeyMap.insert( KeyMap::value_type(SDLK_UNKNOWN, OIS::KC_UNASSIGNED));
+ mKeyMap.insert( KeyMap::value_type(SDLK_ESCAPE, OIS::KC_ESCAPE) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_1, OIS::KC_1) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_2, OIS::KC_2) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_3, OIS::KC_3) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_4, OIS::KC_4) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_5, OIS::KC_5) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_6, OIS::KC_6) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_7, OIS::KC_7) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_8, OIS::KC_8) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_9, OIS::KC_9) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_0, OIS::KC_0) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_MINUS, OIS::KC_MINUS) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_EQUALS, OIS::KC_EQUALS) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_BACKSPACE, OIS::KC_BACK) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_TAB, OIS::KC_TAB) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_q, OIS::KC_Q) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_w, OIS::KC_W) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_e, OIS::KC_E) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_r, OIS::KC_R) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_t, OIS::KC_T) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_y, OIS::KC_Y) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_u, OIS::KC_U) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_i, OIS::KC_I) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_o, OIS::KC_O) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_p, OIS::KC_P) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_RETURN, OIS::KC_RETURN) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_LCTRL, OIS::KC_LCONTROL));
+ mKeyMap.insert( KeyMap::value_type(SDLK_a, OIS::KC_A) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_s, OIS::KC_S) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_d, OIS::KC_D) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_f, OIS::KC_F) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_g, OIS::KC_G) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_h, OIS::KC_H) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_j, OIS::KC_J) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_k, OIS::KC_K) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_l, OIS::KC_L) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_SEMICOLON, OIS::KC_SEMICOLON) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_COLON, OIS::KC_COLON) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_QUOTE, OIS::KC_APOSTROPHE) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_BACKQUOTE, OIS::KC_GRAVE) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_LSHIFT, OIS::KC_LSHIFT) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_BACKSLASH, OIS::KC_BACKSLASH) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_SLASH, OIS::KC_SLASH) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_z, OIS::KC_Z) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_x, OIS::KC_X) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_c, OIS::KC_C) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_v, OIS::KC_V) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_b, OIS::KC_B) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_n, OIS::KC_N) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_m, OIS::KC_M) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_COMMA, OIS::KC_COMMA) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_PERIOD, OIS::KC_PERIOD));
+ mKeyMap.insert( KeyMap::value_type(SDLK_RSHIFT, OIS::KC_RSHIFT));
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_MULTIPLY, OIS::KC_MULTIPLY) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_LALT, OIS::KC_LMENU) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_SPACE, OIS::KC_SPACE));
+ mKeyMap.insert( KeyMap::value_type(SDLK_CAPSLOCK, OIS::KC_CAPITAL) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F1, OIS::KC_F1) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F2, OIS::KC_F2) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F3, OIS::KC_F3) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F4, OIS::KC_F4) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F5, OIS::KC_F5) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F6, OIS::KC_F6) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F7, OIS::KC_F7) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F8, OIS::KC_F8) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F9, OIS::KC_F9) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F10, OIS::KC_F10) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_NUMLOCKCLEAR, OIS::KC_NUMLOCK) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_SCROLLLOCK, OIS::KC_SCROLL));
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_7, OIS::KC_NUMPAD7) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_8, OIS::KC_NUMPAD8) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_9, OIS::KC_NUMPAD9) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_MINUS, OIS::KC_SUBTRACT) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_4, OIS::KC_NUMPAD4) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_5, OIS::KC_NUMPAD5) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_6, OIS::KC_NUMPAD6) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_PLUS, OIS::KC_ADD) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_1, OIS::KC_NUMPAD1) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_2, OIS::KC_NUMPAD2) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_3, OIS::KC_NUMPAD3) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_0, OIS::KC_NUMPAD0) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_PERIOD, OIS::KC_DECIMAL) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F11, OIS::KC_F11) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F12, OIS::KC_F12) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F13, OIS::KC_F13) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F14, OIS::KC_F14) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_F15, OIS::KC_F15) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_EQUALS, OIS::KC_NUMPADEQUALS) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_KP_DIVIDE, OIS::KC_DIVIDE) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_SYSREQ, OIS::KC_SYSRQ) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_RALT, OIS::KC_RMENU) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_HOME, OIS::KC_HOME) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_UP, OIS::KC_UP) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_PAGEUP, OIS::KC_PGUP) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_LEFT, OIS::KC_LEFT) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_RIGHT, OIS::KC_RIGHT) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_END, OIS::KC_END) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_DOWN, OIS::KC_DOWN) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_PAGEDOWN, OIS::KC_PGDOWN) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_INSERT, OIS::KC_INSERT) );
+ mKeyMap.insert( KeyMap::value_type(SDLK_DELETE, OIS::KC_DELETE) );
+ }
+}
diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp
new file mode 100644
index 0000000000..1bd8947a0d
--- /dev/null
+++ b/extern/sdl4ogre/sdlinputwrapper.hpp
@@ -0,0 +1,76 @@
+#ifndef _SDL4OGRE_SDLINPUTWRAPPER_H
+#define _SDL4OGRE_SDLINPUTWRAPPER_H
+
+#include <SDL_events.h>
+
+#include <OgreRenderWindow.h>
+#include <boost/unordered_map.hpp>
+
+#include "OISCompat.h"
+#include "events.h"
+
+
+
+namespace SFO
+{
+ class InputWrapper
+ {
+ public:
+ InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow);
+ ~InputWrapper();
+
+ void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; }
+ void setKeyboardEventCallback(KeyListener* listen) { mKeyboardListener = listen; }
+ void setWindowEventCallback(WindowListener* listen) { mWindowListener = listen; }
+ void setJoyEventCallback(JoyListener* listen) { mJoyListener = listen; }
+
+ void capture(bool windowEventsOnly);
+ bool isModifierHeld(SDL_Keymod mod);
+ bool isKeyDown(SDL_Scancode key);
+
+ void setMouseRelative(bool relative);
+ bool getMouseRelative() { return mMouseRelative; }
+ void setGrabPointer(bool grab);
+
+ OIS::KeyCode sdl2OISKeyCode(SDL_Keycode code);
+
+ void warpMouse(int x, int y);
+
+ private:
+
+ void handleWindowEvent(const SDL_Event& evt);
+
+ bool _handleWarpMotion(const SDL_MouseMotionEvent& evt);
+ void _wrapMousePointer(const SDL_MouseMotionEvent &evt);
+ MouseMotionEvent _packageMouseMotion(const SDL_Event& evt);
+
+ void _setupOISKeys();
+
+ SFO::MouseListener* mMouseListener;
+ SFO::KeyListener* mKeyboardListener;
+ SFO::WindowListener* mWindowListener;
+ SFO::JoyListener* mJoyListener;
+
+ typedef boost::unordered_map<SDL_Keycode, OIS::KeyCode> KeyMap;
+ KeyMap mKeyMap;
+
+ Uint16 mWarpX;
+ Uint16 mWarpY;
+ bool mWarpCompensate;
+ bool mMouseRelative;
+ bool mWrapPointer;
+ bool mGrabPointer;
+
+ Sint32 mMouseZ;
+ Sint32 mMouseX;
+ Sint32 mMouseY;
+
+ bool mMouseInWindow;
+
+ SDL_Window* mSDLWindow;
+ Ogre::RenderWindow* mOgreWindow;
+ };
+
+}
+
+#endif
diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp
new file mode 100644
index 0000000000..f819043cfd
--- /dev/null
+++ b/extern/sdl4ogre/sdlwindowhelper.cpp
@@ -0,0 +1,119 @@
+#include "sdlwindowhelper.hpp"
+
+#include <OgreStringConverter.h>
+#include <OgreRoot.h>
+
+#include <SDL_syswm.h>
+#include <SDL_endian.h>
+
+#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
+#include "osx_utils.h"
+#endif
+
+namespace SFO
+{
+
+SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h,
+ const std::string& title, bool fullscreen, Ogre::NameValuePairList params)
+ : mSDLWindow(window)
+{
+ //get the native whnd
+ struct SDL_SysWMinfo wmInfo;
+ SDL_VERSION(&wmInfo.version);
+
+ if (SDL_GetWindowWMInfo(mSDLWindow, &wmInfo) == SDL_FALSE)
+ throw std::runtime_error("Couldn't get WM Info!");
+
+ Ogre::String winHandle;
+
+ switch (wmInfo.subsystem)
+ {
+#ifdef WIN32
+ case SDL_SYSWM_WINDOWS:
+ // Windows code
+ winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window);
+ break;
+#elif __MACOSX__
+ case SDL_SYSWM_COCOA:
+ //required to make OGRE play nice with our window
+ params.insert(std::make_pair("macAPI", "cocoa"));
+ params.insert(std::make_pair("macAPICocoaUseNSView", "true"));
+
+ winHandle = Ogre::StringConverter::toString(WindowContentViewHandle(wmInfo));
+ break;
+#else
+ case SDL_SYSWM_X11:
+ winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.x11.window);
+ break;
+#endif
+ default:
+ throw std::runtime_error("Unexpected WM!");
+ break;
+ }
+
+ /// \todo externalWindowHandle is deprecated according to the source code. Figure out a way to get parentWindowHandle
+ /// to work properly. On Linux/X11 it causes an occasional GLXBadDrawable error.
+ params.insert(std::make_pair("externalWindowHandle", winHandle));
+
+ mWindow = Ogre::Root::getSingleton().createRenderWindow(title, w, h, fullscreen, &params);
+}
+
+void SDLWindowHelper::setWindowIcon(const std::string &name)
+{
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().load(name, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);
+ if (texture.isNull())
+ {
+ std::stringstream error;
+ error << "Window icon not found: " << name;
+ throw std::runtime_error(error.str());
+ }
+ Ogre::Image image;
+ texture->convertToImage(image);
+
+ SDL_Surface* surface = SDL_CreateRGBSurface(0,texture->getWidth(),texture->getHeight(),32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF);
+
+ //copy the Ogre texture to an SDL surface
+ for(size_t x = 0; x < texture->getWidth(); ++x)
+ {
+ for(size_t y = 0; y < texture->getHeight(); ++y)
+ {
+ Ogre::ColourValue clr = image.getColourAt(x, y, 0);
+
+ //set the pixel on the SDL surface to the same value as the Ogre texture's
+ int bpp = surface->format->BytesPerPixel;
+ /* Here p is the address to the pixel we want to set */
+ Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
+ Uint32 pixel = SDL_MapRGBA(surface->format, clr.r*255, clr.g*255, clr.b*255, clr.a*255);
+ switch(bpp) {
+ case 1:
+ *p = pixel;
+ break;
+
+ case 2:
+ *(Uint16 *)p = pixel;
+ break;
+
+ case 3:
+ if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
+ p[0] = (pixel >> 16) & 0xff;
+ p[1] = (pixel >> 8) & 0xff;
+ p[2] = pixel & 0xff;
+ } else {
+ p[0] = pixel & 0xff;
+ p[1] = (pixel >> 8) & 0xff;
+ p[2] = (pixel >> 16) & 0xff;
+ }
+ break;
+
+ case 4:
+ *(Uint32 *)p = pixel;
+ break;
+ }
+ }
+ }
+
+ SDL_SetWindowIcon(mSDLWindow, surface);
+ SDL_FreeSurface(surface);
+}
+
+}
diff --git a/extern/sdl4ogre/sdlwindowhelper.hpp b/extern/sdl4ogre/sdlwindowhelper.hpp
new file mode 100644
index 0000000000..834716b223
--- /dev/null
+++ b/extern/sdl4ogre/sdlwindowhelper.hpp
@@ -0,0 +1,31 @@
+#ifndef SDL4OGRE_SDLWINDOWHELPER_H
+#define SDL4OGRE_SDLWINDOWHELPER_H
+
+#include <OgreRenderWindow.h>
+
+namespace Ogre
+{
+ class RenderWindow;
+}
+struct SDL_Window;
+
+namespace SFO
+{
+
+ /// @brief Creates an Ogre window from an SDL window and allows setting an Ogre texture as window icon
+ class SDLWindowHelper
+ {
+ public:
+ SDLWindowHelper (SDL_Window* window, int w, int h, const std::string& title, bool fullscreen, Ogre::NameValuePairList params);
+ void setWindowIcon(const std::string& name);
+ Ogre::RenderWindow* getWindow() { return mWindow; }
+
+ private:
+ Ogre::RenderWindow* mWindow;
+ SDL_Window* mSDLWindow;
+ };
+
+}
+
+
+#endif
diff --git a/extern/shiny/CMakeLists.txt b/extern/shiny/CMakeLists.txt
new file mode 100644
index 0000000000..daf2a9df8d
--- /dev/null
+++ b/extern/shiny/CMakeLists.txt
@@ -0,0 +1,54 @@
+cmake_minimum_required(VERSION 2.8)
+
+# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project.
+# Make sure to link against Ogre and boost::filesystem.
+
+option(SHINY_BUILD_OGRE_PLATFORM "build the Ogre platform" ON)
+
+set(SHINY_LIBRARY "shiny")
+set(SHINY_OGREPLATFORM_LIBRARY "shiny.OgrePlatform")
+
+# Sources
+set(SOURCE_FILES
+ Main/Factory.cpp
+ Main/MaterialInstance.cpp
+ Main/MaterialInstancePass.cpp
+ Main/MaterialInstanceTextureUnit.cpp
+ Main/Platform.cpp
+ Main/Preprocessor.cpp
+ Main/PropertyBase.cpp
+ Main/ScriptLoader.cpp
+ Main/ShaderInstance.cpp
+ Main/ShaderSet.cpp
+)
+
+set(OGRE_PLATFORM_SOURCE_FILES
+ Platforms/Ogre/OgreGpuProgram.cpp
+ Platforms/Ogre/OgreMaterial.cpp
+ Platforms/Ogre/OgreMaterialSerializer.cpp
+ Platforms/Ogre/OgrePass.cpp
+ Platforms/Ogre/OgrePlatform.cpp
+ Platforms/Ogre/OgreTextureUnitState.cpp
+)
+
+file(GLOB OGRE_PLATFORM_SOURCE_FILES Platforms/Ogre/*.cpp)
+
+add_library(${SHINY_LIBRARY} STATIC ${SOURCE_FILES})
+
+set(SHINY_LIBRARIES ${SHINY_LIBRARY})
+
+if (SHINY_BUILD_OGRE_PLATFORM)
+ add_library(${SHINY_OGREPLATFORM_LIBRARY} STATIC ${OGRE_PLATFORM_SOURCE_FILES})
+ set(SHINY_LIBRARIES ${SHINY_LIBRARIES} ${SHINY_OGREPLATFORM_LIBRARY})
+endif()
+
+set(SHINY_LIBRARY ${SHINY_LIBRARY} PARENT_SCOPE)
+
+if (DEFINED SHINY_BUILD_MATERIAL_EDITOR)
+ add_subdirectory(Editor)
+
+ set(SHINY_BUILD_EDITOR_FLAG ${SHINY_BUILD_EDITOR_FLAG} PARENT_SCOPE)
+endif()
+
+link_directories(${CMAKE_CURRENT_BINARY_DIR})
+set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE)
diff --git a/extern/shiny/Docs/Configurations.dox b/extern/shiny/Docs/Configurations.dox
new file mode 100644
index 0000000000..570e81d4af
--- /dev/null
+++ b/extern/shiny/Docs/Configurations.dox
@@ -0,0 +1,32 @@
+/*!
+
+ \page configurations Configurations
+
+ A common task in shader development is to provide a different set of simpler shaders for all your materials. Some examples:
+ - When rendering cubic or planar reflection maps in real-time, you will want to disable shadows.
+ - For an in-game minimap render target, you don't want to have fog.
+
+ For this task, the library provides a \a Configuration concept.
+
+ A Configuration is a set of properties that can override global settings, as long as this Configuration is active.
+
+ Here's an example. Say you have a global setting with the name 'shadows' that controls if your materials receive shadows.
+
+ Now, lets create a configuration for our reflection render targets that disables shadows for all materials. Paste the following in a new file with the extension '.configuration':
+
+ \code
+ configuration reflection_targets
+ {
+ shadows false
+ }
+ \endcode
+
+ \note You may also create configurations using sh::Factory::createConfiguration.
+
+ The active Configuration is controlled by the active material scheme in Ogre. So, in order to use the configuration "reflection_targets" for your reflection renders, simply call
+ \code
+ viewport->setMaterialScheme ("reflection_targets");
+ \endcode
+ on the Ogre viewport of your reflection render!
+
+*/
diff --git a/extern/shiny/Docs/Doxyfile b/extern/shiny/Docs/Doxyfile
new file mode 100644
index 0000000000..3564c45f67
--- /dev/null
+++ b/extern/shiny/Docs/Doxyfile
@@ -0,0 +1,1826 @@
+# Doxyfile 1.8.1.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME = shiny
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = /home/scrawl/sh_doxy/generated
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page. This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = ../
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.d \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.idl \
+ *.odl \
+ *.cs \
+ *.php \
+ *.php3 \
+ *.inc \
+ *.m \
+ *.markdown \
+ *.md \
+ *.mm \
+ *.dox \
+ *.py \
+ *.f90 \
+ *.f \
+ *.for \
+ *.vhd \
+ *.vhdl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax. However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/extern/shiny/Docs/GettingStarted.dox b/extern/shiny/Docs/GettingStarted.dox
new file mode 100644
index 0000000000..b9cf58e23a
--- /dev/null
+++ b/extern/shiny/Docs/GettingStarted.dox
@@ -0,0 +1,65 @@
+/*!
+ \page getting-started Getting started
+
+ \section download Download the source
+
+ \code
+ git clone git@github.com:scrawl/shiny.git
+ \endcode
+
+ \section building Build the source
+
+ The source files you want to build are:
+ - Main/*.cpp
+ - Preprocessor/*.cpp (unless you are using the system install of boost::wave, more below)
+ - Platforms/Ogre/*.cpp
+
+ You can either build the sources as a static library, or simply add the sources to the source tree of your project.
+
+ If you use CMake, you might find the included CMakeLists.txt useful. It builds static libraries with the names "shiny" and "shiny.OgrePlatform".
+
+ \note The CMakeLists.txt is not intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project.
+
+ Make sure to link against OGRE and the boost filesystem library.
+
+ If your boost version is older than 1.49, you must set the SHINY_USE_WAVE_SYSTEM_INSTALL variable and additionally link against the boost wave library.
+
+ \code
+ set(SHINY_USE_WAVE_SYSTEM_INSTALL "TRUE")
+ \endcode
+
+ \section code Basic initialisation code
+
+ Add the following code to your application:
+
+ \code{cpp}
+
+ #include <shiny/Main/Factory.hpp>
+ #include <shiny/Platforms/OgrePlatform/OgrePlatform.hpp>
+
+ ....
+
+ sh::OgrePlatform* platform = new sh::OgrePlatform(
+ "General", // OGRE Resource group to use for creating materials.
+ myApplication.getDataPath() + "/" + "materials" // Path to look for materials and shaders. NOTE: This does NOT use the Ogre resource system, so you have to specify an absolute path.
+ );
+
+ sh::Factory* factory = new sh::Factory(platform);
+
+ // Set a language. Valid options: CG, HLSL, GLSL
+ factory->setCurrentLanguage(sh::Language_GLSL);
+
+ factory->loadAllFiles();
+
+ ....
+ your application runs here
+ ....
+
+ // don't forget to delete on exit
+ delete factory;
+
+ \endcode
+
+ That's it! Now you can start defining materials. Refer to the page \ref defining-materials-shaders .
+
+*/
diff --git a/extern/shiny/Docs/Lod.dox b/extern/shiny/Docs/Lod.dox
new file mode 100644
index 0000000000..37d004638e
--- /dev/null
+++ b/extern/shiny/Docs/Lod.dox
@@ -0,0 +1,49 @@
+/*!
+
+ \page lod Material LOD
+
+ \section howitworks How it works
+
+ When Ogre requests a technique for a specific LOD index, the Factory selects the appropriate LOD configuration which then temporarily overrides the global settings in the shaders. We can use this to disable shader features one by one at a lower LOD, resulting in simpler and faster techniques for distant objects.
+
+ \section howtouseit How to use it
+
+ - Create a file with the extension '.lod'. There you can specify shader features to disable at a specific LOD level. Higher LOD index refers to a lower LOD. Example contents:
+
+ \code
+ lod_configuration 1
+ {
+ specular_mapping false
+ }
+
+ lod_configuration 2
+ {
+ specular_mapping false
+ normal_mapping false
+ }
+
+ lod_configuration 3
+ {
+ terrain_composite_map true
+ specular_mapping false
+ normal_mapping false
+ }
+ \endcode
+
+ \note You can also add LOD configurations by calling \a sh::Factory::registerLodConfiguration.
+
+ \note Do not use an index of 0. LOD 0 refers to the highest LOD, and you will never want to disable features at the highest LOD level.
+
+
+ - In your materials, specify the distances at which a lower LOD kicks in. Note that the actual distance might also be affected by the viewport and current entity LOD bias. In this example, the first LOD level (lod index 1) would normally be applied at a distance of 100 units, the next after 300, and the last after 1000 units.
+
+ \code
+ material sample_material
+ {
+ lod_values 100 300 1000
+
+ ... your passes, texture units etc ...
+ }
+ \endcode
+
+*/
diff --git a/extern/shiny/Docs/Macros.dox b/extern/shiny/Docs/Macros.dox
new file mode 100644
index 0000000000..c04ebd3747
--- /dev/null
+++ b/extern/shiny/Docs/Macros.dox
@@ -0,0 +1,283 @@
+/*!
+ \page macros Shader Macros
+
+ \tableofcontents
+
+ \section Shader Language
+
+ These macros are automatically defined, depending on the shader language that has been set by the application using sh::Factory::setCurrentLanguage.
+
+ - SH_GLSL
+ - SH_HLSL
+ - SH_CG
+
+ <B>Example:</B>
+
+ \code
+ #if SH_GLSL == 1
+ // glsl porting code
+ #endif
+
+ #if SH_CG == 1 || SH_HLSL == 1
+ // cg / hlsl porting code (similiar syntax)
+ #endif
+ \endcode
+
+ \note It is encouraged to use the shipped porting header (extra/core.h) by #include-ing it in your shaders. If you do that, you should not have to use the above macros directly.
+
+ \section vertex-fragment Vertex / fragment shader
+
+ These macros are automatically defined, depending on the type of shader that is currently being compiled.
+
+ - SH_VERTEX_SHADER
+ - SH_FRAGMENT_SHADER
+
+ If you use the same source file for both vertex and fragment shader, then it is advised to use these macros for blending out the unused source. This will reduce your compile time.
+
+ \section passthrough Vertex -> Fragment passthrough
+
+ In shader development, a common task is to pass variables from the vertex to the fragment shader. This is no problem if you have a deterministic shader source (i.e. no #ifdefs).
+
+ However, as soon as you begin to have lots of permutations of the same shader source, a problem arises. All current GPUs have a limit of 8 vertex to fragment passthroughs (with 4 components each, for example a float4).
+
+ A common optimization is to put several individual float values together in a float4 (so-called "Packing"). But if your shader has lots of permutations and the passthrough elements you actually need are not known beforehand, it can be very tedious to pack manually. With the following macros, packing can become easier.
+
+ \subsection shAllocatePassthrough shAllocatePassthrough
+
+ <B>Usage:</B> \@shAllocatePassthrough(num_components, name)
+
+ <B>Example:</B>
+ \code
+ #if FRAGMENT_NEED_DEPTH
+ @shAllocatePassthrough(1, depth)
+ #endif
+ \endcode
+
+ This is the first thing you should do before using any of the macros below.
+
+ \subsection shPassthroughVertexOutputs shPassthroughVertexOutputs
+
+ <B>Usage:</B> \@shPassthroughVertexOutputs
+
+ Use this in the inputs/outputs section of your vertex shader, in order to declare all the outputs that are needed for packing the variables that you want passed to the fragment.
+
+ \subsection shPassthroughFragmentInputs shPassthroughFragmentInputs
+
+ <B>Usage:</B> \@shPassthroughFragmentInputs
+
+ Use this in the inputs/outputs section of your fragment shader, in order to declare all the inputs that are needed for receiving the variables that you want passed to the fragment.
+
+ \subsection shPassthroughAssign shPassthroughAssign
+
+ <B>Usage:</B> \@shPassthroughAssign(name, value)
+
+ Use this in the vertex shader for assigning a value to the variable you want passed to the fragment.
+
+ <B>Example:</B>
+ \code
+ #if FRAGMENT_NEED_DEPTH
+ @shPassthroughAssign(depth, shOutputPosition.z);
+ #endif
+
+ \endcode
+
+ \subsection shPassthroughReceive shPassthroughReceive
+
+ <B>Usage:</B> \@shPassthroughReceive(name)
+
+ Use this in the fragment shader to receive the passed value.
+
+ <B>Example:</B>
+
+ \code
+ #if FRAGMENT_NEED_DEPTH
+ float depth = @shPassthroughReceive(depth);
+ #endif
+ \endcode
+
+ \section texUnits Texture units
+
+ \subsection shUseSampler shUseSampler
+
+ <B>Usage:</B> \@shUseSampler(samplerName)
+
+ Requests the texture unit with name \a samplerName to be available for use in this pass.
+
+ Why is this necessary? If you have a derived material that does not use all of the texture units that its parent defines (for example, if an optional asset such as a normal map is not available), there would be no way to know which texture units are actually needed and which can be skipped in the creation process (those that are never referenced in the shader).
+
+ \section properties Property retrieval / binding
+
+ \subsection shPropertyHasValue shPropertyHasValue
+
+ <B>Usage:</B> \@shPropertyHasValue(property)
+
+ Gets replaced by 1 if the property's value is not empty, or 0 if it is empty.
+ Useful for checking whether an optional texture is present or not.
+
+ <B>Example:</B>
+ \code
+ #if @shPropertyHasValue(specularMap)
+ // specular mapping code
+ #endif
+ #if @shPropertyHasValue(normalMap)
+ // normal mapping code
+ #endif
+ \endcode
+
+ \subsection shUniformProperty shUniformProperty
+
+ <B>Usage:</B> \@shUniformProperty<4f|3f|2f|1f|int> (uniformName, property)
+
+ Binds the value of \a property (from the shader_properties of the pass this shader belongs to) to the uniform with name \a uniformName.
+
+ The following variants are available, depending on the type of your uniform variable:
+ - \@shUniformProperty4f
+ - \@shUniformProperty3f
+ - \@shUniformProperty2f
+ - \@shUniformProperty1f
+ - \@shUniformPropertyInt
+
+ <B>Example:</B> \@shUniformProperty1f (uFresnelScale, fresnelScale)
+
+ \subsection shPropertyBool shPropertyBool
+
+ Retrieve a boolean property of the pass that this shader belongs to, gets replaced with either 0 or 1.
+
+ <B>Usage:</B> \@shPropertyBool(propertyName)
+
+ <B>Example:</B>
+ \code
+ #if @shPropertyBool(has_vertex_colors)
+ ...
+ #endif
+ \endcode
+
+ \subsection shPropertyString shPropertyString
+
+ Retrieve a string property of the pass that this shader belongs to
+
+ <B>Usage:</B> \@shPropertyString(propertyName)
+
+ \subsection shPropertyEqual shPropertyEqual
+
+ Check if the value of a property equals a specific value, gets replaced with either 0 or 1. This is useful because the preprocessor cannot compare strings, only numbers.
+
+ <B>Usage:</B> \@shPropertyEqual(propertyName, value)
+
+ <B>Example:</B>
+ \code
+ #if @shPropertyEqual(lighting_mode, phong)
+ ...
+ #endif
+ \endcode
+
+ \section globalSettings Global settings
+
+ \subsection shGlobalSettingBool shGlobalSettingBool
+
+ Retrieves the boolean value of a specific global setting, gets replaced with either 0 or 1. The value can be set using sh::Factory::setGlobalSetting.
+
+ <B>Usage:</B> \@shGlobalSettingBool(settingName)
+
+ \subsection shGlobalSettingEqual shGlobalSettingEqual
+
+ Check if the value of a global setting equals a specific value, gets replaced with either 0 or 1. This is useful because the preprocessor cannot compare strings, only numbers.
+
+ <B>Usage:</B> \@shGlobalSettingEqual(settingName, value)
+
+ \subsection shGlobalSettingString shGlobalSettingString
+
+ Gets replaced with the current value of a given global setting. The value can be set using sh::Factory::setGlobalSetting.
+
+ <B>Usage:</B> \@shGlobalSettingString(settingName)
+
+ \section sharedParams Shared parameters
+
+ \subsection shSharedParameter shSharedParameter
+
+ Allows you to bind a custom value to a uniform parameter.
+
+ <B>Usage</B>: \@shSharedParameter(sharedParameterName)
+
+ <B>Example</B>: \@shSharedParameter(pssmSplitPoints) - now the uniform parameter 'pssmSplitPoints' can be altered in all shaders that use it by executing sh::Factory::setSharedParameter("pssmSplitPoints", value)
+
+ \note You may use the same shared parameter in as many shaders as you want. But don't forget to add the \@shSharedParameter macro to every shader that uses this shared parameter.
+
+ \section autoconstants Auto constants
+
+ \subsection shAutoConstant shAutoConstant
+
+ <B>Usage:</B> \@shAutoConstant(uniformName, autoConstantName, [extraData])
+
+ <B>Example</B>: \@shAutoConstant(uModelViewMatrix, worldviewproj_matrix)
+
+ <B>Example</B>: \@shAutoConstant(uLightPosition4, light_position, 4)
+
+ Binds auto constant with name \a autoConstantName to the uniform \a uniformName. Optionally, you may specify extra data (for example the light index), as required by some auto constants.
+
+ The auto constant names are the same as Ogre's. Read the section "3.1.9 Using Vertex/Geometry/Fragment Programs in a Pass" of the Ogre manual for a list of all auto constant names.
+
+ \section misc Misc
+
+ \subsection shForeach shForeach
+
+ <B>Usage:</B> \@shForeach(n)
+
+ Repeats the content of this foreach block \a n times. The end of the block is marked via \@shEndForeach, and the current iteration number can be retrieved via \@shIterator.
+
+ \note Nested foreach blocks are currently \a not supported.
+
+ \note For technical reasons, you can only use constant numbers, properties (\@shPropertyString) or global settings (\@shGlobalSettingString) as \a n parameter.
+
+ <B>Example:</B>
+
+ \code
+ @shForeach(3)
+ this is iteration number @shIterator
+ @shEndForeach
+
+ Gets replaced with:
+
+ this is iteration number 0
+ this is iteration number 1
+ this is iteration number 2
+ \endcode
+
+ Optionally, you can pass a constant offset to \@shIterator. Example:
+
+ \code
+ @shForeach(3)
+ this is iteration number @shIterator(7)
+ @shEndForeach
+
+ Gets replaced with:
+
+ this is iteration number 7
+ this is iteration number 8
+ this is iteration number 9
+ \endcode
+
+ \subsection shCounter shCounter
+
+ Gets replaced after the preprocessing step with the number that equals the n-th occurence of counters of the same ID.
+
+ <B>Usage:</B> \@shCounter(ID)
+
+ <B>Example</B>:
+ \code
+ @shCounter(0)
+ @shCounter(0)
+ @shCounter(1)
+ @shCounter(0)
+ \endcode
+
+ Gets replaced with:
+
+ \code
+ 0
+ 1
+ 0
+ 2
+ \endcode
+
+*/
diff --git a/extern/shiny/Docs/Mainpage.dox b/extern/shiny/Docs/Mainpage.dox
new file mode 100644
index 0000000000..fb8f596dc0
--- /dev/null
+++ b/extern/shiny/Docs/Mainpage.dox
@@ -0,0 +1,13 @@
+/*!
+
+ \mainpage
+
+ - \ref getting-started
+ - \ref defining-materials-shaders
+ - \ref macros
+ - \ref configurations
+ - \ref lod
+
+ - sh::Factory - the main interface class
+
+*/
diff --git a/extern/shiny/Docs/Materials.dox b/extern/shiny/Docs/Materials.dox
new file mode 100644
index 0000000000..d08599a04a
--- /dev/null
+++ b/extern/shiny/Docs/Materials.dox
@@ -0,0 +1,131 @@
+/*!
+
+ \page defining-materials-shaders Defining materials and shaders
+
+ \section first-material Your first material
+
+ Create a file called "myFirstMaterial.mat" and place it in the path you have used in your initialisation code (see \ref getting-started). Paste the following:
+
+ \code
+
+ material my_first_material
+ {
+ diffuse 1.0 1.0 1.0 1.0
+ specular 0.4 0.4 0.4 32
+ ambient 1.0 1.0 1.0
+ emissive 0.0 0.0 0.0
+ diffuseMap black.png
+
+ pass
+ {
+ diffuse $diffuse
+ specular $specular
+ ambient $ambient
+ emissive $emissive
+
+ texture_unit diffuseMap
+ {
+ texture $diffuseMap
+ create_in_ffp true // use this texture unit for fixed function pipeline
+ }
+ }
+ }
+
+ material material1
+ {
+ parent my_first_material
+ diffuseMap texture1.png
+ }
+
+ material material2
+ {
+ parent my_first_material
+ diffuseMap texture2.png
+ }
+
+ \endcode
+
+ \section first-shader The first shader
+
+ Change the 'pass' section to include some shaders:
+
+ \code
+ pass
+ {
+ vertex_program my_first_shader_vertex
+ fragment_program my_first_shader_fragment
+ ...
+ }
+ \endcode
+
+ \note This does \a not refer to a single shader with a fixed source code, but in fact will automatically create a new \a instance of this shader (if necessary), which can have its own uniform variables, compile-time macros and much more!
+
+ Next, we're going to define our shaders. Paste this in a new file called 'myfirstshader.shaderset'
+
+ \code
+ shader_set my_first_shader_vertex
+ {
+ source example.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+ }
+
+ shader_set my_first_shader_fragment
+ {
+ source example.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+ }
+ \endcode
+
+ Some notes:
+ - There is no entry_point property because the entry point is always \a main.
+ - Both profiles_cg and profiles_hlsl are a list of shader profiles. The first profile that is supported is automatically picked. GLSL does not have shader profiles.
+
+ Now, let's get into writing our shader! As you can guess from above, the filename should be 'example.shader'.
+ Make sure to also copy the 'core.h' file to the same location. It is included in shiny's source tree under 'Extra/'.
+
+ Important: a newline at the end of the file is <b>required</b>. Many editors do this automatically or can be configured to do so. If there is no newline at the end of the file, and the last line is '#endif', you will get the rather cryptic error message " ill formed preprocessor directive: #endif" from boost::wave.
+
+ \code
+ #include "core.h"
+
+ #ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+ UV = uv0;
+ }
+
+ #else
+
+ SH_BEGIN_PROGRAM
+ // NOTE: It is important that the sampler name here (diffuseMap) matches
+ // the name of the texture unit in the material. This is necessary because the system
+ // skips texture units that are never "referenced" in the shader. This can be the case
+ // when your base material has optional assets (for example a normal map) that are not
+ // used by some derived materials.
+ shSampler2D(diffuseMap)
+ shInput(float2, UV)
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = shSample(diffuseMap, UV);
+ }
+
+ #endif
+
+ \endcode
+
+ There you have it! This shader will compile in several languages thanks to the porting defines in "core.h". If you need more defines, feel free to add them and don't forget to send them to me!
+
+ For a full list of macros available when writing your shaders, refer to the page \ref macros
+
+ In the future, some more in-depth shader examples might follow.
+*/
diff --git a/extern/shiny/Editor/Actions.cpp b/extern/shiny/Editor/Actions.cpp
new file mode 100644
index 0000000000..135e819878
--- /dev/null
+++ b/extern/shiny/Editor/Actions.cpp
@@ -0,0 +1,195 @@
+#include "Actions.hpp"
+
+#include "../Main/Factory.hpp"
+
+namespace sh
+{
+
+ void ActionDeleteMaterial::execute()
+ {
+ sh::Factory::getInstance().destroyMaterialInstance(mName);
+ }
+
+ void ActionCloneMaterial::execute()
+ {
+ sh::MaterialInstance* sourceMaterial = sh::Factory::getInstance().getMaterialInstance(mSourceName);
+ std::string sourceMaterialParent = static_cast<sh::MaterialInstance*>(sourceMaterial->getParent())->getName();
+ sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance(
+ mDestName, sourceMaterialParent);
+ sourceMaterial->copyAll(material, sourceMaterial, false);
+
+ material->setSourceFile(sourceMaterial->getSourceFile());
+ }
+
+ void ActionSaveAll::execute()
+ {
+ sh::Factory::getInstance().saveAll();
+ }
+
+ void ActionChangeGlobalSetting::execute()
+ {
+ sh::Factory::getInstance().setGlobalSetting(mName, mNewValue);
+ }
+
+ void ActionCreateConfiguration::execute()
+ {
+ sh::Configuration newConfiguration;
+ sh::Factory::getInstance().createConfiguration(mName);
+ }
+
+ void ActionDeleteConfiguration::execute()
+ {
+ sh::Factory::getInstance().destroyConfiguration(mName);
+ }
+
+ void ActionChangeConfiguration::execute()
+ {
+ sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName);
+ c->setProperty(mKey, sh::makeProperty(new sh::StringValue(mValue)));
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeleteConfigurationProperty::execute()
+ {
+ sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName);
+ c->deleteProperty(mKey);
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionSetMaterialProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ m->setProperty(mKey, sh::makeProperty(mValue));
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeleteMaterialProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ m->deleteProperty(mKey);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionCreatePass::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ m->createPass();
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeletePass::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ m->deletePass(mPassIndex);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionSetPassProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ m->getPasses()->at(mPassIndex).setProperty (mKey, sh::makeProperty(mValue));
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeletePassProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ m->getPasses()->at(mPassIndex).deleteProperty(mKey);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionSetShaderProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ m->getPasses()->at(mPassIndex).mShaderProperties.setProperty (mKey, sh::makeProperty(mValue));
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeleteShaderProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ m->getPasses()->at(mPassIndex).mShaderProperties.deleteProperty (mKey);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionSetTextureProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex);
+ m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).setProperty(mKey, sh::makeProperty(mValue));
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeleteTextureProperty::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex);
+ m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).deleteProperty(mKey);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionCreateTextureUnit::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ m->getPasses()->at(mPassIndex).createTextureUnit(mTexUnitName);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionDeleteTextureUnit::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex);
+
+ m->getPasses()->at(mPassIndex).mTexUnits.erase(m->getPasses()->at(mPassIndex).mTexUnits.begin() + mTextureIndex);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionMoveTextureUnit::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex);
+ if (!mMoveUp)
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex+1);
+
+ std::vector<MaterialInstanceTextureUnit> textures = m->getPasses()->at(mPassIndex).mTexUnits;
+ if (mMoveUp)
+ std::swap(textures[mTextureIndex-1], textures[mTextureIndex]);
+ else
+ std::swap(textures[mTextureIndex+1], textures[mTextureIndex]);
+ m->getPasses()->at(mPassIndex).mTexUnits = textures;
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+
+ void ActionChangeTextureUnitName::execute()
+ {
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ assert (m->getPasses()->size() > mPassIndex);
+ assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex);
+
+ m->getPasses()->at(mPassIndex).mTexUnits[mTextureIndex].setName(mTexUnitName);
+
+ sh::Factory::getInstance().notifyConfigurationChanged();
+ }
+}
diff --git a/extern/shiny/Editor/Actions.hpp b/extern/shiny/Editor/Actions.hpp
new file mode 100644
index 0000000000..1bbdbe5a98
--- /dev/null
+++ b/extern/shiny/Editor/Actions.hpp
@@ -0,0 +1,307 @@
+#ifndef SH_ACTIONS_H
+#define SH_ACTIONS_H
+
+#include <string>
+
+namespace sh
+{
+
+ class Action
+ {
+ public:
+ virtual void execute() = 0;
+ virtual ~Action() {}
+ };
+
+ class ActionDeleteMaterial : public Action
+ {
+ public:
+ ActionDeleteMaterial(const std::string& name)
+ : mName(name) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ };
+
+ class ActionCloneMaterial : public Action
+ {
+ public:
+ ActionCloneMaterial(const std::string& sourceName, const std::string& destName)
+ : mSourceName(sourceName), mDestName(destName) {}
+
+ virtual void execute();
+ private:
+ std::string mSourceName;
+ std::string mDestName;
+ };
+
+ class ActionSaveAll : public Action
+ {
+ public:
+ virtual void execute();
+ };
+
+ class ActionChangeGlobalSetting : public Action
+ {
+ public:
+ ActionChangeGlobalSetting(const std::string& name, const std::string& newValue)
+ : mName(name), mNewValue(newValue) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ std::string mNewValue;
+ };
+
+ // configuration
+
+ class ActionCreateConfiguration : public Action
+ {
+ public:
+ ActionCreateConfiguration(const std::string& name)
+ : mName(name) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+
+ };
+
+ class ActionDeleteConfiguration : public Action
+ {
+ public:
+ ActionDeleteConfiguration(const std::string& name)
+ : mName(name) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+
+ };
+
+ class ActionChangeConfiguration : public Action
+ {
+ public:
+ ActionChangeConfiguration (const std::string& name, const std::string& key, const std::string& value)
+ : mName(name), mKey(key), mValue(value) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ std::string mKey;
+ std::string mValue;
+ };
+
+ class ActionDeleteConfigurationProperty : public Action
+ {
+ public:
+ ActionDeleteConfigurationProperty (const std::string& name, const std::string& key)
+ : mName(name), mKey(key) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ std::string mKey;
+ };
+
+ // material
+
+ class ActionSetMaterialProperty : public Action
+ {
+ public:
+ ActionSetMaterialProperty (const std::string& name, const std::string& key, const std::string& value)
+ : mName(name), mKey(key), mValue(value) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ std::string mKey;
+ std::string mValue;
+ };
+
+ class ActionDeleteMaterialProperty : public Action
+ {
+ public:
+ ActionDeleteMaterialProperty (const std::string& name, const std::string& key)
+ : mName(name), mKey(key) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ std::string mKey;
+ };
+
+ // pass
+
+ class ActionCreatePass : public Action
+ {
+ public:
+ ActionCreatePass (const std::string& name)
+ : mName(name) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ };
+
+ class ActionDeletePass : public Action
+ {
+ public:
+ ActionDeletePass (const std::string& name, int passIndex)
+ : mName(name), mPassIndex(passIndex) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ };
+
+ class ActionSetPassProperty : public Action
+ {
+ public:
+ ActionSetPassProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value)
+ : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ std::string mKey;
+ std::string mValue;
+ };
+
+ class ActionDeletePassProperty : public Action
+ {
+ public:
+ ActionDeletePassProperty (const std::string& name, int passIndex, const std::string& key)
+ : mName(name), mPassIndex(passIndex), mKey(key) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ std::string mKey;
+ };
+
+ // shader
+
+ class ActionSetShaderProperty : public Action
+ {
+ public:
+ ActionSetShaderProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value)
+ : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ std::string mKey;
+ std::string mValue;
+ };
+
+ class ActionDeleteShaderProperty : public Action
+ {
+ public:
+ ActionDeleteShaderProperty (const std::string& name, int passIndex, const std::string& key)
+ : mName(name), mPassIndex(passIndex), mKey(key) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ std::string mKey;
+ };
+
+ // texture unit
+
+ class ActionChangeTextureUnitName : public Action
+ {
+ public:
+ ActionChangeTextureUnitName (const std::string& name, int passIndex, int textureIndex, const std::string& texUnitName)
+ : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mTexUnitName(texUnitName) {}
+
+ virtual void execute();
+
+ private:
+ std::string mName;
+ int mPassIndex;
+ int mTextureIndex;
+ std::string mTexUnitName;
+ };
+
+ class ActionCreateTextureUnit : public Action
+ {
+ public:
+ ActionCreateTextureUnit (const std::string& name, int passIndex, const std::string& texUnitName)
+ : mName(name), mPassIndex(passIndex), mTexUnitName(texUnitName) {}
+
+ virtual void execute();
+
+ private:
+ std::string mName;
+ int mPassIndex;
+ std::string mTexUnitName;
+ };
+
+ class ActionDeleteTextureUnit : public Action
+ {
+ public:
+ ActionDeleteTextureUnit (const std::string& name, int passIndex, int textureIndex)
+ : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex) {}
+
+ virtual void execute();
+
+ private:
+ std::string mName;
+ int mPassIndex;
+ int mTextureIndex;
+ };
+
+ class ActionMoveTextureUnit : public Action
+ {
+ public:
+ ActionMoveTextureUnit (const std::string& name, int passIndex, int textureIndex, bool moveUp)
+ : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mMoveUp(moveUp) {}
+
+ virtual void execute();
+
+ private:
+ std::string mName;
+ int mPassIndex;
+ int mTextureIndex;
+ bool mMoveUp;
+ };
+
+ class ActionSetTextureProperty : public Action
+ {
+ public:
+ ActionSetTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key, const std::string& value)
+ : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key), mValue(value) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ int mTextureIndex;
+ std::string mKey;
+ std::string mValue;
+ };
+
+ class ActionDeleteTextureProperty : public Action
+ {
+ public:
+ ActionDeleteTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key)
+ : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key) {}
+
+ virtual void execute();
+ private:
+ std::string mName;
+ int mPassIndex;
+ int mTextureIndex;
+ std::string mKey;
+ };
+
+}
+
+#endif
diff --git a/extern/shiny/Editor/AddPropertyDialog.cpp b/extern/shiny/Editor/AddPropertyDialog.cpp
new file mode 100644
index 0000000000..71b47feb19
--- /dev/null
+++ b/extern/shiny/Editor/AddPropertyDialog.cpp
@@ -0,0 +1,31 @@
+#include "AddPropertyDialog.hpp"
+#include "ui_addpropertydialog.h"
+
+AddPropertyDialog::AddPropertyDialog(QWidget *parent)
+ : QDialog(parent)
+ , ui(new Ui::AddPropertyDialog)
+ , mType(0)
+{
+ ui->setupUi(this);
+
+ connect(ui->buttonBox, SIGNAL(accepted()),
+ this, SLOT(accepted()));
+ connect(ui->buttonBox, SIGNAL(rejected()),
+ this, SLOT(rejected()));
+}
+
+void AddPropertyDialog::accepted()
+{
+ mName = ui->lineEdit->text();
+ mType = ui->comboBox->currentIndex();
+}
+
+void AddPropertyDialog::rejected()
+{
+ mName = "";
+}
+
+AddPropertyDialog::~AddPropertyDialog()
+{
+ delete ui;
+}
diff --git a/extern/shiny/Editor/AddPropertyDialog.h b/extern/shiny/Editor/AddPropertyDialog.h
new file mode 100644
index 0000000000..c1d2c960b5
--- /dev/null
+++ b/extern/shiny/Editor/AddPropertyDialog.h
@@ -0,0 +1,22 @@
+#ifndef ADDPROPERTYDIALOG_H
+#define ADDPROPERTYDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class AddPropertyDialog;
+}
+
+class AddPropertyDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AddPropertyDialog(QWidget *parent = 0);
+ ~AddPropertyDialog();
+
+private:
+ Ui::AddPropertyDialog *ui;
+};
+
+#endif // ADDPROPERTYDIALOG_H
diff --git a/extern/shiny/Editor/AddPropertyDialog.hpp b/extern/shiny/Editor/AddPropertyDialog.hpp
new file mode 100644
index 0000000000..b4e19b0870
--- /dev/null
+++ b/extern/shiny/Editor/AddPropertyDialog.hpp
@@ -0,0 +1,29 @@
+#ifndef ADDPROPERTYDIALOG_H
+#define ADDPROPERTYDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class AddPropertyDialog;
+}
+
+class AddPropertyDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AddPropertyDialog(QWidget *parent = 0);
+ ~AddPropertyDialog();
+
+ int mType;
+ QString mName;
+
+public slots:
+ void accepted();
+ void rejected();
+
+private:
+ Ui::AddPropertyDialog *ui;
+};
+
+#endif // ADDPROPERTYDIALOG_H
diff --git a/extern/shiny/Editor/CMakeLists.txt b/extern/shiny/Editor/CMakeLists.txt
new file mode 100644
index 0000000000..eead159f08
--- /dev/null
+++ b/extern/shiny/Editor/CMakeLists.txt
@@ -0,0 +1,61 @@
+set(SHINY_EDITOR_LIBRARY "shiny.Editor")
+
+find_package(Qt4)
+
+if (QT_FOUND)
+
+ add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=1)
+ set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=1 PARENT_SCOPE)
+
+ set(QT_USE_QTGUI 1)
+
+ # Headers that must be preprocessed
+ set(SHINY_EDITOR_HEADER_MOC
+ MainWindow.hpp
+ NewMaterialDialog.hpp
+ AddPropertyDialog.hpp
+ PropertySortModel.hpp
+ )
+
+ set(SHINY_EDITOR_UI
+ mainwindow.ui
+ newmaterialdialog.ui
+ addpropertydialog.ui
+ )
+
+ QT4_WRAP_CPP(MOC_SRCS ${SHINY_EDITOR_HEADER_MOC})
+ QT4_WRAP_UI(UI_HDRS ${SHINY_EDITOR_UI})
+
+ set(SOURCE_FILES
+ NewMaterialDialog.cpp
+ AddPropertyDialog.cpp
+ ColoredTabWidget.hpp
+ MainWindow.cpp
+ Editor.cpp
+ Actions.cpp
+ Query.cpp
+ PropertySortModel.cpp
+ ${SHINY_EDITOR_UI} # Just to have them in the IDE's file explorer
+ )
+
+ include(${QT_USE_FILE})
+
+ set (CMAKE_INCLUDE_CURRENT_DIR "true")
+
+ include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+ add_library(${SHINY_EDITOR_LIBRARY} STATIC ${SOURCE_FILES} ${MOC_SRCS} ${UI_HDRS})
+
+ set(SHINY_LIBRARIES ${SHINY_LIBRARIES}
+ ${SHINY_EDITOR_LIBRARY}
+ ${QT_LIBRARIES}
+ )
+ set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE)
+
+else (QT_FOUND)
+
+ add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=0)
+ set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=0 PARENT_SCOPE)
+ message(STATUS "QT4 was not found. You will not be able to use the material editor.")
+
+endif(QT_FOUND)
diff --git a/extern/shiny/Editor/ColoredTabWidget.hpp b/extern/shiny/Editor/ColoredTabWidget.hpp
new file mode 100644
index 0000000000..0bf30f6dda
--- /dev/null
+++ b/extern/shiny/Editor/ColoredTabWidget.hpp
@@ -0,0 +1,24 @@
+#ifndef SHINY_EDITOR_COLOREDTABWIDGET_H
+#define SHINY_EDITOR_COLOREDTABWIDGET_H
+
+#include <QTabWidget>
+
+namespace sh
+{
+
+/// Makes tabBar() public to allow changing tab title colors.
+class ColoredTabWidget : public QTabWidget
+{
+public:
+ ColoredTabWidget(QWidget* parent = 0)
+ : QTabWidget(parent) {}
+
+ QTabBar* tabBar()
+ {
+ return QTabWidget::tabBar();
+ }
+};
+
+}
+
+#endif
diff --git a/extern/shiny/Editor/Editor.cpp b/extern/shiny/Editor/Editor.cpp
new file mode 100644
index 0000000000..8c58d0e66d
--- /dev/null
+++ b/extern/shiny/Editor/Editor.cpp
@@ -0,0 +1,117 @@
+#include "Editor.hpp"
+
+
+#include <QApplication>
+#include <QTimer>
+
+#include <boost/thread.hpp>
+
+#include "../Main/Factory.hpp"
+
+#include "MainWindow.hpp"
+
+namespace sh
+{
+
+ Editor::Editor()
+ : mMainWindow(NULL)
+ , mApplication(NULL)
+ , mInitialized(false)
+ , mThread(NULL)
+ {
+ }
+
+ Editor::~Editor()
+ {
+ if (mMainWindow)
+ mMainWindow->mRequestExit = true;
+
+ if (mThread)
+ mThread->join();
+ delete mThread;
+ }
+
+ void Editor::show()
+ {
+ if (!mInitialized)
+ {
+ mInitialized = true;
+
+ mThread = new boost::thread(boost::bind(&Editor::runThread, this));
+ }
+ else
+ {
+ if (mMainWindow)
+ mMainWindow->mRequestShowWindow = true;
+ }
+ }
+
+ void Editor::runThread()
+ {
+ int argc = 0;
+ char** argv = NULL;
+ mApplication = new QApplication(argc, argv);
+ mApplication->setQuitOnLastWindowClosed(false);
+ mMainWindow = new MainWindow();
+ mMainWindow->mSync = &mSync;
+ mMainWindow->show();
+
+ mApplication->exec();
+
+ delete mApplication;
+ }
+
+ void Editor::update()
+ {
+ sh::Factory::getInstance().doMonitorShaderFiles();
+
+ if (!mMainWindow)
+ return;
+
+
+ {
+ boost::mutex::scoped_lock lock(mSync.mActionMutex);
+
+ // execute pending actions
+ while (mMainWindow->mActionQueue.size())
+ {
+ Action* action = mMainWindow->mActionQueue.front();
+ action->execute();
+ delete action;
+ mMainWindow->mActionQueue.pop();
+ }
+ }
+ {
+ boost::mutex::scoped_lock lock(mSync.mQueryMutex);
+
+ // execute pending queries
+ for (std::vector<Query*>::iterator it = mMainWindow->mQueries.begin(); it != mMainWindow->mQueries.end(); ++it)
+ {
+ Query* query = *it;
+ if (!query->mDone)
+ query->execute();
+ }
+ }
+
+ boost::mutex::scoped_lock lock2(mSync.mUpdateMutex);
+
+ // update the list of materials
+ mMainWindow->mState.mMaterialList.clear();
+ sh::Factory::getInstance().listMaterials(mMainWindow->mState.mMaterialList);
+
+ // update global settings
+ mMainWindow->mState.mGlobalSettingsMap.clear();
+ sh::Factory::getInstance().listGlobalSettings(mMainWindow->mState.mGlobalSettingsMap);
+
+ // update configuration list
+ mMainWindow->mState.mConfigurationList.clear();
+ sh::Factory::getInstance().listConfigurationNames(mMainWindow->mState.mConfigurationList);
+
+ // update shader list
+ mMainWindow->mState.mShaderSets.clear();
+ sh::Factory::getInstance().listShaderSets(mMainWindow->mState.mShaderSets);
+
+ mMainWindow->mState.mErrors += sh::Factory::getInstance().getErrorLog();
+ }
+
+}
diff --git a/extern/shiny/Editor/Editor.hpp b/extern/shiny/Editor/Editor.hpp
new file mode 100644
index 0000000000..2b1e8040d0
--- /dev/null
+++ b/extern/shiny/Editor/Editor.hpp
@@ -0,0 +1,73 @@
+#ifndef SH_EDITOR_H
+#define SH_EDITOR_H
+
+#if SHINY_BUILD_MATERIAL_EDITOR
+class QApplication;
+
+#include <boost/thread/condition_variable.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace boost
+{
+ class thread;
+}
+
+namespace sh
+{
+ class MainWindow;
+
+ struct SynchronizationState
+ {
+ boost::mutex mUpdateMutex;
+ boost::mutex mActionMutex;
+ boost::mutex mQueryMutex;
+ };
+
+ class Editor
+ {
+ public:
+
+ Editor();
+ ~Editor();
+
+ void show();
+ void update();
+
+
+ private:
+ bool mInitialized;
+
+ MainWindow* mMainWindow;
+ QApplication* mApplication;
+
+ SynchronizationState mSync;
+
+ boost::thread* mThread;
+
+ void runThread();
+
+ void processShowWindow();
+ };
+
+}
+
+#else
+
+// Dummy implementation, so that the user's code does not have to be polluted with #ifdefs
+namespace sh
+{
+
+ class Editor
+ {
+ public:
+ Editor() {}
+ ~Editor() {}
+ void show() {}
+ void update() {}
+
+ };
+}
+
+#endif
+
+#endif
diff --git a/extern/shiny/Editor/MainWindow.cpp b/extern/shiny/Editor/MainWindow.cpp
new file mode 100644
index 0000000000..a2c52dc2f8
--- /dev/null
+++ b/extern/shiny/Editor/MainWindow.cpp
@@ -0,0 +1,952 @@
+#include "MainWindow.hpp"
+#include "ui_mainwindow.h"
+
+#include <iostream>
+
+#include <QCloseEvent>
+#include <QTimer>
+
+#include <QInputDialog>
+#include <QMessageBox>
+
+#include "Editor.hpp"
+#include "ColoredTabWidget.hpp"
+#include "AddPropertyDialog.hpp"
+
+sh::MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+ , mRequestShowWindow(false)
+ , mRequestExit(false)
+ , mIgnoreGlobalSettingChange(false)
+ , mIgnoreConfigurationChange(false)
+ , mIgnoreMaterialChange(false)
+ , mIgnoreMaterialPropertyChange(false)
+{
+ ui->setupUi(this);
+
+ QTimer *timer = new QTimer(this);
+ connect(timer, SIGNAL(timeout()), this, SLOT(onIdle()));
+ timer->start(50);
+
+ QList<int> sizes;
+ sizes << 250;
+ sizes << 550;
+ ui->splitter->setSizes(sizes);
+
+ mMaterialModel = new QStringListModel(this);
+
+ mMaterialProxyModel = new QSortFilterProxyModel(this);
+ mMaterialProxyModel->setSourceModel(mMaterialModel);
+ mMaterialProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ mMaterialProxyModel->setDynamicSortFilter(true);
+ mMaterialProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+
+ ui->materialList->setModel(mMaterialProxyModel);
+ ui->materialList->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->materialList->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ ui->materialList->setAlternatingRowColors(true);
+
+ connect(ui->materialList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(onMaterialSelectionChanged(QModelIndex,QModelIndex)));
+
+ mMaterialPropertyModel = new QStandardItemModel(0, 2, this);
+ mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name")));
+ mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value")));
+ connect(mMaterialPropertyModel, SIGNAL(itemChanged(QStandardItem*)),
+ this, SLOT(onMaterialPropertyChanged(QStandardItem*)));
+
+ mMaterialSortModel = new PropertySortModel(this);
+ mMaterialSortModel->setSourceModel(mMaterialPropertyModel);
+ mMaterialSortModel->setDynamicSortFilter(true);
+ mMaterialSortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+
+ ui->materialView->setModel(mMaterialSortModel);
+ ui->materialView->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->materialView->setAlternatingRowColors(true);
+ ui->materialView->setSortingEnabled(true);
+ connect(ui->materialView, SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(onContextMenuRequested(QPoint)));
+
+ mGlobalSettingsModel = new QStandardItemModel(0, 2, this);
+ mGlobalSettingsModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name")));
+ mGlobalSettingsModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value")));
+ connect(mGlobalSettingsModel, SIGNAL(itemChanged(QStandardItem*)),
+ this, SLOT(onGlobalSettingChanged(QStandardItem*)));
+
+ ui->globalSettingsView->setModel(mGlobalSettingsModel);
+ ui->globalSettingsView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+ ui->globalSettingsView->verticalHeader()->hide();
+ ui->globalSettingsView->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ ui->globalSettingsView->setSelectionMode(QAbstractItemView::SingleSelection);
+
+ ui->configurationList->setSelectionMode(QAbstractItemView::SingleSelection);
+ ui->configurationList->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ connect(ui->configurationList, SIGNAL(currentTextChanged(QString)),
+ this, SLOT(onConfigurationSelectionChanged(QString)));
+
+ mConfigurationModel = new QStandardItemModel(0, 2, this);
+ mConfigurationModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name")));
+ mConfigurationModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value")));
+ connect(mConfigurationModel, SIGNAL(itemChanged(QStandardItem*)),
+ this, SLOT(onConfigurationChanged(QStandardItem*)));
+
+ ui->configurationView->setModel(mConfigurationModel);
+ ui->configurationView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+ ui->configurationView->verticalHeader()->hide();
+ ui->configurationView->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ ui->configurationView->setSelectionMode(QAbstractItemView::SingleSelection);
+}
+
+sh::MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void sh::MainWindow::closeEvent(QCloseEvent *event)
+{
+ this->hide();
+ event->ignore();
+}
+
+void sh::MainWindow::onIdle()
+{
+ if (mRequestShowWindow)
+ {
+ mRequestShowWindow = false;
+ show();
+ }
+
+ if (mRequestExit)
+ {
+ QApplication::exit();
+ return;
+ }
+
+ boost::mutex::scoped_lock lock(mSync->mUpdateMutex);
+
+
+ mIgnoreMaterialChange = true;
+ QString selected;
+
+ QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex();
+ if (selectedIndex.isValid())
+ selected = mMaterialModel->data(selectedIndex, Qt::DisplayRole).toString();
+
+ QStringList list;
+
+ for (std::vector<std::string>::const_iterator it = mState.mMaterialList.begin(); it != mState.mMaterialList.end(); ++it)
+ {
+ list.push_back(QString::fromStdString(*it));
+ }
+
+ if (mMaterialModel->stringList() != list)
+ {
+ mMaterialModel->setStringList(list);
+
+ // quick hack to keep our selection when the model has changed
+ if (!selected.isEmpty())
+ for (int i=0; i<mMaterialModel->rowCount(); ++i)
+ {
+ const QModelIndex& index = mMaterialModel->index(i,0);
+ if (mMaterialModel->data(index, Qt::DisplayRole).toString() == selected)
+ {
+ ui->materialList->setCurrentIndex(index);
+ break;
+ }
+ }
+ }
+ mIgnoreMaterialChange = false;
+
+ mIgnoreGlobalSettingChange = true;
+ for (std::map<std::string, std::string>::const_iterator it = mState.mGlobalSettingsMap.begin();
+ it != mState.mGlobalSettingsMap.end(); ++it)
+ {
+ QList<QStandardItem *> list = mGlobalSettingsModel->findItems(QString::fromStdString(it->first));
+ if (!list.empty()) // item was already there
+ {
+ // if it changed, set the value column
+ if (mGlobalSettingsModel->data(mGlobalSettingsModel->index(list.front()->row(), 1)).toString()
+ != QString::fromStdString(it->second))
+ {
+ mGlobalSettingsModel->setItem(list.front()->row(), 1, new QStandardItem(QString::fromStdString(it->second)));
+ }
+ }
+ else // item wasn't there; insert new row
+ {
+ QList<QStandardItem*> toAdd;
+ QStandardItem* name = new QStandardItem(QString::fromStdString(it->first));
+ name->setFlags(name->flags() &= ~Qt::ItemIsEditable);
+ QStandardItem* value = new QStandardItem(QString::fromStdString(it->second));
+ toAdd.push_back(name);
+ toAdd.push_back(value);
+ mGlobalSettingsModel->appendRow(toAdd);
+ }
+ }
+ mIgnoreGlobalSettingChange = false;
+
+
+ mIgnoreConfigurationChange = true;
+ QList<QListWidgetItem*> selected_ = ui->configurationList->selectedItems();
+ QString selectedStr;
+ if (selected_.size())
+ selectedStr = selected_.front()->text();
+
+ ui->configurationList->clear();
+
+ for (std::vector<std::string>::const_iterator it = mState.mConfigurationList.begin(); it != mState.mConfigurationList.end(); ++it)
+ ui->configurationList->addItem(QString::fromStdString(*it));
+
+ if (!selectedStr.isEmpty())
+ for (int i=0; i<ui->configurationList->count(); ++i)
+ {
+ if (ui->configurationList->item(i)->text() == selectedStr)
+ {
+ ui->configurationList->setCurrentItem(ui->configurationList->item(i), QItemSelectionModel::ClearAndSelect);
+ }
+ }
+
+ mIgnoreConfigurationChange = false;
+
+ if (!mState.mErrors.empty())
+ {
+ ui->errorLog->append(QString::fromStdString(mState.mErrors));
+ mState.mErrors = "";
+ QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::Link);
+ ui->tabWidget->tabBar()->setTabTextColor(3, color);
+ }
+
+
+ // process query results
+ boost::mutex::scoped_lock lock2(mSync->mQueryMutex);
+ for (std::vector<Query*>::iterator it = mQueries.begin(); it != mQueries.end();)
+ {
+ if ((*it)->mDone)
+ {
+ if (typeid(**it) == typeid(ConfigurationQuery))
+ buildConfigurationModel(static_cast<ConfigurationQuery*>(*it));
+ else if (typeid(**it) == typeid(MaterialQuery))
+ buildMaterialModel(static_cast<MaterialQuery*>(*it));
+ else if (typeid(**it) == typeid(MaterialPropertyQuery))
+ {
+ MaterialPropertyQuery* q = static_cast<MaterialPropertyQuery*>(*it);
+ mIgnoreMaterialPropertyChange = true;
+ if (getSelectedMaterial().toStdString() == q->mName)
+ {
+ for (int i=0; i<mMaterialPropertyModel->rowCount(); ++i)
+ {
+ if (mMaterialPropertyModel->item(i,0)->text() == QString::fromStdString(q->mPropertyName))
+ {
+ mMaterialPropertyModel->item(i,1)->setText(QString::fromStdString(q->mValue));
+ if (mMaterialPropertyModel->item(i,1)->isCheckable())
+ mMaterialPropertyModel->item(i,1)->setCheckState ((q->mValue == "true")
+ ? Qt::Checked : Qt::Unchecked);
+ }
+ }
+ }
+ mIgnoreMaterialPropertyChange = false;
+ }
+
+ delete *it;
+ it = mQueries.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+void sh::MainWindow::onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous)
+{
+ if (mIgnoreMaterialChange)
+ return;
+
+ QString name = getSelectedMaterial();
+ if (!name.isEmpty())
+ requestQuery(new sh::MaterialQuery(name.toStdString()));
+}
+
+QString sh::MainWindow::getSelectedMaterial()
+{
+ QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex();
+ if (!selectedIndex.isValid())
+ return QString("");
+
+ return mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString();
+}
+
+void sh::MainWindow::onConfigurationSelectionChanged (const QString& current)
+{
+ if (mIgnoreConfigurationChange)
+ return;
+ requestQuery(new sh::ConfigurationQuery(current.toStdString()));
+}
+
+void sh::MainWindow::onGlobalSettingChanged(QStandardItem *item)
+{
+ if (mIgnoreGlobalSettingChange)
+ return; // we are only interested in changes by the user, not by the backend.
+
+ std::string name = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 0)).toString().toStdString();
+ std::string value = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 1)).toString().toStdString();
+
+ queueAction(new sh::ActionChangeGlobalSetting(name, value));
+}
+
+void sh::MainWindow::onConfigurationChanged (QStandardItem* item)
+{
+ QList<QListWidgetItem*> items = ui->configurationList->selectedItems();
+ if (items.size())
+ {
+ std::string name = items.front()->text().toStdString();
+ std::string key = mConfigurationModel->data(mConfigurationModel->index(item->row(), 0)).toString().toStdString();
+ std::string value = mConfigurationModel->data(mConfigurationModel->index(item->row(), 1)).toString().toStdString();
+
+ queueAction(new sh::ActionChangeConfiguration(name, key, value));
+
+ requestQuery(new sh::ConfigurationQuery(name));
+ }
+}
+
+void sh::MainWindow::on_lineEdit_textEdited(const QString &arg1)
+{
+ mMaterialProxyModel->setFilterFixedString(arg1);
+}
+
+void sh::MainWindow::on_actionSave_triggered()
+{
+ queueAction (new sh::ActionSaveAll());
+}
+
+void sh::MainWindow::on_actionNewMaterial_triggered()
+{
+
+}
+
+void sh::MainWindow::on_actionDeleteMaterial_triggered()
+{
+ QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex();
+ QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString();
+
+ queueAction (new sh::ActionDeleteMaterial(name.toStdString()));
+}
+
+void sh::MainWindow::queueAction(Action* action)
+{
+ boost::mutex::scoped_lock lock(mSync->mActionMutex);
+ mActionQueue.push(action);
+}
+
+void sh::MainWindow::requestQuery(Query *query)
+{
+ boost::mutex::scoped_lock lock(mSync->mActionMutex);
+ mQueries.push_back(query);
+}
+
+void sh::MainWindow::on_actionQuit_triggered()
+{
+ hide();
+}
+
+void sh::MainWindow::on_actionNewConfiguration_triggered()
+{
+ QInputDialog dialog(this);
+
+ QString text = QInputDialog::getText(this, tr("New Configuration"),
+ tr("Configuration name:"));
+
+ if (!text.isEmpty())
+ {
+ queueAction(new ActionCreateConfiguration(text.toStdString()));
+ }
+}
+
+void sh::MainWindow::on_actionDeleteConfiguration_triggered()
+{
+ QList<QListWidgetItem*> items = ui->configurationList->selectedItems();
+ if (items.size())
+ queueAction(new ActionDeleteConfiguration(items.front()->text().toStdString()));
+}
+
+void sh::MainWindow::on_actionDeleteConfigurationProperty_triggered()
+{
+ QList<QListWidgetItem*> items = ui->configurationList->selectedItems();
+ if (items.empty())
+ return;
+ std::string configurationName = items.front()->text().toStdString();
+
+ QModelIndex current = ui->configurationView->currentIndex();
+ if (!current.isValid())
+ return;
+
+ std::string propertyName = mConfigurationModel->data(mConfigurationModel->index(current.row(), 0)).toString().toStdString();
+
+ queueAction(new sh::ActionDeleteConfigurationProperty(configurationName, propertyName));
+ requestQuery(new sh::ConfigurationQuery(configurationName));
+}
+
+void sh::MainWindow::on_actionCloneMaterial_triggered()
+{
+ QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex();
+ QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString();
+ if (name.isEmpty())
+ return;
+
+ QInputDialog dialog(this);
+
+ QString text = QInputDialog::getText(this, tr("Clone material"),
+ tr("Name:"));
+
+ if (!text.isEmpty())
+ {
+ queueAction(new ActionCloneMaterial(name.toStdString(), text.toStdString()));
+ }
+}
+
+void sh::MainWindow::onContextMenuRequested(const QPoint &point)
+{
+ QPoint globalPos = ui->materialView->viewport()->mapToGlobal(point);
+
+ QMenu menu;
+
+ QList <QAction*> actions;
+ actions.push_back(ui->actionNewProperty);
+ actions.push_back(ui->actionDeleteProperty);
+ actions.push_back(ui->actionCreatePass);
+ actions.push_back(ui->actionCreateTextureUnit);
+ menu.addActions(actions);
+
+ menu.exec(globalPos);
+}
+
+void sh::MainWindow::getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass, bool* isInTextureUnit)
+{
+ if (passIndex)
+ {
+ *passIndex = 0;
+ if (isInPass)
+ *isInPass = false;
+ QModelIndex passModelIndex = index;
+ // go up until we find the pass item.
+ while (getPropertyKey(passModelIndex) != "pass" && passModelIndex.isValid())
+ passModelIndex = passModelIndex.parent();
+
+ if (passModelIndex.isValid())
+ {
+ if (passModelIndex.column() != 0)
+ passModelIndex = passModelIndex.parent().child(passModelIndex.row(), 0);
+ for (int i=0; i<mMaterialPropertyModel->rowCount(); ++i)
+ {
+ if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass"))
+ {
+ if (mMaterialPropertyModel->index(i, 0) == passModelIndex)
+ {
+ if (isInPass)
+ *isInPass = true;
+ break;
+ }
+ ++(*passIndex);
+ }
+ }
+ }
+ }
+ if (textureIndex)
+ {
+ *textureIndex = 0;
+ if (isInTextureUnit)
+ *isInTextureUnit = false;
+ QModelIndex texModelIndex = index;
+ // go up until we find the texture_unit item.
+ while (getPropertyKey(texModelIndex) != "texture_unit" && texModelIndex.isValid())
+ texModelIndex = texModelIndex.parent();
+ if (texModelIndex.isValid())
+ {
+ if (texModelIndex.column() != 0)
+ texModelIndex = texModelIndex.parent().child(texModelIndex.row(), 0);
+ for (int i=0; i<mMaterialPropertyModel->rowCount(texModelIndex.parent()); ++i)
+ {
+ if (texModelIndex.parent().child(i, 0).data().toString() == QString("texture_unit"))
+ {
+ if (texModelIndex.parent().child(i, 0) == texModelIndex)
+ {
+ if (isInTextureUnit)
+ *isInTextureUnit = true;
+ break;
+ }
+ ++(*textureIndex);
+ }
+ }
+ }
+ }
+}
+
+std::string sh::MainWindow::getPropertyKey(QModelIndex index)
+{
+ if (!index.parent().isValid())
+ return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 0)).toString().toStdString();
+ else
+ return index.parent().child(index.row(), 0).data().toString().toStdString();
+}
+
+std::string sh::MainWindow::getPropertyValue(QModelIndex index)
+{
+ if (!index.parent().isValid())
+ return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 1)).toString().toStdString();
+ else
+ return index.parent().child(index.row(), 1).data().toString().toStdString();
+}
+
+void sh::MainWindow::onMaterialPropertyChanged(QStandardItem *item)
+{
+ if (mIgnoreMaterialPropertyChange)
+ return;
+
+ QString material = getSelectedMaterial();
+ if (material.isEmpty())
+ return;
+
+ // handle checkboxes being checked/unchecked
+ std::string value = getPropertyValue(item->index());
+ if (item->data(Qt::UserRole).toInt() == MaterialProperty::Boolean)
+ {
+ if (item->checkState() == Qt::Checked && value != "true")
+ value = "true";
+ else if (item->checkState() == Qt::Unchecked && value == "true")
+ value = "false";
+ item->setText(QString::fromStdString(value));
+ }
+
+ // handle inherited properties being changed, i.e. overridden by the current (derived) material
+ if (item->data(Qt::UserRole+1).toInt() == MaterialProperty::Inherited_Unchanged)
+ {
+ QColor normalColor = ui->materialView->palette().color(QPalette::Normal, QPalette::WindowText);
+ mIgnoreMaterialPropertyChange = true;
+ mMaterialPropertyModel->item(item->index().row(), 0)
+ ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1);
+ mMaterialPropertyModel->item(item->index().row(), 0)
+ ->setData(normalColor, Qt::ForegroundRole);
+ mMaterialPropertyModel->item(item->index().row(), 1)
+ ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1);
+ mMaterialPropertyModel->item(item->index().row(), 1)
+ ->setData(normalColor, Qt::ForegroundRole);
+ mIgnoreMaterialPropertyChange = false;
+
+ ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(item->index()));
+ }
+
+ if (!item->index().parent().isValid())
+ {
+ // top level material property
+ queueAction(new ActionSetMaterialProperty(
+ material.toStdString(), getPropertyKey(item->index()), value));
+ }
+ else if (getPropertyKey(item->index()) == "texture_unit")
+ {
+ // texture unit name changed
+ int passIndex, textureIndex;
+ getContext(item->index(), &passIndex, &textureIndex);
+ std::cout << "passIndex " << passIndex << " " << textureIndex << std::endl;
+
+ queueAction(new ActionChangeTextureUnitName(
+ material.toStdString(), passIndex, textureIndex, value));
+
+ }
+ else if (item->index().parent().data().toString() == "pass")
+ {
+ // pass property
+ int passIndex;
+ getContext(item->index(), &passIndex, NULL);
+ /// \todo if shaders are changed, check that the material provides all properties needed by the shader
+ queueAction(new ActionSetPassProperty(
+ material.toStdString(), passIndex, getPropertyKey(item->index()), value));
+ }
+ else if (item->index().parent().data().toString() == "shader_properties")
+ {
+ // shader property
+ int passIndex;
+ getContext(item->index(), &passIndex, NULL);
+ queueAction(new ActionSetShaderProperty(
+ material.toStdString(), passIndex, getPropertyKey(item->index()), value));
+ }
+ else if (item->index().parent().data().toString() == "texture_unit")
+ {
+ // texture property
+ int passIndex, textureIndex;
+ getContext(item->index(), &passIndex, &textureIndex);
+ queueAction(new ActionSetTextureProperty(
+ material.toStdString(), passIndex, textureIndex, getPropertyKey(item->index()), value));
+ }
+}
+
+void sh::MainWindow::buildMaterialModel(MaterialQuery *data)
+{
+ mMaterialPropertyModel->clear();
+
+ mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name")));
+ mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value")));
+
+ for (std::map<std::string, MaterialProperty>::const_iterator it = data->mProperties.begin();
+ it != data->mProperties.end(); ++it)
+ {
+ addProperty(mMaterialPropertyModel->invisibleRootItem(), it->first, it->second);
+ }
+
+ for (std::vector<PassInfo>::iterator it = data->mPasses.begin();
+ it != data->mPasses.end(); ++it)
+ {
+ QStandardItem* passItem = new QStandardItem (QString("pass"));
+ passItem->setFlags(passItem->flags() &= ~Qt::ItemIsEditable);
+ passItem->setData(QVariant(static_cast<int>(MaterialProperty::Object)), Qt::UserRole);
+
+ if (it->mShaderProperties.size())
+ {
+ QStandardItem* shaderPropertiesItem = new QStandardItem (QString("shader_properties"));
+ shaderPropertiesItem->setFlags(shaderPropertiesItem->flags() &= ~Qt::ItemIsEditable);
+ shaderPropertiesItem->setData(QVariant(static_cast<int>(MaterialProperty::Object)), Qt::UserRole);
+
+ for (std::map<std::string, MaterialProperty>::iterator pit = it->mShaderProperties.begin();
+ pit != it->mShaderProperties.end(); ++pit)
+ {
+ addProperty(shaderPropertiesItem, pit->first, pit->second);
+ }
+ passItem->appendRow(shaderPropertiesItem);
+ }
+
+ for (std::map<std::string, MaterialProperty>::iterator pit = it->mProperties.begin();
+ pit != it->mProperties.end(); ++pit)
+ {
+ addProperty(passItem, pit->first, pit->second);
+ }
+
+ for (std::vector<TextureUnitInfo>::iterator tIt = it->mTextureUnits.begin();
+ tIt != it->mTextureUnits.end(); ++tIt)
+ {
+ QStandardItem* unitItem = new QStandardItem (QString("texture_unit"));
+ unitItem->setFlags(unitItem->flags() &= ~Qt::ItemIsEditable);
+ unitItem->setData(QVariant(static_cast<int>(MaterialProperty::Object)), Qt::UserRole);
+ QStandardItem* nameItem = new QStandardItem (QString::fromStdString(tIt->mName));
+ nameItem->setData(QVariant(static_cast<int>(MaterialProperty::Object)), Qt::UserRole);
+
+ QList<QStandardItem*> texUnit;
+ texUnit << unitItem << nameItem;
+
+ for (std::map<std::string, MaterialProperty>::iterator pit = tIt->mProperties.begin();
+ pit != tIt->mProperties.end(); ++pit)
+ {
+ addProperty(unitItem, pit->first, pit->second);
+ }
+
+ passItem->appendRow(texUnit);
+ }
+
+ QList<QStandardItem*> toAdd;
+ toAdd << passItem;
+ toAdd << new QStandardItem(QString(""));
+ mMaterialPropertyModel->appendRow(toAdd);
+ }
+
+ ui->materialView->expandAll();
+ ui->materialView->resizeColumnToContents(0);
+ ui->materialView->resizeColumnToContents(1);
+}
+
+void sh::MainWindow::addProperty(QStandardItem *parent, const std::string &key, MaterialProperty value, bool scrollTo)
+{
+ QList<QStandardItem*> toAdd;
+ QStandardItem* keyItem = new QStandardItem(QString::fromStdString(key));
+ keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable);
+ keyItem->setData(QVariant(value.mType), Qt::UserRole);
+ keyItem->setData(QVariant(value.mSource), Qt::UserRole+1);
+ toAdd.push_back(keyItem);
+
+ QStandardItem* valueItem = NULL;
+ if (value.mSource != MaterialProperty::None)
+ {
+ valueItem = new QStandardItem(QString::fromStdString(value.mValue));
+ valueItem->setData(QVariant(value.mType), Qt::UserRole);
+ valueItem->setData(QVariant(value.mSource), Qt::UserRole+1);
+ toAdd.push_back(valueItem);
+ }
+
+
+ if (value.mSource == MaterialProperty::Inherited_Unchanged)
+ {
+ QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText);
+ keyItem->setData(color, Qt::ForegroundRole);
+ if (valueItem)
+ valueItem->setData(color, Qt::ForegroundRole);
+ }
+ if (value.mType == MaterialProperty::Boolean && valueItem)
+ {
+ valueItem->setCheckable(true);
+ valueItem->setCheckState((value.mValue == "true") ? Qt::Checked : Qt::Unchecked);
+ }
+
+ parent->appendRow(toAdd);
+
+ if (scrollTo)
+ ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(keyItem->index()));
+}
+
+void sh::MainWindow::buildConfigurationModel(ConfigurationQuery *data)
+{
+ while (mConfigurationModel->rowCount())
+ mConfigurationModel->removeRow(0);
+ for (std::map<std::string, std::string>::iterator it = data->mProperties.begin();
+ it != data->mProperties.end(); ++it)
+ {
+ QList<QStandardItem*> toAdd;
+ QStandardItem* name = new QStandardItem(QString::fromStdString(it->first));
+ name->setFlags(name->flags() &= ~Qt::ItemIsEditable);
+ QStandardItem* value = new QStandardItem(QString::fromStdString(it->second));
+ toAdd.push_back(name);
+ toAdd.push_back(value);
+ mConfigurationModel->appendRow(toAdd);
+ }
+
+ // add items that are in global settings, but not in this configuration (with a "inactive" color)
+ for (std::map<std::string, std::string>::const_iterator it = mState.mGlobalSettingsMap.begin();
+ it != mState.mGlobalSettingsMap.end(); ++it)
+ {
+ if (data->mProperties.find(it->first) == data->mProperties.end())
+ {
+ QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText);
+ QList<QStandardItem*> toAdd;
+ QStandardItem* name = new QStandardItem(QString::fromStdString(it->first));
+ name->setFlags(name->flags() &= ~Qt::ItemIsEditable);
+ name->setData(color, Qt::ForegroundRole);
+ QStandardItem* value = new QStandardItem(QString::fromStdString(it->second));
+ value->setData(color, Qt::ForegroundRole);
+ toAdd.push_back(name);
+ toAdd.push_back(value);
+ mConfigurationModel->appendRow(toAdd);
+ }
+ }
+}
+
+void sh::MainWindow::on_actionCreatePass_triggered()
+{
+ QString material = getSelectedMaterial();
+ if (!material.isEmpty())
+ {
+ addProperty(mMaterialPropertyModel->invisibleRootItem(),
+ "pass", MaterialProperty("", MaterialProperty::Object, MaterialProperty::None), true);
+
+ queueAction (new ActionCreatePass(material.toStdString()));
+ }
+}
+
+void sh::MainWindow::on_actionDeleteProperty_triggered()
+{
+ QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex());
+ QString material = getSelectedMaterial();
+ if (material.isEmpty())
+ return;
+
+ mIgnoreMaterialPropertyChange = true;
+
+ if (getPropertyKey(selectedIndex) == "pass")
+ {
+ // delete whole pass
+ int passIndex;
+ getContext(selectedIndex, &passIndex, NULL);
+ if (passIndex == 0)
+ {
+ QMessageBox msgBox;
+ msgBox.setText("The first pass can not be deleted.");
+ msgBox.exec();
+ }
+ else
+ {
+ queueAction(new ActionDeletePass(material.toStdString(), passIndex));
+ mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent());
+ }
+ }
+ else if (getPropertyKey(selectedIndex) == "texture_unit")
+ {
+ // delete whole texture unit
+ int passIndex, textureIndex;
+ getContext(selectedIndex, &passIndex, &textureIndex);
+ queueAction(new ActionDeleteTextureUnit(material.toStdString(), passIndex, textureIndex));
+ mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent());
+ }
+ else if (!selectedIndex.parent().isValid())
+ {
+ // top level material property
+ MaterialProperty::Source source = static_cast<MaterialProperty::Source>(
+ mMaterialPropertyModel->itemFromIndex(selectedIndex)->data(Qt::UserRole+1).toInt());
+ if (source == MaterialProperty::Inherited_Unchanged)
+ {
+ QMessageBox msgBox;
+ msgBox.setText("Inherited properties can not be deleted.");
+ msgBox.exec();
+ }
+ else
+ {
+ queueAction(new ActionDeleteMaterialProperty(
+ material.toStdString(), getPropertyKey(selectedIndex)));
+ std::cout << "source is " << source << std::endl;
+ if (source == MaterialProperty::Inherited_Changed)
+ {
+ QColor inactiveColor = ui->materialView->palette().color(QPalette::Disabled, QPalette::WindowText);
+ mMaterialPropertyModel->item(selectedIndex.row(), 0)
+ ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1);
+ mMaterialPropertyModel->item(selectedIndex.row(), 0)
+ ->setData(inactiveColor, Qt::ForegroundRole);
+ mMaterialPropertyModel->item(selectedIndex.row(), 1)
+ ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1);
+ mMaterialPropertyModel->item(selectedIndex.row(), 1)
+ ->setData(inactiveColor, Qt::ForegroundRole);
+
+ // make sure to update the property's value
+ requestQuery(new sh::MaterialPropertyQuery(material.toStdString(), getPropertyKey(selectedIndex)));
+ }
+ else
+ mMaterialPropertyModel->removeRow(selectedIndex.row());
+ }
+ }
+ else if (selectedIndex.parent().data().toString() == "pass")
+ {
+ // pass property
+ int passIndex;
+ getContext(selectedIndex, &passIndex, NULL);
+ queueAction(new ActionDeletePassProperty(
+ material.toStdString(), passIndex, getPropertyKey(selectedIndex)));
+ mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent());
+ }
+ else if (selectedIndex.parent().data().toString() == "shader_properties")
+ {
+ // shader property
+ int passIndex;
+ getContext(selectedIndex, &passIndex, NULL);
+ queueAction(new ActionDeleteShaderProperty(
+ material.toStdString(), passIndex, getPropertyKey(selectedIndex)));
+ mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent());
+ }
+ else if (selectedIndex.parent().data().toString() == "texture_unit")
+ {
+ // texture property
+ int passIndex, textureIndex;
+ getContext(selectedIndex, &passIndex, &textureIndex);
+ queueAction(new ActionDeleteTextureProperty(
+ material.toStdString(), passIndex, textureIndex, getPropertyKey(selectedIndex)));
+ mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent());
+ }
+ mIgnoreMaterialPropertyChange = false;
+}
+
+void sh::MainWindow::on_actionNewProperty_triggered()
+{
+ QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex());
+ QString material = getSelectedMaterial();
+ if (material.isEmpty())
+ return;
+
+ AddPropertyDialog* dialog = new AddPropertyDialog(this);
+ dialog->exec();
+ QString propertyName = dialog->mName;
+ QString defaultValue = "";
+
+ /// \todo check if this property name exists already
+
+ if (!propertyName.isEmpty())
+ {
+ int passIndex, textureIndex;
+ bool isInPass, isInTextureUnit;
+ getContext(selectedIndex, &passIndex, &textureIndex, &isInPass, &isInTextureUnit);
+
+ QList<QStandardItem*> items;
+ QStandardItem* keyItem = new QStandardItem(propertyName);
+ keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable);
+ items << keyItem;
+ items << new QStandardItem(defaultValue);
+
+ // figure out which item the new property should be a child of
+ QModelIndex parentIndex = selectedIndex;
+ if (selectedIndex.data(Qt::UserRole) != MaterialProperty::Object)
+ parentIndex = selectedIndex.parent();
+ QStandardItem* parentItem;
+ if (!parentIndex.isValid())
+ parentItem = mMaterialPropertyModel->invisibleRootItem();
+ else
+ parentItem = mMaterialPropertyModel->itemFromIndex(parentIndex);
+
+ if (isInTextureUnit)
+ {
+ queueAction(new ActionSetTextureProperty(
+ material.toStdString(), passIndex, textureIndex, propertyName.toStdString(), defaultValue.toStdString()));
+ }
+ else if (isInPass)
+ {
+ if (selectedIndex.parent().child(selectedIndex.row(),0).data().toString() == "shader_properties"
+ || selectedIndex.parent().data().toString() == "shader_properties")
+ {
+ queueAction(new ActionSetShaderProperty(
+ material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString()));
+ }
+ else
+ {
+ queueAction(new ActionSetPassProperty(
+ material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString()));
+ }
+ }
+ else
+ {
+ queueAction(new ActionSetMaterialProperty(
+ material.toStdString(), propertyName.toStdString(), defaultValue.toStdString()));
+ }
+
+ addProperty(parentItem, propertyName.toStdString(),
+ MaterialProperty (defaultValue.toStdString(), MaterialProperty::Misc, MaterialProperty::Normal), true);
+
+ /// \todo scroll to newly added property
+ }
+}
+
+void sh::MainWindow::on_actionCreateTextureUnit_triggered()
+{
+ QString material = getSelectedMaterial();
+ if (material.isEmpty())
+ return;
+
+ QInputDialog dialog(this);
+
+ QString text = QInputDialog::getText(this, tr("New texture unit"),
+ tr("Texture unit name (for referencing in shaders):"));
+ if (!text.isEmpty())
+ {
+ QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex());
+ int passIndex;
+ getContext(selectedIndex, &passIndex, NULL);
+ queueAction(new ActionCreateTextureUnit(material.toStdString(), passIndex, text.toStdString()));
+
+ // add to model
+ int index = 0;
+ for (int i=0; i<mMaterialPropertyModel->rowCount(); ++i)
+ {
+ if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass"))
+ {
+ if (index == passIndex)
+ {
+ addProperty(mMaterialPropertyModel->itemFromIndex(mMaterialPropertyModel->index(i, 0)),
+ "texture_unit", MaterialProperty(text.toStdString(), MaterialProperty::Object), true);
+ break;
+ }
+
+ ++index;
+ }
+ }
+ }
+}
+
+void sh::MainWindow::on_clearButton_clicked()
+{
+ ui->errorLog->clear();
+}
+
+void sh::MainWindow::on_tabWidget_currentChanged(int index)
+{
+ QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::WindowText);
+
+ if (index == 3)
+ ui->tabWidget->tabBar()->setTabTextColor(3, color);
+}
diff --git a/extern/shiny/Editor/MainWindow.hpp b/extern/shiny/Editor/MainWindow.hpp
new file mode 100644
index 0000000000..3f0dc295c0
--- /dev/null
+++ b/extern/shiny/Editor/MainWindow.hpp
@@ -0,0 +1,139 @@
+#ifndef SHINY_EDITOR_MAINWINDOW_HPP
+#define SHINY_EDITOR_MAINWINDOW_HPP
+
+#include <QMainWindow>
+
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+#include <QStringListModel>
+
+#include <queue>
+
+#include "Actions.hpp"
+#include "Query.hpp"
+
+#include "PropertySortModel.hpp"
+
+namespace Ui {
+class MainWindow;
+}
+
+namespace sh
+{
+
+struct SynchronizationState;
+
+
+/**
+ * @brief A snapshot of the material system's state. Lock the mUpdateMutex before accessing.
+ */
+struct MaterialSystemState
+{
+ std::vector<std::string> mMaterialList;
+
+ std::map<std::string, std::string> mGlobalSettingsMap;
+
+ std::vector<std::string> mConfigurationList;
+
+ std::vector<std::string> mMaterialFiles;
+ std::vector<std::string> mConfigurationFiles;
+
+ std::vector<std::string> mShaderSets;
+
+ std::string mErrors;
+};
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+ // really should be an std::atomic
+ volatile bool mRequestShowWindow;
+ // dito
+ volatile bool mRequestExit;
+
+ SynchronizationState* mSync;
+
+ /// \todo Is there a better way to ignore manual model changes?
+ bool mIgnoreGlobalSettingChange;
+ bool mIgnoreConfigurationChange;
+ bool mIgnoreMaterialChange;
+ bool mIgnoreMaterialPropertyChange;
+
+ std::queue<Action*> mActionQueue;
+ std::vector<Query*> mQueries;
+
+ MaterialSystemState mState;
+
+private:
+ Ui::MainWindow *ui;
+
+ // material tab
+ QStringListModel* mMaterialModel;
+ QSortFilterProxyModel* mMaterialProxyModel;
+
+ QStandardItemModel* mMaterialPropertyModel;
+ PropertySortModel* mMaterialSortModel;
+
+ // global settings tab
+ QStandardItemModel* mGlobalSettingsModel;
+
+ // configuration tab
+ QStandardItemModel* mConfigurationModel;
+
+ void queueAction(Action* action);
+ void requestQuery(Query* query);
+
+ void buildMaterialModel (MaterialQuery* data);
+ void buildConfigurationModel (ConfigurationQuery* data);
+
+ QString getSelectedMaterial();
+
+ /// get the context of an index in the material property model
+ void getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass=NULL, bool* isInTextureUnit=NULL);
+
+ std::string getPropertyKey(QModelIndex index);
+ std::string getPropertyValue(QModelIndex index);
+
+ void addProperty (QStandardItem* parent, const std::string& key, MaterialProperty value, bool scrollTo=false);
+
+protected:
+ void closeEvent(QCloseEvent *event);
+
+public slots:
+ void onIdle();
+
+ void onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous);
+ void onConfigurationSelectionChanged (const QString& current);
+
+ void onGlobalSettingChanged (QStandardItem* item);
+ void onConfigurationChanged (QStandardItem* item);
+ void onMaterialPropertyChanged (QStandardItem* item);
+
+ void onContextMenuRequested(const QPoint& point);
+
+private slots:
+ void on_lineEdit_textEdited(const QString &arg1);
+ void on_actionSave_triggered();
+ void on_actionNewMaterial_triggered();
+ void on_actionDeleteMaterial_triggered();
+ void on_actionQuit_triggered();
+ void on_actionNewConfiguration_triggered();
+ void on_actionDeleteConfiguration_triggered();
+ void on_actionDeleteConfigurationProperty_triggered();
+ void on_actionCloneMaterial_triggered();
+ void on_actionCreatePass_triggered();
+ void on_actionDeleteProperty_triggered();
+ void on_actionNewProperty_triggered();
+ void on_actionCreateTextureUnit_triggered();
+ void on_clearButton_clicked();
+ void on_tabWidget_currentChanged(int index);
+};
+
+}
+
+#endif // MAINWINDOW_HPP
diff --git a/extern/shiny/Editor/NewMaterialDialog.cpp b/extern/shiny/Editor/NewMaterialDialog.cpp
new file mode 100644
index 0000000000..f1a716a9f0
--- /dev/null
+++ b/extern/shiny/Editor/NewMaterialDialog.cpp
@@ -0,0 +1,14 @@
+#include "NewMaterialDialog.hpp"
+#include "ui_newmaterialdialog.h"
+
+NewMaterialDialog::NewMaterialDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::NewMaterialDialog)
+{
+ ui->setupUi(this);
+}
+
+NewMaterialDialog::~NewMaterialDialog()
+{
+ delete ui;
+}
diff --git a/extern/shiny/Editor/NewMaterialDialog.hpp b/extern/shiny/Editor/NewMaterialDialog.hpp
new file mode 100644
index 0000000000..2a20bbb395
--- /dev/null
+++ b/extern/shiny/Editor/NewMaterialDialog.hpp
@@ -0,0 +1,22 @@
+#ifndef NEWMATERIALDIALOG_HPP
+#define NEWMATERIALDIALOG_HPP
+
+#include <QDialog>
+
+namespace Ui {
+class NewMaterialDialog;
+}
+
+class NewMaterialDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit NewMaterialDialog(QWidget *parent = 0);
+ ~NewMaterialDialog();
+
+private:
+ Ui::NewMaterialDialog *ui;
+};
+
+#endif // NEWMATERIALDIALOG_HPP
diff --git a/extern/shiny/Editor/PropertySortModel.cpp b/extern/shiny/Editor/PropertySortModel.cpp
new file mode 100644
index 0000000000..637fe11b02
--- /dev/null
+++ b/extern/shiny/Editor/PropertySortModel.cpp
@@ -0,0 +1,35 @@
+#include "PropertySortModel.hpp"
+
+#include "Query.hpp"
+
+#include <iostream>
+sh::PropertySortModel::PropertySortModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+}
+
+bool sh::PropertySortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ if (left.data(Qt::UserRole+1).toInt() != 0 && right.data(Qt::UserRole+1).toInt() != 0)
+ {
+ int sourceL = left.data(Qt::UserRole+1).toInt();
+ int sourceR = right.data(Qt::UserRole+1).toInt();
+
+ if (sourceL > sourceR)
+ return true;
+ else if (sourceR > sourceL)
+ return false;
+ }
+
+ int typeL = left.data(Qt::UserRole).toInt();
+ int typeR = right.data(Qt::UserRole).toInt();
+
+ if (typeL > typeR)
+ return true;
+ else if (typeR > typeL)
+ return false;
+
+ QString nameL = left.data().toString();
+ QString nameR = right.data().toString();
+ return nameL > nameR;
+}
diff --git a/extern/shiny/Editor/PropertySortModel.hpp b/extern/shiny/Editor/PropertySortModel.hpp
new file mode 100644
index 0000000000..f96927764e
--- /dev/null
+++ b/extern/shiny/Editor/PropertySortModel.hpp
@@ -0,0 +1,21 @@
+#ifndef SHINY_EDITOR_PROPERTYSORTMODEL_H
+#define SHINY_EDITOR_PROPERTYSORTMODEL_H
+
+#include <QSortFilterProxyModel>
+
+namespace sh
+{
+
+ class PropertySortModel : public QSortFilterProxyModel
+ {
+ Q_OBJECT
+
+ public:
+ PropertySortModel(QObject* parent);
+ protected:
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+ };
+
+}
+
+#endif
diff --git a/extern/shiny/Editor/Query.cpp b/extern/shiny/Editor/Query.cpp
new file mode 100644
index 0000000000..ccf07b62bc
--- /dev/null
+++ b/extern/shiny/Editor/Query.cpp
@@ -0,0 +1,134 @@
+#include "Query.hpp"
+
+#include "../Main/Factory.hpp"
+
+namespace sh
+{
+
+void Query::execute()
+{
+ executeImpl();
+ mDone = true;
+}
+
+ConfigurationQuery::ConfigurationQuery(const std::string &name)
+ : mName(name)
+{
+}
+
+void ConfigurationQuery::executeImpl()
+{
+ sh::Factory::getInstance().listConfigurationSettings(mName, mProperties);
+}
+
+void MaterialQuery::executeImpl()
+{
+ sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(mName);
+
+ if (instance->getParent())
+ mParent = static_cast<sh::MaterialInstance*>(instance->getParent())->getName();
+
+ // add the inherited properties
+ sh::PropertySetGet* parent = instance;
+ std::vector<std::string> inheritedPropertiesVector;
+ while (parent->getParent())
+ {
+ parent = parent->getParent();
+ const sh::PropertyMap& parentProperties = parent->listProperties();
+
+ for (PropertyMap::const_iterator it = parentProperties.begin(); it != parentProperties.end(); ++it)
+ {
+ MaterialProperty::Source source = MaterialProperty::Inherited_Unchanged;
+ MaterialProperty::Type type = getType(it->first, parent->getProperty(it->first));
+ mProperties[it->first] = MaterialProperty (
+ retrieveValue<sh::StringValue>(parent->getProperty(it->first), NULL).get(),
+ type, source);
+ inheritedPropertiesVector.push_back(it->first);
+ }
+ }
+
+ // add our properties
+ const sh::PropertyMap& ourProperties = instance->listProperties();
+ for (PropertyMap::const_iterator it = ourProperties.begin(); it != ourProperties.end(); ++it)
+ {
+ MaterialProperty::Source source =
+ (std::find(inheritedPropertiesVector.begin(), inheritedPropertiesVector.end(), it->first)
+ != inheritedPropertiesVector.end()) ?
+ MaterialProperty::Inherited_Changed : MaterialProperty::Normal;
+ MaterialProperty::Type type = getType(it->first, instance->getProperty(it->first));
+ mProperties[it->first] = MaterialProperty (
+ retrieveValue<sh::StringValue>(instance->getProperty(it->first), NULL).get(),
+ type, source);
+ }
+
+ std::vector<MaterialInstancePass>* passes = instance->getPasses();
+ for (std::vector<MaterialInstancePass>::iterator it = passes->begin(); it != passes->end(); ++it)
+ {
+ mPasses.push_back(PassInfo());
+
+ const sh::PropertyMap& passProperties = it->listProperties();
+ for (PropertyMap::const_iterator pit = passProperties.begin(); pit != passProperties.end(); ++pit)
+ {
+ PropertyValuePtr property = it->getProperty(pit->first);
+ MaterialProperty::Type type = getType(pit->first, property);
+ if (typeid(*property).name() == typeid(sh::LinkedValue).name())
+ mPasses.back().mProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type);
+ else
+ mPasses.back().mProperties[pit->first] = MaterialProperty(
+ retrieveValue<sh::StringValue>(property, NULL).get(), type);
+ }
+
+ const sh::PropertyMap& shaderProperties = it->mShaderProperties.listProperties();
+ for (PropertyMap::const_iterator pit = shaderProperties.begin(); pit != shaderProperties.end(); ++pit)
+ {
+ PropertyValuePtr property = it->mShaderProperties.getProperty(pit->first);
+ MaterialProperty::Type type = getType(pit->first, property);
+ if (typeid(*property).name() == typeid(sh::LinkedValue).name())
+ mPasses.back().mShaderProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type);
+ else
+ mPasses.back().mShaderProperties[pit->first] = MaterialProperty(
+ retrieveValue<sh::StringValue>(property, NULL).get(), type);
+ }
+
+ std::vector<MaterialInstanceTextureUnit>* texUnits = &it->mTexUnits;
+ for (std::vector<MaterialInstanceTextureUnit>::iterator tIt = texUnits->begin(); tIt != texUnits->end(); ++tIt)
+ {
+ mPasses.back().mTextureUnits.push_back(TextureUnitInfo());
+ mPasses.back().mTextureUnits.back().mName = tIt->getName();
+ const sh::PropertyMap& unitProperties = tIt->listProperties();
+ for (PropertyMap::const_iterator pit = unitProperties.begin(); pit != unitProperties.end(); ++pit)
+ {
+ PropertyValuePtr property = tIt->getProperty(pit->first);
+ MaterialProperty::Type type = getType(pit->first, property);
+ if (typeid(*property).name() == typeid(sh::LinkedValue).name())
+ mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty(
+ "$" + property->_getStringValue(), MaterialProperty::Linked);
+ else
+ mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty(
+ retrieveValue<sh::StringValue>(property, NULL).get(), type);
+ }
+ }
+ }
+}
+
+MaterialProperty::Type MaterialQuery::getType(const std::string &key, PropertyValuePtr value)
+{
+ if (typeid(*value).name() == typeid(sh::LinkedValue).name())
+ return MaterialProperty::Linked;
+
+ if (key == "vertex_program" || key == "fragment_program")
+ return MaterialProperty::Shader;
+
+ std::string valueStr = retrieveValue<sh::StringValue>(value, NULL).get();
+
+ if (valueStr == "false" || valueStr == "true")
+ return MaterialProperty::Boolean;
+}
+
+void MaterialPropertyQuery::executeImpl()
+{
+ sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName);
+ mValue = retrieveValue<sh::StringValue>(m->getProperty(mPropertyName), m).get();
+}
+
+}
diff --git a/extern/shiny/Editor/Query.hpp b/extern/shiny/Editor/Query.hpp
new file mode 100644
index 0000000000..d98c8c9b27
--- /dev/null
+++ b/extern/shiny/Editor/Query.hpp
@@ -0,0 +1,121 @@
+#ifndef SH_QUERY_H
+#define SH_QUERY_H
+
+#include <string>
+#include <map>
+#include <vector>
+
+#include "../Main/PropertyBase.hpp"
+
+namespace sh
+{
+
+class Query
+{
+public:
+ Query()
+ : mDone(false) {}
+ virtual ~Query() {}
+
+ void execute();
+
+ bool mDone;
+
+protected:
+ virtual void executeImpl() = 0;
+};
+
+class ConfigurationQuery : public Query
+{
+public:
+ ConfigurationQuery(const std::string& name);
+
+ std::map<std::string, std::string> mProperties;
+protected:
+ std::string mName;
+ virtual void executeImpl();
+};
+
+
+struct MaterialProperty
+{
+
+ enum Type
+ {
+ Texture,
+ Color,
+ Boolean,
+ Shader,
+ Misc,
+ Linked,
+ Object // child object, i.e. pass, texture unit, shader properties
+ };
+
+ enum Source
+ {
+ Normal,
+ Inherited_Changed,
+ Inherited_Unchanged,
+ None // there is no property source (e.g. a pass, which does not have a name)
+ };
+
+ MaterialProperty() {}
+ MaterialProperty (const std::string& value, Type type, Source source=Normal)
+ : mValue(value), mType(type), mSource(source) {}
+
+ std::string mValue;
+ Type mType;
+ Source mSource;
+};
+
+
+struct TextureUnitInfo
+{
+ std::string mName;
+ std::map<std::string, MaterialProperty> mProperties;
+};
+
+struct PassInfo
+{
+ std::map<std::string, MaterialProperty> mShaderProperties;
+
+ std::map<std::string, MaterialProperty> mProperties;
+ std::vector<TextureUnitInfo> mTextureUnits;
+};
+
+class MaterialQuery : public Query
+{
+public:
+ MaterialQuery(const std::string& name)
+ : mName(name) {}
+
+ std::string mParent;
+ std::vector<PassInfo> mPasses;
+ std::map<std::string, MaterialProperty> mProperties;
+
+protected:
+ std::string mName;
+ virtual void executeImpl();
+
+ MaterialProperty::Type getType (const std::string& key, PropertyValuePtr value);
+};
+
+class MaterialPropertyQuery : public Query
+{
+public:
+ MaterialPropertyQuery(const std::string& name, const std::string& propertyName)
+ : mName(name), mPropertyName(propertyName)
+ {
+ }
+
+ std::string mValue;
+
+ std::string mName;
+ std::string mPropertyName;
+protected:
+ virtual void executeImpl();
+};
+
+}
+
+#endif
diff --git a/extern/shiny/Editor/addpropertydialog.ui b/extern/shiny/Editor/addpropertydialog.ui
new file mode 100644
index 0000000000..63de7d1415
--- /dev/null
+++ b/extern/shiny/Editor/addpropertydialog.ui
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AddPropertyDialog</class>
+ <widget class="QDialog" name="AddPropertyDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>257</width>
+ <height>133</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Property name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Editing widget</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="comboBox">
+ <item>
+ <property name="text">
+ <string>Checkbox</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Shader</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Color</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Texture</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Other</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AddPropertyDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AddPropertyDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/extern/shiny/Editor/mainwindow.ui b/extern/shiny/Editor/mainwindow.ui
new file mode 100644
index 0000000000..b27c8357de
--- /dev/null
+++ b/extern/shiny/Editor/mainwindow.ui
@@ -0,0 +1,420 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>647</width>
+ <height>512</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="sh::ColoredTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <property name="accessibleName">
+ <string/>
+ </property>
+ <attribute name="title">
+ <string>Materials</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="verticalLayoutWidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="placeholderText">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="materialList">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolBar" name="toolBar1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <addaction name="actionNewMaterial"/>
+ <addaction name="actionCloneMaterial"/>
+ <addaction name="actionDeleteMaterial"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="verticalLayoutWidget2">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QTreeView" name="materialView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolBar" name="toolBar4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonIconOnly</enum>
+ </property>
+ <addaction name="actionNewProperty"/>
+ <addaction name="actionDeleteProperty"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab2">
+ <attribute name="title">
+ <string>Global settings</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QTableView" name="globalSettingsView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Configurations</string>
+ </attribute>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="layoutWidget">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QListWidget" name="configurationList"/>
+ </item>
+ <item>
+ <widget class="QToolBar" name="toolBar2">
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <addaction name="actionNewConfiguration"/>
+ <addaction name="actionDeleteConfiguration"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="layoutWidget2">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QTableView" name="configurationView"/>
+ </item>
+ <item>
+ <widget class="QToolBar" name="toolBar2_2">
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <addaction name="actionDeleteConfigurationProperty"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>Errors</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QTextEdit" name="errorLog">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clearButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ <property name="icon">
+ <iconset theme="edit-clear"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>647</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <property name="defaultUp">
+ <bool>false</bool>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionSave"/>
+ <addaction name="actionQuit"/>
+ </widget>
+ <widget class="QMenu" name="menuMaterial">
+ <property name="title">
+ <string>Material</string>
+ </property>
+ <addaction name="actionNewMaterial"/>
+ <addaction name="actionCloneMaterial"/>
+ <addaction name="actionDeleteMaterial"/>
+ <addaction name="actionChange_parent"/>
+ </widget>
+ <widget class="QMenu" name="menuHistory">
+ <property name="title">
+ <string>History</string>
+ </property>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuMaterial"/>
+ <addaction name="menuHistory"/>
+ </widget>
+ <action name="actionQuit">
+ <property name="icon">
+ <iconset theme="application-exit">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="icon">
+ <iconset theme="document-save">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Save</string>
+ </property>
+ <property name="toolTip">
+ <string>Save all</string>
+ </property>
+ </action>
+ <action name="actionDeleteMaterial">
+ <property name="icon">
+ <iconset theme="edit-delete">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete selected material</string>
+ </property>
+ </action>
+ <action name="actionChange_parent">
+ <property name="text">
+ <string>Change parent...</string>
+ </property>
+ </action>
+ <action name="actionNewMaterial">
+ <property name="icon">
+ <iconset theme="document-new">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ <property name="toolTip">
+ <string>Create a new material</string>
+ </property>
+ </action>
+ <action name="actionCloneMaterial">
+ <property name="icon">
+ <iconset theme="edit-copy">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Clone</string>
+ </property>
+ <property name="toolTip">
+ <string>Clone selected material</string>
+ </property>
+ </action>
+ <action name="actionDeleteConfiguration">
+ <property name="icon">
+ <iconset theme="edit-delete">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete selected configuration</string>
+ </property>
+ <property name="shortcut">
+ <string>Del</string>
+ </property>
+ </action>
+ <action name="actionNewConfiguration">
+ <property name="icon">
+ <iconset theme="document-new">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ <property name="toolTip">
+ <string>Create a new configuration</string>
+ </property>
+ </action>
+ <action name="actionDeleteConfigurationProperty">
+ <property name="icon">
+ <iconset theme="edit-delete">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete property</string>
+ </property>
+ </action>
+ <action name="actionDeleteProperty">
+ <property name="icon">
+ <iconset theme="remove">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete item</string>
+ </property>
+ </action>
+ <action name="actionNewProperty">
+ <property name="icon">
+ <iconset theme="add">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>New property</string>
+ </property>
+ </action>
+ <action name="actionCreatePass">
+ <property name="icon">
+ <iconset theme="edit-add">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Create pass</string>
+ </property>
+ </action>
+ <action name="actionCreateTextureUnit">
+ <property name="icon">
+ <iconset theme="edit-add">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Create texture unit</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>sh::ColoredTabWidget</class>
+ <extends>QTabWidget</extends>
+ <header>ColoredTabWidget.hpp</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/extern/shiny/Editor/newmaterialdialog.ui b/extern/shiny/Editor/newmaterialdialog.ui
new file mode 100644
index 0000000000..f24561cf79
--- /dev/null
+++ b/extern/shiny/Editor/newmaterialdialog.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewMaterialDialog</class>
+ <widget class="QDialog" name="NewMaterialDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>385</width>
+ <height>198</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Parent material</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lineEdit_2"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>File</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="comboBox"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NewMaterialDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>NewMaterialDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/extern/shiny/Extra/core.h b/extern/shiny/Extra/core.h
new file mode 100644
index 0000000000..cba7167770
--- /dev/null
+++ b/extern/shiny/Extra/core.h
@@ -0,0 +1,168 @@
+#if SH_HLSL == 1 || SH_CG == 1
+
+ #define shTexture2D sampler2D
+ #define shSample(tex, coord) tex2D(tex, coord)
+ #define shCubicSample(tex, coord) texCUBE(tex, coord)
+ #define shLerp(a, b, t) lerp(a, b, t)
+ #define shSaturate(a) saturate(a)
+
+ #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name)
+
+ #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name)
+
+ #define shMatrixMult(m, v) mul(m, v)
+
+ #define shUniform(type, name) , uniform type name
+
+ #define shTangentInput(type) , in type tangent : TANGENT
+ #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1)
+ #define shInput(type, name) , in type name : TEXCOORD@shCounter(1)
+ #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2)
+
+ #define shNormalInput(type) , in type normal : NORMAL
+
+ #define shColourInput(type) , in type colour : COLOR
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define shOutputPosition oPosition
+ #define shInputPosition iPosition
+
+
+ #define SH_BEGIN_PROGRAM \
+ void main( \
+ float4 iPosition : POSITION \
+ , out float4 oPosition : POSITION
+
+ #define SH_START_PROGRAM \
+ ) \
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shOutputColour(num) oColor##num
+
+ #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num
+
+ #define SH_BEGIN_PROGRAM \
+ void main( \
+ out float4 oColor0 : COLOR
+
+ #define SH_START_PROGRAM \
+ ) \
+
+ #endif
+
+#endif
+
+#if SH_GLSL == 1
+
+ @version 120
+
+ #define float2 vec2
+ #define float3 vec3
+ #define float4 vec4
+ #define int2 ivec2
+ #define int3 ivec3
+ #define int4 ivec4
+ #define shTexture2D sampler2D
+ #define shSample(tex, coord) texture2D(tex, coord)
+ #define shCubicSample(tex, coord) textureCube(tex, coord)
+ #define shLerp(a, b, t) mix(a, b, t)
+ #define shSaturate(a) clamp(a, 0.0, 1.0)
+
+ #define shUniform(type, name) uniform type name;
+
+ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name)
+
+ #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name)
+
+ #define shMatrixMult(m, v) (m * v)
+
+ #define shOutputPosition gl_Position
+
+ #define float4x4 mat4
+ #define float3x3 mat3
+
+ // GLSL 1.3
+ #if 0
+
+ // automatically recognized by ogre when the input name equals this
+ #define shInputPosition vertex
+
+ #define shOutputColour(num) oColor##num
+
+ #define shTangentInput(type) in type tangent;
+ #define shVertexInput(type, name) in type name;
+ #define shInput(type, name) in type name;
+ #define shOutput(type, name) out type name;
+
+ // automatically recognized by ogre when the input name equals this
+ #define shNormalInput(type) in type normal;
+ #define shColourInput(type) in type colour;
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define SH_BEGIN_PROGRAM \
+ in float4 vertex;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shDeclareMrtOutput(num) out vec4 oColor##num;
+
+ #define SH_BEGIN_PROGRAM \
+ out float4 oColor0;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+
+ #endif
+
+ #endif
+
+ // GLSL 1.2
+
+ #if 1
+
+ // automatically recognized by ogre when the input name equals this
+ #define shInputPosition vertex
+
+ #define shOutputColour(num) gl_FragData[num]
+
+ #define shTangentInput(type) attribute type tangent;
+ #define shVertexInput(type, name) attribute type name;
+ #define shInput(type, name) varying type name;
+ #define shOutput(type, name) varying type name;
+
+ // automatically recognized by ogre when the input name equals this
+ #define shNormalInput(type) attribute type normal;
+ #define shColourInput(type) attribute type colour;
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define SH_BEGIN_PROGRAM \
+ attribute vec4 vertex;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shDeclareMrtOutput(num)
+
+ #define SH_BEGIN_PROGRAM
+
+ #define SH_START_PROGRAM \
+ void main(void)
+
+
+ #endif
+
+ #endif
+#endif
diff --git a/extern/shiny/License.txt b/extern/shiny/License.txt
new file mode 100644
index 0000000000..d89bcf3ad2
--- /dev/null
+++ b/extern/shiny/License.txt
@@ -0,0 +1,9 @@
+Copyright (c) 2012 <scrawl@baseoftrash.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp
new file mode 100644
index 0000000000..6254edbafb
--- /dev/null
+++ b/extern/shiny/Main/Factory.cpp
@@ -0,0 +1,803 @@
+#include "Factory.hpp"
+
+#include <stdexcept>
+#include <iostream>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "Platform.hpp"
+#include "ScriptLoader.hpp"
+#include "ShaderSet.hpp"
+#include "MaterialInstanceTextureUnit.hpp"
+
+namespace sh
+{
+ Factory* Factory::sThis = 0;
+ const std::string Factory::mBinaryCacheName = "binaryCache";
+
+ Factory& Factory::getInstance()
+ {
+ assert (sThis);
+ return *sThis;
+ }
+
+ Factory* Factory::getInstancePtr()
+ {
+ return sThis;
+ }
+
+ Factory::Factory (Platform* platform)
+ : mPlatform(platform)
+ , mShadersEnabled(true)
+ , mShaderDebugOutputEnabled(false)
+ , mCurrentLanguage(Language_None)
+ , mListener(NULL)
+ , mCurrentConfiguration(NULL)
+ , mCurrentLodConfiguration(NULL)
+ , mReadMicrocodeCache(false)
+ , mWriteMicrocodeCache(false)
+ , mReadSourceCache(false)
+ , mWriteSourceCache(false)
+ {
+ assert (!sThis);
+ sThis = this;
+
+ mPlatform->setFactory(this);
+ }
+
+ void Factory::loadAllFiles()
+ {
+ assert(mCurrentLanguage != Language_None);
+
+ if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt"))
+ {
+ std::ifstream file;
+ file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str());
+
+ std::string line;
+ while (getline(file, line))
+ {
+ std::string sourceFile = line;
+
+ if (!getline(file, line))
+ assert(0);
+
+ int modified = boost::lexical_cast<int>(line);
+
+ mShadersLastModified[sourceFile] = modified;
+ }
+ }
+
+ // load configurations
+ {
+ ScriptLoader shaderSetLoader(".configuration");
+ ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath());
+ std::map <std::string, ScriptNode*> nodes = shaderSetLoader.getAllConfigScripts();
+ for (std::map <std::string, ScriptNode*>::const_iterator it = nodes.begin();
+ it != nodes.end(); ++it)
+ {
+ if (!(it->second->getName() == "configuration"))
+ {
+ std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .configuration" << std::endl;
+ break;
+ }
+
+ Configuration newConfiguration;
+ newConfiguration.setParent(&mGlobalSettings);
+ newConfiguration.setSourceFile (it->second->mFileName);
+
+ std::vector<ScriptNode*> props = it->second->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt)
+ {
+ std::string name = (*propIt)->getName();
+ std::string val = (*propIt)->getValue();
+
+ newConfiguration.setProperty (name, makeProperty(val));
+ }
+
+ mConfigurations[it->first] = newConfiguration;
+ }
+ }
+
+ // load lod configurations
+ {
+ ScriptLoader lodLoader(".lod");
+ ScriptLoader::loadAllFiles (&lodLoader, mPlatform->getBasePath());
+ std::map <std::string, ScriptNode*> nodes = lodLoader.getAllConfigScripts();
+ for (std::map <std::string, ScriptNode*>::const_iterator it = nodes.begin();
+ it != nodes.end(); ++it)
+ {
+ if (!(it->second->getName() == "lod_configuration"))
+ {
+ std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .lod" << std::endl;
+ break;
+ }
+
+ if (it->first == "0")
+ {
+ throw std::runtime_error("lod level 0 (max lod) can't have a configuration");
+ }
+
+ PropertySetGet newLod;
+
+ std::vector<ScriptNode*> props = it->second->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt)
+ {
+ std::string name = (*propIt)->getName();
+ std::string val = (*propIt)->getValue();
+
+ newLod.setProperty (name, makeProperty(val));
+ }
+
+ mLodConfigurations[boost::lexical_cast<int>(it->first)] = newLod;
+ }
+ }
+
+ // load shader sets
+ bool removeBinaryCache = reloadShaders();
+
+ // load materials
+ {
+ ScriptLoader materialLoader(".mat");
+ ScriptLoader::loadAllFiles (&materialLoader, mPlatform->getBasePath());
+
+ std::map <std::string, ScriptNode*> nodes = materialLoader.getAllConfigScripts();
+ for (std::map <std::string, ScriptNode*>::const_iterator it = nodes.begin();
+ it != nodes.end(); ++it)
+ {
+ if (!(it->second->getName() == "material"))
+ {
+ std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .mat" << std::endl;
+ break;
+ }
+
+ MaterialInstance newInstance(it->first, this);
+ newInstance.create(mPlatform);
+ if (!mShadersEnabled)
+ newInstance.setShadersEnabled (false);
+
+ newInstance.setSourceFile (it->second->mFileName);
+
+ std::vector<ScriptNode*> props = it->second->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt)
+ {
+ std::string name = (*propIt)->getName();
+
+ std::string val = (*propIt)->getValue();
+
+ if (name == "pass")
+ {
+ MaterialInstancePass* newPass = newInstance.createPass();
+ std::vector<ScriptNode*> props2 = (*propIt)->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator propIt2 = props2.begin(); propIt2 != props2.end(); ++propIt2)
+ {
+ std::string name2 = (*propIt2)->getName();
+ std::string val2 = (*propIt2)->getValue();
+
+ if (name2 == "shader_properties")
+ {
+ std::vector<ScriptNode*> shaderProps = (*propIt2)->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator shaderPropIt = shaderProps.begin(); shaderPropIt != shaderProps.end(); ++shaderPropIt)
+ {
+ std::string val = (*shaderPropIt)->getValue();
+ newPass->mShaderProperties.setProperty((*shaderPropIt)->getName(), makeProperty(val));
+ }
+ }
+ else if (name2 == "texture_unit")
+ {
+ MaterialInstanceTextureUnit* newTex = newPass->createTextureUnit(val2);
+ std::vector<ScriptNode*> texProps = (*propIt2)->getChildren();
+ for (std::vector<ScriptNode*>::const_iterator texPropIt = texProps.begin(); texPropIt != texProps.end(); ++texPropIt)
+ {
+ std::string val = (*texPropIt)->getValue();
+ newTex->setProperty((*texPropIt)->getName(), makeProperty(val));
+ }
+ }
+ else
+ newPass->setProperty((*propIt2)->getName(), makeProperty(val2));
+ }
+ }
+ else if (name == "parent")
+ newInstance.setParentInstance(val);
+ else
+ newInstance.setProperty((*propIt)->getName(), makeProperty(val));
+ }
+
+ if (newInstance.hasProperty("create_configuration"))
+ {
+ std::string config = retrieveValue<StringValue>(newInstance.getProperty("create_configuration"), NULL).get();
+ newInstance.createForConfiguration (config, 0);
+ }
+
+ mMaterials.insert (std::make_pair(it->first, newInstance));
+ }
+
+ // now that all materials are loaded, replace the parent names with the actual pointers to parent
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ std::string parent = it->second.getParentInstance();
+ if (parent != "")
+ {
+ if (mMaterials.find (it->second.getParentInstance()) == mMaterials.end())
+ throw std::runtime_error ("Unable to find parent for material instance \"" + it->first + "\"");
+ it->second.setParent(&mMaterials.find(parent)->second);
+ }
+ }
+ }
+
+ if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !removeBinaryCache)
+ {
+ std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName;
+ if (boost::filesystem::exists(file))
+ {
+ mPlatform->deserializeShaders (file);
+ }
+ }
+ }
+
+ Factory::~Factory ()
+ {
+ mShaderSets.clear();
+
+ if (mPlatform->supportsShaderSerialization () && mWriteMicrocodeCache)
+ {
+ std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName;
+ mPlatform->serializeShaders (file);
+ }
+
+ if (mReadSourceCache)
+ {
+ // save the last modified time of shader sources (as of when they were loaded)
+ std::ofstream file;
+ file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str());
+
+ for (LastModifiedMap::const_iterator it = mShadersLastModifiedNew.begin(); it != mShadersLastModifiedNew.end(); ++it)
+ {
+ file << it->first << "\n" << it->second << std::endl;
+ }
+
+ file.close();
+ }
+
+ delete mPlatform;
+ sThis = 0;
+ }
+
+ MaterialInstance* Factory::searchInstance (const std::string& name)
+ {
+ if (mMaterials.find(name) != mMaterials.end())
+ return &mMaterials.find(name)->second;
+
+ return NULL;
+ }
+
+ MaterialInstance* Factory::findInstance (const std::string& name)
+ {
+ assert (mMaterials.find(name) != mMaterials.end());
+ return &mMaterials.find(name)->second;
+ }
+
+ MaterialInstance* Factory::requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex)
+ {
+ MaterialInstance* m = searchInstance (name);
+
+ if (configuration != "Default" && mConfigurations.find(configuration) == mConfigurations.end())
+ return NULL;
+
+ if (m)
+ {
+ // make sure all lod techniques below (higher lod) exist
+ int i = lodIndex;
+ while (i>0)
+ {
+ --i;
+ if (m->createForConfiguration (configuration, i))
+ {
+ if (mListener)
+ mListener->materialCreated (m, configuration, i);
+ }
+ else
+ return NULL;
+ }
+
+ if (m->createForConfiguration (configuration, lodIndex))
+ {
+ if (mListener)
+ mListener->materialCreated (m, configuration, lodIndex);
+ }
+ else
+ return NULL;
+ }
+ return m;
+ }
+
+ MaterialInstance* Factory::createMaterialInstance (const std::string& name, const std::string& parentInstance)
+ {
+ if (parentInstance != "" && mMaterials.find(parentInstance) == mMaterials.end())
+ throw std::runtime_error ("trying to clone material that does not exist");
+
+ MaterialInstance newInstance(name, this);
+
+ if (!mShadersEnabled)
+ newInstance.setShadersEnabled(false);
+
+ if (parentInstance != "")
+ newInstance.setParent (&mMaterials.find(parentInstance)->second);
+
+ newInstance.create(mPlatform);
+
+ mMaterials.insert (std::make_pair(name, newInstance));
+
+ return &mMaterials.find(name)->second;
+ }
+
+ void Factory::destroyMaterialInstance (const std::string& name)
+ {
+ if (mMaterials.find(name) != mMaterials.end())
+ mMaterials.erase(name);
+ }
+
+ void Factory::setShadersEnabled (bool enabled)
+ {
+ mShadersEnabled = enabled;
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ it->second.setShadersEnabled(enabled);
+ }
+ }
+
+ void Factory::setGlobalSetting (const std::string& name, const std::string& value)
+ {
+ bool changed = true;
+ if (mGlobalSettings.hasProperty(name))
+ changed = (retrieveValue<StringValue>(mGlobalSettings.getProperty(name), NULL).get() != value);
+
+ mGlobalSettings.setProperty (name, makeProperty<StringValue>(new StringValue(value)));
+
+ if (changed)
+ {
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ it->second.destroyAll();
+ }
+ }
+ }
+
+ void Factory::setSharedParameter (const std::string& name, PropertyValuePtr value)
+ {
+ mPlatform->setSharedParameter(name, value);
+ }
+
+ ShaderSet* Factory::getShaderSet (const std::string& name)
+ {
+ if (mShaderSets.find(name) == mShaderSets.end())
+ {
+ std::stringstream msg;
+ msg << "Shader '" << name << "' not found";
+ throw std::runtime_error(msg.str());
+ }
+ return &mShaderSets.find(name)->second;
+ }
+
+ Platform* Factory::getPlatform ()
+ {
+ return mPlatform;
+ }
+
+ Language Factory::getCurrentLanguage ()
+ {
+ return mCurrentLanguage;
+ }
+
+ void Factory::setCurrentLanguage (Language lang)
+ {
+ bool changed = (mCurrentLanguage != lang);
+ mCurrentLanguage = lang;
+
+ if (changed)
+ {
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ it->second.destroyAll();
+ }
+ }
+ }
+
+ void Factory::notifyConfigurationChanged()
+ {
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ it->second.destroyAll();
+ }
+ }
+
+ MaterialInstance* Factory::getMaterialInstance (const std::string& name)
+ {
+ return findInstance(name);
+ }
+
+ void Factory::setTextureAlias (const std::string& alias, const std::string& realName)
+ {
+ mTextureAliases[alias] = realName;
+
+ // update the already existing texture units
+ for (std::map<TextureUnitState*, std::string>::iterator it = mTextureAliasInstances.begin(); it != mTextureAliasInstances.end(); ++it)
+ {
+ if (it->second == alias)
+ {
+ it->first->setTextureName(realName);
+ }
+ }
+ }
+
+ std::string Factory::retrieveTextureAlias (const std::string& name)
+ {
+ if (mTextureAliases.find(name) != mTextureAliases.end())
+ return mTextureAliases[name];
+ else
+ return "";
+ }
+
+ Configuration* Factory::getConfiguration (const std::string& name)
+ {
+ return &mConfigurations[name];
+ }
+
+ void Factory::createConfiguration (const std::string& name)
+ {
+ mConfigurations[name].setParent (&mGlobalSettings);
+ }
+
+ void Factory::destroyConfiguration(const std::string &name)
+ {
+ mConfigurations.erase(name);
+ }
+
+ void Factory::registerLodConfiguration (int index, PropertySetGet configuration)
+ {
+ mLodConfigurations[index] = configuration;
+ }
+
+ void Factory::setMaterialListener (MaterialListener* listener)
+ {
+ mListener = listener;
+ }
+
+ void Factory::addTextureAliasInstance (const std::string& name, TextureUnitState* t)
+ {
+ mTextureAliasInstances[t] = name;
+ }
+
+ void Factory::removeTextureAliasInstances (TextureUnitState* t)
+ {
+ mTextureAliasInstances.erase(t);
+ }
+
+ void Factory::setActiveConfiguration (const std::string& configuration)
+ {
+ if (configuration == "Default")
+ mCurrentConfiguration = 0;
+ else
+ {
+ assert (mConfigurations.find(configuration) != mConfigurations.end());
+ mCurrentConfiguration = &mConfigurations[configuration];
+ }
+ }
+
+ void Factory::setActiveLodLevel (int level)
+ {
+ if (level == 0)
+ mCurrentLodConfiguration = 0;
+ else
+ {
+ assert (mLodConfigurations.find(level) != mLodConfigurations.end());
+ mCurrentLodConfiguration = &mLodConfigurations[level];
+ }
+ }
+
+ void Factory::setShaderDebugOutputEnabled (bool enabled)
+ {
+ mShaderDebugOutputEnabled = enabled;
+ }
+
+ PropertySetGet* Factory::getCurrentGlobalSettings()
+ {
+ PropertySetGet* p = &mGlobalSettings;
+
+ // current global settings are affected by active configuration & active lod configuration
+
+ if (mCurrentConfiguration)
+ {
+ p = mCurrentConfiguration;
+ }
+
+ if (mCurrentLodConfiguration)
+ {
+ mCurrentLodConfiguration->setParent(p);
+ p = mCurrentLodConfiguration;
+ }
+
+ return p;
+ }
+
+ void Factory::saveAll ()
+ {
+ std::map<std::string, std::ofstream*> files;
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ if (it->second.getSourceFile().empty())
+ continue;
+ if (files.find(it->second.getSourceFile()) == files.end())
+ {
+ /// \todo check if this is actually the same file, since there can be different paths to the same file
+ std::ofstream* stream = new std::ofstream();
+ stream->open (it->second.getSourceFile().c_str());
+
+ files[it->second.getSourceFile()] = stream;
+ }
+ it->second.save (*files[it->second.getSourceFile()]);
+ }
+
+ for (std::map<std::string, std::ofstream*>::iterator it = files.begin(); it != files.end(); ++it)
+ {
+ delete it->second;
+ }
+ files.clear();
+
+ for (ConfigurationMap::iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it)
+ {
+ if (it->second.getSourceFile().empty())
+ continue;
+ if (files.find(it->second.getSourceFile()) == files.end())
+ {
+ /// \todo check if this is actually the same file, since there can be different paths to the same file
+ std::ofstream* stream = new std::ofstream();
+ stream->open (it->second.getSourceFile().c_str());
+
+ files[it->second.getSourceFile()] = stream;
+ }
+ it->second.save (it->first, *files[it->second.getSourceFile()]);
+ }
+
+ for (std::map<std::string, std::ofstream*>::iterator it = files.begin(); it != files.end(); ++it)
+ {
+ delete it->second;
+ }
+ }
+
+ void Factory::listMaterials(std::vector<std::string> &out)
+ {
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ out.push_back(it->first);
+ }
+ }
+
+ void Factory::listGlobalSettings(std::map<std::string, std::string> &out)
+ {
+ const PropertyMap& properties = mGlobalSettings.listProperties();
+
+ for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it)
+ {
+ out[it->first] = retrieveValue<StringValue>(mGlobalSettings.getProperty(it->first), NULL).get();
+ }
+ }
+
+ void Factory::listConfigurationSettings(const std::string& name, std::map<std::string, std::string> &out)
+ {
+ const PropertyMap& properties = mConfigurations[name].listProperties();
+
+ for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it)
+ {
+ out[it->first] = retrieveValue<StringValue>(mConfigurations[name].getProperty(it->first), NULL).get();
+ }
+ }
+
+ void Factory::listConfigurationNames(std::vector<std::string> &out)
+ {
+ for (ConfigurationMap::const_iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it)
+ {
+ out.push_back(it->first);
+ }
+ }
+
+ void Factory::listShaderSets(std::vector<std::string> &out)
+ {
+ for (ShaderSetMap::const_iterator it = mShaderSets.begin(); it != mShaderSets.end(); ++it)
+ {
+ out.push_back(it->first);
+ }
+ }
+
+ void Factory::_ensureMaterial(const std::string& name, const std::string& configuration)
+ {
+ MaterialInstance* m = searchInstance (name);
+ assert(m);
+ m->createForConfiguration (configuration, 0);
+ }
+
+ bool Factory::removeCache(const std::string& pattern)
+ {
+ bool ret = false;
+ if ( boost::filesystem::exists(mPlatform->getCacheFolder())
+ && boost::filesystem::is_directory(mPlatform->getCacheFolder()))
+ {
+ boost::filesystem::directory_iterator end_iter;
+ for( boost::filesystem::directory_iterator dir_iter(mPlatform->getCacheFolder()) ; dir_iter != end_iter ; ++dir_iter)
+ {
+ if (boost::filesystem::is_regular_file(dir_iter->status()) )
+ {
+ boost::filesystem::path file = dir_iter->path();
+
+ std::string pathname = file.filename().string();
+
+ // get first part of filename, e.g. main_fragment_546457654 -> main_fragment
+ // there is probably a better method for this...
+ std::vector<std::string> tokens;
+ boost::split(tokens, pathname, boost::is_any_of("_"));
+ tokens.erase(--tokens.end());
+ std::string shaderName;
+ for (std::vector<std::string>::const_iterator vector_iter = tokens.begin(); vector_iter != tokens.end();)
+ {
+ shaderName += *(vector_iter++);
+ if (vector_iter != tokens.end())
+ shaderName += "_";
+ }
+
+ if (shaderName == pattern)
+ {
+ boost::filesystem::remove(file);
+ ret = true;
+ std::cout << "Removing outdated shader: " << file << std::endl;
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ bool Factory::reloadShaders()
+ {
+ mShaderSets.clear();
+ notifyConfigurationChanged();
+
+ bool removeBinaryCache = false;
+ ScriptLoader shaderSetLoader(".shaderset");
+ ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath());
+ std::map <std::string, ScriptNode*> nodes = shaderSetLoader.getAllConfigScripts();
+ for (std::map <std::string, ScriptNode*>::const_iterator it = nodes.begin();
+ it != nodes.end(); ++it)
+ {
+ if (!(it->second->getName() == "shader_set"))
+ {
+ std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .shaderset" << std::endl;
+ break;
+ }
+
+ if (!it->second->findChild("profiles_cg"))
+ throw std::runtime_error ("missing \"profiles_cg\" field for \"" + it->first + "\"");
+ if (!it->second->findChild("profiles_hlsl"))
+ throw std::runtime_error ("missing \"profiles_hlsl\" field for \"" + it->first + "\"");
+ if (!it->second->findChild("source"))
+ throw std::runtime_error ("missing \"source\" field for \"" + it->first + "\"");
+ if (!it->second->findChild("type"))
+ throw std::runtime_error ("missing \"type\" field for \"" + it->first + "\"");
+
+ std::vector<std::string> profiles_cg;
+ boost::split (profiles_cg, it->second->findChild("profiles_cg")->getValue(), boost::is_any_of(" "));
+ std::string cg_profile;
+ for (std::vector<std::string>::iterator it2 = profiles_cg.begin(); it2 != profiles_cg.end(); ++it2)
+ {
+ if (mPlatform->isProfileSupported(*it2))
+ {
+ cg_profile = *it2;
+ break;
+ }
+ }
+
+ std::vector<std::string> profiles_hlsl;
+ boost::split (profiles_hlsl, it->second->findChild("profiles_hlsl")->getValue(), boost::is_any_of(" "));
+ std::string hlsl_profile;
+ for (std::vector<std::string>::iterator it2 = profiles_hlsl.begin(); it2 != profiles_hlsl.end(); ++it2)
+ {
+ if (mPlatform->isProfileSupported(*it2))
+ {
+ hlsl_profile = *it2;
+ break;
+ }
+ }
+
+ std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue();
+ std::string sourceRelative = it->second->findChild("source")->getValue();
+
+ ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile,
+ sourceAbsolute,
+ mPlatform->getBasePath(),
+ it->first,
+ &mGlobalSettings);
+
+ int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute));
+ mShadersLastModifiedNew[sourceRelative] = lastModified;
+ if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end())
+ {
+ if (mShadersLastModified[sourceRelative] != lastModified)
+ {
+ // delete any outdated shaders based on this shader set
+ if (removeCache (it->first))
+ removeBinaryCache = true;
+ }
+ }
+ else
+ {
+ // if we get here, this is either the first run or a new shader file was added
+ // in both cases we can safely delete
+ if (removeCache (it->first))
+ removeBinaryCache = true;
+ }
+ mShaderSets.insert(std::make_pair(it->first, newSet));
+ }
+
+ // new is now current
+ mShadersLastModified = mShadersLastModifiedNew;
+
+ return removeBinaryCache;
+ }
+
+ void Factory::doMonitorShaderFiles()
+ {
+ bool reload=false;
+ ScriptLoader shaderSetLoader(".shaderset");
+ ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath());
+ std::map <std::string, ScriptNode*> nodes = shaderSetLoader.getAllConfigScripts();
+ for (std::map <std::string, ScriptNode*>::const_iterator it = nodes.begin();
+ it != nodes.end(); ++it)
+ {
+
+ std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue();
+ std::string sourceRelative = it->second->findChild("source")->getValue();
+
+ int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute));
+ if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end())
+ {
+ if (mShadersLastModified[sourceRelative] != lastModified)
+ {
+ reload=true;
+ break;
+ }
+ }
+ }
+ if (reload)
+ reloadShaders();
+ }
+
+ void Factory::logError(const std::string &msg)
+ {
+ mErrorLog << msg << '\n';
+ }
+
+ std::string Factory::getErrorLog()
+ {
+ std::string errors = mErrorLog.str();
+ mErrorLog.str("");
+ return errors;
+ }
+
+ void Factory::unloadUnreferencedMaterials()
+ {
+ for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it)
+ {
+ if (it->second.getMaterial()->isUnreferenced())
+ it->second.destroyAll();
+ }
+ }
+
+ void Configuration::save(const std::string& name, std::ofstream &stream)
+ {
+ stream << "configuration " << name << '\n';
+ stream << "{\n";
+ PropertySetGet::save(stream, "\t");
+ stream << "}\n";
+ }
+}
diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp
new file mode 100644
index 0000000000..97984609ea
--- /dev/null
+++ b/extern/shiny/Main/Factory.hpp
@@ -0,0 +1,271 @@
+#ifndef SH_FACTORY_H
+#define SH_FACTORY_H
+
+#include <map>
+#include <string>
+#include <sstream>
+
+#include "MaterialInstance.hpp"
+#include "ShaderSet.hpp"
+#include "Language.hpp"
+
+namespace sh
+{
+ class Platform;
+
+ class Configuration : public PropertySetGet
+ {
+ public:
+ void setSourceFile (const std::string& file) { mSourceFile = file ; }
+ std::string getSourceFile () { return mSourceFile; }
+
+ void save(const std::string& name, std::ofstream &stream);
+
+ private:
+ std::string mSourceFile;
+ };
+
+ typedef std::map<std::string, MaterialInstance> MaterialMap;
+ typedef std::map<std::string, ShaderSet> ShaderSetMap;
+ typedef std::map<std::string, Configuration> ConfigurationMap;
+ typedef std::map<int, PropertySetGet> LodConfigurationMap;
+ typedef std::map<std::string, int> LastModifiedMap;
+
+ typedef std::map<std::string, std::string> TextureAliasMap;
+
+ /**
+ * @brief
+ * Allows you to be notified when a certain material was just created. Useful for changing material properties that you can't
+ * do in a .mat script (for example a series of animated textures) \n
+ * When receiving the event, you can get the platform material by calling m->getMaterial()
+ * and casting that to the platform specific material (e.g. for Ogre, sh::OgreMaterial)
+ */
+ class MaterialListener
+ {
+ public:
+ virtual void materialCreated (MaterialInstance* m, const std::string& configuration, unsigned short lodIndex) = 0;
+ };
+
+ /**
+ * @brief
+ * The main interface class
+ */
+ class Factory
+ {
+ public:
+ Factory(Platform* platform);
+ ///< @note Ownership of \a platform is transferred to this class, so you don't have to delete it.
+
+ ~Factory();
+
+ /**
+ * Create a MaterialInstance, optionally copying all properties from \a parentInstance
+ * @param name name of the new instance
+ * @param name of the parent (optional)
+ * @return newly created instance
+ */
+ MaterialInstance* createMaterialInstance (const std::string& name, const std::string& parentInstance = "");
+
+ /// @note It is safe to call this if the instance does not exist
+ void destroyMaterialInstance (const std::string& name);
+
+ /// Use this to enable or disable shaders on-the-fly
+ void setShadersEnabled (bool enabled);
+
+ /// write generated shaders to current directory, useful for debugging
+ void setShaderDebugOutputEnabled (bool enabled);
+
+ /// Use this to manage user settings. \n
+ /// Global settings can be retrieved in shaders through a macro. \n
+ /// When a global setting is changed, the shaders that depend on them are recompiled automatically.
+ void setGlobalSetting (const std::string& name, const std::string& value);
+
+ /// Adjusts the given shared parameter. \n
+ /// Internally, this will change all uniform parameters of this name marked with the macro \@shSharedParameter \n
+ /// @param name of the shared parameter
+ /// @param value of the parameter, use sh::makeProperty to construct this value
+ void setSharedParameter (const std::string& name, PropertyValuePtr value);
+
+ Language getCurrentLanguage ();
+
+ /// Switch between different shader languages (cg, glsl, hlsl)
+ void setCurrentLanguage (Language lang);
+
+ /// Get a MaterialInstance by name
+ MaterialInstance* getMaterialInstance (const std::string& name);
+
+ /// Create a configuration, which can then be altered by using Factory::getConfiguration
+ void createConfiguration (const std::string& name);
+
+ /// Register a lod configuration, which can then be used by setting up lod distance values for the material \n
+ /// 0 refers to highest lod, so use 1 or higher as index parameter
+ void registerLodConfiguration (int index, PropertySetGet configuration);
+
+ /// Set an alias name for a texture, the real name can then be retrieved with the "texture_alias"
+ /// property in a texture unit - this is useful if you don't know the name of your texture beforehand. \n
+ /// Example: \n
+ /// - In the material definition: texture_alias ReflectionMap \n
+ /// - At runtime: factory->setTextureAlias("ReflectionMap", "rtt_654654"); \n
+ /// You can call factory->setTextureAlias as many times as you want, and if the material was already created, its texture will be updated!
+ void setTextureAlias (const std::string& alias, const std::string& realName);
+
+ /// Retrieve the real texture name for a texture alias (the real name is set by the user)
+ std::string retrieveTextureAlias (const std::string& name);
+
+ /// Attach a listener for material created events
+ void setMaterialListener (MaterialListener* listener);
+
+ /// Call this after you have set up basic stuff, like the shader language.
+ void loadAllFiles ();
+
+ /// Controls writing of generated shader source code to the cache folder, so that the
+ /// (rather expensive) preprocessing step can be skipped on the next run. See Factory::setReadSourceCache \n
+ /// \note The default is off (no cache writing)
+ void setWriteSourceCache(bool write) { mWriteSourceCache = write; }
+
+ /// Controls reading of generated shader sources from the cache folder
+ /// \note The default is off (no cache reading)
+ /// \note Even if microcode caching is enabled, generating (or caching) the source is still required due to the macros.
+ void setReadSourceCache(bool read) { mReadSourceCache = read; }
+
+ /// Controls writing the microcode of the generated shaders to the cache folder. Microcode is machine independent
+ /// and loads very fast compared to regular compilation. Note that the availability of this feature depends on the \a Platform.
+ /// \note The default is off (no cache writing)
+ void setWriteMicrocodeCache(bool write) { mWriteMicrocodeCache = write; }
+
+ /// Controls reading of shader microcode from the cache folder. Microcode is machine independent
+ /// and loads very fast compared to regular compilation. Note that the availability of this feature depends on the \a Platform.
+ /// \note The default is off (no cache reading)
+ void setReadMicrocodeCache(bool read) { mReadMicrocodeCache = read; }
+
+ /// Lists all materials currently registered with the factory. Whether they are
+ /// loaded or not does not matter.
+ void listMaterials (std::vector<std::string>& out);
+
+ /// Lists current name & value of all global settings.
+ void listGlobalSettings (std::map<std::string, std::string>& out);
+
+ /// Lists configuration names.
+ void listConfigurationNames (std::vector<std::string>& out);
+
+ /// Lists current name & value of settings for a given configuration.
+ void listConfigurationSettings (const std::string& name, std::map<std::string, std::string>& out);
+
+ /// Lists shader sets.
+ void listShaderSets (std::vector<std::string>& out);
+
+ /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache
+ /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9.
+ bool reloadShaders();
+
+ /// Calls reloadShaders() if shader files have been modified since the last reload.
+ /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache
+ /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9.
+ void doMonitorShaderFiles();
+
+ /// Unloads all materials that are currently not referenced. This will not unload the textures themselves,
+ /// but it will let go of the SharedPtr's to the textures, so that you may unload them if you so desire. \n
+ /// A good time to call this would be after a new level has been loaded, but just calling it occasionally after a period
+ /// of time should work just fine too.
+ void unloadUnreferencedMaterials();
+
+ void destroyConfiguration (const std::string& name);
+
+ void notifyConfigurationChanged();
+
+ /// Saves all materials and configurations, by default to the file they were loaded from.
+ /// If you wish to save them elsewhere, use setSourceFile first.
+ void saveAll ();
+
+ /// Returns the error log as a string, then clears it.
+ /// Note: Errors are also written to the standard error output, or thrown if they are fatal.
+ std::string getErrorLog ();
+
+ static Factory& getInstance();
+ ///< Return instance of this class.
+
+ static Factory* getInstancePtr();
+
+ /// Make sure a material technique is loaded.\n
+ /// You will probably never have to use this.
+ void _ensureMaterial(const std::string& name, const std::string& configuration);
+
+
+ Configuration* getConfiguration (const std::string& name);
+
+ private:
+
+ MaterialInstance* requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex);
+ ShaderSet* getShaderSet (const std::string& name);
+ Platform* getPlatform ();
+
+ PropertySetGet* getCurrentGlobalSettings();
+
+ void addTextureAliasInstance (const std::string& name, TextureUnitState* t);
+ void removeTextureAliasInstances (TextureUnitState* t);
+
+ std::string getCacheFolder () { return mPlatform->getCacheFolder (); }
+ bool getReadSourceCache() { return mReadSourceCache; }
+ bool getWriteSourceCache() { return mReadSourceCache; }
+ public:
+ bool getWriteMicrocodeCache() { return mWriteMicrocodeCache; } // Fixme
+
+ private:
+ void setActiveConfiguration (const std::string& configuration);
+ void setActiveLodLevel (int level);
+
+ bool getShaderDebugOutputEnabled() { return mShaderDebugOutputEnabled; }
+
+ std::map<TextureUnitState*, std::string> mTextureAliasInstances;
+
+ void logError (const std::string& msg);
+
+ friend class Platform;
+ friend class MaterialInstance;
+ friend class ShaderInstance;
+ friend class ShaderSet;
+ friend class TextureUnitState;
+
+ private:
+ static Factory* sThis;
+
+ bool mShadersEnabled;
+ bool mShaderDebugOutputEnabled;
+
+ bool mReadMicrocodeCache;
+ bool mWriteMicrocodeCache;
+ bool mReadSourceCache;
+ bool mWriteSourceCache;
+ std::stringstream mErrorLog;
+
+ MaterialMap mMaterials;
+ ShaderSetMap mShaderSets;
+ ConfigurationMap mConfigurations;
+ LodConfigurationMap mLodConfigurations;
+ LastModifiedMap mShadersLastModified;
+ LastModifiedMap mShadersLastModifiedNew;
+
+ PropertySetGet mGlobalSettings;
+
+ PropertySetGet* mCurrentConfiguration;
+ PropertySetGet* mCurrentLodConfiguration;
+
+ TextureAliasMap mTextureAliases;
+
+ Language mCurrentLanguage;
+
+ MaterialListener* mListener;
+
+ Platform* mPlatform;
+
+ MaterialInstance* findInstance (const std::string& name);
+ MaterialInstance* searchInstance (const std::string& name);
+
+ /// @return was anything removed?
+ bool removeCache (const std::string& pattern);
+
+ static const std::string mBinaryCacheName;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/Language.hpp b/extern/shiny/Main/Language.hpp
new file mode 100644
index 0000000000..6b271cb86a
--- /dev/null
+++ b/extern/shiny/Main/Language.hpp
@@ -0,0 +1,17 @@
+#ifndef SH_LANGUAGE_H
+#define SH_LANGUAGE_H
+
+namespace sh
+{
+ enum Language
+ {
+ Language_CG,
+ Language_HLSL,
+ Language_GLSL,
+ Language_GLSLES,
+ Language_Count,
+ Language_None
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp
new file mode 100644
index 0000000000..b8032d681e
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstance.cpp
@@ -0,0 +1,260 @@
+#include "MaterialInstance.hpp"
+
+#include <stdexcept>
+#include <iostream>
+
+#include "Factory.hpp"
+#include "ShaderSet.hpp"
+
+namespace sh
+{
+ MaterialInstance::MaterialInstance (const std::string& name, Factory* f)
+ : mName(name)
+ , mShadersEnabled(true)
+ , mFactory(f)
+ , mListener(NULL)
+ , mFailedToCreate(false)
+ {
+ }
+
+ MaterialInstance::~MaterialInstance ()
+ {
+ }
+
+ void MaterialInstance::setParentInstance (const std::string& name)
+ {
+ mParentInstance = name;
+ }
+
+ std::string MaterialInstance::getParentInstance ()
+ {
+ return mParentInstance;
+ }
+
+ void MaterialInstance::create (Platform* platform)
+ {
+ mMaterial = platform->createMaterial(mName);
+
+ if (hasProperty ("shadow_caster_material"))
+ mMaterial->setShadowCasterMaterial (retrieveValue<StringValue>(getProperty("shadow_caster_material"), NULL).get());
+
+ if (hasProperty ("lod_values"))
+ mMaterial->setLodLevels (retrieveValue<StringValue>(getProperty("lod_values"), NULL).get());
+ }
+
+ void MaterialInstance::destroyAll ()
+ {
+ if (hasProperty("create_configuration"))
+ return;
+ mMaterial->removeAll();
+ mTexUnits.clear();
+ mFailedToCreate = false;
+ }
+
+ void MaterialInstance::setProperty (const std::string& name, PropertyValuePtr value)
+ {
+ PropertySetGet::setProperty (name, value);
+ destroyAll(); // trigger updates
+ }
+
+ bool MaterialInstance::createForConfiguration (const std::string& configuration, unsigned short lodIndex)
+ {
+ if (mFailedToCreate)
+ return false;
+ try{
+ mMaterial->ensureLoaded();
+ bool res = mMaterial->createConfiguration(configuration, lodIndex);
+ if (!res)
+ return false; // listener was false positive
+
+ if (mListener)
+ mListener->requestedConfiguration (this, configuration);
+
+ mFactory->setActiveConfiguration (configuration);
+ mFactory->setActiveLodLevel (lodIndex);
+
+ bool allowFixedFunction = true;
+ if (!mShadersEnabled && hasProperty("allow_fixed_function"))
+ {
+ allowFixedFunction = retrieveValue<BooleanValue>(getProperty("allow_fixed_function"), NULL).get();
+ }
+
+ bool useShaders = mShadersEnabled || !allowFixedFunction;
+
+ // get passes of the top-most parent
+ PassVector* passes = getParentPasses();
+ if (passes->empty())
+ throw std::runtime_error ("material \"" + mName + "\" does not have any passes");
+
+ for (PassVector::iterator it = passes->begin(); it != passes->end(); ++it)
+ {
+ boost::shared_ptr<Pass> pass = mMaterial->createPass (configuration, lodIndex);
+ it->copyAll (pass.get(), this);
+
+ // texture samplers used in the shaders
+ std::vector<std::string> usedTextureSamplersVertex;
+ std::vector<std::string> usedTextureSamplersFragment;
+
+ PropertySetGet* context = this;
+
+ // create or retrieve shaders
+ bool hasVertex = it->hasProperty("vertex_program")
+ && !retrieveValue<StringValue>(it->getProperty("vertex_program"), context).get().empty();
+ bool hasFragment = it->hasProperty("fragment_program")
+ && !retrieveValue<StringValue>(it->getProperty("fragment_program"), context).get().empty();
+ if (useShaders)
+ {
+ it->setContext(context);
+ it->mShaderProperties.setContext(context);
+ if (hasVertex)
+ {
+ ShaderSet* vertex = mFactory->getShaderSet(retrieveValue<StringValue>(it->getProperty("vertex_program"), context).get());
+ ShaderInstance* v = vertex->getInstance(&it->mShaderProperties);
+ if (v)
+ {
+ pass->assignProgram (GPT_Vertex, v->getName());
+ v->setUniformParameters (pass, &it->mShaderProperties);
+
+ std::vector<std::string> sharedParams = v->getSharedParameters ();
+ for (std::vector<std::string>::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2)
+ {
+ pass->addSharedParameter (GPT_Vertex, *it2);
+ }
+
+ std::vector<std::string> vector = v->getUsedSamplers ();
+ usedTextureSamplersVertex.insert(usedTextureSamplersVertex.end(), vector.begin(), vector.end());
+ }
+ }
+ if (hasFragment)
+ {
+ ShaderSet* fragment = mFactory->getShaderSet(retrieveValue<StringValue>(it->getProperty("fragment_program"), context).get());
+ ShaderInstance* f = fragment->getInstance(&it->mShaderProperties);
+ if (f)
+ {
+ pass->assignProgram (GPT_Fragment, f->getName());
+ f->setUniformParameters (pass, &it->mShaderProperties);
+
+ std::vector<std::string> sharedParams = f->getSharedParameters ();
+ for (std::vector<std::string>::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2)
+ {
+ pass->addSharedParameter (GPT_Fragment, *it2);
+ }
+
+ std::vector<std::string> vector = f->getUsedSamplers ();
+ usedTextureSamplersFragment.insert(usedTextureSamplersFragment.end(), vector.begin(), vector.end());
+ }
+ }
+ }
+
+ // create texture units
+ std::vector<MaterialInstanceTextureUnit>* texUnits = &it->mTexUnits;
+ int i=0;
+ for (std::vector<MaterialInstanceTextureUnit>::iterator texIt = texUnits->begin(); texIt != texUnits->end(); ++texIt )
+ {
+ // only create those that are needed by the shader, OR those marked to be created in fixed function pipeline if shaders are disabled
+ bool foundVertex = std::find(usedTextureSamplersVertex.begin(), usedTextureSamplersVertex.end(), texIt->getName()) != usedTextureSamplersVertex.end();
+ bool foundFragment = std::find(usedTextureSamplersFragment.begin(), usedTextureSamplersFragment.end(), texIt->getName()) != usedTextureSamplersFragment.end();
+ if ( (foundVertex || foundFragment)
+ || (((!useShaders || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue<BooleanValue>(texIt->getProperty("create_in_ffp"), this).get()))
+ {
+ boost::shared_ptr<TextureUnitState> texUnit = pass->createTextureUnitState (texIt->getName());
+ texIt->copyAll (texUnit.get(), context);
+
+ mTexUnits.push_back(texUnit);
+
+ // set texture unit indices (required by GLSL)
+ if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && mFactory->getCurrentLanguage () == Language_GLSL)
+ {
+ pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i);
+
+ ++i;
+ }
+ }
+ }
+ }
+
+ if (mListener)
+ mListener->createdConfiguration (this, configuration);
+ return true;
+
+ } catch (std::runtime_error& e)
+ {
+ destroyAll();
+ mFailedToCreate = true;
+ std::stringstream msg;
+ msg << "Error while creating material " << mName << ": " << e.what();
+ std::cerr << msg.str() << std::endl;
+ mFactory->logError(msg.str());
+ return false;
+ }
+ }
+
+ Material* MaterialInstance::getMaterial ()
+ {
+ return mMaterial.get();
+ }
+
+ MaterialInstancePass* MaterialInstance::createPass ()
+ {
+ mPasses.push_back (MaterialInstancePass());
+ mPasses.back().setContext(this);
+ return &mPasses.back();
+ }
+
+ void MaterialInstance::deletePass(unsigned int index)
+ {
+ assert(mPasses.size() > index);
+ mPasses.erase(mPasses.begin()+index);
+ }
+
+ PassVector* MaterialInstance::getParentPasses()
+ {
+ if (mParent)
+ return static_cast<MaterialInstance*>(mParent)->getParentPasses();
+ else
+ return &mPasses;
+ }
+
+ PassVector* MaterialInstance::getPasses()
+ {
+ return &mPasses;
+ }
+
+ void MaterialInstance::setShadersEnabled (bool enabled)
+ {
+ if (enabled == mShadersEnabled)
+ return;
+ mShadersEnabled = enabled;
+
+ // trigger updates
+ if (mMaterial.get())
+ destroyAll();
+ }
+
+ void MaterialInstance::save (std::ofstream& stream)
+ {
+ stream << "material " << mName << "\n"
+ << "{\n";
+
+ if (mParent)
+ {
+ stream << "\t" << "parent " << static_cast<MaterialInstance*>(mParent)->getName() << "\n";
+ }
+
+ const PropertyMap& properties = listProperties ();
+ for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it)
+ {
+ stream << "\t" << it->first << " " << retrieveValue<StringValue>(getProperty(it->first), NULL).get() << "\n";
+ }
+
+ for (PassVector::iterator it = mPasses.begin(); it != mPasses.end(); ++it)
+ {
+ stream << "\tpass" << '\n';
+ stream << "\t{" << '\n';
+ it->save(stream);
+ stream << "\t}" << '\n';
+ }
+
+ stream << "}\n";
+ }
+}
diff --git a/extern/shiny/Main/MaterialInstance.hpp b/extern/shiny/Main/MaterialInstance.hpp
new file mode 100644
index 0000000000..72c78c7b7c
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstance.hpp
@@ -0,0 +1,109 @@
+#ifndef SH_MATERIALINSTANCE_H
+#define SH_MATERIALINSTANCE_H
+
+#include <vector>
+#include <fstream>
+
+#include "PropertyBase.hpp"
+#include "Platform.hpp"
+#include "MaterialInstancePass.hpp"
+
+namespace sh
+{
+ class Factory;
+
+ typedef std::vector<MaterialInstancePass> PassVector;
+
+ /**
+ * @brief
+ * Allows you to be notified when a certain configuration for a material was just about to be created. \n
+ * Useful for adjusting some properties prior to the material being created (Or you could also re-create
+ * the whole material from scratch, i.e. use this as a method to create this material entirely in code)
+ */
+ class MaterialInstanceListener
+ {
+ public:
+ virtual void requestedConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called before creating
+ virtual void createdConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called after creating
+ virtual ~MaterialInstanceListener(){}
+ };
+
+ /**
+ * @brief
+ * A specific material instance, which has all required properties set
+ * (for example the diffuse & normal map, ambient/diffuse/specular values). \n
+ * Depending on these properties, the system will automatically select a shader permutation
+ * that suits these and create the backend materials / passes (provided by the \a Platform class).
+ */
+ class MaterialInstance : public PropertySetGet
+ {
+ public:
+ MaterialInstance (const std::string& name, Factory* f);
+ virtual ~MaterialInstance ();
+
+ PassVector* getParentPasses(); ///< gets the passes of the top-most parent
+
+ PassVector* getPasses(); ///< get our passes (for derived materials, none)
+
+ MaterialInstancePass* createPass ();
+ void deletePass (unsigned int index);
+
+ /// @attention Because the backend material passes are created on demand, the returned material here might not contain anything yet!
+ /// The only place where you should use this method, is for the MaterialInstance given by the MaterialListener::materialCreated event!
+ Material* getMaterial();
+
+ /// attach a \a MaterialInstanceListener to this specific material (as opposed to \a MaterialListener, which listens to all materials)
+ void setListener (MaterialInstanceListener* l) { mListener = l; }
+
+ std::string getName() { return mName; }
+
+ virtual void setProperty (const std::string& name, PropertyValuePtr value);
+
+ void setSourceFile(const std::string& sourceFile) { mSourceFile = sourceFile; }
+
+ std::string getSourceFile() { return mSourceFile; }
+ ///< get the name of the file this material was read from, or empty if it was created dynamically by code
+
+ private:
+ void setParentInstance (const std::string& name);
+ std::string getParentInstance ();
+
+ void create (Platform* platform);
+ bool createForConfiguration (const std::string& configuration, unsigned short lodIndex);
+
+ void destroyAll ();
+
+ void setShadersEnabled (bool enabled);
+
+ void save (std::ofstream& stream);
+
+ bool mFailedToCreate;
+
+ friend class Factory;
+
+
+ private:
+ std::string mParentInstance;
+ ///< this is only used during the file-loading phase. an instance could be loaded before its parent is loaded,
+ /// so initially only the parent's name is written to this member.
+ /// once all instances are loaded, the actual mParent pointer (from PropertySetGet class) can be set
+
+ std::vector< boost::shared_ptr<TextureUnitState> > mTexUnits;
+
+ MaterialInstanceListener* mListener;
+
+ PassVector mPasses;
+
+ std::string mName;
+
+ std::string mSourceFile;
+
+ boost::shared_ptr<Material> mMaterial;
+
+ bool mShadersEnabled;
+
+ Factory* mFactory;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/MaterialInstancePass.cpp b/extern/shiny/Main/MaterialInstancePass.cpp
new file mode 100644
index 0000000000..a628cd64c7
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstancePass.cpp
@@ -0,0 +1,35 @@
+#include "MaterialInstancePass.hpp"
+
+#include <fstream>
+
+namespace sh
+{
+
+ MaterialInstanceTextureUnit* MaterialInstancePass::createTextureUnit (const std::string& name)
+ {
+ mTexUnits.push_back(MaterialInstanceTextureUnit(name));
+ return &mTexUnits.back();
+ }
+
+ void MaterialInstancePass::save(std::ofstream &stream)
+ {
+ if (mShaderProperties.listProperties().size())
+ {
+ stream << "\t\t" << "shader_properties" << '\n';
+ stream << "\t\t{\n";
+ mShaderProperties.save(stream, "\t\t\t");
+ stream << "\t\t}\n";
+ }
+
+ PropertySetGet::save(stream, "\t\t");
+
+ for (std::vector <MaterialInstanceTextureUnit>::iterator it = mTexUnits.begin();
+ it != mTexUnits.end(); ++it)
+ {
+ stream << "\t\ttexture_unit " << it->getName() << '\n';
+ stream << "\t\t{\n";
+ it->save(stream, "\t\t\t");
+ stream << "\t\t}\n";
+ }
+ }
+}
diff --git a/extern/shiny/Main/MaterialInstancePass.hpp b/extern/shiny/Main/MaterialInstancePass.hpp
new file mode 100644
index 0000000000..3d83d8fa35
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstancePass.hpp
@@ -0,0 +1,29 @@
+#ifndef SH_MATERIALINSTANCEPASS_H
+#define SH_MATERIALINSTANCEPASS_H
+
+#include <vector>
+
+#include "PropertyBase.hpp"
+#include "MaterialInstanceTextureUnit.hpp"
+
+namespace sh
+{
+ /**
+ * @brief
+ * Holds properties of a single texture unit in a \a MaterialInstancePass. \n
+ * No inheritance here for now.
+ */
+ class MaterialInstancePass : public PropertySetGet
+ {
+ public:
+ MaterialInstanceTextureUnit* createTextureUnit (const std::string& name);
+
+ void save (std::ofstream& stream);
+
+ PropertySetGet mShaderProperties;
+
+ std::vector <MaterialInstanceTextureUnit> mTexUnits;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/MaterialInstanceTextureUnit.cpp b/extern/shiny/Main/MaterialInstanceTextureUnit.cpp
new file mode 100644
index 0000000000..0e3078af34
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstanceTextureUnit.cpp
@@ -0,0 +1,14 @@
+#include "MaterialInstanceTextureUnit.hpp"
+
+namespace sh
+{
+ MaterialInstanceTextureUnit::MaterialInstanceTextureUnit (const std::string& name)
+ : mName(name)
+ {
+ }
+
+ std::string MaterialInstanceTextureUnit::getName() const
+ {
+ return mName;
+ }
+}
diff --git a/extern/shiny/Main/MaterialInstanceTextureUnit.hpp b/extern/shiny/Main/MaterialInstanceTextureUnit.hpp
new file mode 100644
index 0000000000..5ca400fd49
--- /dev/null
+++ b/extern/shiny/Main/MaterialInstanceTextureUnit.hpp
@@ -0,0 +1,27 @@
+#ifndef SH_MATERIALINSTANCETEXTUREUNIT_H
+#define SH_MATERIALINSTANCETEXTUREUNIT_H
+
+#include "PropertyBase.hpp"
+
+namespace sh
+{
+ /**
+ * @brief
+ * A single texture unit state that belongs to a \a MaterialInstancePass \n
+ * this is not the real "backend" \a TextureUnitState (provided by \a Platform),
+ * it is merely a placeholder for properties. \n
+ * @note The backend \a TextureUnitState will only be created if this texture unit is
+ * actually used (i.e. referenced in the shader, or marked with property create_in_ffp = true).
+ */
+ class MaterialInstanceTextureUnit : public PropertySetGet
+ {
+ public:
+ MaterialInstanceTextureUnit (const std::string& name);
+ std::string getName() const;
+ void setName (const std::string& name) { mName = name; }
+ private:
+ std::string mName;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/Platform.cpp b/extern/shiny/Main/Platform.cpp
new file mode 100644
index 0000000000..3eb7f4ad34
--- /dev/null
+++ b/extern/shiny/Main/Platform.cpp
@@ -0,0 +1,89 @@
+#include "Platform.hpp"
+
+#include <stdexcept>
+
+#include "Factory.hpp"
+
+namespace sh
+{
+ Platform::Platform (const std::string& basePath)
+ : mBasePath(basePath)
+ , mCacheFolder("./")
+ , mFactory(NULL)
+ {
+ }
+
+ Platform::~Platform ()
+ {
+ }
+
+ void Platform::setFactory (Factory* factory)
+ {
+ mFactory = factory;
+ }
+
+ std::string Platform::getBasePath ()
+ {
+ return mBasePath;
+ }
+
+ bool Platform::supportsMaterialQueuedListener ()
+ {
+ return false;
+ }
+
+ bool Platform::supportsShaderSerialization ()
+ {
+ return false;
+ }
+
+ MaterialInstance* Platform::fireMaterialRequested (const std::string& name, const std::string& configuration, unsigned short lodIndex)
+ {
+ return mFactory->requestMaterial (name, configuration, lodIndex);
+ }
+
+ void Platform::serializeShaders (const std::string& file)
+ {
+ throw std::runtime_error ("Shader serialization not supported by this platform");
+ }
+
+ void Platform::deserializeShaders (const std::string& file)
+ {
+ throw std::runtime_error ("Shader serialization not supported by this platform");
+ }
+
+ void Platform::setCacheFolder (const std::string& folder)
+ {
+ mCacheFolder = folder;
+ }
+
+ std::string Platform::getCacheFolder() const
+ {
+ return mCacheFolder;
+ }
+
+ // ------------------------------------------------------------------------------
+
+ bool TextureUnitState::setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet *context)
+ {
+ if (name == "texture_alias")
+ {
+ std::string aliasName = retrieveValue<StringValue>(value, context).get();
+
+ Factory::getInstance().addTextureAliasInstance (aliasName, this);
+
+ setTextureName (Factory::getInstance().retrieveTextureAlias (aliasName));
+
+ return true;
+ }
+ else
+ return false;
+ }
+
+ TextureUnitState::~TextureUnitState()
+ {
+ Factory* f = Factory::getInstancePtr ();
+ if (f)
+ f->removeTextureAliasInstances (this);
+ }
+}
diff --git a/extern/shiny/Main/Platform.hpp b/extern/shiny/Main/Platform.hpp
new file mode 100644
index 0000000000..24afea03ba
--- /dev/null
+++ b/extern/shiny/Main/Platform.hpp
@@ -0,0 +1,146 @@
+#ifndef SH_PLATFORM_H
+#define SH_PLATFORM_H
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include "Language.hpp"
+#include "PropertyBase.hpp"
+
+namespace sh
+{
+ class Factory;
+ class MaterialInstance;
+
+ enum GpuProgramType
+ {
+ GPT_Vertex,
+ GPT_Fragment
+ // GPT_Geometry
+ };
+
+ // These classes are supposed to be filled by the platform implementation
+ class GpuProgram
+ {
+ public:
+ virtual ~GpuProgram() {}
+ virtual bool getSupported () = 0; ///< @return true if the compilation was successful
+
+ /// @param name name of the uniform in the shader
+ /// @param autoConstantName name of the auto constant (for example world_viewproj_matrix)
+ /// @param extraInfo if any extra info is needed (e.g. light index), put it here
+ virtual void setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo = "") = 0;
+ };
+
+ class TextureUnitState : public PropertySet
+ {
+ public:
+ virtual ~TextureUnitState();
+ virtual void setTextureName (const std::string& textureName) = 0;
+
+ protected:
+ virtual bool setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet *context);
+ };
+
+ class Pass : public PropertySet
+ {
+ public:
+ virtual boost::shared_ptr<TextureUnitState> createTextureUnitState (const std::string& name) = 0;
+ virtual void assignProgram (GpuProgramType type, const std::string& name) = 0;
+
+ /// @param type gpu program type
+ /// @param name name of the uniform in the shader
+ /// @param vt type of value, e.g. vector4
+ /// @param value value to set
+ /// @param context used for retrieving linked values
+ virtual void setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context) = 0;
+
+ virtual void setTextureUnitIndex (int programType, const std::string& name, int index) = 0;
+
+ virtual void addSharedParameter (int type, const std::string& name) = 0;
+ };
+
+ class Material : public PropertySet
+ {
+ public:
+ virtual boost::shared_ptr<Pass> createPass (const std::string& configuration, unsigned short lodIndex) = 0;
+ virtual bool createConfiguration (const std::string& name, unsigned short lodIndex) = 0; ///< @return false if already exists
+ virtual void removeAll () = 0; ///< remove all configurations
+
+ virtual bool isUnreferenced() = 0;
+ virtual void ensureLoaded() = 0;
+
+ virtual void setLodLevels (const std::string& lodLevels) = 0;
+
+ virtual void setShadowCasterMaterial (const std::string& name) = 0;
+ };
+
+ class Platform
+ {
+ public:
+ Platform (const std::string& basePath);
+ virtual ~Platform ();
+
+ /// set the folder to use for shader caching
+ void setCacheFolder (const std::string& folder);
+
+ private:
+ virtual boost::shared_ptr<Material> createMaterial (const std::string& name) = 0;
+
+ virtual boost::shared_ptr<GpuProgram> createGpuProgram (
+ GpuProgramType type,
+ const std::string& compileArguments,
+ const std::string& name, const std::string& profile,
+ const std::string& source, Language lang) = 0;
+
+ virtual void destroyGpuProgram (const std::string& name) = 0;
+
+ virtual void setSharedParameter (const std::string& name, PropertyValuePtr value) = 0;
+
+ virtual bool isProfileSupported (const std::string& profile) = 0;
+
+ virtual void serializeShaders (const std::string& file);
+ virtual void deserializeShaders (const std::string& file);
+
+ std::string getCacheFolder () const;
+
+ friend class Factory;
+ friend class MaterialInstance;
+ friend class ShaderInstance;
+ friend class ShaderSet;
+
+ protected:
+ /**
+ * this will be \a true if the platform supports serialization (writing shader microcode
+ * to disk) and deserialization (create gpu program from saved microcode)
+ */
+ virtual bool supportsShaderSerialization ();
+
+ /**
+ * this will be \a true if the platform supports a listener that notifies the system
+ * whenever a material is requested for rendering. if this is supported, shaders can be
+ * compiled on-demand when needed (and not earlier)
+ * @todo the Factory is not designed yet to handle the case where this method returns false
+ */
+ virtual bool supportsMaterialQueuedListener ();
+
+ /**
+ * fire event: material requested for rendering
+ * @param name material name
+ * @param configuration requested configuration
+ */
+ MaterialInstance* fireMaterialRequested (const std::string& name, const std::string& configuration, unsigned short lodIndex);
+
+ std::string mCacheFolder;
+ Factory* mFactory;
+
+ private:
+ void setFactory (Factory* factory);
+
+ std::string mBasePath;
+ std::string getBasePath();
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/Preprocessor.cpp b/extern/shiny/Main/Preprocessor.cpp
new file mode 100644
index 0000000000..1a97668bcc
--- /dev/null
+++ b/extern/shiny/Main/Preprocessor.cpp
@@ -0,0 +1,99 @@
+#include "Preprocessor.hpp"
+
+#include <boost/wave.hpp>
+#include <boost/wave/cpplexer/cpp_lex_token.hpp>
+#include <boost/wave/cpplexer/cpp_lex_iterator.hpp>
+
+namespace sh
+{
+ std::string Preprocessor::preprocess (std::string source, const std::string& includePath, std::vector<std::string> definitions, const std::string& name)
+ {
+ std::stringstream returnString;
+
+ // current file position is saved for exception handling
+ boost::wave::util::file_position_type current_position;
+
+ try
+ {
+ // This token type is one of the central types used throughout the library.
+ // It is a template parameter to some of the public classes and instances
+ // of this type are returned from the iterators.
+ typedef boost::wave::cpplexer::lex_token<> token_type;
+
+ // The template boost::wave::cpplexer::lex_iterator<> is the lexer type to
+ // to use as the token source for the preprocessing engine. It is
+ // parametrized with the token type.
+ typedef boost::wave::cpplexer::lex_iterator<token_type> lex_iterator_type;
+
+ // This is the resulting context type. The first template parameter should
+ // match the iterator type used during construction of the context
+ // instance (see below). It is the type of the underlying input stream.
+ typedef boost::wave::context<std::string::iterator, lex_iterator_type
+ , boost::wave::iteration_context_policies::load_file_to_string,
+ emit_custom_line_directives_hooks>
+ context_type;
+
+ // The preprocessor iterator shouldn't be constructed directly. It is
+ // generated through a wave::context<> object. This wave:context<> object
+ // is additionally used to initialize and define different parameters of
+ // the actual preprocessing.
+ //
+ // The preprocessing of the input stream is done on the fly behind the
+ // scenes during iteration over the range of context_type::iterator_type
+ // instances.
+ context_type ctx (source.begin(), source.end(), name.c_str());
+ ctx.add_include_path(includePath.c_str());
+ for (std::vector<std::string>::iterator it = definitions.begin(); it != definitions.end(); ++it)
+ {
+ ctx.add_macro_definition(*it);
+ }
+
+ // Get the preprocessor iterators and use them to generate the token
+ // sequence.
+ context_type::iterator_type first = ctx.begin();
+ context_type::iterator_type last = ctx.end();
+
+ // The input stream is preprocessed for you while iterating over the range
+ // [first, last). The dereferenced iterator returns tokens holding
+ // information about the preprocessed input stream, such as token type,
+ // token value, and position.
+ while (first != last)
+ {
+ current_position = (*first).get_position();
+ returnString << (*first).get_value();
+ ++first;
+ }
+ }
+ catch (boost::wave::cpp_exception const& e)
+ {
+ // some preprocessing error
+ std::stringstream error;
+ error
+ << e.file_name() << "(" << e.line_no() << "): "
+ << e.description();
+ throw std::runtime_error(error.str());
+ }
+ catch (std::exception const& e)
+ {
+ // use last recognized token to retrieve the error position
+ std::stringstream error;
+ error
+ << current_position.get_file()
+ << "(" << current_position.get_line() << "): "
+ << "exception caught: " << e.what();
+ throw std::runtime_error(error.str());
+ }
+ catch (...)
+ {
+ // use last recognized token to retrieve the error position
+ std::stringstream error;
+ error
+ << current_position.get_file()
+ << "(" << current_position.get_line() << "): "
+ << "unexpected exception caught.";
+ throw std::runtime_error(error.str());
+ }
+
+ return returnString.str();
+ }
+}
diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp
new file mode 100644
index 0000000000..7ee30ae7fc
--- /dev/null
+++ b/extern/shiny/Main/Preprocessor.hpp
@@ -0,0 +1,69 @@
+#ifndef SH_PREPROCESSOR_H
+#define SH_PREPROCESSOR_H
+
+#include <string>
+#include <vector>
+
+#include <cstdio>
+#include <ostream>
+#include <string>
+#include <algorithm>
+
+#include <boost/assert.hpp>
+#include <boost/config.hpp>
+
+#include <boost/wave/cpp_throw.hpp>
+#include <boost/wave/cpp_exceptions.hpp>
+#include <boost/wave/token_ids.hpp>
+#include <boost/wave/util/macro_helpers.hpp>
+#include <boost/wave/preprocessing_hooks.hpp>
+
+namespace sh
+{
+ /**
+ * @brief A simple interface for the boost::wave preprocessor
+ */
+ class Preprocessor
+ {
+ public:
+ /**
+ * @brief Run a shader source string through the preprocessor
+ * @param source source string
+ * @param includePath path to search for includes (that are included with #include)
+ * @param definitions macros to predefine (vector of strings of the format MACRO=value, or just MACRO to define it as 1)
+ * @param name name to use for error messages
+ * @return processed string
+ */
+ static std::string preprocess (std::string source, const std::string& includePath, std::vector<std::string> definitions, const std::string& name);
+ };
+
+
+
+ class emit_custom_line_directives_hooks
+ : public boost::wave::context_policies::default_preprocessing_hooks
+ {
+ public:
+
+ template <typename ContextT, typename ContainerT>
+ bool
+ emit_line_directive(ContextT const& ctx, ContainerT &pending,
+ typename ContextT::token_type const& act_token)
+ {
+ // emit a #line directive showing the relative filename instead
+ typename ContextT::position_type pos = act_token.get_position();
+ unsigned int column = 1;
+
+ typedef typename ContextT::token_type result_type;
+
+ // no line directives for now
+ pos.set_column(column);
+ pending.push_back(result_type(boost::wave::T_GENERATEDNEWLINE, "\n", pos));
+
+ return true;
+ }
+ };
+
+
+}
+
+#endif
diff --git a/extern/shiny/Main/PropertyBase.cpp b/extern/shiny/Main/PropertyBase.cpp
new file mode 100644
index 0000000000..8592712fa9
--- /dev/null
+++ b/extern/shiny/Main/PropertyBase.cpp
@@ -0,0 +1,302 @@
+#include "PropertyBase.hpp"
+
+#include <vector>
+#include <iostream>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <fstream>
+
+namespace sh
+{
+
+ IntValue::IntValue(int in)
+ : mValue(in)
+ {
+ }
+
+ IntValue::IntValue(const std::string& in)
+ {
+ mValue = boost::lexical_cast<int>(in);
+ }
+
+ std::string IntValue::serialize()
+ {
+ return boost::lexical_cast<std::string>(mValue);
+ }
+
+ // ------------------------------------------------------------------------------
+
+ BooleanValue::BooleanValue (bool in)
+ : mValue(in)
+ {
+ }
+
+ BooleanValue::BooleanValue (const std::string& in)
+ {
+ if (in == "true")
+ mValue = true;
+ else if (in == "false")
+ mValue = false;
+ else
+ {
+ std::stringstream msg;
+ msg << "sh::BooleanValue: Warning: Unrecognized value \"" << in << "\" for property value of type BooleanValue";
+ throw std::runtime_error(msg.str());
+ }
+ }
+
+ std::string BooleanValue::serialize ()
+ {
+ if (mValue)
+ return "true";
+ else
+ return "false";
+ }
+
+ // ------------------------------------------------------------------------------
+
+ StringValue::StringValue (const std::string& in)
+ {
+ mStringValue = in;
+ }
+
+ std::string StringValue::serialize()
+ {
+ return mStringValue;
+ }
+
+ // ------------------------------------------------------------------------------
+
+ LinkedValue::LinkedValue (const std::string& in)
+ {
+ mStringValue = in;
+ mStringValue.erase(0, 1);
+ }
+
+ std::string LinkedValue::serialize()
+ {
+ throw std::runtime_error ("can't directly get a linked value");
+ }
+
+ std::string LinkedValue::get(PropertySetGet* context) const
+ {
+ PropertyValuePtr p = context->getProperty(mStringValue);
+ return retrieveValue<StringValue>(p, NULL).get();
+ }
+
+ // ------------------------------------------------------------------------------
+
+ FloatValue::FloatValue (float in)
+ {
+ mValue = in;
+ }
+
+ FloatValue::FloatValue (const std::string& in)
+ {
+ mValue = boost::lexical_cast<float>(in);
+ }
+
+ std::string FloatValue::serialize ()
+ {
+ return boost::lexical_cast<std::string>(mValue);
+ }
+
+ // ------------------------------------------------------------------------------
+
+ Vector2::Vector2 (float x, float y)
+ : mX(x)
+ , mY(y)
+ {
+ }
+
+ Vector2::Vector2 (const std::string& in)
+ {
+ std::vector<std::string> tokens;
+ boost::split(tokens, in, boost::is_any_of(" "));
+ assert ((tokens.size() == 2) && "Invalid Vector2 conversion");
+ mX = boost::lexical_cast<float> (tokens[0]);
+ mY = boost::lexical_cast<float> (tokens[1]);
+ }
+
+ std::string Vector2::serialize ()
+ {
+ return boost::lexical_cast<std::string>(mX) + " "
+ + boost::lexical_cast<std::string>(mY);
+ }
+
+ // ------------------------------------------------------------------------------
+
+ Vector3::Vector3 (float x, float y, float z)
+ : mX(x)
+ , mY(y)
+ , mZ(z)
+ {
+ }
+
+ Vector3::Vector3 (const std::string& in)
+ {
+ std::vector<std::string> tokens;
+ boost::split(tokens, in, boost::is_any_of(" "));
+ assert ((tokens.size() == 3) && "Invalid Vector3 conversion");
+ mX = boost::lexical_cast<float> (tokens[0]);
+ mY = boost::lexical_cast<float> (tokens[1]);
+ mZ = boost::lexical_cast<float> (tokens[2]);
+ }
+
+ std::string Vector3::serialize ()
+ {
+ return boost::lexical_cast<std::string>(mX) + " "
+ + boost::lexical_cast<std::string>(mY) + " "
+ + boost::lexical_cast<std::string>(mZ);
+ }
+
+ // ------------------------------------------------------------------------------
+
+ Vector4::Vector4 (float x, float y, float z, float w)
+ : mX(x)
+ , mY(y)
+ , mZ(z)
+ , mW(w)
+ {
+ }
+
+ Vector4::Vector4 (const std::string& in)
+ {
+ std::vector<std::string> tokens;
+ boost::split(tokens, in, boost::is_any_of(" "));
+ assert ((tokens.size() == 4) && "Invalid Vector4 conversion");
+ mX = boost::lexical_cast<float> (tokens[0]);
+ mY = boost::lexical_cast<float> (tokens[1]);
+ mZ = boost::lexical_cast<float> (tokens[2]);
+ mW = boost::lexical_cast<float> (tokens[3]);
+ }
+
+ std::string Vector4::serialize ()
+ {
+ return boost::lexical_cast<std::string>(mX) + " "
+ + boost::lexical_cast<std::string>(mY) + " "
+ + boost::lexical_cast<std::string>(mZ) + " "
+ + boost::lexical_cast<std::string>(mW);
+ }
+
+ // ------------------------------------------------------------------------------
+
+ void PropertySet::setProperty (const std::string& name, PropertyValuePtr &value, PropertySetGet* context)
+ {
+ if (!setPropertyOverride (name, value, context))
+ {
+ std::stringstream msg;
+ msg << "sh::PropertySet: Warning: No match for property with name '" << name << "'";
+ throw std::runtime_error(msg.str());
+ }
+ }
+
+ bool PropertySet::setPropertyOverride (const std::string& name, PropertyValuePtr &value, PropertySetGet* context)
+ {
+ // if we got here, none of the sub-classes were able to make use of the property
+ return false;
+ }
+
+ // ------------------------------------------------------------------------------
+
+ PropertySetGet::PropertySetGet (PropertySetGet* parent)
+ : mParent(parent)
+ , mContext(NULL)
+ {
+ }
+
+ PropertySetGet::PropertySetGet ()
+ : mParent(NULL)
+ , mContext(NULL)
+ {
+ }
+
+ void PropertySetGet::setParent (PropertySetGet* parent)
+ {
+ mParent = parent;
+ }
+
+ void PropertySetGet::setContext (PropertySetGet* context)
+ {
+ mContext = context;
+ }
+
+ PropertySetGet* PropertySetGet::getContext()
+ {
+ return mContext;
+ }
+
+ void PropertySetGet::setProperty (const std::string& name, PropertyValuePtr value)
+ {
+ mProperties [name] = value;
+ }
+
+ void PropertySetGet::deleteProperty(const std::string &name)
+ {
+ mProperties.erase(name);
+ }
+
+ PropertyValuePtr& PropertySetGet::getProperty (const std::string& name)
+ {
+ bool found = (mProperties.find(name) != mProperties.end());
+
+ if (!found)
+ {
+ if (!mParent)
+ throw std::runtime_error ("Trying to retrieve property \"" + name + "\" that does not exist");
+ else
+ return mParent->getProperty (name);
+ }
+ else
+ return mProperties[name];
+ }
+
+ bool PropertySetGet::hasProperty (const std::string& name) const
+ {
+ bool found = (mProperties.find(name) != mProperties.end());
+
+ if (!found)
+ {
+ if (!mParent)
+ return false;
+ else
+ return mParent->hasProperty (name);
+ }
+ else
+ return true;
+ }
+
+ void PropertySetGet::copyAll (PropertySet* target, PropertySetGet* context, bool copyParent)
+ {
+ if (mParent && copyParent)
+ mParent->copyAll (target, context);
+ for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it)
+ {
+ target->setProperty(it->first, it->second, context);
+ }
+ }
+
+ void PropertySetGet::copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent)
+ {
+ if (mParent && copyParent)
+ mParent->copyAll (target, context);
+ for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it)
+ {
+ std::string val = retrieveValue<StringValue>(it->second, this).get();
+ target->setProperty(it->first, sh::makeProperty(new sh::StringValue(val)));
+ }
+ }
+
+ void PropertySetGet::save(std::ofstream &stream, const std::string& indentation)
+ {
+ for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it)
+ {
+ if (typeid( *(it->second) ) == typeid(LinkedValue))
+ stream << indentation << it->first << " " << "$" + static_cast<LinkedValue*>(&*(it->second))->_getStringValue() << '\n';
+ else
+ stream << indentation << it->first << " " << retrieveValue<StringValue>(it->second, this).get() << '\n';
+ }
+ }
+}
diff --git a/extern/shiny/Main/PropertyBase.hpp b/extern/shiny/Main/PropertyBase.hpp
new file mode 100644
index 0000000000..13809443e2
--- /dev/null
+++ b/extern/shiny/Main/PropertyBase.hpp
@@ -0,0 +1,244 @@
+#ifndef SH_PROPERTYBASE_H
+#define SH_PROPERTYBASE_H
+
+#include <string>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+namespace sh
+{
+ class StringValue;
+ class PropertySetGet;
+ class LinkedValue;
+
+ enum ValueType
+ {
+ VT_String,
+ VT_Int,
+ VT_Float,
+ VT_Vector2,
+ VT_Vector3,
+ VT_Vector4
+ };
+
+ class PropertyValue
+ {
+ public:
+ PropertyValue() {}
+
+ virtual ~PropertyValue() {}
+
+ std::string _getStringValue() { return mStringValue; }
+
+ virtual std::string serialize() = 0;
+
+ protected:
+ std::string mStringValue; ///< this will possibly not contain anything in the specialised classes
+ };
+ typedef boost::shared_ptr<PropertyValue> PropertyValuePtr;
+
+ class StringValue : public PropertyValue
+ {
+ public:
+ StringValue (const std::string& in);
+ std::string get() const { return mStringValue; }
+
+ virtual std::string serialize();
+ };
+
+ /**
+ * @brief Used for retrieving a named property from a context
+ */
+ class LinkedValue : public PropertyValue
+ {
+ public:
+ LinkedValue (const std::string& in);
+
+ std::string get(PropertySetGet* context) const;
+
+ virtual std::string serialize();
+ };
+
+ class FloatValue : public PropertyValue
+ {
+ public:
+ FloatValue (float in);
+ FloatValue (const std::string& in);
+ float get() const { return mValue; }
+
+ virtual std::string serialize();
+ private:
+ float mValue;
+ };
+
+ class IntValue : public PropertyValue
+ {
+ public:
+ IntValue (int in);
+ IntValue (const std::string& in);
+ int get() const { return mValue; }
+
+ virtual std::string serialize();
+ private:
+ int mValue;
+ };
+
+ class BooleanValue : public PropertyValue
+ {
+ public:
+ BooleanValue (bool in);
+ BooleanValue (const std::string& in);
+ bool get() const { return mValue; }
+
+ virtual std::string serialize();
+ private:
+ bool mValue;
+ };
+
+ class Vector2 : public PropertyValue
+ {
+ public:
+ Vector2 (float x, float y);
+ Vector2 (const std::string& in);
+
+ float mX, mY;
+
+ virtual std::string serialize();
+ };
+
+ class Vector3 : public PropertyValue
+ {
+ public:
+ Vector3 (float x, float y, float z);
+ Vector3 (const std::string& in);
+
+ float mX, mY, mZ;
+
+ virtual std::string serialize();
+ };
+
+ class Vector4 : public PropertyValue
+ {
+ public:
+ Vector4 (float x, float y, float z, float w);
+ Vector4 (const std::string& in);
+
+ float mX, mY, mZ, mW;
+
+ virtual std::string serialize();
+ };
+
+ /// \brief base class that allows setting properties with any kind of value-type
+ class PropertySet
+ {
+ public:
+ virtual ~PropertySet() {}
+ void setProperty (const std::string& name, PropertyValuePtr& value, PropertySetGet* context);
+
+ protected:
+ virtual bool setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet* context);
+ ///< @return \a true if the specified property was found, or false otherwise
+ };
+
+ typedef std::map<std::string, PropertyValuePtr> PropertyMap;
+
+ /// \brief base class that allows setting properties with any kind of value-type and retrieving them
+ class PropertySetGet
+ {
+ public:
+ PropertySetGet (PropertySetGet* parent);
+ PropertySetGet ();
+
+ virtual ~PropertySetGet() {}
+
+ void save (std::ofstream& stream, const std::string& indentation);
+
+ void copyAll (PropertySet* target, PropertySetGet* context, bool copyParent=true);
+ ///< call setProperty for each property/value pair stored in \a this
+ void copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent=true);
+ ///< call setProperty for each property/value pair stored in \a this
+
+ void setParent (PropertySetGet* parent);
+ PropertySetGet* getParent () { return mParent; }
+ void setContext (PropertySetGet* context);
+ PropertySetGet* getContext();
+
+ virtual void setProperty (const std::string& name, PropertyValuePtr value);
+ PropertyValuePtr& getProperty (const std::string& name);
+
+ void deleteProperty (const std::string& name);
+
+ const PropertyMap& listProperties() { return mProperties; }
+
+ bool hasProperty (const std::string& name) const;
+
+ private:
+ PropertyMap mProperties;
+
+ protected:
+ PropertySetGet* mParent;
+ ///< the parent can provide properties as well (when they are retrieved via getProperty) \n
+ /// multiple levels of inheritance are also supported \n
+ /// children can override properties of their parents
+
+ PropertySetGet* mContext;
+ ///< used to retrieve linked property values
+ };
+
+ template <typename T>
+ static T retrieveValue (boost::shared_ptr<PropertyValue>& value, PropertySetGet* context)
+ {
+ if (typeid(*value).name() == typeid(LinkedValue).name())
+ {
+ std::string v = static_cast<LinkedValue*>(value.get())->get(context);
+ PropertyValuePtr newVal = PropertyValuePtr (new StringValue(v));
+ return retrieveValue<T>(newVal, NULL);
+ }
+ if (typeid(T).name() == typeid(*value).name())
+ {
+ // requested type is the same as source type, only have to cast it
+ return *static_cast<T*>(value.get());
+ }
+
+ if ((typeid(T).name() == typeid(StringValue).name())
+ && typeid(*value).name() != typeid(StringValue).name())
+ {
+ // if string type is requested and value is not string, use serialize method to convert to string
+ T* ptr = new T (value->serialize()); // note that T is always StringValue here, but we can't use it here
+ value = boost::shared_ptr<PropertyValue> (static_cast<PropertyValue*>(ptr));
+ return *ptr;
+ }
+
+ {
+ // remaining case: deserialization from string by passing the string to constructor of class T
+ T* ptr = new T(value->_getStringValue());
+ PropertyValuePtr newVal (static_cast<PropertyValue*>(ptr));
+ value = newVal;
+ return *ptr;
+ }
+ }
+ ///<
+ /// @brief alternate version that supports linked values (use of $variables in parent material)
+ /// @note \a value is changed in-place to the converted object
+ /// @return converted object \n
+
+ /// Create a property from a string
+ inline PropertyValuePtr makeProperty (const std::string& prop)
+ {
+ if (prop.size() > 1 && prop[0] == '$')
+ return PropertyValuePtr (static_cast<PropertyValue*>(new LinkedValue(prop)));
+ else
+ return PropertyValuePtr (static_cast<PropertyValue*> (new StringValue(prop)));
+ }
+
+ template <typename T>
+ /// Create a property of any type
+ /// Example: sh::makeProperty (new sh::Vector4(1, 1, 1, 1))
+ inline PropertyValuePtr makeProperty (T* p)
+ {
+ return PropertyValuePtr ( static_cast<PropertyValue*>(p) );
+ }
+}
+
+#endif
diff --git a/extern/shiny/Main/ScriptLoader.cpp b/extern/shiny/Main/ScriptLoader.cpp
new file mode 100644
index 0000000000..bafe07fc8e
--- /dev/null
+++ b/extern/shiny/Main/ScriptLoader.cpp
@@ -0,0 +1,414 @@
+#include "ScriptLoader.hpp"
+
+#include <vector>
+#include <map>
+#include <exception>
+#include <fstream>
+
+#include <boost/filesystem.hpp>
+
+namespace sh
+{
+ void ScriptLoader::loadAllFiles(ScriptLoader* c, const std::string& path)
+ {
+ for ( boost::filesystem::recursive_directory_iterator end, dir(path); dir != end; ++dir )
+ {
+ boost::filesystem::path p(*dir);
+ if(p.extension() == c->mFileEnding)
+ {
+ c->mCurrentFileName = (*dir).path().string();
+ std::ifstream in((*dir).path().string().c_str(), std::ios::binary);
+ c->parseScript(in);
+ }
+ }
+ }
+
+ ScriptLoader::ScriptLoader(const std::string& fileEnding)
+ : mLoadOrder(0)
+ , mToken(TOKEN_NewLine)
+ , mLastToken(TOKEN_NewLine)
+
+ {
+ mFileEnding = fileEnding;
+ }
+
+ ScriptLoader::~ScriptLoader()
+ {
+ clearScriptList();
+ }
+
+ void ScriptLoader::clearScriptList()
+ {
+ std::map <std::string, ScriptNode *>::iterator i;
+ for (i = m_scriptList.begin(); i != m_scriptList.end(); ++i)
+ {
+ delete i->second;
+ }
+ m_scriptList.clear();
+ }
+
+ ScriptNode *ScriptLoader::getConfigScript(const std::string &name)
+ {
+ std::map <std::string, ScriptNode*>::iterator i;
+
+ std::string key = name;
+ i = m_scriptList.find(key);
+
+ //If found..
+ if (i != m_scriptList.end())
+ {
+ return i->second;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ std::map <std::string, ScriptNode*> ScriptLoader::getAllConfigScripts ()
+ {
+ return m_scriptList;
+ }
+
+ void ScriptLoader::parseScript(std::ifstream &stream)
+ {
+ //Get first token
+ _nextToken(stream);
+ if (mToken == TOKEN_EOF)
+ {
+ stream.close();
+ return;
+ }
+
+ //Parse the script
+ _parseNodes(stream, 0);
+
+ stream.close();
+ }
+
+ void ScriptLoader::_nextToken(std::ifstream &stream)
+ {
+ //EOF token
+ if (!stream.good())
+ {
+ mToken = TOKEN_EOF;
+ return;
+ }
+
+ //(Get next character)
+ int ch = stream.get();
+
+ while ((ch == ' ' || ch == 9) && !stream.eof())
+ { //Skip leading spaces / tabs
+ ch = stream.get();
+ }
+
+ if (!stream.good())
+ {
+ mToken = TOKEN_EOF;
+ return;
+ }
+
+ //Newline token
+ if (ch == '\r' || ch == '\n')
+ {
+ do
+ {
+ ch = stream.get();
+ } while ((ch == '\r' || ch == '\n') && !stream.eof());
+
+ stream.unget();
+
+ mToken = TOKEN_NewLine;
+ return;
+ }
+
+ //Open brace token
+ else if (ch == '{')
+ {
+ mToken = TOKEN_OpenBrace;
+ return;
+ }
+
+ //Close brace token
+ else if (ch == '}')
+ {
+ mToken = TOKEN_CloseBrace;
+ return;
+ }
+
+ //Text token
+ if (ch < 32 || ch > 122) //Verify valid char
+ {
+ throw std::runtime_error("Parse Error: Invalid character, ConfigLoader::load()");
+ }
+
+ mTokenValue = "";
+ mToken = TOKEN_Text;
+ do
+ {
+ //Skip comments
+ if (ch == '/')
+ {
+ int ch2 = stream.peek();
+
+ //C++ style comment (//)
+ if (ch2 == '/')
+ {
+ stream.get();
+ do
+ {
+ ch = stream.get();
+ } while (ch != '\r' && ch != '\n' && !stream.eof());
+
+ mToken = TOKEN_NewLine;
+ return;
+ }
+ }
+
+ //Add valid char to tokVal
+ mTokenValue += (char)ch;
+
+ //Next char
+ ch = stream.get();
+
+ } while (ch > 32 && ch <= 122 && !stream.eof());
+
+ stream.unget();
+
+ return;
+ }
+
+ void ScriptLoader::_skipNewLines(std::ifstream &stream)
+ {
+ while (mToken == TOKEN_NewLine)
+ {
+ _nextToken(stream);
+ }
+ }
+
+ void ScriptLoader::_parseNodes(std::ifstream &stream, ScriptNode *parent)
+ {
+ typedef std::pair<std::string, ScriptNode*> ScriptItem;
+
+ while (true)
+ {
+ switch (mToken)
+ {
+ //Node
+ case TOKEN_Text:
+ {
+ //Add the new node
+ ScriptNode *newNode;
+ if (parent)
+ {
+ newNode = parent->addChild(mTokenValue);
+ }
+ else
+ {
+ newNode = new ScriptNode(0, mTokenValue);
+ }
+
+ //Get values
+ _nextToken(stream);
+ std::string valueStr;
+ int i=0;
+ while (mToken == TOKEN_Text)
+ {
+ if (i == 0)
+ valueStr += mTokenValue;
+ else
+ valueStr += " " + mTokenValue;
+ _nextToken(stream);
+ ++i;
+ }
+ newNode->setValue(valueStr);
+
+ //Add root nodes to scriptList
+ if (!parent)
+ {
+ std::string key;
+
+ if (newNode->getValue() == "")
+ throw std::runtime_error("Root node must have a name (\"" + newNode->getName() + "\")");
+ key = newNode->getValue();
+
+ m_scriptList.insert(ScriptItem(key, newNode));
+ }
+
+ _skipNewLines(stream);
+
+ //Add any sub-nodes
+ if (mToken == TOKEN_OpenBrace)
+ {
+ //Parse nodes
+ _nextToken(stream);
+ _parseNodes(stream, newNode);
+ //Check for matching closing brace
+ if (mToken != TOKEN_CloseBrace)
+ {
+ throw std::runtime_error("Parse Error: Expecting closing brace");
+ }
+ _nextToken(stream);
+ _skipNewLines(stream);
+ }
+
+ newNode->mFileName = mCurrentFileName;
+
+ break;
+ }
+
+ //Out of place brace
+ case TOKEN_OpenBrace:
+ throw std::runtime_error("Parse Error: Opening brace out of plane");
+ break;
+
+ //Return if end of nodes have been reached
+ case TOKEN_CloseBrace:
+ return;
+
+ //Return if reached end of file
+ case TOKEN_EOF:
+ return;
+
+ case TOKEN_NewLine:
+ _nextToken(stream);
+ break;
+ }
+ };
+ }
+
+ ScriptNode::ScriptNode(ScriptNode *parent, const std::string &name)
+ {
+ mName = name;
+ mParent = parent;
+ mRemoveSelf = true; //For proper destruction
+ mLastChildFound = -1;
+
+ //Add self to parent's child list (unless this is the root node being created)
+ if (parent != NULL)
+ {
+ mParent->mChildren.push_back(this);
+ mIter = --(mParent->mChildren.end());
+ }
+ }
+
+ ScriptNode::~ScriptNode()
+ {
+ //Delete all children
+ std::vector<ScriptNode*>::iterator i;
+ for (i = mChildren.begin(); i != mChildren.end(); ++i)
+ {
+ ScriptNode *node = *i;
+ node->mRemoveSelf = false;
+ delete node;
+ }
+ mChildren.clear();
+
+ //Remove self from parent's child list
+ if (mRemoveSelf && mParent != NULL)
+ {
+ mParent->mChildren.erase(mIter);
+ }
+ }
+
+ ScriptNode *ScriptNode::addChild(const std::string &name, bool replaceExisting)
+ {
+ if (replaceExisting)
+ {
+ ScriptNode *node = findChild(name, false);
+ if (node)
+ {
+ return node;
+ }
+ }
+ return new ScriptNode(this, name);
+ }
+
+ ScriptNode *ScriptNode::findChild(const std::string &name, bool recursive)
+ {
+ int indx;
+ int childCount = (int)mChildren.size();
+
+ if (mLastChildFound != -1)
+ {
+ //If possible, try checking the nodes neighboring the last successful search
+ //(often nodes searched for in sequence, so this will boost search speeds).
+ int prevC = mLastChildFound-1;
+ if (prevC < 0)
+ prevC = 0;
+ else if (prevC >= childCount)
+ prevC = childCount-1;
+ int nextC = mLastChildFound+1;
+ if (nextC < 0)
+ nextC = 0;
+ else if (nextC >= childCount)
+ nextC = childCount-1;
+
+ for (indx = prevC; indx <= nextC; ++indx)
+ {
+ ScriptNode *node = mChildren[indx];
+ if (node->mName == name)
+ {
+ mLastChildFound = indx;
+ return node;
+ }
+ }
+
+ //If not found that way, search for the node from start to finish, avoiding the
+ //already searched area above.
+ for (indx = nextC + 1; indx < childCount; ++indx)
+ {
+ ScriptNode *node = mChildren[indx];
+ if (node->mName == name) {
+ mLastChildFound = indx;
+ return node;
+ }
+ }
+ for (indx = 0; indx < prevC; ++indx)
+ {
+ ScriptNode *node = mChildren[indx];
+ if (node->mName == name) {
+ mLastChildFound = indx;
+ return node;
+ }
+ }
+ }
+ else
+ {
+ //Search for the node from start to finish
+ for (indx = 0; indx < childCount; ++indx){
+ ScriptNode *node = mChildren[indx];
+ if (node->mName == name) {
+ mLastChildFound = indx;
+ return node;
+ }
+ }
+ }
+
+ //If not found, search child nodes (if recursive == true)
+ if (recursive)
+ {
+ for (indx = 0; indx < childCount; ++indx)
+ {
+ mChildren[indx]->findChild(name, recursive);
+ }
+ }
+
+ //Not found anywhere
+ return NULL;
+ }
+
+ void ScriptNode::setParent(ScriptNode *newParent)
+ {
+ //Remove self from current parent
+ mParent->mChildren.erase(mIter);
+
+ //Set new parent
+ mParent = newParent;
+
+ //Add self to new parent
+ mParent->mChildren.push_back(this);
+ mIter = --(mParent->mChildren.end());
+ }
+}
diff --git a/extern/shiny/Main/ScriptLoader.hpp b/extern/shiny/Main/ScriptLoader.hpp
new file mode 100644
index 0000000000..89720fb5d0
--- /dev/null
+++ b/extern/shiny/Main/ScriptLoader.hpp
@@ -0,0 +1,134 @@
+#ifndef SH_CONFIG_LOADER_H__
+#define SH_CONFIG_LOADER_H__
+
+#include <map>
+#include <vector>
+#include <cassert>
+#include <string>
+
+namespace sh
+{
+ class ScriptNode;
+
+ /**
+ * @brief The base class of loaders that read Ogre style script files to get configuration and settings.
+ * Heavily inspired by: http://www.ogre3d.org/tikiwiki/All-purpose+script+parser
+ * ( "Non-ogre version")
+ */
+ class ScriptLoader
+ {
+ public:
+ static void loadAllFiles(ScriptLoader* c, const std::string& path);
+
+ ScriptLoader(const std::string& fileEnding);
+ virtual ~ScriptLoader();
+
+ std::string mFileEnding;
+
+ // For a line like
+ // entity animals/dog
+ // {
+ // ...
+ // }
+ // The type is "entity" and the name is "animals/dog"
+ // Or if animal/dog was not there then name is ""
+ ScriptNode *getConfigScript (const std::string &name);
+
+ std::map <std::string, ScriptNode*> getAllConfigScripts ();
+
+ void parseScript(std::ifstream &stream);
+
+ std::string mCurrentFileName;
+
+ protected:
+
+ float mLoadOrder;
+ // like "*.object"
+
+ std::map <std::string, ScriptNode*> m_scriptList;
+
+ enum Token
+ {
+ TOKEN_Text,
+ TOKEN_NewLine,
+ TOKEN_OpenBrace,
+ TOKEN_CloseBrace,
+ TOKEN_EOF
+ };
+
+ Token mToken, mLastToken;
+ std::string mTokenValue;
+
+ void _parseNodes(std::ifstream &stream, ScriptNode *parent);
+ void _nextToken(std::ifstream &stream);
+ void _skipNewLines(std::ifstream &stream);
+
+ void clearScriptList();
+ };
+
+ class ScriptNode
+ {
+ public:
+ ScriptNode(ScriptNode *parent, const std::string &name = "untitled");
+ ~ScriptNode();
+
+ inline void setName(const std::string &name)
+ {
+ this->mName = name;
+ }
+
+ inline std::string &getName()
+ {
+ return mName;
+ }
+
+ inline void setValue(const std::string &value)
+ {
+ mValue = value;
+ }
+
+ inline std::string &getValue()
+ {
+ return mValue;
+ }
+
+ ScriptNode *addChild(const std::string &name = "untitled", bool replaceExisting = false);
+ ScriptNode *findChild(const std::string &name, bool recursive = false);
+
+ inline std::vector<ScriptNode*> &getChildren()
+ {
+ return mChildren;
+ }
+
+ inline ScriptNode *getChild(unsigned int index = 0)
+ {
+ assert(index < mChildren.size());
+ return mChildren[index];
+ }
+
+ void setParent(ScriptNode *newParent);
+
+ inline ScriptNode *getParent()
+ {
+ return mParent;
+ }
+
+ std::string mFileName;
+
+
+ private:
+ std::string mName;
+ std::string mValue;
+ std::vector<ScriptNode*> mChildren;
+ ScriptNode *mParent;
+
+
+ int mLastChildFound; //The last child node's index found with a call to findChild()
+
+ std::vector<ScriptNode*>::iterator mIter;
+ bool mRemoveSelf;
+ };
+
+}
+
+#endif
diff --git a/extern/shiny/Main/ShaderInstance.cpp b/extern/shiny/Main/ShaderInstance.cpp
new file mode 100644
index 0000000000..270aaba68f
--- /dev/null
+++ b/extern/shiny/Main/ShaderInstance.cpp
@@ -0,0 +1,698 @@
+#include "ShaderInstance.hpp"
+
+#include <stdexcept>
+#include <iostream>
+#include <fstream>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+
+#include <boost/filesystem.hpp>
+
+#include "Preprocessor.hpp"
+#include "Factory.hpp"
+#include "ShaderSet.hpp"
+
+namespace
+{
+ std::string convertLang (sh::Language lang)
+ {
+ if (lang == sh::Language_CG)
+ return "SH_CG";
+ else if (lang == sh::Language_HLSL)
+ return "SH_HLSL";
+ else if (lang == sh::Language_GLSL)
+ return "SH_GLSL";
+ else if (lang == sh::Language_GLSLES)
+ return "SH_GLSLES";
+ throw std::runtime_error("invalid language");
+ }
+
+ char getComponent(int num)
+ {
+ if (num == 0)
+ return 'x';
+ else if (num == 1)
+ return 'y';
+ else if (num == 2)
+ return 'z';
+ else if (num == 3)
+ return 'w';
+ else
+ throw std::runtime_error("invalid component");
+ }
+
+ std::string getFloat(sh::Language lang, int num_components)
+ {
+ if (lang == sh::Language_CG || lang == sh::Language_HLSL)
+ return (num_components == 1) ? "float" : "float" + boost::lexical_cast<std::string>(num_components);
+ else
+ return (num_components == 1) ? "float" : "vec" + boost::lexical_cast<std::string>(num_components);
+ }
+
+ bool isCmd (const std::string& source, size_t pos, const std::string& cmd)
+ {
+ return (source.size() >= pos + cmd.size() && source.substr(pos, cmd.size()) == cmd);
+ }
+
+ void writeDebugFile (const std::string& content, const std::string& filename)
+ {
+ boost::filesystem::path full_path(boost::filesystem::current_path());
+ std::ofstream of ((full_path / filename ).string().c_str() , std::ios_base::out);
+ of.write(content.c_str(), content.size());
+ of.close();
+ }
+}
+
+namespace sh
+{
+ std::string Passthrough::expand_assign(std::string toAssign)
+ {
+ std::string res;
+
+ int i = 0;
+ int current_passthrough = passthrough_number;
+ int current_component_left = component_start;
+ int current_component_right = 0;
+ int components_left = num_components;
+ int components_at_once;
+ while (i < num_components)
+ {
+ if (components_left + current_component_left <= 4)
+ components_at_once = components_left;
+ else
+ components_at_once = 4 - current_component_left;
+
+ std::string componentStr = ".";
+ for (int j = 0; j < components_at_once; ++j)
+ componentStr += getComponent(j + current_component_left);
+ std::string componentStr2 = ".";
+ for (int j = 0; j < components_at_once; ++j)
+ componentStr2 += getComponent(j + current_component_right);
+ if (num_components == 1)
+ {
+ componentStr2 = "";
+ }
+ res += "passthrough" + boost::lexical_cast<std::string>(current_passthrough) + componentStr + " = " + toAssign + componentStr2;
+
+ current_component_left += components_at_once;
+ current_component_right += components_at_once;
+ components_left -= components_at_once;
+
+ i += components_at_once;
+
+ if (components_left == 0)
+ {
+ // finished
+ return res;
+ }
+ else
+ {
+ // add semicolon to every instruction but the last
+ res += "; ";
+ }
+
+ if (current_component_left == 4)
+ {
+ current_passthrough++;
+ current_component_left = 0;
+ }
+ }
+ throw std::runtime_error("expand_assign error"); // this should never happen, but gets us rid of the "control reaches end of non-void function" warning
+ }
+
+ std::string Passthrough::expand_receive()
+ {
+ std::string res;
+
+ res += getFloat(lang, num_components) + "(";
+
+ int i = 0;
+ int current_passthrough = passthrough_number;
+ int current_component = component_start;
+ int components_left = num_components;
+ while (i < num_components)
+ {
+ int components_at_once = std::min(components_left, 4 - current_component);
+
+ std::string componentStr;
+ for (int j = 0; j < components_at_once; ++j)
+ componentStr += getComponent(j + current_component);
+
+ res += "passthrough" + boost::lexical_cast<std::string>(current_passthrough) + "." + componentStr;
+
+ current_component += components_at_once;
+
+ components_left -= components_at_once;
+
+ i += components_at_once;
+
+ if (components_left == 0)
+ {
+ // finished
+ return res + ")";
+;
+ }
+ else
+ {
+ // add comma to every variable but the last
+ res += ", ";
+ }
+
+ if (current_component == 4)
+ {
+ current_passthrough++;
+ current_component = 0;
+ }
+ }
+
+ throw std::runtime_error("expand_receive error"); // this should never happen, but gets us rid of the "control reaches end of non-void function" warning
+ }
+
+ // ------------------------------------------------------------------------------
+
+ void ShaderInstance::parse (std::string& source, PropertySetGet* properties)
+ {
+ size_t pos = 0;
+ while (true)
+ {
+ pos = source.find("@", pos);
+ if (pos == std::string::npos)
+ break;
+
+ if (isCmd(source, pos, "@shProperty"))
+ {
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+
+ size_t start = source.find("(", pos);
+ size_t end = source.find(")", pos);
+ std::string cmd = source.substr(pos+1, start-(pos+1));
+
+ std::string replaceValue;
+ if (cmd == "shPropertyBool")
+ {
+ std::string propertyName = args[0];
+ PropertyValuePtr value = properties->getProperty(propertyName);
+ bool val = retrieveValue<BooleanValue>(value, properties->getContext()).get();
+ replaceValue = val ? "1" : "0";
+ }
+ else if (cmd == "shPropertyString")
+ {
+ std::string propertyName = args[0];
+ PropertyValuePtr value = properties->getProperty(propertyName);
+ replaceValue = retrieveValue<StringValue>(value, properties->getContext()).get();
+ }
+ else if (cmd == "shPropertyEqual")
+ {
+ std::string propertyName = args[0];
+ std::string comparedAgainst = args[1];
+ std::string value = retrieveValue<StringValue>(properties->getProperty(propertyName), properties->getContext()).get();
+ replaceValue = (value == comparedAgainst) ? "1" : "0";
+ }
+ else if (isCmd(source, pos, "@shPropertyHasValue"))
+ {
+ assert(args.size() == 1);
+ std::string propertyName = args[0];
+ PropertyValuePtr value = properties->getProperty(propertyName);
+ std::string val = retrieveValue<StringValue>(value, properties->getContext()).get();
+ replaceValue = (val.empty() ? "0" : "1");
+ }
+ else
+ throw std::runtime_error ("unknown command \"" + cmd + "\"");
+ source.replace(pos, (end+1)-pos, replaceValue);
+ }
+ else if (isCmd(source, pos, "@shGlobalSetting"))
+ {
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+
+ std::string cmd = source.substr(pos+1, source.find("(", pos)-(pos+1));
+ std::string replaceValue;
+ if (cmd == "shGlobalSettingBool")
+ {
+ std::string settingName = args[0];
+ std::string value = retrieveValue<StringValue>(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get();
+ replaceValue = (value == "true" || value == "1") ? "1" : "0";
+ }
+ else if (cmd == "shGlobalSettingEqual")
+ {
+ std::string settingName = args[0];
+ std::string comparedAgainst = args[1];
+ std::string value = retrieveValue<StringValue>(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get();
+ replaceValue = (value == comparedAgainst) ? "1" : "0";
+ }
+ else if (cmd == "shGlobalSettingString")
+ {
+ std::string settingName = args[0];
+ replaceValue = retrieveValue<StringValue>(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get();
+ }
+ else
+ throw std::runtime_error ("unknown command \"" + cmd + "\"");
+
+ source.replace(pos, (source.find(")", pos)+1)-pos, replaceValue);
+ }
+ else if (isCmd(source, pos, "@shForeach"))
+ {
+
+ assert(source.find("@shEndForeach", pos) != std::string::npos);
+ size_t block_end = source.find("@shEndForeach", pos);
+
+ // get the argument for parsing
+ size_t start = source.find("(", pos);
+ size_t end = start;
+ int brace_depth = 1;
+ while (brace_depth > 0)
+ {
+ ++end;
+ if (source[end] == '(')
+ ++brace_depth;
+ else if (source[end] == ')')
+ --brace_depth;
+ }
+ std::string arg = source.substr(start+1, end-(start+1));
+ parse(arg, properties);
+
+ int num = boost::lexical_cast<int>(arg);
+
+ // get the content of the inner block
+ std::string content = source.substr(end+1, block_end - (end+1));
+
+ // replace both outer and inner block with content of inner block num times
+ std::string replaceStr;
+ for (int i=0; i<num; ++i)
+ {
+ // replace @shIterator with the current iteration
+ std::string addStr = content;
+
+ while (true)
+ {
+ size_t pos2 = addStr.find("@shIterator");
+ if (pos2 == std::string::npos)
+ break;
+
+ // optional offset parameter.
+ size_t openBracePos = pos2 + std::string("@shIterator").length();
+ if (addStr[openBracePos] == '(')
+ {
+ // get the argument for parsing
+ size_t _start = openBracePos;
+ size_t _end = _start;
+ int _brace_depth = 1;
+ while (_brace_depth > 0)
+ {
+ ++_end;
+ if (addStr[_end] == '(')
+ ++_brace_depth;
+ else if (addStr[_end] == ')')
+ --_brace_depth;
+ }
+ std::string arg = addStr.substr(_start+1, _end-(_start+1));
+ parse(arg, properties);
+
+ int offset = boost::lexical_cast<int> (arg);
+ addStr.replace(pos2, (_end+1)-pos2, boost::lexical_cast<std::string>(i+offset));
+ }
+ else
+ {
+ addStr.replace(pos2, std::string("@shIterator").length(), boost::lexical_cast<std::string>(i));
+ }
+ }
+
+ replaceStr += addStr;
+ }
+ source.replace(pos, (block_end+std::string("@shEndForeach").length())-pos, replaceStr);
+ }
+ else if (source.size() > pos+1)
+ ++pos; // skip
+ }
+
+ }
+
+ ShaderInstance::ShaderInstance (ShaderSet* parent, const std::string& name, PropertySetGet* properties)
+ : mName(name)
+ , mParent(parent)
+ , mSupported(true)
+ , mCurrentPassthrough(0)
+ , mCurrentComponent(0)
+ {
+ std::string source = mParent->getSource();
+ int type = mParent->getType();
+ std::string basePath = mParent->getBasePath();
+ size_t pos;
+
+ bool readCache = Factory::getInstance ().getReadSourceCache () && boost::filesystem::exists(
+ Factory::getInstance ().getCacheFolder () + "/" + mName);
+ bool writeCache = Factory::getInstance ().getWriteSourceCache ();
+
+
+ if (readCache)
+ {
+ std::ifstream ifs( std::string(Factory::getInstance ().getCacheFolder () + "/" + mName).c_str() );
+ std::stringstream ss;
+ ss << ifs.rdbuf();
+ source = ss.str();
+ }
+ else
+ {
+ std::vector<std::string> definitions;
+
+ if (mParent->getType() == GPT_Vertex)
+ definitions.push_back("SH_VERTEX_SHADER");
+ else
+ definitions.push_back("SH_FRAGMENT_SHADER");
+ definitions.push_back(convertLang(Factory::getInstance().getCurrentLanguage()));
+
+ parse(source, properties);
+
+ if (Factory::getInstance ().getShaderDebugOutputEnabled ())
+ writeDebugFile(source, name + ".pre");
+
+ // why do we need our own preprocessor? there are several custom commands available in the shader files
+ // (for example for binding uniforms to properties or auto constants) - more below. it is important that these
+ // commands are _only executed if the specific code path actually "survives" the compilation.
+ // thus, we run the code through a preprocessor first to remove the parts that are unused because of
+ // unmet #if conditions (or other preprocessor directives).
+ source = Preprocessor::preprocess(source, basePath, definitions, name);
+
+ // parse counter
+ std::map<int, int> counters;
+ while (true)
+ {
+ pos = source.find("@shCounter");
+ if (pos == std::string::npos)
+ break;
+
+ size_t end = source.find(")", pos);
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size());
+
+ int index = boost::lexical_cast<int>(args[0]);
+
+ if (counters.find(index) == counters.end())
+ counters[index] = 0;
+
+ source.replace(pos, (end+1)-pos, boost::lexical_cast<std::string>(counters[index]++));
+ }
+
+ // parse passthrough declarations
+ while (true)
+ {
+ pos = source.find("@shAllocatePassthrough");
+ if (pos == std::string::npos)
+ break;
+
+ if (mCurrentPassthrough > 7)
+ throw std::runtime_error ("too many passthrough's requested (max 8)");
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size() == 2);
+
+ size_t end = source.find(")", pos);
+
+ Passthrough passthrough;
+
+ passthrough.num_components = boost::lexical_cast<int>(args[0]);
+ assert (passthrough.num_components != 0);
+
+ std::string passthroughName = args[1];
+ passthrough.lang = Factory::getInstance().getCurrentLanguage ();
+ passthrough.component_start = mCurrentComponent;
+ passthrough.passthrough_number = mCurrentPassthrough;
+
+ mPassthroughMap[passthroughName] = passthrough;
+
+ mCurrentComponent += passthrough.num_components;
+ if (mCurrentComponent > 3)
+ {
+ mCurrentComponent -= 4;
+ ++mCurrentPassthrough;
+ }
+
+ source.erase(pos, (end+1)-pos);
+ }
+
+ // passthrough assign
+ while (true)
+ {
+ pos = source.find("@shPassthroughAssign");
+ if (pos == std::string::npos)
+ break;
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size() == 2);
+
+ size_t end = source.find(")", pos);
+
+ std::string passthroughName = args[0];
+ std::string assignTo = args[1];
+
+ assert(mPassthroughMap.find(passthroughName) != mPassthroughMap.end());
+ Passthrough& p = mPassthroughMap[passthroughName];
+
+ source.replace(pos, (end+1)-pos, p.expand_assign(assignTo));
+ }
+
+ // passthrough receive
+ while (true)
+ {
+ pos = source.find("@shPassthroughReceive");
+ if (pos == std::string::npos)
+ break;
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size() == 1);
+
+ size_t end = source.find(")", pos);
+ std::string passthroughName = args[0];
+
+ assert(mPassthroughMap.find(passthroughName) != mPassthroughMap.end());
+ Passthrough& p = mPassthroughMap[passthroughName];
+
+ source.replace(pos, (end+1)-pos, p.expand_receive());
+ }
+
+ // passthrough vertex outputs
+ while (true)
+ {
+ pos = source.find("@shPassthroughVertexOutputs");
+ if (pos == std::string::npos)
+ break;
+
+ std::string result;
+ for (int i = 0; i < mCurrentPassthrough+1; ++i)
+ {
+ // not using newlines here, otherwise the line numbers reported by compiler would be messed up..
+ if (Factory::getInstance().getCurrentLanguage () == Language_CG || Factory::getInstance().getCurrentLanguage () == Language_HLSL)
+ result += ", out float4 passthrough" + boost::lexical_cast<std::string>(i) + " : TEXCOORD" + boost::lexical_cast<std::string>(i);
+
+ /*
+ else
+ result += "out vec4 passthrough" + boost::lexical_cast<std::string>(i) + "; ";
+ */
+ else
+ result += "varying vec4 passthrough" + boost::lexical_cast<std::string>(i) + "; ";
+ }
+
+ source.replace(pos, std::string("@shPassthroughVertexOutputs").length(), result);
+ }
+
+ // passthrough fragment inputs
+ while (true)
+ {
+ pos = source.find("@shPassthroughFragmentInputs");
+ if (pos == std::string::npos)
+ break;
+
+ std::string result;
+ for (int i = 0; i < mCurrentPassthrough+1; ++i)
+ {
+ // not using newlines here, otherwise the line numbers reported by compiler would be messed up..
+ if (Factory::getInstance().getCurrentLanguage () == Language_CG || Factory::getInstance().getCurrentLanguage () == Language_HLSL)
+ result += ", in float4 passthrough" + boost::lexical_cast<std::string>(i) + " : TEXCOORD" + boost::lexical_cast<std::string>(i);
+ /*
+ else
+ result += "in vec4 passthrough" + boost::lexical_cast<std::string>(i) + "; ";
+ */
+ else
+ result += "varying vec4 passthrough" + boost::lexical_cast<std::string>(i) + "; ";
+ }
+
+ source.replace(pos, std::string("@shPassthroughFragmentInputs").length(), result);
+ }
+ }
+
+ // save to cache _here_ - we want to preserve some macros
+ if (writeCache && !readCache)
+ {
+ std::ofstream of (std::string(Factory::getInstance ().getCacheFolder () + "/" + mName).c_str(), std::ios_base::out);
+ of.write(source.c_str(), source.size());
+ of.close();
+ }
+
+
+ // parse shared parameters
+ while (true)
+ {
+ pos = source.find("@shSharedParameter");
+ if (pos == std::string::npos)
+ break;
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size());
+
+ size_t end = source.find(")", pos);
+
+ mSharedParameters.push_back(args[0]);
+
+ source.erase(pos, (end+1)-pos);
+ }
+
+ // parse auto constants
+ typedef std::map< std::string, std::pair<std::string, std::string> > AutoConstantMap;
+ AutoConstantMap autoConstants;
+ while (true)
+ {
+ pos = source.find("@shAutoConstant");
+ if (pos == std::string::npos)
+ break;
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size() >= 2);
+
+ size_t end = source.find(")", pos);
+
+ std::string autoConstantName, uniformName;
+ std::string extraData;
+
+ uniformName = args[0];
+ autoConstantName = args[1];
+ if (args.size() > 2)
+ extraData = args[2];
+
+ autoConstants[uniformName] = std::make_pair(autoConstantName, extraData);
+
+ source.erase(pos, (end+1)-pos);
+ }
+
+ // parse uniform properties
+ while (true)
+ {
+ pos = source.find("@shUniformProperty");
+ if (pos == std::string::npos)
+ break;
+
+ std::vector<std::string> args = extractMacroArguments (pos, source);
+ assert(args.size() == 2);
+
+ size_t start = source.find("(", pos);
+ size_t end = source.find(")", pos);
+ std::string cmd = source.substr(pos, start-pos);
+
+ ValueType vt;
+ if (cmd == "@shUniformProperty4f")
+ vt = VT_Vector4;
+ else if (cmd == "@shUniformProperty3f")
+ vt = VT_Vector3;
+ else if (cmd == "@shUniformProperty2f")
+ vt = VT_Vector2;
+ else if (cmd == "@shUniformProperty1f")
+ vt = VT_Float;
+ else if (cmd == "@shUniformPropertyInt")
+ vt = VT_Int;
+ else
+ throw std::runtime_error ("unsupported command \"" + cmd + "\"");
+
+
+ std::string propertyName, uniformName;
+ uniformName = args[0];
+ propertyName = args[1];
+ mUniformProperties[uniformName] = std::make_pair(propertyName, vt);
+
+ source.erase(pos, (end+1)-pos);
+ }
+
+ // parse texture samplers used
+ while (true)
+ {
+ pos = source.find("@shUseSampler");
+ if (pos == std::string::npos)
+ break;
+
+ size_t end = source.find(")", pos);
+
+ mUsedSamplers.push_back(extractMacroArguments (pos, source)[0]);
+ source.erase(pos, (end+1)-pos);
+ }
+
+ // convert any left-over @'s to #
+ boost::algorithm::replace_all(source, "@", "#");
+
+ Platform* platform = Factory::getInstance().getPlatform();
+
+ std::string profile;
+ if (Factory::getInstance ().getCurrentLanguage () == Language_CG)
+ profile = mParent->getCgProfile ();
+ else if (Factory::getInstance ().getCurrentLanguage () == Language_HLSL)
+ profile = mParent->getHlslProfile ();
+
+
+ if (type == GPT_Vertex)
+ mProgram = boost::shared_ptr<GpuProgram>(platform->createGpuProgram(GPT_Vertex, "", mName, profile, source, Factory::getInstance().getCurrentLanguage()));
+ else if (type == GPT_Fragment)
+ mProgram = boost::shared_ptr<GpuProgram>(platform->createGpuProgram(GPT_Fragment, "", mName, profile, source, Factory::getInstance().getCurrentLanguage()));
+
+
+ if (Factory::getInstance ().getShaderDebugOutputEnabled ())
+ writeDebugFile(source, name);
+
+ if (!mProgram->getSupported())
+ {
+ std::cerr << " Full source code below: \n" << source << std::endl;
+ mSupported = false;
+ return;
+ }
+
+ // set auto constants
+ for (AutoConstantMap::iterator it = autoConstants.begin(); it != autoConstants.end(); ++it)
+ {
+ mProgram->setAutoConstant(it->first, it->second.first, it->second.second);
+ }
+ }
+
+ std::string ShaderInstance::getName ()
+ {
+ return mName;
+ }
+
+ bool ShaderInstance::getSupported () const
+ {
+ return mSupported;
+ }
+
+ std::vector<std::string> ShaderInstance::getUsedSamplers()
+ {
+ return mUsedSamplers;
+ }
+
+ void ShaderInstance::setUniformParameters (boost::shared_ptr<Pass> pass, PropertySetGet* properties)
+ {
+ for (UniformMap::iterator it = mUniformProperties.begin(); it != mUniformProperties.end(); ++it)
+ {
+ pass->setGpuConstant(mParent->getType(), it->first, it->second.second, properties->getProperty(it->second.first), properties->getContext());
+ }
+ }
+
+ std::vector<std::string> ShaderInstance::extractMacroArguments (size_t pos, const std::string& source)
+ {
+ size_t start = source.find("(", pos);
+ size_t end = source.find(")", pos);
+ std::string args = source.substr(start+1, end-(start+1));
+ std::vector<std::string> results;
+ boost::algorithm::split(results, args, boost::is_any_of(","));
+ std::for_each(results.begin(), results.end(),
+ boost::bind(&boost::trim<std::string>,
+ _1, std::locale() ));
+ return results;
+ }
+}
diff --git a/extern/shiny/Main/ShaderInstance.hpp b/extern/shiny/Main/ShaderInstance.hpp
new file mode 100644
index 0000000000..76326ce0c3
--- /dev/null
+++ b/extern/shiny/Main/ShaderInstance.hpp
@@ -0,0 +1,71 @@
+#ifndef SH_SHADERINSTANCE_H
+#define SH_SHADERINSTANCE_H
+
+#include <vector>
+
+#include "Platform.hpp"
+
+namespace sh
+{
+ class ShaderSet;
+
+ typedef std::map< std::string, std::pair<std::string, ValueType > > UniformMap;
+
+ struct Passthrough
+ {
+ Language lang; ///< language to generate for
+
+ int num_components; ///< e.g. 4 for a float4
+
+ int passthrough_number;
+ int component_start; ///< 0 = x
+
+ std::string expand_assign(std::string assignTo);
+ std::string expand_receive();
+ };
+ typedef std::map<std::string, Passthrough> PassthroughMap;
+
+ /**
+ * @brief A specific instance of a \a ShaderSet with a deterministic shader source
+ */
+ class ShaderInstance
+ {
+ public:
+ ShaderInstance (ShaderSet* parent, const std::string& name, PropertySetGet* properties);
+
+ std::string getName();
+
+ bool getSupported () const;
+
+ std::vector<std::string> getUsedSamplers();
+ std::vector<std::string> getSharedParameters() { return mSharedParameters; }
+
+ void setUniformParameters (boost::shared_ptr<Pass> pass, PropertySetGet* properties);
+
+ private:
+ boost::shared_ptr<GpuProgram> mProgram;
+ std::string mName;
+ ShaderSet* mParent;
+ bool mSupported; ///< shader compilation was sucessful?
+
+ std::vector<std::string> mUsedSamplers;
+ ///< names of the texture samplers that are used by this shader
+
+ std::vector<std::string> mSharedParameters;
+
+ UniformMap mUniformProperties;
+ ///< uniforms that this depends on, and their property names / value-types
+ /// @note this lists shared uniform parameters as well
+
+ int mCurrentPassthrough; ///< 0 - x
+ int mCurrentComponent; ///< 0:x, 1:y, 2:z, 3:w
+
+ PassthroughMap mPassthroughMap;
+
+ std::vector<std::string> extractMacroArguments (size_t pos, const std::string& source); ///< take a macro invocation and return vector of arguments
+
+ void parse (std::string& source, PropertySetGet* properties);
+ };
+}
+
+#endif
diff --git a/extern/shiny/Main/ShaderSet.cpp b/extern/shiny/Main/ShaderSet.cpp
new file mode 100644
index 0000000000..8fb530d394
--- /dev/null
+++ b/extern/shiny/Main/ShaderSet.cpp
@@ -0,0 +1,195 @@
+#include "ShaderSet.hpp"
+
+#include <fstream>
+#include <sstream>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/functional/hash.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
+
+#include "Factory.hpp"
+
+namespace sh
+{
+ ShaderSet::ShaderSet (const std::string& type, const std::string& cgProfile, const std::string& hlslProfile, const std::string& sourceFile, const std::string& basePath,
+ const std::string& name, PropertySetGet* globalSettingsPtr)
+ : mBasePath(basePath)
+ , mName(name)
+ , mCgProfile(cgProfile)
+ , mHlslProfile(hlslProfile)
+ {
+ if (type == "vertex")
+ mType = GPT_Vertex;
+ else // if (type == "fragment")
+ mType = GPT_Fragment;
+
+ std::ifstream stream(sourceFile.c_str(), std::ifstream::in);
+ std::stringstream buffer;
+
+ boost::filesystem::path p (sourceFile);
+ p = p.branch_path();
+ mBasePath = p.string();
+
+ buffer << stream.rdbuf();
+ stream.close();
+ mSource = buffer.str();
+ parse();
+ }
+
+ ShaderSet::~ShaderSet()
+ {
+ for (ShaderInstanceMap::iterator it = mInstances.begin(); it != mInstances.end(); ++it)
+ {
+ sh::Factory::getInstance().getPlatform()->destroyGpuProgram(it->second.getName());
+ }
+ }
+
+ void ShaderSet::parse()
+ {
+ std::string currentToken;
+ bool tokenIsRecognized = false;
+ bool isInBraces = false;
+ for (std::string::const_iterator it = mSource.begin(); it != mSource.end(); ++it)
+ {
+ char c = *it;
+ if (((c == ' ') && !isInBraces) || (c == '\n') ||
+ ( ((c == '(') || (c == ')'))
+ && !tokenIsRecognized))
+ {
+ if (tokenIsRecognized)
+ {
+ if (boost::starts_with(currentToken, "@shGlobalSetting"))
+ {
+ assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos));
+ size_t start = currentToken.find('(')+1;
+ mGlobalSettings.push_back(currentToken.substr(start, currentToken.find(')')-start));
+ }
+ else if (boost::starts_with(currentToken, "@shPropertyHasValue"))
+ {
+ assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos));
+ size_t start = currentToken.find('(')+1;
+ mPropertiesToExist.push_back(currentToken.substr(start, currentToken.find(')')-start));
+ }
+ else if (boost::starts_with(currentToken, "@shPropertyEqual"))
+ {
+ assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos)
+ && (currentToken.find(',') != std::string::npos));
+ size_t start = currentToken.find('(')+1;
+ size_t end = currentToken.find(',');
+ mProperties.push_back(currentToken.substr(start, end-start));
+ }
+ else if (boost::starts_with(currentToken, "@shProperty"))
+ {
+ assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos));
+ size_t start = currentToken.find('(')+1;
+ std::string propertyName = currentToken.substr(start, currentToken.find(')')-start);
+ // if the property name is constructed dynamically (e.g. through an iterator) then there is nothing we can do
+ if (propertyName.find("@") == std::string::npos)
+ mProperties.push_back(propertyName);
+ }
+ }
+
+ currentToken = "";
+ }
+ else
+ {
+ if (currentToken == "")
+ {
+ if (c == '@')
+ tokenIsRecognized = true;
+ else
+ tokenIsRecognized = false;
+ }
+ else
+ {
+ if (c == '@')
+ {
+ // ouch, there are nested macros
+ // ( for example @shForeach(@shPropertyString(foobar)) )
+ currentToken = "";
+ }
+ }
+
+ if (c == '(' && tokenIsRecognized)
+ isInBraces = true;
+ else if (c == ')' && tokenIsRecognized)
+ isInBraces = false;
+
+ currentToken += c;
+
+ }
+ }
+ }
+
+ ShaderInstance* ShaderSet::getInstance (PropertySetGet* properties)
+ {
+ size_t h = buildHash (properties);
+ if (std::find(mFailedToCompile.begin(), mFailedToCompile.end(), h) != mFailedToCompile.end())
+ return NULL;
+ if (mInstances.find(h) == mInstances.end())
+ {
+ ShaderInstance newInstance(this, mName + "_" + boost::lexical_cast<std::string>(h), properties);
+ if (!newInstance.getSupported())
+ {
+ mFailedToCompile.push_back(h);
+ return NULL;
+ }
+ mInstances.insert(std::make_pair(h, newInstance));
+ }
+ return &mInstances.find(h)->second;
+ }
+
+ size_t ShaderSet::buildHash (PropertySetGet* properties)
+ {
+ size_t seed = 0;
+ PropertySetGet* currentGlobalSettings = getCurrentGlobalSettings ();
+
+ for (std::vector<std::string>::iterator it = mProperties.begin(); it != mProperties.end(); ++it)
+ {
+ std::string v = retrieveValue<StringValue>(properties->getProperty(*it), properties->getContext()).get();
+ boost::hash_combine(seed, v);
+ }
+ for (std::vector <std::string>::iterator it = mGlobalSettings.begin(); it != mGlobalSettings.end(); ++it)
+ {
+ boost::hash_combine(seed, retrieveValue<StringValue>(currentGlobalSettings->getProperty(*it), NULL).get());
+ }
+ for (std::vector<std::string>::iterator it = mPropertiesToExist.begin(); it != mPropertiesToExist.end(); ++it)
+ {
+ std::string v = retrieveValue<StringValue>(properties->getProperty(*it), properties->getContext()).get();
+ boost::hash_combine(seed, static_cast<bool>(v != ""));
+ }
+ boost::hash_combine(seed, static_cast<int>(Factory::getInstance().getCurrentLanguage()));
+ return seed;
+ }
+
+ PropertySetGet* ShaderSet::getCurrentGlobalSettings() const
+ {
+ return Factory::getInstance ().getCurrentGlobalSettings ();
+ }
+
+ std::string ShaderSet::getBasePath() const
+ {
+ return mBasePath;
+ }
+
+ std::string ShaderSet::getSource() const
+ {
+ return mSource;
+ }
+
+ std::string ShaderSet::getCgProfile() const
+ {
+ return mCgProfile;
+ }
+
+ std::string ShaderSet::getHlslProfile() const
+ {
+ return mHlslProfile;
+ }
+
+ int ShaderSet::getType() const
+ {
+ return mType;
+ }
+}
diff --git a/extern/shiny/Main/ShaderSet.hpp b/extern/shiny/Main/ShaderSet.hpp
new file mode 100644
index 0000000000..988c6769fa
--- /dev/null
+++ b/extern/shiny/Main/ShaderSet.hpp
@@ -0,0 +1,69 @@
+#ifndef SH_SHADERSET_H
+#define SH_SHADERSET_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "ShaderInstance.hpp"
+
+namespace sh
+{
+ class PropertySetGet;
+
+ typedef std::map<size_t, ShaderInstance> ShaderInstanceMap;
+
+ /**
+ * @brief Contains possible shader permutations of a single uber-shader (represented by one source file)
+ */
+ class ShaderSet
+ {
+ public:
+ ShaderSet (const std::string& type, const std::string& cgProfile, const std::string& hlslProfile, const std::string& sourceFile, const std::string& basePath,
+ const std::string& name, PropertySetGet* globalSettingsPtr);
+ ~ShaderSet();
+
+ /// Retrieve a shader instance for the given properties. \n
+ /// If a \a ShaderInstance with the same properties exists already, simply returns this instance. \n
+ /// Otherwise, creates a new \a ShaderInstance (i.e. compiles a new shader). \n
+ /// Might also return NULL if the shader failed to compile. \n
+ /// @note Only the properties that actually affect the shader source are taken into consideration here,
+ /// so it does not matter if you pass any extra properties that the shader does not care about.
+ ShaderInstance* getInstance (PropertySetGet* properties);
+
+ private:
+ PropertySetGet* getCurrentGlobalSettings() const;
+ std::string getBasePath() const;
+ std::string getSource() const;
+ std::string getCgProfile() const;
+ std::string getHlslProfile() const;
+ int getType() const;
+
+ friend class ShaderInstance;
+
+ private:
+ GpuProgramType mType;
+ std::string mSource;
+ std::string mBasePath;
+ std::string mCgProfile;
+ std::string mHlslProfile;
+ std::string mName;
+
+ std::vector <size_t> mFailedToCompile;
+
+ std::vector <std::string> mGlobalSettings; ///< names of the global settings that affect the shader source
+ std::vector <std::string> mProperties; ///< names of the per-material properties that affect the shader source
+
+ std::vector <std::string> mPropertiesToExist;
+ ///< same as mProperties, however in this case, it is only relevant if the property is empty or not
+ /// (we don't care about the value)
+
+ ShaderInstanceMap mInstances; ///< maps permutation ID (generated from the properties) to \a ShaderInstance
+
+ void parse(); ///< find out which properties and global settings affect the shader source
+
+ size_t buildHash (PropertySetGet* properties);
+ };
+}
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp b/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp
new file mode 100644
index 0000000000..e718540197
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp
@@ -0,0 +1,69 @@
+#include <stdexcept>
+
+#include "OgreGpuProgram.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+#include <OgreHighLevelGpuProgramManager.h>
+#include <OgreGpuProgramManager.h>
+#include <OgreVector4.h>
+
+namespace sh
+{
+ OgreGpuProgram::OgreGpuProgram(
+ GpuProgramType type,
+ const std::string& compileArguments,
+ const std::string& name, const std::string& profile,
+ const std::string& source, const std::string& lang,
+ const std::string& resourceGroup)
+ : GpuProgram()
+ {
+ Ogre::HighLevelGpuProgramManager& mgr = Ogre::HighLevelGpuProgramManager::getSingleton();
+ assert (mgr.getByName(name).isNull() && "Vertex program already exists");
+
+ Ogre::GpuProgramType t;
+ if (type == GPT_Vertex)
+ t = Ogre::GPT_VERTEX_PROGRAM;
+ else
+ t = Ogre::GPT_FRAGMENT_PROGRAM;
+
+ mProgram = mgr.createProgram(name, resourceGroup, lang, t);
+ if (lang != "glsl" && lang != "glsles")
+ mProgram->setParameter("entry_point", "main");
+ if (lang == "hlsl")
+ mProgram->setParameter("target", profile);
+ else if (lang == "cg")
+ mProgram->setParameter("profiles", profile);
+
+ mProgram->setSource(source);
+ mProgram->load();
+
+ if (mProgram.isNull() || !mProgram->isSupported())
+ std::cerr << "Failed to compile shader \"" << name << "\". Consider the OGRE log for more information." << std::endl;
+ }
+
+ bool OgreGpuProgram::getSupported()
+ {
+ return (!mProgram.isNull() && mProgram->isSupported());
+ }
+
+ void OgreGpuProgram::setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo)
+ {
+ assert (!mProgram.isNull() && mProgram->isSupported());
+ const Ogre::GpuProgramParameters::AutoConstantDefinition* d = Ogre::GpuProgramParameters::getAutoConstantDefinition(autoConstantName);
+
+ if (!d)
+ throw std::runtime_error ("can't find auto constant with name \"" + autoConstantName + "\"");
+ Ogre::GpuProgramParameters::AutoConstantType t = d->acType;
+
+ // this simplifies debugging for CG a lot.
+ mProgram->getDefaultParameters()->setIgnoreMissingParams(true);
+
+ if (d->dataType == Ogre::GpuProgramParameters::ACDT_NONE)
+ mProgram->getDefaultParameters()->setNamedAutoConstant (name, t, 0);
+ else if (d->dataType == Ogre::GpuProgramParameters::ACDT_INT)
+ mProgram->getDefaultParameters()->setNamedAutoConstant (name, t, extraInfo == "" ? 0 : boost::lexical_cast<int>(extraInfo));
+ else if (d->dataType == Ogre::GpuProgramParameters::ACDT_REAL)
+ mProgram->getDefaultParameters()->setNamedAutoConstantReal (name, t, extraInfo == "" ? 0.f : boost::lexical_cast<float>(extraInfo));
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp b/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp
new file mode 100644
index 0000000000..42673ed9b4
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp
@@ -0,0 +1,31 @@
+#ifndef SH_OGREGPUPROGRAM_H
+#define SH_OGREGPUPROGRAM_H
+
+#include <string>
+
+#include <OgreHighLevelGpuProgram.h>
+
+#include "../../Main/Platform.hpp"
+
+namespace sh
+{
+ class OgreGpuProgram : public GpuProgram
+ {
+ public:
+ OgreGpuProgram (
+ GpuProgramType type,
+ const std::string& compileArguments,
+ const std::string& name, const std::string& profile,
+ const std::string& source, const std::string& lang,
+ const std::string& resourceGroup);
+
+ virtual bool getSupported();
+
+ virtual void setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo = "");
+
+ private:
+ Ogre::HighLevelGpuProgramPtr mProgram;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.cpp b/extern/shiny/Platforms/Ogre/OgreMaterial.cpp
new file mode 100644
index 0000000000..77091fe03d
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreMaterial.cpp
@@ -0,0 +1,115 @@
+#include "OgreMaterial.hpp"
+
+#include <OgreMaterialManager.h>
+#include <OgreTechnique.h>
+#include <stdexcept>
+
+#include "OgrePass.hpp"
+#include "OgreMaterialSerializer.hpp"
+#include "OgrePlatform.hpp"
+
+namespace sh
+{
+ static const std::string sDefaultTechniqueName = "SH_DefaultTechnique";
+
+ OgreMaterial::OgreMaterial (const std::string& name, const std::string& resourceGroup)
+ : Material()
+ {
+ mName = name;
+ assert (Ogre::MaterialManager::getSingleton().getByName(name).isNull() && "Material already exists");
+ mMaterial = Ogre::MaterialManager::getSingleton().create (name, resourceGroup);
+ mMaterial->removeAllTechniques();
+ mMaterial->createTechnique()->setSchemeName (sDefaultTechniqueName);
+ mMaterial->compile();
+ }
+
+ void OgreMaterial::ensureLoaded()
+ {
+ if (mMaterial.isNull())
+ mMaterial = Ogre::MaterialManager::getSingleton().getByName(mName);
+ }
+
+ bool OgreMaterial::isUnreferenced()
+ {
+ // Resource system internals hold 3 shared pointers, we hold one, so usecount of 4 means unused
+ return (!mMaterial.isNull() && mMaterial.useCount() <= Ogre::ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS+1);
+ }
+
+ OgreMaterial::~OgreMaterial()
+ {
+ if (!mMaterial.isNull())
+ Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
+ }
+
+ boost::shared_ptr<Pass> OgreMaterial::createPass (const std::string& configuration, unsigned short lodIndex)
+ {
+ return boost::shared_ptr<Pass> (new OgrePass (this, configuration, lodIndex));
+ }
+
+ void OgreMaterial::removeAll ()
+ {
+ if (mMaterial.isNull())
+ return;
+ mMaterial->removeAllTechniques();
+ mMaterial->createTechnique()->setSchemeName (sDefaultTechniqueName);
+ mMaterial->compile();
+ }
+
+ void OgreMaterial::setLodLevels (const std::string& lodLevels)
+ {
+ OgreMaterialSerializer& s = OgrePlatform::getSerializer();
+
+ s.setMaterialProperty ("lod_values", lodLevels, mMaterial);
+ }
+
+ bool OgreMaterial::createConfiguration (const std::string& name, unsigned short lodIndex)
+ {
+ for (int i=0; i<mMaterial->getNumTechniques(); ++i)
+ {
+ if (mMaterial->getTechnique(i)->getSchemeName() == name && mMaterial->getTechnique(i)->getLodIndex() == lodIndex)
+ return false;
+ }
+
+ Ogre::Technique* t = mMaterial->createTechnique();
+ t->setSchemeName (name);
+ t->setLodIndex (lodIndex);
+ if (mShadowCasterMaterial != "")
+ t->setShadowCasterMaterial(mShadowCasterMaterial);
+
+ mMaterial->compile();
+
+ return true;
+ }
+
+ Ogre::MaterialPtr OgreMaterial::getOgreMaterial ()
+ {
+ return mMaterial;
+ }
+
+ Ogre::Technique* OgreMaterial::getOgreTechniqueForConfiguration (const std::string& configurationName, unsigned short lodIndex)
+ {
+ for (int i=0; i<mMaterial->getNumTechniques(); ++i)
+ {
+ if (mMaterial->getTechnique(i)->getSchemeName() == configurationName && mMaterial->getTechnique(i)->getLodIndex() == lodIndex)
+ {
+ return mMaterial->getTechnique(i);
+ }
+ }
+
+ // Prepare and throw error message
+ std::stringstream message;
+ message << "Could not find configurationName '" << configurationName
+ << "' and lodIndex " << lodIndex;
+
+ throw std::runtime_error(message.str());
+ }
+
+ void OgreMaterial::setShadowCasterMaterial (const std::string& name)
+ {
+ mShadowCasterMaterial = name;
+ for (int i=0; i<mMaterial->getNumTechniques(); ++i)
+ {
+ mMaterial->getTechnique(i)->setShadowCasterMaterial(mShadowCasterMaterial);
+ }
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.hpp b/extern/shiny/Platforms/Ogre/OgreMaterial.hpp
new file mode 100644
index 0000000000..bfa6e2c6f8
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreMaterial.hpp
@@ -0,0 +1,42 @@
+#ifndef SH_OGREMATERIAL_H
+#define SH_OGREMATERIAL_H
+
+#include <string>
+
+#include <OgreMaterial.h>
+
+#include "../../Main/Platform.hpp"
+
+namespace sh
+{
+ class OgreMaterial : public Material
+ {
+ public:
+ OgreMaterial (const std::string& name, const std::string& resourceGroup);
+ virtual ~OgreMaterial();
+
+ virtual boost::shared_ptr<Pass> createPass (const std::string& configuration, unsigned short lodIndex);
+ virtual bool createConfiguration (const std::string& name, unsigned short lodIndex);
+
+ virtual bool isUnreferenced();
+ virtual void ensureLoaded();
+
+ virtual void removeAll ();
+
+ Ogre::MaterialPtr getOgreMaterial();
+
+ virtual void setLodLevels (const std::string& lodLevels);
+
+ Ogre::Technique* getOgreTechniqueForConfiguration (const std::string& configurationName, unsigned short lodIndex = 0);
+
+ virtual void setShadowCasterMaterial (const std::string& name);
+
+ private:
+ Ogre::MaterialPtr mMaterial;
+ std::string mName;
+
+ std::string mShadowCasterMaterial;
+ };
+}
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp
new file mode 100644
index 0000000000..f45e641557
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp
@@ -0,0 +1,85 @@
+#include "OgreMaterialSerializer.hpp"
+
+#include <OgrePass.h>
+
+#include <OgreStringConverter.h>
+
+namespace sh
+{
+ void OgreMaterialSerializer::reset()
+ {
+ mScriptContext.section = Ogre::MSS_NONE;
+ mScriptContext.material.setNull();
+ mScriptContext.technique = 0;
+ mScriptContext.pass = 0;
+ mScriptContext.textureUnit = 0;
+ mScriptContext.program.setNull();
+ mScriptContext.lineNo = 0;
+ mScriptContext.filename.clear();
+ mScriptContext.techLev = -1;
+ mScriptContext.passLev = -1;
+ mScriptContext.stateLev = -1;
+ }
+
+ bool OgreMaterialSerializer::setPassProperty (const std::string& param, std::string value, Ogre::Pass* pass)
+ {
+ // workaround https://ogre3d.atlassian.net/browse/OGRE-158
+ if (param == "transparent_sorting" && value == "force")
+ {
+ pass->setTransparentSortingForced(true);
+ return true;
+ }
+
+ reset();
+
+ mScriptContext.section = Ogre::MSS_PASS;
+ mScriptContext.pass = pass;
+
+ if (mPassAttribParsers.find (param) == mPassAttribParsers.end())
+ return false;
+ else
+ {
+ mPassAttribParsers.find(param)->second(value, mScriptContext);
+ return true;
+ }
+ }
+
+ bool OgreMaterialSerializer::setTextureUnitProperty (const std::string& param, std::string value, Ogre::TextureUnitState* t)
+ {
+ // quick access to automip setting, without having to use 'texture' which doesn't like spaces in filenames
+ if (param == "num_mipmaps")
+ {
+ t->setNumMipmaps(Ogre::StringConverter::parseInt(value));
+ return true;
+ }
+
+ reset();
+
+ mScriptContext.section = Ogre::MSS_TEXTUREUNIT;
+ mScriptContext.textureUnit = t;
+
+ if (mTextureUnitAttribParsers.find (param) == mTextureUnitAttribParsers.end())
+ return false;
+ else
+ {
+ mTextureUnitAttribParsers.find(param)->second(value, mScriptContext);
+ return true;
+ }
+ }
+
+ bool OgreMaterialSerializer::setMaterialProperty (const std::string& param, std::string value, Ogre::MaterialPtr m)
+ {
+ reset();
+
+ mScriptContext.section = Ogre::MSS_MATERIAL;
+ mScriptContext.material = m;
+
+ if (mMaterialAttribParsers.find (param) == mMaterialAttribParsers.end())
+ return false;
+ else
+ {
+ mMaterialAttribParsers.find(param)->second(value, mScriptContext);
+ return true;
+ }
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp
new file mode 100644
index 0000000000..acfc5a362f
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp
@@ -0,0 +1,29 @@
+#ifndef SH_OGREMATERIALSERIALIZER_H
+#define SH_OGREMATERIALSERIALIZER_H
+
+#include <OgreMaterialSerializer.h>
+
+namespace Ogre
+{
+ class Pass;
+}
+
+namespace sh
+{
+ /**
+ * @brief This class allows me to let Ogre handle the pass & texture unit properties
+ */
+ class OgreMaterialSerializer : public Ogre::MaterialSerializer
+ {
+ public:
+ bool setPassProperty (const std::string& param, std::string value, Ogre::Pass* pass);
+ bool setTextureUnitProperty (const std::string& param, std::string value, Ogre::TextureUnitState* t);
+ bool setMaterialProperty (const std::string& param, std::string value, Ogre::MaterialPtr m);
+
+ private:
+ void reset();
+ };
+
+}
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgrePass.cpp b/extern/shiny/Platforms/Ogre/OgrePass.cpp
new file mode 100644
index 0000000000..3a25c5c740
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgrePass.cpp
@@ -0,0 +1,131 @@
+#include <stdexcept>
+
+#include "OgrePass.hpp"
+
+#include <OgrePass.h>
+#include <OgreTechnique.h>
+
+#include "OgreTextureUnitState.hpp"
+#include "OgreGpuProgram.hpp"
+#include "OgreMaterial.hpp"
+#include "OgreMaterialSerializer.hpp"
+#include "OgrePlatform.hpp"
+
+namespace sh
+{
+ OgrePass::OgrePass (OgreMaterial* parent, const std::string& configuration, unsigned short lodIndex)
+ : Pass()
+ {
+ Ogre::Technique* t = parent->getOgreTechniqueForConfiguration(configuration, lodIndex);
+ mPass = t->createPass();
+ }
+
+ boost::shared_ptr<TextureUnitState> OgrePass::createTextureUnitState (const std::string& name)
+ {
+ return boost::shared_ptr<TextureUnitState> (new OgreTextureUnitState (this, name));
+ }
+
+ void OgrePass::assignProgram (GpuProgramType type, const std::string& name)
+ {
+ if (type == GPT_Vertex)
+ mPass->setVertexProgram (name);
+ else if (type == GPT_Fragment)
+ mPass->setFragmentProgram (name);
+ else
+ throw std::runtime_error("unsupported GpuProgramType");
+ }
+
+ Ogre::Pass* OgrePass::getOgrePass ()
+ {
+ return mPass;
+ }
+
+ bool OgrePass::setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context)
+ {
+ if (((typeid(*value) == typeid(StringValue)) || typeid(*value) == typeid(LinkedValue))
+ && retrieveValue<StringValue>(value, context).get() == "default")
+ return true;
+
+ if (name == "vertex_program")
+ return true; // handled already
+ else if (name == "fragment_program")
+ return true; // handled already
+ else
+ {
+ OgreMaterialSerializer& s = OgrePlatform::getSerializer();
+
+ return s.setPassProperty (name, retrieveValue<StringValue>(value, context).get(), mPass);
+ }
+ }
+
+ void OgrePass::setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context)
+ {
+ Ogre::GpuProgramParametersSharedPtr params;
+ if (type == GPT_Vertex)
+ {
+ if (!mPass->hasVertexProgram ())
+ return;
+ params = mPass->getVertexProgramParameters();
+ }
+ else if (type == GPT_Fragment)
+ {
+ if (!mPass->hasFragmentProgram ())
+ return;
+ params = mPass->getFragmentProgramParameters();
+ }
+
+ if (vt == VT_Float)
+ params->setNamedConstant (name, retrieveValue<FloatValue>(value, context).get());
+ else if (vt == VT_Int)
+ params->setNamedConstant (name, retrieveValue<IntValue>(value, context).get());
+ else if (vt == VT_Vector4)
+ {
+ Vector4 v = retrieveValue<Vector4>(value, context);
+ params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, v.mZ, v.mW));
+ }
+ else if (vt == VT_Vector3)
+ {
+ Vector3 v = retrieveValue<Vector3>(value, context);
+ params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, v.mZ, 1.0));
+ }
+ else if (vt == VT_Vector2)
+ {
+ Vector2 v = retrieveValue<Vector2>(value, context);
+ params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, 1.0, 1.0));
+ }
+ else
+ throw std::runtime_error ("unsupported constant type");
+ }
+
+ void OgrePass::addSharedParameter (int type, const std::string& name)
+ {
+ Ogre::GpuProgramParametersSharedPtr params;
+ if (type == GPT_Vertex)
+ params = mPass->getVertexProgramParameters();
+ else if (type == GPT_Fragment)
+ params = mPass->getFragmentProgramParameters();
+
+ try
+ {
+ params->addSharedParameters (name);
+ }
+ catch (Ogre::Exception& e)
+ {
+ std::stringstream msg;
+ msg << "Could not create a shared parameter instance for '"
+ << name << "'. Make sure this shared parameter has a value set (via Factory::setSharedParameter)!";
+ throw std::runtime_error(msg.str());
+ }
+ }
+
+ void OgrePass::setTextureUnitIndex (int programType, const std::string& name, int index)
+ {
+ Ogre::GpuProgramParametersSharedPtr params;
+ if (programType == GPT_Vertex)
+ params = mPass->getVertexProgramParameters();
+ else if (programType == GPT_Fragment)
+ params = mPass->getFragmentProgramParameters();
+
+ params->setNamedConstant(name, index);
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgrePass.hpp b/extern/shiny/Platforms/Ogre/OgrePass.hpp
new file mode 100644
index 0000000000..e7cfd4e337
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgrePass.hpp
@@ -0,0 +1,35 @@
+#ifndef SH_OGREPASS_H
+#define SH_OGREPASS_H
+
+#include <OgrePass.h>
+
+#include "../../Main/Platform.hpp"
+
+namespace sh
+{
+ class OgreMaterial;
+
+ class OgrePass : public Pass
+ {
+ public:
+ OgrePass (OgreMaterial* parent, const std::string& configuration, unsigned short lodIndex);
+
+ virtual boost::shared_ptr<TextureUnitState> createTextureUnitState (const std::string& name);
+ virtual void assignProgram (GpuProgramType type, const std::string& name);
+
+ Ogre::Pass* getOgrePass();
+
+ virtual void setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context);
+
+ virtual void addSharedParameter (int type, const std::string& name);
+ virtual void setTextureUnitIndex (int programType, const std::string& name, int index);
+
+ private:
+ Ogre::Pass* mPass;
+
+ protected:
+ virtual bool setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context);
+ };
+}
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp
new file mode 100644
index 0000000000..3725d5f354
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp
@@ -0,0 +1,183 @@
+#include <stdexcept>
+
+#include "OgrePlatform.hpp"
+
+#include <OgreDataStream.h>
+#include <OgreGpuProgramManager.h>
+#include <OgreHighLevelGpuProgramManager.h>
+#include <OgreRoot.h>
+
+#include "OgreMaterial.hpp"
+#include "OgreGpuProgram.hpp"
+#include "OgreMaterialSerializer.hpp"
+
+#include "../../Main/MaterialInstance.hpp"
+#include "../../Main/Factory.hpp"
+
+namespace
+{
+ std::string convertLang (sh::Language lang)
+ {
+ if (lang == sh::Language_CG)
+ return "cg";
+ else if (lang == sh::Language_HLSL)
+ return "hlsl";
+ else if (lang == sh::Language_GLSL)
+ return "glsl";
+ else if (lang == sh::Language_GLSLES)
+ return "glsles";
+ throw std::runtime_error ("invalid language, valid are: cg, hlsl, glsl, glsles");
+ }
+}
+
+namespace sh
+{
+ OgreMaterialSerializer* OgrePlatform::sSerializer = 0;
+
+ OgrePlatform::OgrePlatform(const std::string& resourceGroupName, const std::string& basePath)
+ : Platform(basePath)
+ , mResourceGroup(resourceGroupName)
+ {
+ Ogre::MaterialManager::getSingleton().addListener(this);
+
+ if (supportsShaderSerialization())
+ Ogre::GpuProgramManager::getSingletonPtr()->setSaveMicrocodesToCache(true);
+
+ sSerializer = new OgreMaterialSerializer();
+ }
+
+ OgreMaterialSerializer& OgrePlatform::getSerializer()
+ {
+ assert(sSerializer);
+ return *sSerializer;
+ }
+
+ OgrePlatform::~OgrePlatform ()
+ {
+ delete sSerializer;
+ }
+
+ bool OgrePlatform::isProfileSupported (const std::string& profile)
+ {
+ return Ogre::GpuProgramManager::getSingleton().isSyntaxSupported(profile);
+ }
+
+ bool OgrePlatform::supportsShaderSerialization ()
+ {
+ // Not very reliable in OpenGL mode (requires extension), and somehow doesn't work on linux even if the extension is present
+ return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos;
+ }
+
+ bool OgrePlatform::supportsMaterialQueuedListener ()
+ {
+ return true;
+ }
+
+ boost::shared_ptr<Material> OgrePlatform::createMaterial (const std::string& name)
+ {
+ OgreMaterial* material = new OgreMaterial(name, mResourceGroup);
+ return boost::shared_ptr<Material> (material);
+ }
+
+ void OgrePlatform::destroyGpuProgram(const std::string &name)
+ {
+ Ogre::HighLevelGpuProgramManager::getSingleton().remove(name);
+ }
+
+ boost::shared_ptr<GpuProgram> OgrePlatform::createGpuProgram (
+ GpuProgramType type,
+ const std::string& compileArguments,
+ const std::string& name, const std::string& profile,
+ const std::string& source, Language lang)
+ {
+ OgreGpuProgram* prog = new OgreGpuProgram (type, compileArguments, name, profile, source, convertLang(lang), mResourceGroup);
+ return boost::shared_ptr<GpuProgram> (static_cast<GpuProgram*>(prog));
+ }
+
+ Ogre::Technique* OgrePlatform::handleSchemeNotFound (
+ unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial,
+ unsigned short lodIndex, const Ogre::Renderable *rend)
+ {
+ MaterialInstance* m = fireMaterialRequested(originalMaterial->getName(), schemeName, lodIndex);
+ if (m)
+ {
+ OgreMaterial* _m = static_cast<OgreMaterial*>(m->getMaterial());
+ return _m->getOgreTechniqueForConfiguration (schemeName, lodIndex);
+ }
+ else
+ return 0; // material does not belong to us
+ }
+
+ 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);
+ }
+
+ void OgrePlatform::deserializeShaders (const std::string& file)
+ {
+ std::ifstream inp;
+ inp.open(file.c_str(), std::ios::in | std::ios::binary);
+ Ogre::DataStreamPtr shaderCache(OGRE_NEW Ogre::FileStreamDataStream(file, &inp, false));
+ Ogre::GpuProgramManager::getSingleton().loadMicrocodeCache(shaderCache);
+ }
+
+ void OgrePlatform::setSharedParameter (const std::string& name, PropertyValuePtr value)
+ {
+ Ogre::GpuSharedParametersPtr params;
+ if (mSharedParameters.find(name) == mSharedParameters.end())
+ {
+ params = Ogre::GpuProgramManager::getSingleton().createSharedParameters(name);
+
+ Ogre::GpuConstantType type;
+ if (typeid(*value) == typeid(Vector4))
+ type = Ogre::GCT_FLOAT4;
+ else if (typeid(*value) == typeid(Vector3))
+ type = Ogre::GCT_FLOAT3;
+ else if (typeid(*value) == typeid(Vector2))
+ type = Ogre::GCT_FLOAT2;
+ else if (typeid(*value) == typeid(FloatValue))
+ type = Ogre::GCT_FLOAT1;
+ else if (typeid(*value) == typeid(IntValue))
+ type = Ogre::GCT_INT1;
+ else
+ assert(0);
+ params->addConstantDefinition(name, type);
+ mSharedParameters[name] = params;
+ }
+ else
+ params = mSharedParameters.find(name)->second;
+
+ Ogre::Vector4 v (1.0, 1.0, 1.0, 1.0);
+ if (typeid(*value) == typeid(Vector4))
+ {
+ Vector4 vec = retrieveValue<Vector4>(value, NULL);
+ v.x = vec.mX;
+ v.y = vec.mY;
+ v.z = vec.mZ;
+ v.w = vec.mW;
+ }
+ else if (typeid(*value) == typeid(Vector3))
+ {
+ Vector3 vec = retrieveValue<Vector3>(value, NULL);
+ v.x = vec.mX;
+ v.y = vec.mY;
+ v.z = vec.mZ;
+ }
+ else if (typeid(*value) == typeid(Vector2))
+ {
+ Vector2 vec = retrieveValue<Vector2>(value, NULL);
+ v.x = vec.mX;
+ v.y = vec.mY;
+ }
+ else if (typeid(*value) == typeid(FloatValue))
+ v.x = retrieveValue<FloatValue>(value, NULL).get();
+ else if (typeid(*value) == typeid(IntValue))
+ v.x = static_cast<float>(retrieveValue<IntValue>(value, NULL).get());
+ else
+ throw std::runtime_error ("unsupported property type for shared parameter \"" + name + "\"");
+ params->setNamedConstant(name, v);
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.hpp b/extern/shiny/Platforms/Ogre/OgrePlatform.hpp
new file mode 100644
index 0000000000..d3a1a83608
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgrePlatform.hpp
@@ -0,0 +1,74 @@
+#ifndef SH_OGREPLATFORM_H
+#define SH_OGREPLATFORM_H
+
+/**
+ * @addtogroup Platforms
+ * @{
+ */
+
+/**
+ * @addtogroup Ogre
+ * A set of classes to interact with Ogre's material system
+ * @{
+ */
+
+#include "../../Main/Platform.hpp"
+
+#include <OgreMaterialManager.h>
+#include <OgreGpuProgramParams.h>
+
+namespace sh
+{
+ class OgreMaterialSerializer;
+
+ class OgrePlatform : public Platform, public Ogre::MaterialManager::Listener
+ {
+ public:
+ OgrePlatform (const std::string& resourceGroupName, const std::string& basePath);
+ virtual ~OgrePlatform ();
+
+ virtual Ogre::Technique* handleSchemeNotFound (
+ unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial,
+ unsigned short lodIndex, const Ogre::Renderable *rend);
+
+ static OgreMaterialSerializer& getSerializer();
+
+ private:
+ virtual bool isProfileSupported (const std::string& profile);
+
+ virtual void serializeShaders (const std::string& file);
+ virtual void deserializeShaders (const std::string& file);
+
+ virtual boost::shared_ptr<Material> createMaterial (const std::string& name);
+
+ virtual boost::shared_ptr<GpuProgram> createGpuProgram (
+ GpuProgramType type,
+ const std::string& compileArguments,
+ const std::string& name, const std::string& profile,
+ const std::string& source, Language lang);
+
+ virtual void destroyGpuProgram (const std::string& name);
+
+ virtual void setSharedParameter (const std::string& name, PropertyValuePtr value);
+
+ friend class ShaderInstance;
+ friend class Factory;
+
+ protected:
+ virtual bool supportsShaderSerialization ();
+ virtual bool supportsMaterialQueuedListener ();
+
+ std::string mResourceGroup;
+
+ static OgreMaterialSerializer* sSerializer;
+
+ std::map <std::string, Ogre::GpuSharedParametersPtr> mSharedParameters;
+ };
+}
+
+/**
+ * @}
+ * @}
+ */
+
+#endif
diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp
new file mode 100644
index 0000000000..54dda3523f
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp
@@ -0,0 +1,41 @@
+#include "OgreTextureUnitState.hpp"
+
+#include "OgrePass.hpp"
+#include "OgrePlatform.hpp"
+#include "OgreMaterialSerializer.hpp"
+
+namespace sh
+{
+ OgreTextureUnitState::OgreTextureUnitState (OgrePass* parent, const std::string& name)
+ : TextureUnitState()
+ {
+ mTextureUnitState = parent->getOgrePass()->createTextureUnitState("");
+ mTextureUnitState->setName(name);
+ }
+
+ bool OgreTextureUnitState::setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context)
+ {
+ OgreMaterialSerializer& s = OgrePlatform::getSerializer();
+
+ if (name == "texture_alias")
+ {
+ // texture alias in this library refers to something else than in ogre
+ // delegate up
+ return TextureUnitState::setPropertyOverride (name, value, context);
+ }
+ else if (name == "direct_texture")
+ {
+ setTextureName (retrieveValue<StringValue>(value, context).get());
+ return true;
+ }
+ else if (name == "create_in_ffp")
+ return true; // handled elsewhere
+
+ return s.setTextureUnitProperty (name, retrieveValue<StringValue>(value, context).get(), mTextureUnitState);
+ }
+
+ void OgreTextureUnitState::setTextureName (const std::string& textureName)
+ {
+ mTextureUnitState->setTextureName(textureName);
+ }
+}
diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp
new file mode 100644
index 0000000000..0f1914f62e
--- /dev/null
+++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp
@@ -0,0 +1,27 @@
+#ifndef SH_OGRETEXTUREUNITSTATE_H
+#define SH_OGRETEXTUREUNITSTATE_H
+
+#include <OgreTextureUnitState.h>
+
+#include "../../Main/Platform.hpp"
+
+namespace sh
+{
+ class OgrePass;
+
+ class OgreTextureUnitState : public TextureUnitState
+ {
+ public:
+ OgreTextureUnitState (OgrePass* parent, const std::string& name);
+
+ virtual void setTextureName (const std::string& textureName);
+
+ private:
+ Ogre::TextureUnitState* mTextureUnitState;
+
+ protected:
+ virtual bool setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context);
+ };
+}
+
+#endif
diff --git a/extern/shiny/Readme.txt b/extern/shiny/Readme.txt
new file mode 100644
index 0000000000..6133219906
--- /dev/null
+++ b/extern/shiny/Readme.txt
@@ -0,0 +1,33 @@
+shiny - a shader and material management library for OGRE
+
+FEATURES
+
+- High-level layer on top of OGRE's material system. It allows you to generate multiple techniques for all your materials from a set of high-level per-material properties.
+
+- Several available Macros in shader source files. Just a few examples of the possibilities: binding OGRE auto constants, binding uniforms to material properties, foreach loops (repeat shader source a given number of times), retrieving per-material properties in an #if condition, automatic packing for vertex to fragment passthroughs. These macros allow you to generate even very complex shaders (for example the Ogre::Terrain shader) without assembling them in C++ code.
+
+- Integrated preprocessor (no, I didn't reinvent the wheel, I used boost::wave which turned out to be an excellent choice) that allows me to blend out macros that shouldn't be in use because e.g. the shader permutation doesn't need this specific feature.
+
+- User settings integration. They can be set by a C++ interface and retrieved through a macro in shader files.
+
+- Automatic handling of shader permutations, i.e. shaders are shared between materials in a smart way.
+
+- An optional "meta-language" (well, actually it's just a small header with some conditional defines) that you may use to compile the same shader source for different target languages. If you don't like it, you can still code in GLSL / CG etc separately. You can also switch between the languages at runtime.
+
+- On-demand material and shader creation. It uses Ogre's material listener to compile the shaders as soon as they are needed for rendering, and not earlier.
+
+- Shader changes are fully dynamic and real-time. Changing a user setting will recompile all shaders affected by this setting when they are next needed.
+
+- Serialization system that extends Ogre's material script system, it uses Ogre's script parser, but also adds some additional properties that are not available in Ogre's material system.
+
+- A concept called "Configuration" allowing you to create a different set of your shaders, doing the same thing except for some minor differences: the properties that are overridden by the active configuration. Possible uses for this are using simpler shaders (no shadows, no fog etc) when rendering for example realtime reflections or a minimap. You can easily switch between configurations by changing the active Ogre material scheme (for example on a viewport level).
+
+- Fixed function support. You can globally enable or disable shaders at any time, and for texture units you can specify if they're only needed for the shader-based path (e.g. normal maps) or if they should also be created in the fixed function path.
+
+LICENSE
+
+see License.txt
+
+AUTHOR
+
+scrawl <scrawl@baseoftrash.de>
diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt
new file mode 100644
index 0000000000..9b2325744e
--- /dev/null
+++ b/files/CMakeLists.txt
@@ -0,0 +1,54 @@
+project(resources)
+
+set(WATER_FILES
+ water_nm.png
+ circle.png
+)
+
+set(MATERIAL_FILES
+ atmosphere.shader
+ atmosphere.shaderset
+ clouds.shader
+ clouds.shaderset
+ core.h
+ moon.shader
+ moon.shaderset
+ objects.mat
+ objects.shader
+ objects.shaderset
+ openmw.configuration
+ quad.mat
+ quad.shader
+ quad.shaderset
+ shadowcaster.mat
+ shadowcaster.shader
+ shadowcaster.shaderset
+ shadows.h
+ sky.mat
+ stars.shader
+ stars.shaderset
+ sun.shader
+ sun.shaderset
+ terrain.shader
+ terrain.shaderset
+ underwater.h
+ water.mat
+ water.shader
+ water.shaderset
+ selection.mat
+ selection.shader
+ selection.shaderset
+ watersim_heightmap.shader
+ watersim_addimpulse.shader
+ watersim_heighttonormal.shader
+ watersim_common.h
+ watersim.mat
+ watersim.shaderset
+ mygui.mat
+ mygui.shader
+ mygui.shaderset
+)
+
+copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water "${OpenMW_BINARY_DIR}/resources/water/" "${WATER_FILES}")
+
+copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials "${OpenMW_BINARY_DIR}/resources/materials/" "${MATERIAL_FILES}")
diff --git a/files/launcher/icons/tango/document-new.png b/files/launcher/icons/tango/document-new.png
new file mode 100644
index 0000000000..4c3efdd6fa
--- /dev/null
+++ b/files/launcher/icons/tango/document-new.png
Binary files differ
diff --git a/files/launcher/icons/tango/edit-copy.png b/files/launcher/icons/tango/edit-copy.png
new file mode 100644
index 0000000000..8dd48c4949
--- /dev/null
+++ b/files/launcher/icons/tango/edit-copy.png
Binary files differ
diff --git a/files/launcher/icons/tango/edit-delete.png b/files/launcher/icons/tango/edit-delete.png
new file mode 100644
index 0000000000..d7667c36b4
--- /dev/null
+++ b/files/launcher/icons/tango/edit-delete.png
Binary files differ
diff --git a/files/launcher/icons/tango/go-bottom.png b/files/launcher/icons/tango/go-bottom.png
new file mode 100644
index 0000000000..2c5a80385c
--- /dev/null
+++ b/files/launcher/icons/tango/go-bottom.png
Binary files differ
diff --git a/files/launcher/icons/tango/go-down.png b/files/launcher/icons/tango/go-down.png
new file mode 100644
index 0000000000..3dd7fccdf0
--- /dev/null
+++ b/files/launcher/icons/tango/go-down.png
Binary files differ
diff --git a/files/launcher/icons/tango/go-top.png b/files/launcher/icons/tango/go-top.png
new file mode 100644
index 0000000000..70f2c996cd
--- /dev/null
+++ b/files/launcher/icons/tango/go-top.png
Binary files differ
diff --git a/files/launcher/icons/tango/go-up.png b/files/launcher/icons/tango/go-up.png
new file mode 100644
index 0000000000..fa9a7d71b5
--- /dev/null
+++ b/files/launcher/icons/tango/go-up.png
Binary files differ
diff --git a/files/launcher/icons/tango/index.theme b/files/launcher/icons/tango/index.theme
new file mode 100644
index 0000000000..1f54489ebb
--- /dev/null
+++ b/files/launcher/icons/tango/index.theme
@@ -0,0 +1,8 @@
+[Icon Theme]
+Name=Tango
+Comment=Tango Theme
+Inherits=default
+Directories=16x16
+
+[16x16]
+Size=16 \ No newline at end of file
diff --git a/files/launcher/icons/tango/video-display.png b/files/launcher/icons/tango/video-display.png
new file mode 100644
index 0000000000..1331436846
--- /dev/null
+++ b/files/launcher/icons/tango/video-display.png
Binary files differ
diff --git a/files/launcher/images/clear.png b/files/launcher/images/clear.png
new file mode 100644
index 0000000000..0e72a65ea8
--- /dev/null
+++ b/files/launcher/images/clear.png
Binary files differ
diff --git a/files/launcher/images/down.png b/files/launcher/images/down.png
new file mode 100644
index 0000000000..f529cda7de
--- /dev/null
+++ b/files/launcher/images/down.png
Binary files differ
diff --git a/files/launcher/images/openmw-header.png b/files/launcher/images/openmw-header.png
new file mode 100644
index 0000000000..82223e7fad
--- /dev/null
+++ b/files/launcher/images/openmw-header.png
Binary files differ
diff --git a/files/launcher/images/openmw-plugin.png b/files/launcher/images/openmw-plugin.png
new file mode 100644
index 0000000000..c34cba5543
--- /dev/null
+++ b/files/launcher/images/openmw-plugin.png
Binary files differ
diff --git a/files/launcher/images/openmw.ico b/files/launcher/images/openmw.ico
new file mode 100644
index 0000000000..aa248e6ad5
--- /dev/null
+++ b/files/launcher/images/openmw.ico
Binary files differ
diff --git a/files/launcher/images/openmw.png b/files/launcher/images/openmw.png
new file mode 100644
index 0000000000..7a5393821f
--- /dev/null
+++ b/files/launcher/images/openmw.png
Binary files differ
diff --git a/files/launcher/images/playpage-background.png b/files/launcher/images/playpage-background.png
new file mode 100644
index 0000000000..ccd36d0291
--- /dev/null
+++ b/files/launcher/images/playpage-background.png
Binary files differ
diff --git a/files/launcher/launcher.qrc b/files/launcher/launcher.qrc
new file mode 100644
index 0000000000..19b1c5a6f4
--- /dev/null
+++ b/files/launcher/launcher.qrc
@@ -0,0 +1,21 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="images">
+ <file alias="clear.png">images/clear.png</file>
+ <file alias="down.png">images/down.png</file>
+ <file alias="openmw.png">images/openmw.png</file>
+ <file alias="openmw-plugin.png">images/openmw-plugin.png</file>
+ <file alias="openmw-header.png">images/openmw-header.png</file>
+ <file alias="playpage-background.png">images/playpage-background.png</file>
+</qresource>
+<qresource prefix="icons/tango">
+ <file alias="index.theme">icons/tango/index.theme</file>
+ <file alias="video-display.png">icons/tango/video-display.png</file>
+ <file alias="16x16/document-new.png">icons/tango/document-new.png</file>
+ <file alias="16x16/edit-copy.png">icons/tango/edit-copy.png</file>
+ <file alias="16x16/edit-delete.png">icons/tango/edit-delete.png</file>
+ <file alias="16x16/go-bottom.png">icons/tango/go-bottom.png</file>
+ <file alias="16x16/go-down.png">icons/tango/go-down.png</file>
+ <file alias="16x16/go-top.png">icons/tango/go-top.png</file>
+ <file alias="16x16/go-up.png">icons/tango/go-up.png</file>
+</qresource>
+</RCC>
diff --git a/files/launcher/launcher.rc b/files/launcher/launcher.rc
new file mode 100644
index 0000000000..df2121f469
--- /dev/null
+++ b/files/launcher/launcher.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "images/openmw.ico"
diff --git a/files/mac/Info.plist b/files/mac/Info.plist
new file mode 100644
index 0000000000..bc7efd0754
--- /dev/null
+++ b/files/mac/Info.plist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>OpenMW.icns</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>omwlauncher</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleLongVersionString</key>
+ <string></string>
+ <key>CFBundleName</key>
+ <string>OpenMW</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>${OPENMW_VERSION}</string>
+ <key>CSResourcesFileMapped</key>
+ <true/>
+ <key>LSRequiresCarbon</key>
+ <true/>
+ <key>NSHumanReadableCopyright</key>
+ <string></string>
+</dict>
+</plist>
diff --git a/files/mac/openmw.icns b/files/mac/openmw.icns
new file mode 100644
index 0000000000..3ff899a799
--- /dev/null
+++ b/files/mac/openmw.icns
Binary files differ
diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader
new file mode 100644
index 0000000000..f02c5766fe
--- /dev/null
+++ b/files/materials/atmosphere.shader
@@ -0,0 +1,39 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
+ shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
+
+ shOutput(float, alphaFade)
+
+ SH_START_PROGRAM
+ {
+ float4x4 viewFixed = view;
+#if !SH_GLSL
+ viewFixed[0][3] = 0;
+ viewFixed[1][3] = 0;
+ viewFixed[2][3] = 0;
+#else
+ viewFixed[3][0] = 0;
+ viewFixed[3][1] = 0;
+ viewFixed[3][2] = 0;
+#endif
+ shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition));
+ alphaFade = shInputPosition.z < 150.0 ? 0.0 : 1.0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shInput(float, alphaFade)
+ shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour)
+ shUniform(float4, horizonColour) @shSharedParameter(horizonColour, horizonColour)
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = alphaFade * atmosphereColour + (1.f - alphaFade) * horizonColour;
+ }
+
+#endif
diff --git a/files/materials/atmosphere.shaderset b/files/materials/atmosphere.shaderset
new file mode 100644
index 0000000000..54108dbba6
--- /dev/null
+++ b/files/materials/atmosphere.shaderset
@@ -0,0 +1,15 @@
+shader_set atmosphere_vertex
+{
+ source atmosphere.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set atmosphere_fragment
+{
+ source atmosphere.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader
new file mode 100644
index 0000000000..b76f2ded72
--- /dev/null
+++ b/files/materials/clouds.shader
@@ -0,0 +1,53 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
+shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+ shOutput(float, alphaFade)
+
+ SH_START_PROGRAM
+ {
+ float4x4 viewFixed = view;
+#if !SH_GLSL
+ viewFixed[0][3] = 0;
+ viewFixed[1][3] = 0;
+ viewFixed[2][3] = 0;
+#else
+ viewFixed[3][0] = 0;
+ viewFixed[3][1] = 0;
+ viewFixed[3][2] = 0;
+#endif
+ shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition));
+ UV = uv0;
+ alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shInput(float, alphaFade)
+
+ shSampler2D(diffuseMap1)
+ shSampler2D(diffuseMap2)
+
+ shUniform(float, cloudBlendFactor) @shSharedParameter(cloudBlendFactor)
+ shUniform(float, cloudAnimationTimer) @shSharedParameter(cloudAnimationTimer)
+ shUniform(float, cloudOpacity) @shSharedParameter(cloudOpacity)
+ shUniform(float3, cloudColour) @shSharedParameter(cloudColour)
+
+ SH_START_PROGRAM
+ {
+ // Scroll in y direction
+ float2 scrolledUV = UV + float2(0,1) * cloudAnimationTimer * 0.003;
+
+ float4 albedo = shSample(diffuseMap1, scrolledUV) * (1-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor;
+
+ shOutputColour(0) = float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity * alphaFade);
+ }
+
+#endif
diff --git a/files/materials/clouds.shaderset b/files/materials/clouds.shaderset
new file mode 100644
index 0000000000..5fffb5658d
--- /dev/null
+++ b/files/materials/clouds.shaderset
@@ -0,0 +1,15 @@
+shader_set clouds_vertex
+{
+ source clouds.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set clouds_fragment
+{
+ source clouds.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/core.h b/files/materials/core.h
new file mode 100644
index 0000000000..6f8179c08d
--- /dev/null
+++ b/files/materials/core.h
@@ -0,0 +1,181 @@
+#if SH_HLSL == 1 || SH_CG == 1
+
+ #define shTexture2D sampler2D
+ #define shSample(tex, coord) tex2D(tex, coord)
+ #define shCubicSample(tex, coord) texCUBE(tex, coord)
+ #define shLerp(a, b, t) lerp(a, b, t)
+ #define shSaturate(a) saturate(a)
+
+ #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name)
+
+ #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name)
+
+ #define shMatrixMult(m, v) mul(m, v)
+
+ #define shUniform(type, name) , uniform type name
+
+ #define shTangentInput(type) , in type tangent : TANGENT
+ #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1)
+ #define shInput(type, name) , in type name : TEXCOORD@shCounter(1)
+ #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2)
+
+ #define shNormalInput(type) , in type normal : NORMAL
+
+ #define shColourInput(type) , in type colour : COLOR
+
+ #define shFract(val) frac(val)
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define shOutputPosition oPosition
+ #define shInputPosition iPosition
+
+
+ #define SH_BEGIN_PROGRAM \
+ void main( \
+ float4 iPosition : POSITION \
+ , out float4 oPosition : POSITION
+
+ #define SH_START_PROGRAM \
+ ) \
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shOutputColour(num) oColor##num
+
+ #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num
+
+ #define SH_BEGIN_PROGRAM \
+ void main( \
+ out float4 oColor0 : COLOR
+
+ #define SH_START_PROGRAM \
+ ) \
+
+ #endif
+
+#endif
+
+#if SH_GLSL == 1 || SH_GLSLES == 1
+
+ #define shFract(val) fract(val)
+
+#if SH_GLSLES == 1
+ @version 100
+#else
+ @version 120
+#endif
+
+#if SH_GLSLES == 1 && SH_FRAGMENT_SHADER
+precision mediump int;
+precision mediump float;
+#endif
+
+ #define float2 vec2
+ #define float3 vec3
+ #define float4 vec4
+ #define int2 ivec2
+ #define int3 ivec3
+ #define int4 ivec4
+ #define shTexture2D sampler2D
+ #define shSample(tex, coord) texture2D(tex, coord)
+ #define shCubicSample(tex, coord) textureCube(tex, coord)
+ #define shLerp(a, b, t) mix(a, b, t)
+ #define shSaturate(a) clamp(a, 0.0, 1.0)
+
+ #define shUniform(type, name) uniform type name;
+
+ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name)
+
+ #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name)
+
+ #define shMatrixMult(m, v) (m * v)
+
+ #define shOutputPosition gl_Position
+
+ #define float4x4 mat4
+ #define float3x3 mat3
+
+ // GLSL 1.3
+ #if 0
+
+ // automatically recognized by ogre when the input name equals this
+ #define shInputPosition vertex
+
+ #define shOutputColour(num) oColor##num
+
+ #define shTangentInput(type) in type tangent;
+ #define shVertexInput(type, name) in type name;
+ #define shInput(type, name) in type name;
+ #define shOutput(type, name) out type name;
+
+ // automatically recognized by ogre when the input name equals this
+ #define shNormalInput(type) in type normal;
+ #define shColourInput(type) in type colour;
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define SH_BEGIN_PROGRAM \
+ in float4 vertex;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shDeclareMrtOutput(num) out vec4 oColor##num;
+
+ #define SH_BEGIN_PROGRAM \
+ out float4 oColor0;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+
+ #endif
+
+ #endif
+
+ // GLSL 1.2
+
+ #if 1
+
+ // automatically recognized by ogre when the input name equals this
+ #define shInputPosition vertex
+
+ #define shOutputColour(num) gl_FragData[num]
+
+ #define shTangentInput(type) attribute type tangent;
+ #define shVertexInput(type, name) attribute type name;
+ #define shInput(type, name) varying type name;
+ #define shOutput(type, name) varying type name;
+
+ // automatically recognized by ogre when the input name equals this
+ #define shNormalInput(type) attribute type normal;
+ #define shColourInput(type) attribute type colour;
+
+ #ifdef SH_VERTEX_SHADER
+
+ #define SH_BEGIN_PROGRAM \
+ attribute vec4 vertex;
+ #define SH_START_PROGRAM \
+ void main(void)
+
+ #endif
+
+ #ifdef SH_FRAGMENT_SHADER
+
+ #define shDeclareMrtOutput(num)
+
+ #define SH_BEGIN_PROGRAM
+
+ #define SH_START_PROGRAM \
+ void main(void)
+
+
+ #endif
+
+ #endif
+#endif
diff --git a/files/materials/moon.shader b/files/materials/moon.shader
new file mode 100644
index 0000000000..a7d183d108
--- /dev/null
+++ b/files/materials/moon.shader
@@ -0,0 +1,53 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, world) @shAutoConstant(world, world_matrix)
+ shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
+shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+
+ SH_START_PROGRAM
+ {
+ float4x4 viewFixed = view;
+#if !SH_GLSL
+ viewFixed[0][3] = 0;
+ viewFixed[1][3] = 0;
+ viewFixed[2][3] = 0;
+#else
+ viewFixed[3][0] = 0;
+ viewFixed[3][1] = 0;
+ viewFixed[3][2] = 0;
+#endif
+ shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shMatrixMult(world, shInputPosition)));
+ UV = uv0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shSampler2D(diffuseMap)
+ shSampler2D(alphaMap)
+ shInput(float2, UV)
+ shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour)
+ shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour)
+
+ shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour)
+
+ SH_START_PROGRAM
+ {
+
+ float4 tex = shSample(diffuseMap, UV);
+
+ shOutputColour(0) = float4(materialEmissive.xyz, 1) * tex;
+
+ shOutputColour(0).a = shSample(alphaMap, UV).a * materialDiffuse.a;
+
+ shOutputColour(0).rgb += (1-tex.a) * shOutputColour(0).a * atmosphereColour.rgb; //fill dark side of moon with atmosphereColour
+ shOutputColour(0).rgb += (1-materialDiffuse.a) * atmosphereColour.rgb; //fade bump
+
+ }
+
+#endif
diff --git a/files/materials/moon.shaderset b/files/materials/moon.shaderset
new file mode 100644
index 0000000000..659481a961
--- /dev/null
+++ b/files/materials/moon.shaderset
@@ -0,0 +1,15 @@
+shader_set moon_vertex
+{
+ source moon.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set moon_fragment
+{
+ source moon.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/mygui.mat b/files/materials/mygui.mat
new file mode 100644
index 0000000000..78cba3f897
--- /dev/null
+++ b/files/materials/mygui.mat
@@ -0,0 +1,25 @@
+material MyGUI/NoTexture
+{
+ pass
+ {
+ vertex_program mygui_vertex
+ fragment_program mygui_fragment
+ shader_properties
+ {
+ has_texture false
+ }
+ }
+}
+
+material MyGUI/OneTexture
+{
+ pass
+ {
+ vertex_program mygui_vertex
+ fragment_program mygui_fragment
+ shader_properties
+ {
+ has_texture true
+ }
+ }
+}
diff --git a/files/materials/mygui.shader b/files/materials/mygui.shader
new file mode 100644
index 0000000000..014558d972
--- /dev/null
+++ b/files/materials/mygui.shader
@@ -0,0 +1,45 @@
+#include "core.h"
+
+#define TEXTURE @shPropertyBool(has_texture)
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+#if TEXTURE
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+#endif
+ shColourInput(float4)
+ shOutput(float4, colourPassthrough)
+
+ SH_START_PROGRAM
+ {
+ shOutputPosition = float4(shInputPosition.xyz, 1.f);
+#if TEXTURE
+ UV.xy = uv0;
+#endif
+ colourPassthrough = colour;
+ }
+
+#else
+
+
+ SH_BEGIN_PROGRAM
+
+#if TEXTURE
+ shSampler2D(diffuseMap)
+ shInput(float2, UV)
+#endif
+
+ shInput(float4, colourPassthrough)
+
+ SH_START_PROGRAM
+ {
+#if TEXTURE
+ shOutputColour(0) = shSample(diffuseMap, UV.xy) * colourPassthrough;
+#else
+ shOutputColour(0) = colourPassthrough;
+#endif
+ }
+
+#endif
diff --git a/files/materials/mygui.shaderset b/files/materials/mygui.shaderset
new file mode 100644
index 0000000000..980cd4caf4
--- /dev/null
+++ b/files/materials/mygui.shaderset
@@ -0,0 +1,15 @@
+shader_set mygui_vertex
+{
+ source mygui.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_3_0 vs_2_0
+}
+
+shader_set mygui_fragment
+{
+ source mygui.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
diff --git a/files/materials/objects.mat b/files/materials/objects.mat
new file mode 100644
index 0000000000..8f8734d629
--- /dev/null
+++ b/files/materials/objects.mat
@@ -0,0 +1,98 @@
+material openmw_objects_base
+{
+ diffuse 1.0 1.0 1.0 1.0
+ specular 0 0 0 0
+ ambient 1.0 1.0 1.0
+ emissive 0.0 0.0 0.0
+ vertmode 0
+ diffuseMap black.png
+ normalMap
+ emissiveMap
+ use_emissive_map false
+ use_detail_map false
+ emissiveMapUVSet 0
+ detailMapUVSet 0
+
+ scene_blend default
+ depth_write default
+ depth_check default
+ alpha_rejection default
+ transparent_sorting default
+ polygon_mode default
+
+ pass
+ {
+ vertex_program openmw_objects_vertex
+ fragment_program openmw_objects_fragment
+
+ shader_properties
+ {
+ vertexcolor_mode $vertmode
+ normalMap $normalMap
+ emissiveMapUVSet $emissiveMapUVSet
+ detailMapUVSet $detailMapUVSet
+ emissiveMap $emissiveMap
+ detailMap $detailMap
+ }
+
+ diffuse $diffuse
+ specular $specular
+ ambient $ambient
+ emissive $emissive
+ scene_blend $scene_blend
+ alpha_rejection $alpha_rejection
+ depth_write $depth_write
+ depth_check $depth_check
+ transparent_sorting $transparent_sorting
+ polygon_mode $polygon_mode
+
+ texture_unit diffuseMap
+ {
+ direct_texture $diffuseMap
+ create_in_ffp true
+ tex_coord_set $emissiveMapUVSet
+ }
+
+ texture_unit normalMap
+ {
+ direct_texture $normalMap
+ // force automips here for now
+ num_mipmaps 4
+ }
+
+ texture_unit emissiveMap
+ {
+ create_in_ffp $use_emissive_map
+ colour_op add
+ direct_texture $emissiveMap
+ tex_coord_set $emissiveMapUVSet
+ }
+
+ texture_unit detailMap
+ {
+ create_in_ffp $use_detail_map
+ colour_op_ex modulate_x2 src_current src_texture
+ direct_texture $detailMap
+ tex_coord_set $detailMapUVSet
+ }
+
+ texture_unit shadowMap0
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ texture_unit shadowMap1
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ texture_unit shadowMap2
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ }
+}
diff --git a/files/materials/objects.shader b/files/materials/objects.shader
new file mode 100644
index 0000000000..36f92bfd91
--- /dev/null
+++ b/files/materials/objects.shader
@@ -0,0 +1,447 @@
+#include "core.h"
+
+
+#define FOG @shGlobalSettingBool(fog)
+
+#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm)
+#define SHADOWS @shGlobalSettingBool(shadows)
+
+#if SHADOWS || SHADOWS_PSSM
+ #include "shadows.h"
+#endif
+
+#if FOG || SHADOWS_PSSM
+#define NEED_DEPTH
+#endif
+
+#define NORMAL_MAP @shPropertyHasValue(normalMap)
+#define EMISSIVE_MAP @shPropertyHasValue(emissiveMap)
+#define DETAIL_MAP @shPropertyHasValue(detailMap)
+
+// 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))
+
+// if normal mapping is enabled, we force pixel lighting
+#define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap))
+
+#define UNDERWATER @shGlobalSettingBool(render_refraction)
+
+#define VERTEXCOLOR_MODE @shPropertyString(vertexcolor_mode)
+
+#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
+
+#ifdef SH_VERTEX_SHADER
+
+ // ------------------------------------- VERTEX ---------------------------------------
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+
+ shUniform(float4x4, textureMatrix0) @shAutoConstant(textureMatrix0, texture_matrix, 0)
+
+#if (VIEWPROJ_FIX) || (SHADOWS)
+ shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
+#endif
+
+#if VIEWPROJ_FIX
+ shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix)
+ shUniform(float4x4, vpMatrix) @shAutoConstant(vpMatrix, viewproj_matrix)
+#endif
+
+ shVertexInput(float2, uv0)
+#if SECOND_UV_SET
+ shVertexInput(float2, uv1)
+#endif
+ shOutput(float4, UV)
+
+ shNormalInput(float4)
+
+#if NORMAL_MAP
+ shTangentInput(float4)
+ shOutput(float3, tangentPassthrough)
+#endif
+
+#if !VERTEX_LIGHTING
+ shOutput(float3, normalPassthrough)
+#endif
+
+#ifdef NEED_DEPTH
+ shOutput(float, depthPassthrough)
+#endif
+
+ shOutput(float3, objSpacePositionPassthrough)
+
+#if VERTEXCOLOR_MODE != 0
+ shColourInput(float4)
+#endif
+
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ shOutput(float4, colourPassthrough)
+#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
+
+#endif
+
+#if SHADOWS
+ shOutput(float4, lightSpacePos0)
+ shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
+#endif
+
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shOutput(float4, lightSpacePos@shIterator)
+ shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator)
+ @shEndForeach
+#if !VIEWPROJ_FIX
+ shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
+#endif
+#endif
+
+#if VERTEX_LIGHTING
+ shOutput(float4, lightResult)
+ shOutput(float3, directionalResult)
+#endif
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+
+ UV.xy = shMatrixMult (textureMatrix0, float4(uv0,0,1)).xy;
+#if SECOND_UV_SET
+ UV.zw = uv1;
+#endif
+
+#if NORMAL_MAP
+ tangentPassthrough = tangent.xyz;
+#endif
+#if !VERTEX_LIGHTING
+ normalPassthrough = normal.xyz;
+#endif
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ colourPassthrough = colour;
+#endif
+
+#ifdef NEED_DEPTH
+
+
+#if VIEWPROJ_FIX
+ float4x4 vpFixed = vpMatrix;
+#if !SH_GLSL
+ vpFixed[2] = vpRow2Fix;
+#else
+ vpFixed[0][2] = vpRow2Fix.x;
+ vpFixed[1][2] = vpRow2Fix.y;
+ vpFixed[2][2] = vpRow2Fix.z;
+ vpFixed[3][2] = vpRow2Fix.w;
+#endif
+
+ float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix);
+
+ depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z;
+#else
+ depthPassthrough = shOutputPosition.z;
+#endif
+
+#endif
+
+ objSpacePositionPassthrough = shInputPosition.xyz;
+
+#if SHADOWS
+ lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
+#endif
+#if SHADOWS_PSSM
+ float4 wPos = shMatrixMult(worldMatrix, shInputPosition);
+ @shForeach(3)
+ lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos);
+ @shEndForeach
+#endif
+
+
+#if VERTEX_LIGHTING
+ float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz;
+ float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
+
+ float3 lightDir;
+ float d;
+ lightResult = float4(0,0,0,1);
+ @shForeach(@shGlobalSettingString(num_lights))
+ lightDir = lightPosition[@shIterator].xyz - (viewPos * lightPosition[@shIterator].w);
+ d = length(lightDir);
+ lightDir = normalize(lightDir);
+
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += colour.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#else
+ lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#endif
+
+#if @shIterator == 0
+ directionalResult = lightResult.xyz;
+#endif
+
+ @shEndForeach
+
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += lightAmbient.xyz * colour.xyz + materialEmissive.xyz;
+ lightResult.a *= colour.a;
+#endif
+#if VERTEXCOLOR_MODE == 1
+ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colour.xyz;
+#endif
+#if VERTEXCOLOR_MODE == 0
+ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz;
+#endif
+
+#if VERTEXCOLOR_MODE != 2
+ lightResult.a *= materialDiffuse.a;
+#endif
+
+#endif
+ }
+
+#else
+
+ // ----------------------------------- FRAGMENT ------------------------------------------
+
+#if UNDERWATER
+ #include "underwater.h"
+#endif
+
+ SH_BEGIN_PROGRAM
+ shSampler2D(diffuseMap)
+
+#if NORMAL_MAP
+ shSampler2D(normalMap)
+#endif
+
+#if EMISSIVE_MAP
+ shSampler2D(emissiveMap)
+#endif
+
+#if DETAIL_MAP
+ shSampler2D(detailMap)
+#endif
+
+ shInput(float4, UV)
+
+#if NORMAL_MAP
+ shInput(float3, tangentPassthrough)
+#endif
+#if !VERTEX_LIGHTING
+ shInput(float3, normalPassthrough)
+#endif
+
+#ifdef NEED_DEPTH
+ shInput(float, depthPassthrough)
+#endif
+
+ shInput(float3, objSpacePositionPassthrough)
+
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ shInput(float4, colourPassthrough)
+#endif
+
+#if FOG
+ shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour)
+ shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params)
+#endif
+
+#if SHADOWS
+ shInput(float4, lightSpacePos0)
+ shSampler2D(shadowMap0)
+ shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1)
+#endif
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shInput(float4, lightSpacePos@shIterator)
+ shSampler2D(shadowMap@shIterator)
+ shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1))
+ @shEndForeach
+ shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints)
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart)
+#endif
+
+#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)
+ shUniform(float, waterEnabled) @shSharedParameter(waterEnabled)
+#endif
+
+#if VERTEX_LIGHTING
+ 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))
+ 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
+#endif
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = shSample(diffuseMap, UV.xy);
+
+#if DETAIL_MAP
+#if @shPropertyString(detailMapUVSet)
+ shOutputColour(0) *= shSample(detailMap, UV.zw)*2;
+#else
+ shOutputColour(0) *= shSample(detailMap, UV.xy)*2;
+#endif
+#endif
+
+#if NORMAL_MAP
+ float3 normal = normalPassthrough;
+ float3 binormal = cross(tangentPassthrough.xyz, normal.xyz);
+ float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz);
+
+ #if SH_GLSL
+ tbn = transpose(tbn);
+ #endif
+
+ float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1;
+
+ normal = normalize (shMatrixMult( transpose(tbn), TSnormal ));
+#endif
+
+#if !VERTEX_LIGHTING
+ float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz;
+ float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
+
+ float3 lightDir;
+ float d;
+ float4 lightResult = float4(0,0,0,1);
+ @shForeach(@shGlobalSettingString(num_lights))
+ lightDir = lightPosition[@shIterator].xyz - (viewPos * lightPosition[@shIterator].w);
+ d = length(lightDir);
+ lightDir = normalize(lightDir);
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += colourPassthrough.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#else
+ lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#endif
+
+#if @shIterator == 0
+ float3 directionalResult = lightResult.xyz;
+#endif
+
+ @shEndForeach
+
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += lightAmbient.xyz * colourPassthrough.xyz + materialEmissive.xyz;
+ lightResult.a *= colourPassthrough.a;
+#endif
+#if VERTEXCOLOR_MODE == 1
+ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colourPassthrough.xyz;
+#endif
+#if VERTEXCOLOR_MODE == 0
+ 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
+ float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0);
+#endif
+#if SHADOWS_PSSM
+ float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints);
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y;
+ float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange);
+ shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1.0 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow);
+#endif
+
+#if !SHADOWS && !SHADOWS_PSSM
+ float shadow = 1.0;
+#endif
+
+
+
+#if (UNDERWATER) || (FOG)
+ float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz;
+#endif
+
+#if UNDERWATER
+ float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel);
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ shOutputColour(0) *= (lightResult - float4(directionalResult * (1.0-shadow),0));
+#else
+ shOutputColour(0) *= lightResult;
+#endif
+
+#if FOG
+ float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w);
+
+
+#if UNDERWATER
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY));
+#else
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue);
+#endif
+
+#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));
+ }
+
+#endif
diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset
new file mode 100644
index 0000000000..028c15ce8a
--- /dev/null
+++ b/files/materials/objects.shaderset
@@ -0,0 +1,15 @@
+shader_set openmw_objects_vertex
+{
+ source objects.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_3_0 vs_2_0
+}
+
+shader_set openmw_objects_fragment
+{
+ source objects.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration
new file mode 100644
index 0000000000..b953a91311
--- /dev/null
+++ b/files/materials/openmw.configuration
@@ -0,0 +1,20 @@
+configuration water_reflection
+{
+ shadows false
+ shadows_pssm false
+ viewproj_fix true
+}
+
+configuration water_refraction
+{
+ viewproj_fix true
+ render_refraction true
+}
+
+configuration local_map
+{
+ fog false
+ shadows false
+ shadows_pssm false
+ simple_water true
+}
diff --git a/files/materials/quad.mat b/files/materials/quad.mat
new file mode 100644
index 0000000000..77a2c0c340
--- /dev/null
+++ b/files/materials/quad.mat
@@ -0,0 +1,22 @@
+material quad
+{
+ depth_write on
+
+ pass
+ {
+ vertex_program transform_vertex
+ fragment_program quad_fragment
+
+ depth_write $depth_write
+
+ texture_unit SceneBuffer
+ {
+ }
+ }
+}
+
+material quad_noDepthWrite
+{
+ parent quad
+ depth_write off
+}
diff --git a/files/materials/quad.shader b/files/materials/quad.shader
new file mode 100644
index 0000000000..4620588c3c
--- /dev/null
+++ b/files/materials/quad.shader
@@ -0,0 +1,25 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+ UV = uv0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shSampler2D(SceneBuffer)
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = shSample(SceneBuffer, UV);
+ }
+
+#endif
diff --git a/files/materials/quad.shaderset b/files/materials/quad.shaderset
new file mode 100644
index 0000000000..71fd82da44
--- /dev/null
+++ b/files/materials/quad.shaderset
@@ -0,0 +1,15 @@
+shader_set transform_vertex
+{
+ source quad.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set quad_fragment
+{
+ source quad.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/selection.mat b/files/materials/selection.mat
new file mode 100644
index 0000000000..2cb92f8843
--- /dev/null
+++ b/files/materials/selection.mat
@@ -0,0 +1,9 @@
+material SelectionColour
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program selection_vertex
+ fragment_program selection_fragment
+ }
+}
diff --git a/files/materials/selection.shader b/files/materials/selection.shader
new file mode 100644
index 0000000000..095a31259d
--- /dev/null
+++ b/files/materials/selection.shader
@@ -0,0 +1,24 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4, colour) @shAutoConstant(colour, custom, 1)
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = colour;
+ //shOutputColour(0) = float4(1,0,0,1);
+ }
+
+#endif
diff --git a/files/materials/selection.shaderset b/files/materials/selection.shaderset
new file mode 100644
index 0000000000..c90826282e
--- /dev/null
+++ b/files/materials/selection.shaderset
@@ -0,0 +1,15 @@
+shader_set selection_vertex
+{
+ source selection.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set selection_fragment
+{
+ source selection.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/shadowcaster.mat b/files/materials/shadowcaster.mat
new file mode 100644
index 0000000000..5c5c8e088d
--- /dev/null
+++ b/files/materials/shadowcaster.mat
@@ -0,0 +1,35 @@
+material openmw_shadowcaster_default
+{
+ create_configuration Default
+ allow_fixed_function false
+ pass
+ {
+ fog_override true
+
+ vertex_program openmw_shadowcaster_vertex
+ fragment_program openmw_shadowcaster_fragment
+
+ shader_properties
+ {
+ shadow_transparency true
+ }
+ }
+}
+
+material openmw_shadowcaster_noalpha
+{
+ create_configuration Default
+ allow_fixed_function false
+ pass
+ {
+ fog_override true
+
+ vertex_program openmw_shadowcaster_vertex
+ fragment_program openmw_shadowcaster_fragment
+
+ shader_properties
+ {
+ shadow_transparency false
+ }
+ }
+}
diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader
new file mode 100644
index 0000000000..b992366a7e
--- /dev/null
+++ b/files/materials/shadowcaster.shader
@@ -0,0 +1,55 @@
+#include "core.h"
+
+#define ALPHA @shPropertyBool(shadow_transparency)
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+#if ALPHA
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+#endif
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+ shOutput(float2, depth)
+ SH_START_PROGRAM
+ {
+ // this is the view space position
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+
+ // depth info for the fragment.
+ depth.x = shOutputPosition.z;
+ depth.y = shOutputPosition.w;
+
+ // clamp z to zero. seem to do the trick. :-/
+ shOutputPosition.z = max(shOutputPosition.z, 0);
+
+#if ALPHA
+ UV = uv0;
+#endif
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+#if ALPHA
+ shInput(float2, UV)
+ shSampler2D(texture1)
+#endif
+ shInput(float2, depth)
+ SH_START_PROGRAM
+ {
+ float finalDepth = depth.x / depth.y;
+
+
+#if ALPHA
+ // use alpha channel of the first texture
+ float alpha = shSample(texture1, UV).a;
+
+ if (alpha < 0.5)
+ discard;
+#endif
+
+ shOutputColour(0) = float4(finalDepth, finalDepth, finalDepth, 1);
+ }
+
+#endif
diff --git a/files/materials/shadowcaster.shaderset b/files/materials/shadowcaster.shaderset
new file mode 100644
index 0000000000..5f4990ed11
--- /dev/null
+++ b/files/materials/shadowcaster.shaderset
@@ -0,0 +1,15 @@
+shader_set openmw_shadowcaster_vertex
+{
+ source shadowcaster.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set openmw_shadowcaster_fragment
+{
+ source shadowcaster.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/shadows.h b/files/materials/shadows.h
new file mode 100644
index 0000000000..65dffe4923
--- /dev/null
+++ b/files/materials/shadows.h
@@ -0,0 +1,51 @@
+
+#define FIXED_BIAS 0.0003
+
+float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset)
+{
+ shadowMapPos /= shadowMapPos.w;
+ float3 o = float3(offset.xy, -offset.x) * 0.3;
+ //float3 o = float3(0,0,0);
+ float c = (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left
+ c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right
+ c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left
+ c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right
+ return c / 4;
+}
+
+
+
+float pssmDepthShadow (
+
+
+ float4 lightSpacePos0,
+ float2 invShadowmapSize0,
+ shTexture2D shadowMap0,
+
+ float4 lightSpacePos1,
+ float2 invShadowmapSize1,
+ shTexture2D shadowMap1,
+
+ float4 lightSpacePos2,
+ float2 invShadowmapSize2,
+ shTexture2D shadowMap2,
+
+ float depth,
+ float3 pssmSplitPoints)
+
+{
+ float shadow;
+
+ float pcf1 = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0);
+ float pcf2 = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1);
+ float pcf3 = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2);
+
+ if (depth < pssmSplitPoints.x)
+ shadow = pcf1;
+ else if (depth < pssmSplitPoints.y)
+ shadow = pcf2;
+ else
+ shadow = pcf3;
+
+ return shadow;
+}
diff --git a/files/materials/sky.mat b/files/materials/sky.mat
new file mode 100644
index 0000000000..ccf2a8053c
--- /dev/null
+++ b/files/materials/sky.mat
@@ -0,0 +1,140 @@
+material QueryTotalPixels
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program sun_vertex
+ fragment_program sun_fragment
+ cull_hardware none
+ polygon_mode_overrideable off
+ depth_check off
+ depth_write off
+ colour_write off
+ }
+}
+
+material QueryVisiblePixels
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program sun_vertex
+ fragment_program sun_fragment
+ cull_hardware none
+ cull_software none
+ polygon_mode_overrideable off
+ depth_check on
+ depth_write off
+ colour_write off
+ }
+}
+
+material openmw_moon
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program moon_vertex
+ fragment_program moon_fragment
+ cull_hardware none
+
+ polygon_mode_overrideable off
+ depth_write off
+ depth_check off
+ scene_blend alpha_blend
+
+ texture_unit diffuseMap
+ {
+ texture_alias $texture
+ }
+
+ texture_unit alphaMap
+ {
+ direct_texture textures\tx_secunda_full.dds
+ }
+ }
+}
+
+material openmw_clouds
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program clouds_vertex
+ fragment_program clouds_fragment
+
+ polygon_mode_overrideable off
+
+ scene_blend alpha_blend
+ depth_write off
+
+ // second diffuse map is used for weather transitions
+ texture_unit diffuseMap1
+ {
+ texture_alias cloud_texture_1
+ }
+
+ texture_unit diffuseMap2
+ {
+ texture_alias cloud_texture_2
+ }
+ }
+}
+
+material openmw_atmosphere
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program atmosphere_vertex
+ fragment_program atmosphere_fragment
+
+ polygon_mode_overrideable off
+
+ depth_write off
+ }
+}
+
+material openmw_stars
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program stars_vertex
+ fragment_program stars_fragment
+
+ polygon_mode_overrideable off
+
+ depth_check off
+ depth_write off
+ scene_blend alpha_blend
+
+ texture_unit diffuseMap
+ {
+ direct_texture $texture
+ }
+ }
+}
+
+// used for both sun and sun glare
+material openmw_sun
+{
+ allow_fixed_function false
+ pass
+ {
+ vertex_program sun_vertex
+ fragment_program sun_fragment
+ cull_hardware none
+
+ polygon_mode_overrideable off
+
+ depth_check off
+ depth_write off
+ scene_blend alpha_blend
+
+ texture_unit diffuseMap
+ {
+ direct_texture $texture
+ }
+ }
+}
diff --git a/files/materials/stars.shader b/files/materials/stars.shader
new file mode 100644
index 0000000000..33f22bd14b
--- /dev/null
+++ b/files/materials/stars.shader
@@ -0,0 +1,47 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
+shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
+
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+ shOutput(float, fade)
+
+ SH_START_PROGRAM
+ {
+ float4x4 viewFixed = view;
+#if !SH_GLSL
+ viewFixed[0][3] = 0;
+ viewFixed[1][3] = 0;
+ viewFixed[2][3] = 0;
+#else
+ viewFixed[3][0] = 0;
+ viewFixed[3][1] = 0;
+ viewFixed[3][2] = 0;
+#endif
+ shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition));
+ UV = uv0;
+
+ fade = (shInputPosition.z > 50) ? 1 : 0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+
+ shInput(float2, UV)
+ shInput(float, fade)
+
+ shSampler2D(diffuseMap)
+ shUniform(float, nightFade) @shSharedParameter(nightFade)
+
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = shSample(diffuseMap, UV) * float4(1,1,1, nightFade * fade);
+ }
+
+#endif
diff --git a/files/materials/stars.shaderset b/files/materials/stars.shaderset
new file mode 100644
index 0000000000..0f8803450b
--- /dev/null
+++ b/files/materials/stars.shaderset
@@ -0,0 +1,15 @@
+shader_set stars_vertex
+{
+ source stars.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set stars_fragment
+{
+ source stars.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/sun.shader b/files/materials/sun.shader
new file mode 100644
index 0000000000..abe4c97f15
--- /dev/null
+++ b/files/materials/sun.shader
@@ -0,0 +1,41 @@
+#include "core.h"
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, world) @shAutoConstant(world, world_matrix)
+ shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
+shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+
+ SH_START_PROGRAM
+ {
+ float4x4 viewFixed = view;
+#if !SH_GLSL
+ viewFixed[0][3] = 0;
+ viewFixed[1][3] = 0;
+ viewFixed[2][3] = 0;
+#else
+ viewFixed[3][0] = 0;
+ viewFixed[3][1] = 0;
+ viewFixed[3][2] = 0;
+#endif
+ shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shMatrixMult(world, shInputPosition)));
+ UV = uv0;
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shSampler2D(diffuseMap)
+ shInput(float2, UV)
+ shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour)
+ //shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour)
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * shSample(diffuseMap, UV);
+ }
+
+#endif
diff --git a/files/materials/sun.shaderset b/files/materials/sun.shaderset
new file mode 100644
index 0000000000..1b9e92a439
--- /dev/null
+++ b/files/materials/sun.shaderset
@@ -0,0 +1,15 @@
+shader_set sun_vertex
+{
+ source sun.shader
+ type vertex
+ profiles_cg vs_2_0 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set sun_fragment
+{
+ source sun.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps arbfp1
+ profiles_hlsl ps_2_0
+}
diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader
new file mode 100644
index 0000000000..861841a84c
--- /dev/null
+++ b/files/materials/terrain.shader
@@ -0,0 +1,348 @@
+#include "core.h"
+
+#define IS_FIRST_PASS (@shPropertyString(pass_index) == 0)
+
+#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map))
+
+#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm)
+#define SHADOWS @shGlobalSettingBool(shadows)
+
+#if SHADOWS || SHADOWS_PSSM
+#include "shadows.h"
+#endif
+
+#define NUM_LAYERS @shPropertyString(num_layers)
+
+#if FOG || SHADOWS_PSSM
+#define NEED_DEPTH 1
+#endif
+
+#define UNDERWATER @shGlobalSettingBool(render_refraction)
+
+#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
+
+#define RENDERCMP @shPropertyBool(render_composite_map)
+
+#define LIGHTING !RENDERCMP
+
+#define COMPOSITE_MAP @shPropertyBool(display_composite_map)
+
+
+#if NEED_DEPTH
+@shAllocatePassthrough(1, depth)
+#endif
+
+@shAllocatePassthrough(2, UV)
+
+@shAllocatePassthrough(3, worldPos)
+
+#if LIGHTING
+@shAllocatePassthrough(3, lightResult)
+@shAllocatePassthrough(3, directionalResult)
+
+#if SHADOWS
+@shAllocatePassthrough(4, lightSpacePos0)
+#endif
+#if SHADOWS_PSSM
+@shForeach(3)
+ @shAllocatePassthrough(4, lightSpacePos@shIterator)
+@shEndForeach
+#endif
+#endif
+
+#ifdef SH_VERTEX_SHADER
+
+ // ------------------------------------- VERTEX ---------------------------------------
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
+ shUniform(float4x4, viewProjMatrix) @shAutoConstant(viewProjMatrix, viewproj_matrix)
+
+#if VIEWPROJ_FIX
+ shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix)
+#endif
+
+ shVertexInput(float2, uv0)
+ shVertexInput(float2, uv1) // lodDelta, lodThreshold
+
+#if LIGHTING
+ shNormalInput(float4)
+ shColourInput(float4)
+
+ shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
+ 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)
+
+#if SHADOWS
+ shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
+#endif
+
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator)
+ @shEndForeach
+#endif
+
+#endif
+
+
+ @shPassthroughVertexOutputs
+
+ SH_START_PROGRAM
+ {
+ float4 worldPos = shMatrixMult(worldMatrix, shInputPosition);
+
+ shOutputPosition = shMatrixMult(viewProjMatrix, worldPos);
+
+#if NEED_DEPTH
+#if VIEWPROJ_FIX
+ float4x4 vpFixed = viewProjMatrix;
+#if !SH_GLSL
+ vpFixed[2] = vpRow2Fix;
+#else
+ vpFixed[0][2] = vpRow2Fix.x;
+ vpFixed[1][2] = vpRow2Fix.y;
+ vpFixed[2][2] = vpRow2Fix.z;
+ vpFixed[3][2] = vpRow2Fix.w;
+#endif
+
+ float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix);
+
+ float depth = shMatrixMult(fixedWVP, shInputPosition).z;
+ @shPassthroughAssign(depth, depth);
+#else
+ @shPassthroughAssign(depth, shOutputPosition.z);
+#endif
+
+#endif
+
+ @shPassthroughAssign(UV, uv0);
+
+ @shPassthroughAssign(worldPos, worldPos.xyz);
+
+#if LIGHTING
+
+#if SHADOWS
+ float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
+ @shPassthroughAssign(lightSpacePos0, lightSpacePos);
+#endif
+#if SHADOWS_PSSM
+ float4 wPos = shMatrixMult(worldMatrix, shInputPosition);
+
+ float4 lightSpacePos;
+ @shForeach(3)
+ lightSpacePos = shMatrixMult(texViewProjMatrix@shIterator, wPos);
+ @shPassthroughAssign(lightSpacePos@shIterator, lightSpacePos);
+ @shEndForeach
+#endif
+
+
+ // Lighting
+ float3 lightDir;
+ float d;
+ float3 lightResult = float3(0,0,0);
+ float3 directionalResult = float3(0,0,0);
+ @shForeach(@shGlobalSettingString(num_lights))
+ lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * 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
+ directionalResult = lightResult.xyz;
+#endif
+ @shEndForeach
+ lightResult.xyz += lightAmbient.xyz;
+ lightResult.xyz *= colour.xyz;
+ directionalResult.xyz *= colour.xyz;
+
+ @shPassthroughAssign(lightResult, lightResult);
+ @shPassthroughAssign(directionalResult, directionalResult);
+
+#endif
+ }
+
+#else
+
+ // ----------------------------------- FRAGMENT ------------------------------------------
+
+#if UNDERWATER
+ #include "underwater.h"
+#endif
+
+ SH_BEGIN_PROGRAM
+
+
+#if COMPOSITE_MAP
+ shSampler2D(compositeMap)
+#else
+
+@shForeach(@shPropertyString(num_blendmaps))
+ shSampler2D(blendMap@shIterator)
+@shEndForeach
+
+@shForeach(@shPropertyString(num_layers))
+ shSampler2D(diffuseMap@shIterator)
+@shEndForeach
+
+#endif
+
+#if FOG
+ shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour)
+ shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params)
+#endif
+
+ @shPassthroughFragmentInputs
+
+#if LIGHTING
+#if SHADOWS
+ shSampler2D(shadowMap0)
+ shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset))
+#endif
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shSampler2D(shadowMap@shIterator)
+ shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(@shPropertyString(shadowtexture_offset)))
+ @shEndForeach
+ shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints)
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart)
+#endif
+#endif
+
+#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
+
+ SH_START_PROGRAM
+ {
+
+#if NEED_DEPTH
+ float depth = @shPassthroughReceive(depth);
+#endif
+
+ float2 UV = @shPassthroughReceive(UV);
+
+ float3 worldPos = @shPassthroughReceive(worldPos);
+
+
+#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)
+float previousAlpha = 1.f;
+#endif
+
+
+shOutputColour(0) = float4(1,1,1,1);
+
+#if COMPOSITE_MAP
+ shOutputColour(0).xyz = shSample(compositeMap, UV).xyz;
+#else
+
+ // Layer calculations
+// rescale UV to directly map edge vertices to texel centers - this is
+// important to get correct blending at cell transitions
+// TODO: parameterize texel size
+float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
+@shForeach(@shPropertyString(num_blendmaps))
+ float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV));
+@shEndForeach
+
+
+ float3 albedo = float3(0,0,0);
+
+ float2 layerUV = UV * 16;
+
+@shForeach(@shPropertyString(num_layers))
+
+
+#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;
+ #else
+ albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator));
+ #endif
+#else
+ #if @shIterator == 0
+ albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator);
+ #else
+ albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator));
+ #endif
+ previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator);
+#endif
+@shEndForeach
+
+ shOutputColour(0).rgb *= albedo;
+
+#endif
+
+#if LIGHTING
+ // Lighting
+ float3 lightResult = @shPassthroughReceive(lightResult);
+ float3 directionalResult = @shPassthroughReceive(directionalResult);
+
+ // shadows only for the first (directional) light
+#if SHADOWS
+ float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0);
+ float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0);
+#endif
+#if SHADOWS_PSSM
+ @shForeach(3)
+ float4 lightSpacePos@shIterator = @shPassthroughReceive(lightSpacePos@shIterator);
+ @shEndForeach
+
+ float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depth, pssmSplitPoints);
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y;
+ float fade = 1-((depth - shadowFar_fadeStart.y) / fadeRange);
+ shadow = (depth > shadowFar_fadeStart.x) ? 1.0 : ((depth > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow);
+#endif
+
+#if !SHADOWS && !SHADOWS_PSSM
+ float shadow = 1.0;
+#endif
+
+ shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow));
+#endif
+
+#if FOG
+ float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);
+
+ #if UNDERWATER
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY));
+ #else
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue);
+ #endif
+#endif
+
+ // prevent negative colour output (for example with negative lights)
+ shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0));
+
+#if IS_FIRST_PASS
+ shOutputColour(0).a = 1;
+#else
+ shOutputColour(0).a = 1.f-previousAlpha;
+#endif
+ }
+
+#endif
diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset
new file mode 100644
index 0000000000..a72f2358fd
--- /dev/null
+++ b/files/materials/terrain.shaderset
@@ -0,0 +1,15 @@
+shader_set terrain_vertex
+{
+ source terrain.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_3_0 vs_2_0
+}
+
+shader_set terrain_fragment
+{
+ source terrain.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
diff --git a/files/materials/underwater.h b/files/materials/underwater.h
new file mode 100644
index 0000000000..8474f299d0
--- /dev/null
+++ b/files/materials/underwater.h
@@ -0,0 +1,121 @@
+#define UNDERWATER_COLOUR float3(0.18039, 0.23137, 0.25490)
+
+#define VISIBILITY 1000.0 // how far you can look through water
+
+#define BIG_WAVES_X 0.3 // strength of big waves
+#define BIG_WAVES_Y 0.3
+
+#define MID_WAVES_X 0.3 // strength of middle sized waves
+#define MID_WAVES_Y 0.15
+
+#define SMALL_WAVES_X 0.15 // strength of small waves
+#define SMALL_WAVES_Y 0.1
+
+#define WAVE_CHOPPYNESS 0.15 // wave choppyness
+#define WAVE_SCALE 0.01 // overall wave scale
+
+#define ABBERATION 0.001 // chromatic abberation amount
+
+#define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction
+
+float3 intercept(float3 lineP,
+ float3 lineN,
+ float3 planeN,
+ float planeD)
+{
+
+ float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN);
+ return lineP + lineN * distance;
+}
+
+float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer)
+{
+ float2 nCoord = float2(0,0);
+ bend *= WAVE_CHOPPYNESS;
+ nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04);
+ float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0;
+ nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend;
+ float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0;
+
+ nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend;
+ float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0;
+ nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend;
+ float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0;
+
+ nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend;
+ float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0;
+ nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend;
+ float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0;
+
+
+ float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y +
+ normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y +
+ normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y);
+ return normal;
+}
+
+float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer)
+{
+ bend *= WAVE_CHOPPYNESS;
+ float3 col = float3(0,0,0);
+ float2 nCoord = float2(0,0); //normal coords
+
+ nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03);
+ col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20;
+ nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend;
+ col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20;
+
+ nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend;
+ col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20;
+ nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend;
+ col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15;
+
+ nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend;
+ col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15;
+ nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend;
+ col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10;
+
+ return col;
+}
+
+
+float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed)
+{
+ float waterDepth = shSaturate((waterEyePos.z - worldPos.z) / 50.0);
+
+ float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,0,1), waterLevel);
+
+ ///\ todo clean this up
+ float causticdepth = length(causticPos-worldPos.xyz);
+ causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY);
+ causticdepth = shSaturate(causticdepth);
+
+ // NOTE: the original shader calculated a tangent space basis here,
+ // but using only the world normal is cheaper and i couldn't see a visual difference
+ // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information
+ float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xyz * 2 - 1;
+ causticNorm = float3(causticNorm.x, causticNorm.y, -causticNorm.z);
+
+ //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0);
+
+ float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0);
+
+ float causticR = 1.0-perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z;
+
+ /// \todo sunFade
+
+ // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth;
+ float3 caustics = clamp(pow(float3(causticR,causticR,causticR)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth;
+ float causticG = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z;
+ float causticB = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z;
+ //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth;
+ caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)))*NdotL*causticdepth;
+
+ caustics *= 3;
+
+ // shore transition
+ caustics = shLerp (float3(1,1,1), caustics, waterDepth);
+
+ return caustics;
+}
+
diff --git a/files/materials/water.mat b/files/materials/water.mat
new file mode 100644
index 0000000000..1e5f8c8e04
--- /dev/null
+++ b/files/materials/water.mat
@@ -0,0 +1,77 @@
+material Water
+{
+ allow_fixed_function false
+
+ pass
+ {
+ emissive 1.0 1.0 1.0
+ ambient 0 0 0
+ diffuse 0 0 0 1
+ specular 0 0 0 32
+
+ vertex_program water_vertex
+ fragment_program water_fragment
+
+ cull_hardware none
+
+ scene_blend alpha_blend
+ depth_write off
+
+ texture_unit reflectionMap
+ {
+ texture_alias WaterReflection
+ tex_address_mode clamp
+ }
+
+ texture_unit refractionMap
+ {
+ direct_texture WaterRefraction
+ tex_address_mode clamp
+ }
+
+ texture_unit depthMap
+ {
+ texture_alias SceneDepth
+ tex_address_mode clamp
+ }
+
+ texture_unit normalMap
+ {
+ texture water_nm.png 5
+ }
+
+ texture_unit rippleNormalMap
+ {
+ direct_texture RippleNormal
+ tex_address_mode border
+ tex_border_colour 0.5 0.5 1.0
+ }
+
+ // for simple_water
+ texture_unit animatedTexture
+ {
+ create_in_ffp true
+ scale 0.1 0.1
+ alpha_op_ex source1 src_manual src_current 0.7
+ }
+
+ texture_unit shadowMap0
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ texture_unit shadowMap1
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ texture_unit shadowMap2
+ {
+ content_type shadow
+ tex_address_mode clamp
+ filtering none
+ }
+ }
+}
diff --git a/files/materials/water.shader b/files/materials/water.shader
new file mode 100644
index 0000000000..87e90a2916
--- /dev/null
+++ b/files/materials/water.shader
@@ -0,0 +1,370 @@
+#include "core.h"
+
+
+#define SIMPLE_WATER @shGlobalSettingBool(simple_water)
+
+#if SIMPLE_WATER
+ // --------------------------------------- SIMPLE WATER ---------------------------------------------------
+
+#define FOG @shGlobalSettingBool(fog)
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+
+#if FOG
+ shOutput(float, depth)
+#endif
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+ UV = uv0;
+#if FOG
+ depth = shOutputPosition.z;
+#endif
+ }
+
+#else
+
+ SH_BEGIN_PROGRAM
+ shSampler2D(animatedTexture)
+ shInput(float2, UV)
+ shInput(float, depth)
+
+ shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour)
+ shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params)
+
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0).xyz = shSample(animatedTexture, UV * 15).xyz * float3(1.0, 1.0, 1.0);
+ shOutputColour(0).w = 0.7;
+
+#if FOG
+ float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue);
+#endif
+ }
+
+#endif
+
+#else
+
+
+
+// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html )
+
+#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm)
+#define SHADOWS @shGlobalSettingBool(shadows)
+
+#if SHADOWS || SHADOWS_PSSM
+ #include "shadows.h"
+#endif
+
+#define RIPPLES 1
+#define REFRACTION @shGlobalSettingBool(refraction)
+
+#ifdef SH_VERTEX_SHADER
+
+ SH_BEGIN_PROGRAM
+ shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix)
+ shVertexInput(float2, uv0)
+ shOutput(float2, UV)
+
+ shOutput(float3, screenCoordsPassthrough)
+ shOutput(float4, position)
+ shOutput(float, depthPassthrough)
+
+
+#if SHADOWS
+ shOutput(float4, lightSpacePos0)
+ shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
+#endif
+
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shOutput(float4, lightSpacePos@shIterator)
+ shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator)
+ @shEndForeach
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
+#endif
+
+ SH_START_PROGRAM
+ {
+ shOutputPosition = shMatrixMult(wvp, shInputPosition);
+ UV = uv0;
+
+
+ #if !SH_GLSL
+ float4x4 scalemat = float4x4( 0.5, 0, 0, 0.5,
+ 0, -0.5, 0, 0.5,
+ 0, 0, 0.5, 0.5,
+ 0, 0, 0, 1 );
+ #else
+ mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0,
+ 0.0, -0.5, 0.0, 0.0,
+ 0.0, 0.0, 0.5, 0.0,
+ 0.5, 0.5, 0.5, 1.0);
+ #endif
+
+ float4 texcoordProj = shMatrixMult(scalemat, shOutputPosition);
+ screenCoordsPassthrough = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w);
+
+ position = shInputPosition;
+
+ depthPassthrough = shOutputPosition.z;
+
+
+#if SHADOWS
+ lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
+#endif
+#if SHADOWS_PSSM
+ float4 wPos = shMatrixMult(worldMatrix, shInputPosition);
+ @shForeach(3)
+ lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos);
+ @shEndForeach
+#endif
+ }
+
+#else
+
+ // tweakables ----------------------------------------------------
+
+ #define VISIBILITY 1500.0 // how far you can look through water
+
+ #define BIG_WAVES_X 0.3 // strength of big waves
+ #define BIG_WAVES_Y 0.3
+
+ #define MID_WAVES_X 0.3 // strength of middle sized waves
+ #define MID_WAVES_Y 0.15
+
+ #define SMALL_WAVES_X 0.15 // strength of small waves
+ #define SMALL_WAVES_Y 0.1
+
+ #define WAVE_CHOPPYNESS 0.15 // wave choppyness
+ #define WAVE_SCALE 75 // overall wave scale
+
+ #define BUMP 1.5 // overall water surface bumpiness
+ #define REFL_BUMP 0.08 // reflection distortion amount
+ #define REFR_BUMP 0.06 // refraction distortion amount
+
+ #define SCATTER_AMOUNT 0.3 // amount of sunlight scattering
+ #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering
+
+ #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction
+
+ #define SPEC_HARDNESS 256 // specular highlights hardness
+
+
+ // ---------------------------------------------------------------
+
+
+
+ float fresnel_dielectric(float3 Incoming, float3 Normal, float eta)
+ {
+ /* compute fresnel reflectance without explicitly computing
+ the refracted direction */
+ float c = abs(dot(Incoming, Normal));
+ float g = eta * eta - 1.0 + c * c;
+ float result;
+
+ if(g > 0.0) {
+ g = sqrt(g);
+ float A =(g - c)/(g + c);
+ float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0);
+ result = 0.5 * A * A *(1.0 + B * B);
+ }
+ else
+ result = 1.0; /* TIR (no refracted component) */
+
+ return result;
+ }
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shInput(float3, screenCoordsPassthrough)
+ shInput(float4, position)
+ shInput(float, depthPassthrough)
+
+ #if RIPPLES
+ shUniform(float3, rippleCenter) @shSharedParameter(rippleCenter, rippleCenter)
+ shUniform(float, rippleAreaLength) @shSharedParameter(rippleAreaLength, rippleAreaLength)
+ #endif
+
+ shUniform(float, far) @shAutoConstant(far, far_clip_distance)
+
+ shSampler2D(reflectionMap)
+#if REFRACTION
+ shSampler2D(refractionMap)
+#endif
+ shSampler2D(depthMap)
+ shSampler2D(normalMap)
+
+ #if RIPPLES
+ shSampler2D(rippleNormalMap)
+ shUniform(float4x4, wMat) @shAutoConstant(wMat, world_matrix)
+ #endif
+
+ shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed)
+ #define WIND_SPEED windDir_windSpeed.z
+ #define WIND_DIR windDir_windSpeed.xy
+
+ shUniform(float, waterTimer) @shSharedParameter(waterTimer)
+ shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight)
+
+ shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0)
+ shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0)
+
+ shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping)
+
+
+ shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour)
+ shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params)
+
+ shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position_object_space)
+
+
+#if SHADOWS
+ shInput(float4, lightSpacePos0)
+ shSampler2D(shadowMap0)
+ shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1)
+#endif
+#if SHADOWS_PSSM
+ @shForeach(3)
+ shInput(float4, lightSpacePos@shIterator)
+ shSampler2D(shadowMap@shIterator)
+ shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1))
+ @shEndForeach
+ shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints)
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart)
+#endif
+
+
+ SH_START_PROGRAM
+ {
+#if SHADOWS
+ float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0);
+#endif
+#if SHADOWS_PSSM
+ float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints);
+#endif
+
+#if SHADOWS || SHADOWS_PSSM
+ float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y;
+ float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange);
+ shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1.0 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow);
+#endif
+
+#if !SHADOWS && !SHADOWS_PSSM
+ float shadow = 1.0;
+#endif
+
+
+ float2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z;
+ screenCoords.y = (1-shSaturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y;
+
+ float2 nCoord = float2(0,0);
+
+ nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04);
+ float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0;
+ nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS;
+ float3 normal1 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0;
+
+ nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS;
+ float3 normal2 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0;
+ nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS;
+ float3 normal3 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0;
+
+ nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS;
+ float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0;
+ nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS;
+ float3 normal5 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0;
+
+
+
+ float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y +
+ normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y +
+ normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y);
+
+ float4 worldPosition = shMatrixMult(wMat, float4(position.xyz, 1));
+ float2 relPos = (worldPosition.xy - rippleCenter.xy) / rippleAreaLength + 0.5;
+ float3 normal_ripple = normalize(shSample(rippleNormalMap, relPos.xy).xyz * 2 - 1);
+
+ //normal = normalize(normal + normal_ripple);
+ normal = normalize(float3(normal.x * BUMP + normal_ripple.x, normal.y * BUMP + normal_ripple.y, normal.z));
+ normal = float3(normal.x, normal.y, -normal.z);
+
+ // normal for sunlight scattering
+ float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 +
+ normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 +
+ normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz;
+ lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z));
+ lNormal = float3(lNormal.x, lNormal.y, -lNormal.z);
+
+
+ float3 lVec = normalize(sunPosition.xyz);
+ float3 vVec = normalize(position.xyz - cameraPos.xyz);
+
+
+ float isUnderwater = (cameraPos.z > 0) ? 0.0 : 1.0;
+
+ // sunlight scattering
+ float3 pNormal = float3(0,0,1);
+ float3 lR = reflect(lVec, lNormal);
+ float3 llR = reflect(lVec, pNormal);
+
+ float s = shSaturate(dot(lR, vVec)*2.0-1.2);
+ float lightScatter = shadow * shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y));
+ float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*float3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT)));
+
+ // fresnel
+ float ior = (cameraPos.z>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air
+ float fresnel = fresnel_dielectric(-vVec, normal, ior);
+
+ fresnel = shSaturate(fresnel);
+
+ // reflection
+ float3 reflection = shSample(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb;
+
+ // refraction
+ float3 R = reflect(vVec, normal);
+
+#if REFRACTION
+ float3 refraction = shSample(refractionMap, (screenCoords-(normal.xy*REFR_BUMP))*1.0).rgb;
+
+ // brighten up the refraction underwater
+ refraction = (cameraPos.z < 0) ? shSaturate(refraction * 1.5) : refraction;
+#endif
+
+ // specular
+ float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS) * shadow;
+
+#if REFRACTION
+ shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz;
+#else
+ shOutputColour(0).xyz = shLerp(reflection, float3(0.18039, 0.23137, 0.25490), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz;
+#endif
+ // fog
+ float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w);
+ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue);
+
+#if REFRACTION
+ shOutputColour(0).w = 1;
+#else
+ shOutputColour(0).w = shSaturate(fresnel*2 + specular);
+#endif
+ }
+
+#endif
+
+
+#endif
diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset
new file mode 100644
index 0000000000..5e070a45a9
--- /dev/null
+++ b/files/materials/water.shaderset
@@ -0,0 +1,15 @@
+shader_set water_vertex
+{
+ source water.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_3_0 vs_2_0
+}
+
+shader_set water_fragment
+{
+ source water.shader
+ type fragment
+ profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
diff --git a/files/materials/watersim.mat b/files/materials/watersim.mat
new file mode 100644
index 0000000000..b58b1a8512
--- /dev/null
+++ b/files/materials/watersim.mat
@@ -0,0 +1,59 @@
+material HeightmapSimulation
+{
+ allow_fixed_function false
+ pass
+ {
+ depth_check off
+ depth_write off
+ vertex_program transform_vertex
+ fragment_program watersim_fragment
+
+ texture_unit heightPrevSampler
+ {
+ tex_address_mode border
+ tex_border_colour 0 0 0
+ texture_alias Heightmap0
+ }
+ texture_unit heightCurrentSampler
+ {
+ tex_address_mode border
+ tex_border_colour 0 0 0
+ texture_alias Heightmap1
+ }
+ }
+}
+
+material HeightToNormalMap
+{
+ allow_fixed_function false
+ pass
+ {
+ depth_check off
+ depth_write off
+ vertex_program transform_vertex
+ fragment_program height_to_normal_fragment
+
+ texture_unit heightCurrentSampler
+ {
+ texture_alias Heightmap2
+ }
+ }
+}
+
+material AddImpulse
+{
+ allow_fixed_function false
+ pass
+ {
+ depth_check off
+ depth_write off
+ scene_blend alpha_blend
+ vertex_program transform_vertex
+ fragment_program add_impulse_fragment
+
+ texture_unit alphaMap
+ {
+ texture circle.png
+ }
+ }
+}
diff --git a/files/materials/watersim.shaderset b/files/materials/watersim.shaderset
new file mode 100644
index 0000000000..ea512e25f0
--- /dev/null
+++ b/files/materials/watersim.shaderset
@@ -0,0 +1,31 @@
+shader_set transform_vertex
+{
+ source quad.shader
+ type vertex
+ profiles_cg vs_2_0 vp40 arbvp1
+ profiles_hlsl vs_2_0
+}
+
+shader_set watersim_fragment
+{
+ source watersim_heightmap.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
+
+shader_set height_to_normal_fragment
+{
+ source watersim_heighttonormal.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
+
+shader_set add_impulse_fragment
+{
+ source watersim_addimpulse.shader
+ type fragment
+ profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
+ profiles_hlsl ps_3_0 ps_2_0
+}
diff --git a/files/materials/watersim_addimpulse.shader b/files/materials/watersim_addimpulse.shader
new file mode 100644
index 0000000000..3ca4192cd7
--- /dev/null
+++ b/files/materials/watersim_addimpulse.shader
@@ -0,0 +1,12 @@
+#include "core.h"
+#include "watersim_common.h"
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shSampler2D(alphaMap)
+
+ SH_START_PROGRAM
+ {
+ shOutputColour(0) = EncodeHeightmap(1.0);
+ shOutputColour(0).a = shSample (alphaMap, UV.xy).a;
+ }
diff --git a/files/materials/watersim_common.h b/files/materials/watersim_common.h
new file mode 100644
index 0000000000..aa7a636a06
--- /dev/null
+++ b/files/materials/watersim_common.h
@@ -0,0 +1,25 @@
+float DecodeHeightmap(float4 heightmap)
+{
+ float4 table = float4(1.0, -1.0, 0.0, 0.0);
+ return dot(heightmap, table);
+}
+
+float DecodeHeightmap(shTexture2D HeightmapSampler, float2 texcoord)
+{
+ float4 heightmap = shSample(HeightmapSampler, texcoord);
+ return DecodeHeightmap(heightmap);
+}
+
+float4 EncodeHeightmap(float fHeight)
+{
+ float h = fHeight;
+ float positive = fHeight > 0.0 ? fHeight : 0.0;
+ float negative = fHeight < 0.0 ? -fHeight : 0.0;
+
+ float4 color = float4(0,0,0,0);
+
+ color.r = positive;
+ color.g = negative;
+
+ return color;
+}
diff --git a/files/materials/watersim_heightmap.shader b/files/materials/watersim_heightmap.shader
new file mode 100644
index 0000000000..ec8ae4174a
--- /dev/null
+++ b/files/materials/watersim_heightmap.shader
@@ -0,0 +1,51 @@
+#include "core.h"
+
+#define DAMPING 0.95
+
+#include "watersim_common.h"
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shSampler2D(heightPrevSampler)
+ shSampler2D(heightCurrentSampler)
+ shUniform(float3, previousFrameOffset) @shSharedParameter(previousFrameOffset, previousFrameOffset)
+ shUniform(float3, currentFrameOffset) @shSharedParameter(currentFrameOffset, currentFrameOffset)
+ shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize)
+
+ SH_START_PROGRAM
+ {
+#if !SH_HLSL
+ const float3 offset[4] = float3[4](
+ float3(-1.0, 0.0, 0.25),
+ float3( 1.0, 0.0, 0.25),
+ float3( 0.0,-1.0, 0.25),
+ float3( 0.0, 1.0, 0.25)
+ );
+#else
+ const float3 offset[4] = {
+ float3(-1.0, 0.0, 0.25),
+ float3( 1.0, 0.0, 0.25),
+ float3( 0.0,-1.0, 0.25),
+ float3( 0.0, 1.0, 0.25)
+ };
+#endif
+
+ float fHeightPrev = DecodeHeightmap(heightPrevSampler, UV.xy + previousFrameOffset.xy + currentFrameOffset.xy);
+
+ float fNeighCurrent = 0;
+ for ( int i=0; i<4; i++ )
+ {
+ float2 vTexcoord = UV + currentFrameOffset.xy + offset[i].xy * rippleTextureSize.xy;
+ fNeighCurrent += (DecodeHeightmap(heightCurrentSampler, vTexcoord) * offset[i].z);
+ }
+
+ float fHeight = fNeighCurrent * 2.0 - fHeightPrev;
+
+ fHeight *= DAMPING;
+
+ shOutputColour(0) = EncodeHeightmap(fHeight);
+ }
+
+
+
+
diff --git a/files/materials/watersim_heighttonormal.shader b/files/materials/watersim_heighttonormal.shader
new file mode 100644
index 0000000000..eaa4af4983
--- /dev/null
+++ b/files/materials/watersim_heighttonormal.shader
@@ -0,0 +1,36 @@
+#include "core.h"
+#include "watersim_common.h"
+
+ SH_BEGIN_PROGRAM
+ shInput(float2, UV)
+ shSampler2D(heightCurrentSampler)
+ shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize)
+
+ SH_START_PROGRAM
+ {
+#if !SH_HLSL
+ float2 offset[4] = float2[4] (
+ float2(-1.0, 0.0),
+ float2( 1.0, 0.0),
+ float2( 0.0,-1.0),
+ float2( 0.0, 1.0)
+ );
+#else
+ float2 offset[4] = {
+ float2(-1.0, 0.0),
+ float2( 1.0, 0.0),
+ float2( 0.0,-1.0),
+ float2( 0.0, 1.0)
+ };
+#endif
+
+ float fHeightL = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[0]*rippleTextureSize.xy);
+ float fHeightR = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[1]*rippleTextureSize.xy);
+ float fHeightT = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[2]*rippleTextureSize.xy);
+ float fHeightB = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[3]*rippleTextureSize.xy);
+
+ float3 n = float3(fHeightB - fHeightT, fHeightR - fHeightL, 1.0);
+ float3 normal = (n + 1.0) * 0.5;
+
+ shOutputColour(0) = float4(normal.rgb, 1.0);
+ }
diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt
new file mode 100644
index 0000000000..1ec1e08cb5
--- /dev/null
+++ b/files/mygui/CMakeLists.txt
@@ -0,0 +1,91 @@
+
+# Copy resource files into the build directory
+set(SDIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui)
+
+set(MYGUI_FILES
+ bigbars.png
+ black.png
+ core.skin
+ core.xml
+ EBGaramond-Regular.ttf
+ Obliviontt.zip
+ openmw_alchemy_window.layout
+ openmw_book.layout
+ openmw_box.skin.xml
+ openmw_button.skin.xml
+ openmw_chargen_birth.layout
+ openmw_chargen_class_description.layout
+ openmw_chargen_class.layout
+ openmw_chargen_create_class.layout
+ openmw_chargen_generate_class_result.layout
+ openmw_chargen_race.layout
+ openmw_chargen_review.layout
+ openmw_chargen_select_attribute.layout
+ openmw_chargen_select_skill.layout
+ openmw_chargen_select_specialization.layout
+ openmw_confirmation_dialog.layout
+ openmw_console.layout
+ openmw_console.skin.xml
+ openmw_container_window.layout
+ openmw_count_window.layout
+ openmw_dialogue_window.layout
+ openmw_dialogue_window_skin.xml
+ openmw_edit.skin.xml
+ openmw_font.xml
+ openmw_hud_box.skin.xml
+ openmw_hud_energybar.skin.xml
+ openmw_hud.layout
+ openmw_infobox.layout
+ openmw_interactive_messagebox.layout
+ openmw_inventory_window.layout
+ openmw_journal.layout
+ openmw_journal_skin.xml
+ openmw_layers.xml
+ openmw_list.skin.xml
+ openmw_mainmenu.layout
+ openmw_mainmenu_skin.xml
+ openmw_map_window.layout
+ openmw_map_window_skin.xml
+ openmw_messagebox.layout
+ openmw_pointer.xml
+ openmw_progress.skin.xml
+ openmw_resources.xml
+ openmw_scroll.layout
+ openmw_scroll_skin.xml
+ openmw_settings_window.layout
+ openmw_settings.xml
+ openmw_spell_window.layout
+ openmw_stats_window.layout
+ openmw_text_input.layout
+ openmw_text.skin.xml
+ openmw_tooltips.layout
+ openmw_trade_window.layout
+ openmw_spell_buying_window.layout
+ openmw_windows.skin.xml
+ openmw_quickkeys_menu.layout
+ openmw_quickkeys_menu_assign.layout
+ openmw_itemselection_dialog.layout
+ openmw_magicselection_dialog.layout
+ openmw_spell_buying_window.layout
+ openmw_loading_screen.layout
+ openmw_levelup_dialog.layout
+ openmw_wait_dialog.layout
+ openmw_wait_dialog_progressbar.layout
+ openmw_spellcreation_dialog.layout
+ openmw_edit_effect.layout
+ openmw_enchanting_dialog.layout
+ openmw_trainingwindow.layout
+ openmw_travel_window.layout
+ openmw_persuasion_dialog.layout
+ openmw_merchantrepair.layout
+ openmw_repair.layout
+ openmw_companion_window.layout
+ smallbars.png
+ DejaVuLGCSansMono.ttf
+ markers.png
+ ../launcher/images/openmw.png
+)
+
+
+copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${MYGUI_FILES}")
diff --git a/files/mygui/DejaVuLGCSansMono.ttf b/files/mygui/DejaVuLGCSansMono.ttf
new file mode 100644
index 0000000000..80c45b73e9
--- /dev/null
+++ b/files/mygui/DejaVuLGCSansMono.ttf
Binary files differ
diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf
new file mode 100644
index 0000000000..3f6f6c191d
--- /dev/null
+++ b/files/mygui/EBGaramond-Regular.ttf
Binary files differ
diff --git a/files/mygui/Obliviontt.zip b/files/mygui/Obliviontt.zip
new file mode 100644
index 0000000000..af4f809fd8
--- /dev/null
+++ b/files/mygui/Obliviontt.zip
Binary files differ
diff --git a/files/mygui/bigbars.png b/files/mygui/bigbars.png
new file mode 100644
index 0000000000..ee91da19e3
--- /dev/null
+++ b/files/mygui/bigbars.png
Binary files differ
diff --git a/files/mygui/black.png b/files/mygui/black.png
new file mode 100644
index 0000000000..69db2911a7
--- /dev/null
+++ b/files/mygui/black.png
Binary files differ
diff --git a/files/mygui/core.skin b/files/mygui/core.skin
new file mode 100644
index 0000000000..ea3e2debce
--- /dev/null
+++ b/files/mygui/core.skin
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <Skin name = "TextBox" size = "16 16">
+ <Property key="FontHeight" value = "16" />
+ <Property key="TextAlign" value = "ALIGN_DEFAULT" />
+ <Property key="TextColour" value = "0.7 0.7 0.7" />
+
+ <BasisSkin type="SimpleText" offset = "0 0 16 16" align = "ALIGN_STRETCH"/>
+ </Skin>
+
+ <Skin name = "ImageBox" size = "16 16">
+ <BasisSkin type="MainSkin" offset = "0 0 16 16"/>
+ </Skin>
+
+ <Skin name = "RotatingSkin" size = "16 16">
+ <BasisSkin type="RotatingSkin" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/core.xml b/files/mygui/core.xml
new file mode 100644
index 0000000000..ea16278757
--- /dev/null
+++ b/files/mygui/core.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI>
+ <MyGUI type="List">
+ <List file="core.skin" />
+ <List file="openmw_resources.xml" />
+ <List file="openmw_layers.xml" />
+ <List file="openmw_pointer.xml" />
+ <List file="openmw_font.xml" />
+ <List file="openmw_text.skin.xml" />
+ <List file="openmw_windows.skin.xml" />
+ <List file="openmw_button.skin.xml" />
+ <List file="openmw_list.skin.xml" />
+ <List file="openmw_edit.skin.xml" />
+ <List file="openmw_box.skin.xml" />
+ <List file="openmw_progress.skin.xml" />
+ <List file="openmw_hud_energybar.skin.xml" />
+ <List file="openmw_hud_box.skin.xml" />
+ <List file="openmw_mainmenu_skin.xml" />
+ <List file="openmw_console.skin.xml" />
+ <List file="openmw_journal_skin.xml" />
+ <List file="openmw_map_window_skin.xml" />
+ <List file="openmw_dialogue_window_skin.xml" />
+ <List file="openmw_scroll_skin.xml" />
+ <List file="openmw_settings.xml" />
+ </MyGUI>
+</MyGUI>
+
diff --git a/files/mygui/core_layouteditor.xml b/files/mygui/core_layouteditor.xml
new file mode 100644
index 0000000000..007b5e638e
--- /dev/null
+++ b/files/mygui/core_layouteditor.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI>
+ <MyGUI type="List">
+ <List file="core.skin"/>
+ <List file="openmw_resources.xml"/>
+ <List file="openmw_font.xml"/>
+ <List file="openmw_text.skin.xml"/>
+ <List file="openmw_windows.skin.xml"/>
+ <List file="openmw_button.skin.xml"/>
+ <List file="openmw_list.skin.xml"/>
+ <List file="openmw_edit.skin.xml"/>
+ <List file="openmw_box.skin.xml"/>
+ <List file="openmw_progress.skin.xml"/>
+ <List file="openmw_hud_energybar.skin.xml"/>
+ <List file="openmw_hud_box.skin.xml"/>
+ <List file="openmw_mainmenu_skin.xml"/>
+ <List file="openmw_console.skin.xml"/>
+ <List file="openmw_journal_skin.xml"/>
+ <List file="openmw_map_window_skin.xml"/>
+ <List file="openmw_dialogue_window_skin.xml"/>
+ <List file="openmw_scroll_skin.xml"/>
+ <List file="openmw_settings.xml"/>
+ </MyGUI>
+</MyGUI>
+
diff --git a/files/mygui/markers.png b/files/mygui/markers.png
new file mode 100644
index 0000000000..76b6b9913b
--- /dev/null
+++ b/files/mygui/markers.png
Binary files differ
diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout
new file mode 100644
index 0000000000..4b15ac1bd5
--- /dev/null
+++ b/files/mygui/openmw_alchemy_window.layout
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window_NoCaption" layer="Windows" position="0 0 588 444" name="_Main">
+ <Property key="MinSize" value="425 360"/>
+
+ <!-- Name -->
+
+ <Widget type="TextBox" skin="SandText" position="10 10 65 24">
+ <Property key="Caption" value="#{sName}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="EditBox" skin="MW_TextEdit" position="70 10 492 24" align="Top Left HStretch" name="NameEdit"/>
+
+
+ <!-- Apparatus -->
+
+ <Widget type="TextBox" skin="SandText" position="10 40 260 24">
+ <Property key="Caption" value="#{sApparatus}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="10 66 260 50">
+
+ <Widget type="Widget" skin="MW_Box" position="0 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Apparatus1"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="60 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Apparatus2"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="120 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Apparatus3"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="180 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Apparatus4"/>
+ </Widget>
+
+ </Widget>
+
+
+ <!-- Used Ingredients -->
+
+ <Widget type="TextBox" skin="SandText" position="10 120 260 24">
+ <Property key="Caption" value="#{sIngredients}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="10 146 260 50">
+
+ <Widget type="Widget" skin="MW_Box" position="0 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Ingredient1"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="60 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Ingredient2"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="120 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Ingredient3"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="180 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="Ingredient4"/>
+ </Widget>
+
+ </Widget>
+
+
+ <!-- Available Ingredients -->
+
+ <Widget type="ItemView" skin="MW_ItemView" position="10 206 552 158" name="ItemView" align="Left Top Stretch"/>
+
+
+ <!-- Created Effects -->
+
+ <Widget type="TextBox" skin="SandText" position="250 40 320 24">
+ <Property key="Caption" value="#{sCreatedEffects}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="250 66 312 130" align="Top Left HStretch">
+ <Widget type="Widget" skin="" position="4 4 316 122" name="CreatedEffects" align="HStretch"/>
+ </Widget>
+
+
+ <!-- Buttons -->
+
+ <Widget type="HBox" skin="" position="110 374 452 24" align="Bottom Right">
+
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CreateButton">
+ <Property key="Caption" value="#{sCreate}"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+</MyGUI> \ No newline at end of file
diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout
new file mode 100644
index 0000000000..9f30bf2531
--- /dev/null
+++ b/files/mygui/openmw_book.layout
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+
+<Widget type="Window" skin="" layer="Windows" align="Left|Top" position="0 0 565 390" name="_Main">
+
+ <Widget type="ImageBox" skin="ImageBox" position="-70 0 705 390" align="Top|Right" name="JImage">
+ <Property key="ImageTexture" value="textures\tx_menubook.dds"/>
+ <Widget type="Widget" position="70 0 565 390" align="Top|Right">
+
+ <Widget type="Widget" position="0 0 282 390">
+ <Widget type="ImageButton" skin="ImageBox" position="205 350 48 32" name="PrevPageBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_prev_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_prev_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_prev_pressed.dds"/>
+ </Widget>
+ </Widget>
+ <Widget type="Widget" position="282 0 282 390">
+ <Widget type="ImageButton" skin="ImageBox" position="18 350 48 32" name="NextPageBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_next_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_next_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_next_pressed.dds"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="40 350 64 32" name="TakeButton">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_take_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_take_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_take_pressed.dds"/>
+ </Widget>
+ <Widget type="ImageButton" skin="ImageBox" position="460 350 48 32" name="CloseButton">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_close_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_close_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_close_pressed.dds"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="150 350 32 16" name="LeftPageNumber">
+ <Property key="TextColour" value="0 0 0"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="410 350 32 16" name="RightPageNumber">
+ <Property key="TextColour" value="0 0 0"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="30 22 240 300" name="LeftPage"/>
+ <Widget type="Widget" skin="" position="300 22 240 300" name="RightPage"/>
+ </Widget>
+ </Widget>
+</Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_box.skin.xml b/files/mygui/openmw_box.skin.xml
new file mode 100644
index 0000000000..620f49e2b3
--- /dev/null
+++ b/files/mygui/openmw_box.skin.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This file defines the box you see around many GUI elements, such
+as around the sections of the stats window, or around popup info windows -->
+
+<MyGUI type="Skin">
+ <!-- Box borders -->
+ <Skin name="IB_T" size="512 2" texture="textures\menu_thin_border_top.dds">
+ <BasisSkin type="TileRect" offset="0 0 512 2" align="HStretch">
+ <State name="normal" offset="0 0 512 2">
+ <Property key="TileSize" value="512 2"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_B" size="512 2" texture="textures\menu_thin_border_bottom.dds">
+ <BasisSkin type="TileRect" offset="0 0 512 2" align="HStretch">
+ <State name="normal" offset="0 0 512 2">
+ <Property key="TileSize" value="512 2"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_L" size="2 512" texture="textures\menu_thin_border_left.dds">
+ <BasisSkin type="TileRect" offset="0 0 2 512" align="VStretch">
+ <State name="normal" offset="0 0 2 512">
+ <Property key="TileSize" value="2 512"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_R" size="2 512" texture="textures\menu_thin_border_right.dds">
+ <BasisSkin type="TileRect" offset="0 0 2 512" align="VStretch">
+ <State name="normal" offset="0 0 2 512">
+ <Property key="TileSize" value="2 512"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_TL" size="2 2" texture="textures\menu_thin_border_top_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_TR" size="2 2" texture="textures\menu_thin_border_top_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_BL" size="2 2" texture="textures\menu_thin_border_bottom_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="IB_BR" size="2 2" texture="textures\menu_thin_border_bottom_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Main box definition -->
+ <Skin name="MW_Box" size="516 516">
+ <Child type="Widget" skin="IB_T" offset="2 0 512 2" align="Top HStretch"/>
+ <Child type="Widget" skin="IB_B" offset="2 514 512 2" align="Bottom HStretch"/>
+ <Child type="Widget" skin="IB_L" offset="0 2 2 512" align="Left VStretch"/>
+ <Child type="Widget" skin="IB_R" offset="514 2 2 512" align="Right VStretch"/>
+ <Child type="Widget" skin="IB_TL" offset="0 0 2 2" align="Top Left"/>
+ <Child type="Widget" skin="IB_TR" offset="514 0 2 2" align="Top Right"/>
+ <Child type="Widget" skin="IB_BL" offset="0 514 2 2" align="Bottom Left"/>
+ <Child type="Widget" skin="IB_BR" offset="514 514 2 2" align="Bottom Right"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml
new file mode 100644
index 0000000000..e152a91124
--- /dev/null
+++ b/files/mygui/openmw_button.skin.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <!-- Button graphics -->
+ <Skin name="BTN_Top" size="128 4" texture="textures\menu_button_frame_top.dds">
+ <BasisSkin type="TileRect" offset="0 0 128 4" align="HStretch">
+ <State name="normal" offset="0 0 128 4">
+ <Property key="TileSize" value="128 4"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_Bottom" size="128 4" texture="textures\menu_button_frame_bottom.dds">
+ <BasisSkin type="TileRect" offset="0 0 128 4" align="HStretch">
+ <State name="normal" offset="0 0 128 4">
+ <Property key="TileSize" value="128 4"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_Left" size="4 16" texture="textures\menu_button_frame_left.dds">
+ <BasisSkin type="TileRect" offset="0 0 4 16" align="VStretch">
+ <State name="normal" offset="0 0 4 16">
+ <Property key="TileSize" value="4 16"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_Right" size="4 16" texture="textures\menu_button_frame_right.dds">
+ <BasisSkin type="TileRect" offset="0 0 4 16" align="VStretch">
+ <State name="normal" offset="0 0 4 16">
+ <Property key="TileSize" value="4 16"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_TopLeft" size="4 4" texture="textures\menu_button_frame_top_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_TopRight" size="4 4" texture="textures\menu_button_frame_top_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_BottomLeft" size="4 4" texture="textures\menu_button_frame_bottom_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="BTN_BottomRight" size="4 4" texture="textures\menu_button_frame_bottom_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Button widget -->
+ <Skin name="MW_Button" size="136 24">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+
+ <Child type="Widget" skin="BTN_Left" offset="0 4 4 16" align="VStretch Left"/>
+ <Child type="Widget" skin="BTN_Right" offset="132 4 4 16" align="VStretch Right"/>
+ <Child type="Widget" skin="BTN_Top" offset="4 0 128 4" align="HStretch Top"/>
+ <Child type="Widget" skin="BTN_Bottom" offset="4 20 128 4" align="HStretch Bottom"/>
+ <Child type="Widget" skin="BTN_TopLeft" offset="0 0 4 4" align="Top Left"/>
+ <Child type="Widget" skin="BTN_TopRight" offset="132 0 4 4" align="Top Right"/>
+ <Child type="Widget" skin="BTN_BottomLeft" offset="0 20 4 4" align="Bottom Left"/>
+ <Child type="Widget" skin="BTN_BottomRight" offset="132 20 4 4" align="Bottom Right"/>
+
+ <BasisSkin type="SimpleText" offset="4 4 128 16" align="Stretch">
+ <State name="disabled" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="normal" colour="0.75 0.6 0.35" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="1 1 1" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout
new file mode 100644
index 0000000000..b368a6407c
--- /dev/null
+++ b/files/mygui/openmw_chargen_birth.layout
@@ -0,0 +1,30 @@
+<?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">
+
+ <!-- Birthsign list -->
+ <Widget type="ListBox" skin="MW_List" position="8 8 196 137" name="BirthsignList"/>
+
+ <!-- Birthsign image -->
+ <Widget type="Widget" skin="MW_Box" position="212 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"/>
+
+ <!-- Dialog buttons -->
+ <Widget type="HBox" position="0 338 475 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton">
+ <Property key="Caption" value="OK"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout
new file mode 100644
index 0000000000..3c0348b663
--- /dev/null
+++ b/files/mygui/openmw_chargen_class.layout
@@ -0,0 +1,81 @@
+<?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">
+
+ <!-- Class list -->
+ <Widget type="ListBox" skin="MW_List" position="8 8 181 138" name="ClassList"/>
+
+ <!-- Class image -->
+ <Widget type="Widget" skin="MW_Box" position="197 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="TextBox" skin="HeaderText" position="0 0 162 18" name="SpecializationT" align="Left Top">
+ <Property key="Caption" value="#{sChooseClassMenu1}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextToolTip"/>
+ <UserString key="Caption_Text" value="#{sCreateClassMenuHelp1}"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="0 18 162 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">
+ <Property key="Caption" value="#{sChooseClassMenu2}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextToolTip"/>
+ <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"/>
+
+ <!-- Major Skills -->
+ <Widget type="TextBox" skin="HeaderText" position="162 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"/>
+
+ <!-- Minor Skills -->
+ <Widget type="TextBox" skin="HeaderText" position="325 0 162 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>
+
+ <!-- Dialog buttons -->
+ <Widget type="HBox" position="0 276 462 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton">
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout
new file mode 100644
index 0000000000..eaf754697d
--- /dev/null
+++ b/files/mygui/openmw_chargen_class_description.layout
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 244 248" name="_Main">
+
+ <!-- Edit box -->
+ <Widget type="Widget" skin="MW_Box" position="8 8 220 192" align="Stretch" name="Client"/>
+
+ <Widget type="EditBox" skin="MW_TextBoxEdit" position="10 10 218 190" name="TextEdit" align="Left Top Stretch">
+ <Property key="MultiLine" value="true"/>
+ <Property key="VisibleVScroll" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ </Widget>
+
+ <!-- Dialog buttons -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="171 208 57 24" name="OKButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="Enter"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout
new file mode 100644
index 0000000000..92382640b9
--- /dev/null
+++ b/files/mygui/openmw_chargen_create_class.layout
@@ -0,0 +1,84 @@
+<?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">
+
+ <!-- Class name -->
+ <Widget type="TextBox" skin="ProgressText" position="8 8 48 23" name="LabelT" align="Left Top">
+ <Property key="Caption" value="#{sName}"/>
+ <Property key="TextAlign" value="Left VCenter"/>
+ </Widget>
+ <Widget type="EditBox" skin="MW_TextEdit" position="72 8 362 23" name="EditName" align="HStretch Top">
+ <Property key="Caption" value="#{sCustomClassName}"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="8 38 480 110" align="Stretch">
+
+ <!-- Specialization -->
+ <Widget type="TextBox" skin="HeaderText" position="0 0 156 18" name="SpecializationT" align="Left Top">
+ <Property key="Caption" value="#{sChooseClassMenu1}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextToolTip"/>
+ <UserString key="Caption_Text" value="#{sCreateClassMenuHelp1}"/>
+ </Widget>
+
+ <Widget type="Button" skin="SandTextButton" position="0 18 156 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">
+ <Property key="Caption" value="#{sChooseClassMenu2}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextToolTip"/>
+ <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"/>
+
+ <!-- Major Skills -->
+ <Widget type="TextBox" skin="HeaderText" position="156 0 158 18" name="MajorSkillT" align="Left Top">
+ <Property key="Caption" value="Major Skills:"/>
+ <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"/>
+
+ <!-- Minor Skills -->
+ <Widget type="TextBox" skin="HeaderText" position="314 0 140 18" name="MinorSkillT" align="Left Top">
+ <Property key="Caption" value="Minor Skills:"/>
+ <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>
+
+ <!-- Dialog buttons -->
+ <Widget type="HBox" position="0 158 434 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="DescriptionButton">
+ <Property key="Caption" value="#{sCreateClassMenu1}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton">
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_generate_class_result.layout b/files/mygui/openmw_chargen_generate_class_result.layout
new file mode 100644
index 0000000000..f7178042fe
--- /dev/null
+++ b/files/mygui/openmw_chargen_generate_class_result.layout
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 289 256" name="_Main">
+ <!-- Class image -->
+ <Widget type="Widget" skin="MW_Box" position="8 8 265 138" align="Left Top">
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 261 134" name="ClassImage" align="Left Top"/>
+ </Widget>
+
+ <!-- Class text -->
+ <Widget type="EditBox" skin="SandText" position="8 152 265 40" name="ReflectT" align="Left Top">
+ <Property key="TextAlign" value="Top HCenter"/>
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="Static" value="true"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="8 183 265 23" name="ClassName" align="Left Top">
+ <Property key="Caption" value="[Class]"/>
+ <Property key="TextAlign" value="Top HCenter"/>
+ </Widget>
+
+ <!-- Dialog buttons -->
+ <Widget type="HBox" position="0 216 273 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton">
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout
new file mode 100644
index 0000000000..1290795edc
--- /dev/null
+++ b/files/mygui/openmw_chargen_race.layout
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 588 433" name="_Main">
+
+ <!-- Appearance -->
+ <Widget type="TextBox" skin="HeaderText" position="8 16 241 18" name="AppearanceT" align="Left Top">
+ <Property key="Caption" value="Appearance"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="Widget" skin="MW_Box" position="8 39 241 220">
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 237 216" align="Stretch" name="PreviewImage"/>
+ </Widget>
+
+ <!-- Sliders -->
+ <!-- Rotation of head -->
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="8 270 241 14" name="HeadRotate"/>
+
+ <!-- Gender choice -->
+
+ <Widget type="Widget" skin="MW_Box" position="8 298 15 14">
+ <Widget type="Button" skin="MW_ArrowLeft" position="3 2 10 10" align="Left VStretch" name="PrevGenderButton"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="HeaderText" position="25 294 205 18" name="GenderChoiceT"/>
+
+ <Widget type="Widget" skin="MW_Box" position="234 298 15 14">
+ <Widget type="Button" skin="MW_ArrowRight" position="1 2 10 10" align="Right VStretch" name="NextGenderButton"/>
+ </Widget>
+
+ <!-- Face choice -->
+
+ <Widget type="Widget" skin="MW_Box" position="8 320 15 14">
+ <Widget type="Button" skin="MW_ArrowLeft" position="3 2 10 10" align="Left VStretch" name="PrevFaceButton"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="HeaderText" position="25 316 205 18" name="FaceChoiceT"/>
+
+ <Widget type="Widget" skin="MW_Box" position="234 320 15 14">
+ <Widget type="Button" skin="MW_ArrowRight" position="1 2 10 10" align="Right VStretch" name="NextFaceButton"/>
+ </Widget>
+
+ <!-- Hair choice -->
+
+ <Widget type="Widget" skin="MW_Box" position="8 342 15 14">
+ <Widget type="Button" skin="MW_ArrowLeft" position="3 2 10 10" align="Left VStretch" name="PrevHairButton"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="HeaderText" position="25 338 205 18" name="HairChoiceT"/>
+
+ <Widget type="Widget" skin="MW_Box" position="234 342 15 14">
+ <Widget type="Button" skin="MW_ArrowRight" position="1 2 10 10" align="Right VStretch" name="NextHairButton"/>
+ </Widget>
+
+ <!-- Race -->
+
+ <Widget type="TextBox" skin="HeaderText" position="261 16 132 18" name="RaceT" align="Left Top">
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="ListBox" skin="MW_List" position="264 39 132 161" name="RaceList">
+ </Widget>
+
+ <!-- Spell powers -->
+ <Widget type="TextBox" skin="HeaderText" position="261 210 132 18" name="SpellPowerT" align="Left Top">
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <!-- Spell power sub-widgets will be placed here, no skin to make it invisible -->
+ <Widget type="Widget" skin="" position="261 230 250 140" name="SpellPowerList"/>
+
+ <!-- Skill bonus -->
+ <Widget type="TextBox" skin="HeaderText" position="403 39 159 18" name="SkillsT" align="Left Top">
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <!-- Skill bonus sub-widgets will be placed here, no skin to make it invisible -->
+ <Widget type="Widget" skin="" position="403 59 159 360" name="SkillList"/>
+
+ <!-- Dialog buttons -->
+ <Widget type="HBox" position="0 393 572 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="471 397 53 23" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="532 397 42 23" name="OKButton">
+ <Property key="Caption" value="#{sOK]"/>
+ </Widget>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout
new file mode 100644
index 0000000000..5d18f4bff8
--- /dev/null
+++ b/files/mygui/openmw_chargen_review.layout
@@ -0,0 +1,126 @@
+<?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">
+
+ <!-- Player Name, Race, Class and Birthsign -->
+ <Widget type="Widget" skin="MW_Box" position="8 8 244 126">
+ <Widget type="Button" skin="MW_Button" position="8 8 64 23" name="NameButton">
+ <Property key="Caption" value="#{sName}"/>
+ </Widget>
+ <Widget type="Button" skin="MW_Button" position="8 37 56 23" name="RaceButton">
+ <Property key="Caption" value="#{sRace}"/>
+ </Widget>
+ <Widget type="Button" skin="MW_Button" position="8 66 56 23" name="ClassButton">
+ <Property key="Caption" value="#{sClass}"/>
+ </Widget>
+ <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>
+
+ <!-- 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">
+ <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">
+ <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">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\fatigue.dds"/>
+ <Property key="Caption" value="#{sFatigue}"/>
+ </Widget>
+ </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">
+ <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">
+ <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">
+ <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">
+ <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">
+ <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">
+ <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">
+ <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">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="AttributeToolTip"/>
+ <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
+ <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
+ <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
+ </Widget>
+ </Widget>
+
+ <!-- Player Skills -->
+ <Widget type="Widget" skin="MW_Box" position="260 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="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BackButton">
+ <Property key="Caption" value="#{sBack}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton">
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_select_attribute.layout b/files/mygui/openmw_chargen_select_attribute.layout
new file mode 100644
index 0000000000..f0f72bb0fa
--- /dev/null
+++ b/files/mygui/openmw_chargen_select_attribute.layout
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 217 234" name="_Main">
+ <Widget type="Widget" skin="" position="14 14 186 203" align="Stretch">
+
+ <!-- Label -->
+ <Widget type="TextBox" skin="HeaderText" position="0 0 186 18" name="LabelT" align="Left Top">
+ <Property key="Caption" value="Choose a Specialization"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <!-- Attribute list -->
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 28 186 18" name="Attribute0" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 46 186 18" name="Attribute1" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 64 186 18" name="Attribute2" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 82 186 18" name="Attribute3" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 100 186 18" name="Attribute4" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 118 186 18" name="Attribute5" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 136 186 18" name="Attribute6" align="Left Top"/>
+ <Widget type="MWAttribute" skin="MW_StatNameButtonC" position="0 154 186 18" name="Attribute7" align="Left Top"/>
+
+ <!-- Dialog buttons -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="120 180 66 21" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="Cancel"/>
+ </Widget>
+
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_select_skill.layout b/files/mygui/openmw_chargen_select_skill.layout
new file mode 100644
index 0000000000..dc1798995b
--- /dev/null
+++ b/files/mygui/openmw_chargen_select_skill.layout
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 477 270" name="_Main">
+ <Widget type="Widget" skin="" position="17 14 447 239" align="Stretch">
+
+ <!-- Label -->
+ <Widget type="TextBox" skin="HeaderText" position="0 0 447 18" name="LabelT" align="HCenter Top">
+ <Property key="Caption" value="Choose a Skill"/>
+ <Property key="TextAlign" value="HCenter Top"/>
+ </Widget>
+
+ <!-- Combat list -->
+ <Widget type="TextBox" skin="HeaderText" position="0 32 154 18" name="CombatLabelT" align="Left Top">
+ <Property key="Caption" value="Combat"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 50 154 18" name="CombatSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 68 154 18" name="CombatSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 86 154 18" name="CombatSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 104 154 18" name="CombatSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 122 154 18" name="CombatSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 140 154 18" name="CombatSkill5" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 158 154 18" name="CombatSkill6" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 176 154 18" name="CombatSkill7" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="0 194 154 18" name="CombatSkill8" align="Left Top"/>
+
+ <!-- Magic list -->
+ <Widget type="TextBox" skin="HeaderText" position="158 32 154 18" name="MagicLabelT" align="Left Top">
+ <Property key="Caption" value="Magic"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 50 154 18" name="MagicSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 68 154 18" name="MagicSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 86 154 18" name="MagicSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 104 154 18" name="MagicSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 122 154 18" name="MagicSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 140 154 18" name="MagicSkill5" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 158 154 18" name="MagicSkill6" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 176 154 18" name="MagicSkill7" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="158 194 154 18" name="MagicSkill8" align="Left Top"/>
+
+ <!-- Stealth list -->
+ <Widget type="TextBox" skin="HeaderText" position="316 32 131 18" name="StealthLabelT" align="Left Top">
+ <Property key="Caption" value="Stealth"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 50 131 18" name="StealthSkill0" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 68 131 18" name="StealthSkill1" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 86 131 18" name="StealthSkill2" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 104 131 18" name="StealthSkill3" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 122 131 18" name="StealthSkill4" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 140 131 18" name="StealthSkill5" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 158 131 18" name="StealthSkill6" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 176 131 18" name="StealthSkill7" align="Left Top"/>
+ <Widget type="MWSkill" skin="MW_StatNameButton" position="316 194 131 18" name="StealthSkill8" align="Left Top"/>
+
+ <!-- Dialog buttons -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="381 218 66 21" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="Cancel"/>
+ </Widget>
+
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_chargen_select_specialization.layout b/files/mygui/openmw_chargen_select_specialization.layout
new file mode 100644
index 0000000000..7f8b5e02b5
--- /dev/null
+++ b/files/mygui/openmw_chargen_select_specialization.layout
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <!-- correct size is 247 144, adjust when skin is changed to a dialog -->
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 247 144" name="_Main">
+ <Widget type="Widget" skin="" position="14 14 216 113" align="Stretch">
+
+ <!-- Label -->
+ <Widget type="TextBox" skin="HeaderText" position="0 0 216 18" name="LabelT" align="Left Top">
+ <Property key="Caption" value="Choose a Specialization"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <!-- Specialization list -->
+ <Widget type="Button" skin="SandTextButton" position="0 28 216 18" name="Specialization0" align="Left Top">
+ <Property key="TextAlign" value="Top HCenter"/>
+ </Widget>
+ <Widget type="Button" skin="SandTextButton" position="0 46 216 18" name="Specialization1" align="Left Top">
+ <Property key="TextAlign" value="Top HCenter"/>
+ </Widget>
+ <Widget type="Button" skin="SandTextButton" position="0 64 216 18" name="Specialization2" align="Left Top">
+ <Property key="TextAlign" value="Top HCenter"/>
+ </Widget>
+
+ <!-- Dialog buttons -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="150 90 66 21" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="Cancel"/>
+ </Widget>
+
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_companion_window.layout b/files/mygui/openmw_companion_window.layout
new file mode 100644
index 0000000000..1266da3970
--- /dev/null
+++ b/files/mygui/openmw_companion_window.layout
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 600 300" name="_Main">
+ <Property key="MinSize" value="245 145"/>
+
+ <!-- Items -->
+ <Widget type="ItemView" skin="MW_ItemView" position="5 5 575 225" name="ItemView" align="Left Top Stretch">
+ </Widget>
+
+ <Widget type="HBox" position="5 235 575 24" align="Bottom HStretch">
+ <Widget type="MWDynamicStat" skin="MW_ChargeBar_Blue" position="8 8 212 24" name="EncumbranceBar" align="Left Top HStretch">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="ProfitLabel">
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CloseButton">
+ <Property key="Caption" value="#{sClose}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout
new file mode 100644
index 0000000000..47e1fd2b85
--- /dev/null
+++ b/files/mygui/openmw_confirmation_dialog.layout
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 300 130" name="_Main">
+ <Property key="Visible" value="false"/>
+
+ <Widget type="EditBox" skin="MW_TextEditClient" position="8 8 284 400" name="Message" align="Left Top Stretch">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Top HCenter"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+ <Property key="Static" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="MultiLine" value="true"/>
+ </Widget>
+
+ <Widget type="HBox" position="0 89 272 24" align="Right Bottom">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton" align="Right Bottom">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OkButton" align="Right Bottom">
+ <Property key="Caption" value="#{sOk}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
+
diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout
new file mode 100644
index 0000000000..bfda40c68b
--- /dev/null
+++ b/files/mygui/openmw_console.layout
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window" position="0 0 400 400" layer="Console" name="_Main">
+ <Property key="Caption" value="#{sConsoleTitle}"/>
+ <Property key="MinSize" value="400 245"/>
+ <Property key="MaxSize" value="2000 2000"/>
+ <Property key="Visible" value="false"/>
+
+ <!-- Log window -->
+ <Widget type="EditBox" skin="MW_ConsoleLog" position="5 5 380 328" align="Stretch" name="list_History">
+ <Property key="MultiLine" value="1"/>
+ </Widget>
+
+ <!-- Command line -->
+ <Widget type="EditBox" skin="MW_ConsoleCommand" position="0 338 384 28" align="HStretch Bottom" name="edit_Command"/>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml
new file mode 100644
index 0000000000..219cce39ae
--- /dev/null
+++ b/files/mygui/openmw_console.skin.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <!-- Console Output -->
+
+ <Skin name="MW_LogClient" size="10 10">
+ <Property key="FontName" value="MonoFont"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <Property key="TextColour" value="1 1 1"/>
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+ </Skin>
+
+ <Skin name="MW_ConsoleLog" size="0 0 50 50">
+ <Property key="WordWrap" value="true"/>
+ <Child type="TextBox" skin="MW_LogClient" offset="0 0 35 10" align="Stretch" name="Client"/>
+ </Skin>
+
+ <!-- Console Input -->
+
+ <Skin name="MW_EditClient" size="10 10">
+ <Property key="FontName" value="MonoFont"/>
+ <Property key="TextAlign" value="Left VCenter"/>
+ <Property key="TextColour" value="1 1 1"/>
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+ </Skin>
+
+ <Skin name="MW_ConsoleCommand" size="29 28">
+ <Child type="TextBox" skin="MW_EditClient" offset="4 2 19 22" align="Bottom Stretch" name="Client"/>
+ <Child type="Widget" skin="MW_Box" offset="0 0 29 26" align="Bottom Stretch"/>
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout
new file mode 100644
index 0000000000..06cc04ebeb
--- /dev/null
+++ b/files/mygui/openmw_container_window.layout
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 600 300" name="_Main">
+ <Property key="MinSize" value="245 145"/>
+
+ <!-- Items -->
+ <Widget type="ItemView" skin="MW_ItemView" position="5 5 575 225" name="ItemView" align="Left Top Stretch">
+ </Widget>
+
+ <Widget type="HBox" position="0 235 580 24" align="Right Bottom">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="DisposeCorpseButton" align="Right Bottom">
+ <Property key="Caption" value="#{sDisposeofCorpse}"/>
+ <Property key="Visible" value="false"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="TakeButton" align="Right Bottom">
+ <Property key="Caption" value="#{sTakeAll}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CloseButton" align="Right Bottom">
+ <Property key="Caption" value="#{sClose}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout
new file mode 100644
index 0000000000..4e24277afb
--- /dev/null
+++ b/files/mygui/openmw_count_window.layout
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 600 125" name="_Main">
+ <Property key="Visible" value="false"/>
+
+ <Widget type="TextBox" skin="SandText" position="4 0 592 24" name="LabelText" align="Left Top HStretch">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="4 30 532 24" name="ItemText" align="Left Top HStretch">
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="EditBox" skin="MW_TextEdit" position="540 30 32 24" name="ItemEdit" align="Right Top">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="28 60 544 18" name="CountSlider" align="Left Top HStretch">
+ <Property key="MoveToClick" value="true"/>
+ </Widget>
+
+
+ <Widget type="HBox" position="0 90 572 24" align="Right Bottom">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="417 90 60 24" name="CancelButton" align="Right Top">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="512 90 60 24" name="OkButton" align="Right Top">
+ <Property key="Caption" value="#{sOk}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout
new file mode 100644
index 0000000000..78daa0705f
--- /dev/null
+++ b/files/mygui/openmw_dialogue_window.layout
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 588 433" name="_Main">
+ <Property key="MinSize" value="380 230"/>
+
+ <Widget type="Widget" skin="MW_Box" position="8 8 415 381" align="Stretch" name="Client"/>
+
+ <Widget type="Widget" position="13 13 391 371" align="Left Top Stretch">
+ <Widget type="BookPage" skin="MW_BookPage" position="0 0 391 371" name="History" align="Left Top Stretch">
+ </Widget>
+ </Widget>
+
+ <Widget type="MWScrollBar" skin="MW_VScroll" position="404 13 14 371" align="Right VStretch" name="VScroll">
+ <Property key="Visible" value="false"/>
+ </Widget>
+
+ <!-- The disposition bar-->
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Blue" position="432 8 132 18"
+ align="Right Top" name="Disposition">
+ <Widget type="EditBox" skin="MW_DispositionEdit" position_real="0 0 1 1" align="Stretch" name="DispositionText"/>
+ </Widget>
+ <!-- The list of topics -->
+ <Widget type="MWList" skin="MW_SimpleList" position="432 31 132 328" name="TopicsList" align="Right VStretch">
+ </Widget>
+
+ <!-- The Goodbye button -->
+ <Widget type="Button" skin="MW_Button" position="432 366 132 23" name="ByeButton" align="Right Bottom">
+ <Property key="Caption" value="#{sGoodbye}"/>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_dialogue_window_skin.xml b/files/mygui/openmw_dialogue_window_skin.xml
new file mode 100644
index 0000000000..4f68a90faf
--- /dev/null
+++ b/files/mygui/openmw_dialogue_window_skin.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <Skin name="MW_DispEdit" size="10 10">
+ <!--Property key="Pointer" value="beam" /-->
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+ </Skin>
+
+ <Skin name="MW_DispositionEdit" size="0 0 50 50">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Colour" value="0000FF"/>
+ <Property key="Static" value="1"/>
+ <Property key="WordWrap" value="true"/>
+ <Child type="TextBox" skin="MW_DispEdit" offset="0 0 0 -4" align="Stretch" name="Client"/>
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml
new file mode 100644
index 0000000000..b10854b19e
--- /dev/null
+++ b/files/mygui/openmw_edit.skin.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <!-- Text edit widget -->
+
+ <Skin name="MW_TextEditClient" size="10 10">
+
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+
+ </Skin>
+
+ <Skin name="MW_TextBoxEditClient" size="10 10">
+
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+
+ </Skin>
+
+ <Skin name="MW_TextEdit" size="512 20">
+
+ <!-- Input -->
+
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left VCenter"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+
+ <Child type="TextBox" skin="MW_TextEditClient" offset="4 1 502 18" align="Stretch" name="Client"/>
+
+
+ <!-- Borders -->
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 512 20" align="Stretch" name="Client"/>
+
+ </Skin>
+
+ <Skin name="MW_TextBoxEdit" size="512 20">
+
+ <Property key="FontName" value="Default"/>
+
+ <Property key="TextAlign" value="Left Top"/>
+
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+
+ <Child type="TextBox" skin="MW_TextBoxEditClient" offset="2 2 490 18" align="Stretch" name="Client"/>
+
+ <Child type="MWScrollBar" skin="MW_VScroll" offset="494 3 14 14" align="Right VStretch" name="VScroll"/>
+
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout
new file mode 100644
index 0000000000..fa1e58b9d2
--- /dev/null
+++ b/files/mygui/openmw_edit_effect.layout
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 362 310" name="_Main">
+
+ <Widget type="ImageBox" skin="ImageBox" position="8 12 16 16" name="EffectImage">
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="36 8 400 24" name="EffectName">
+ <Property key="TextAlign" value="Left HCenter"/>
+ </Widget>
+
+
+ <!-- Range -->
+ <Widget type="TextBox" skin="NormalText" position="8 36 400 24">
+ <Property key="Caption" value="#{sRange}"/>
+ <Property key="TextAlign" value="Left HCenter"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="130 36 0 24" name="RangeButton">
+ <Property key="Caption" value="#{sRangeTouch}"/>
+ </Widget>
+
+ <!-- Magnitude -->
+ <Widget type="Widget" position="8 80 400 70" name="MagnitudeBox">
+ <Widget type="TextBox" skin="NormalText" position="0 0 400 24">
+ <Property key="Caption" value="#{sMagnitude}"/>
+ <Property key="TextAlign" value="Left HCenter"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="122 0 210 20" name="MagnitudeMinValue">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="0"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="122 20 210 13" name="MagnitudeMinSlider">
+ <Property key="Range" value="100"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="122 32 210 20" name="MagnitudeMaxValue">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="0"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="122 52 210 13" name="MagnitudeMaxSlider">
+ <Property key="Range" value="100"/>
+ </Widget>
+ </Widget>
+
+
+ <!-- Duration -->
+ <Widget type="Widget" position="8 153 400 40" name="DurationBox">
+ <Widget type="TextBox" skin="NormalText" position="0 20 400 24">
+ <Property key="Caption" value="#{sDuration}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="122 0 210 20" name="DurationValue">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="0"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="122 20 210 13" name="DurationSlider">
+ <Property key="Range" value="1440"/>
+ </Widget>
+ </Widget>
+
+ <!-- Area -->
+ <Widget type="Widget" position="8 197 400 40" name="AreaBox">
+ <Widget type="TextBox" skin="NormalText" position="0 20 400 24" name="AreaText">
+ <Property key="Caption" value="#{sArea}"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="122 0 210 20" name="AreaValue">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="0"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="122 20 210 13" name="AreaSlider">
+ <Property key="Range" value="51"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="8 266 336 24">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="DeleteButton">
+ <Property key="Caption" value="#{sDelete}"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OkButton">
+ <Property key="Caption" value="#{sOk}"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_enchanting_dialog.layout b/files/mygui/openmw_enchanting_dialog.layout
new file mode 100644
index 0000000000..f64d21deaa
--- /dev/null
+++ b/files/mygui/openmw_enchanting_dialog.layout
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 560 400" name="_Main">
+
+ <Widget type="HBox" position="12 12 250 30">
+
+ <Widget type="AutoSizedTextBox" skin="NormalText">
+ <Property key="Caption" value="#{sName}"/>
+ </Widget>
+
+ <Widget type="EditBox" skin="MW_TextEdit" position="0 0 30 30" name="NameEdit">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="Widget">
+ </Widget>
+
+ </Widget>
+
+ <!-- Item -->
+
+ <Widget type="HBox" position="12 48 400 59">
+ <Property key="Spacing" value="8"/>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText">
+ <Property key="Caption" value="#{sItem}"/>
+ </Widget>
+ <Widget type="Widget" skin="MW_Box" position="0 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="ItemBox"/>
+ </Widget>
+
+ <Widget type="Widget" position="0 0 8 0"/>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText">
+ <Property key="Caption" value="#{sSoulGem}"/>
+ </Widget>
+ <Widget type="Button" skin="MW_Box" position="0 0 50 50">
+ <Widget type="ImageBox" skin="ImageBox" position="9 9 32 32" name="SoulBox"/>
+ </Widget>
+
+ </Widget>
+
+
+ <Widget type="TextBox" skin="NormalText" position="320 0 300 24">
+ <Property key="Caption" value="#{sEnchantmentMenu3}:"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="280 0 258 24" name="Enchantment">
+ <Property key="Caption" value="1"/>
+ <Property key="TextAlign" value="Right HCenter"/>
+ </Widget>
+
+
+ <Widget type="TextBox" skin="NormalText" position="320 24 300 24">
+ <Property key="Caption" value="#{sCastCost}:"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="280 24 258 24" name="CastCost">
+ <Property key="Caption" value="39"/>
+ <Property key="TextAlign" value="Right HCenter"/>
+ </Widget>
+
+
+ <Widget type="TextBox" skin="NormalText" position="320 48 300 24">
+ <Property key="Caption" value="#{sCharges}"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="280 48 258 24" name="Charge">
+ <Property key="Caption" value="39"/>
+ <Property key="TextAlign" value="Right HCenter"/>
+ </Widget>
+
+
+ <!-- Available effects -->
+ <Widget type="TextBox" skin="NormalText" position="12 108 300 24">
+ <Property key="Caption" value="#{sMagicEffects}"/>
+ </Widget>
+ <Widget type="MWList" skin="MW_SimpleList" position="12 136 202 209" name="AvailableEffects">
+ </Widget>
+
+ <!-- Used effects -->
+ <Widget type="TextBox" skin="NormalText" position="226 108 300 24">
+ <Property key="Caption" value="#{sEffects}"/>
+ </Widget>
+ <Widget type="Widget" skin="MW_Box" position="226 136 316 209">
+ <Widget type="ScrollView" skin="MW_ScrollViewH" position="4 4 308 201" name="UsedEffects">
+ <Property key="CanvasAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="0 340 560 60">
+ <Property key="Padding" value="16"/>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="TypeButton">
+ <Property key="Caption" value="Constant effect"/>
+ </Widget>
+
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText" name="PriceTextLabel">
+ <Property key="Caption" value="#{sBarterDialog7}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="PriceLabel">
+ <Property key="Caption" value="0"/>
+ </Widget>
+
+
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BuyButton">
+ <Property key="Caption" value="#{sBuy}"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml
new file mode 100644
index 0000000000..726bfb281c
--- /dev/null
+++ b/files/mygui/openmw_font.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Resource" version="1.1">
+ <Resource type="ResourceTrueTypeFont" name="EB Garamond">
+ <Property key="Source" value="EBGaramond-Regular.ttf"/>
+ <Property key="Size" value="16"/>
+ <Property key="Resolution" value="72"/>
+ <Property key="Antialias" value="false"/>
+ <Property key="TabWidth" value="8"/>
+ <Property key="OffsetHeight" value="0"/>
+ <Codes>
+ <Code range="33 126"/>
+ <Code range="160"/> <!-- Non-breaking space -->
+ <Code range="192 382"/> <!-- Central and Eastern European languages glyphs -->
+ <Code range="1025 1105"/>
+ <Code range="2026"/> <!-- Ellipsis -->
+ <Code range="8470"/>
+ <Code range="8211"/> <!-- Minus -->
+ <Code range="8216 8217"/> <!-- Single quotes -->
+ <Code range="8220 8221"/> <!-- Right and Left Double Quotation mark -->
+ <Code hide="128"/>
+ <Code hide="1026 1039"/>
+ <Code hide="1104"/>
+ </Codes>
+ </Resource>
+
+ <Resource type="ResourceTrueTypeFont" name="MonoFont">
+ <Property key="Source" value="DejaVuLGCSansMono.ttf"/>
+ <Property key="Size" value="18"/>
+ <Property key="Resolution" value="50"/>
+ <Property key="Antialias" value="false"/>
+ <Property key="TabWidth" value="8"/>
+ <Property key="OffsetHeight" value="0"/>
+ <Codes>
+ <Code range="33 126"/>
+ <Code range="192 382"/>
+ <Code range="1025 1105"/>
+ <Code range="8470"/>
+ <Code hide="128"/>
+ <Code hide="1026 1039"/>
+ <Code hide="1104"/>
+ </Codes>
+ </Resource>
+</MyGUI>
diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout
new file mode 100644
index 0000000000..e39777dd0f
--- /dev/null
+++ b/files/mygui/openmw_hud.layout
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Widget" layer="HUD" position="0 0 300 200" name="_Main">
+ <!-- Energy bars -->
+ <Widget type="Widget" skin="" position="13 131 65 12" align="Left Bottom" name="EnemyHealthFrame">
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Yellow" position="0 0 65 12" align="Left Bottom" name="EnemyHealth">
+ <Property key="Visible" value="false"/>
+ </Widget>
+ </Widget>
+ <Widget type="Button" skin="" position="13 146 65 12" align="Left Bottom" name="HealthFrame">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\health.dds"/>
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Red" position="0 0 65 12" align="Left Bottom" name="Health">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ <Widget type="Button" skin="" position="13 161 65 12" align="Left Bottom" name="MagickaFrame">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\magicka.dds"/>
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Blue" position="0 0 65 12" align="Left Bottom" name="Magicka">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ <Widget type="Button" skin="" position="13 176 65 12" align="Left Bottom" name="FatigueFrame">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\fatigue.dds"/>
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Green" position="0 0 65 12" align="Left Bottom" name="Stamina">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Drowning bar -->
+ <Widget type="Widget" skin="HUD_Box" position="0 36 220 56" align="Center Top" name="DrowningFrame">
+ <Property key="Visible" value="false"/>
+ <Widget type="TextBox" skin="SandText" position="0 8 220 24" name="DrowningTitle" align="Center Top HStretch">
+ <Property key="Caption" value="#{sBreath}"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextShadow" value="true"/>
+ <Property key="TextShadowColour" value="0 0 0"/>
+ </Widget>
+ <Widget type="ProgressBar" skin="MW_Progress_Loading" position="12 36 196 8" align="Center Top" name="Drowning">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Equipped weapon/selected spell name display for a few seconds after it changes -->
+ <Widget type="TextBox" skin="SandText" position="13 118 270 24" name="WeaponSpellName" align="Left Bottom HStretch">
+ <Property key="Visible" value="false"/>
+ <Property key="TextAlign" value="Left"/>
+ <Property key="TextShadow" value="true"/>
+ <Property key="TextShadowColour" value="0 0 0"/>
+ </Widget>
+
+ <!-- Equipped weapon box -->
+ <Widget type="Button" skin="" position="82 146 36 41" align="Left Bottom" name="WeapBox">
+ <Widget type="Widget" skin="HUD_Box" position="0 0 36 36">
+ <Property key="NeedMouse" value="false"/>
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 32 32" align="Left Top" name="WeapImage">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Red" position="0 36 36 6" align="Left Bottom" name="WeapStatus">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Selected spell box -->
+ <Widget type="Button" position="122 146 36 41" align="Left Bottom" name="SpellBox">
+ <Widget type="Widget" skin="HUD_Box" position="0 0 36 36">
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 32 32" align="Left Top" name="SpellImage"/>
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="ProgressBar" skin="MW_EnergyBar_Red" position="0 36 36 6" align="Left Bottom" name="SpellStatus">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Sneak indicator box -->
+ <Widget type="Button" skin="" position="162 146 36 36" align="Left Bottom" name="SneakBox">
+ <Widget type="Widget" skin="HUD_Box" position="0 0 36 36">
+ <Property key="NeedMouse" value="false"/>
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 32 32" align="Left Top" name="SneakImage">
+ <Property key="NeedMouse" value="false"/>
+ <Property key="ImageTexture" value="icons\k\stealth_sneak.dds"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <!-- Spell effects box -->
+ <Widget type="Widget" skin="HUD_Box_Transparent" position="199 168 20 20" align="Right Bottom" name="EffectBox">
+ </Widget>
+
+ <!-- Cell name display when cell changes -->
+ <Widget type="TextBox" skin="SandText" position="0 89 288 24" name="CellName" align="Left Bottom HStretch">
+ <Property key="Visible" value="false"/>
+ <Property key="TextAlign" value="Right"/>
+ <Property key="TextShadow" value="true"/>
+ <Property key="TextShadowColour" value="0 0 0"/>
+ </Widget>
+
+ <!-- Map box -->
+ <Widget type="Widget" skin="" position="223 123 65 65" name="MiniMapBox" align="Right Bottom">
+ <Widget type="Widget" skin="HUD_Box" position="0 0 65 65" align="Center">
+
+ <Widget type="ScrollView" skin="MW_MapView" position="2 2 61 61" align="Left Bottom" name="MiniMap">
+ <Property key="CanvasSize" value="1536 1536"/>
+
+ <Widget type="ImageBox" skin="RotatingSkin" position="0 0 32 32" align="Bottom Left" name="Compass">
+ <Property key="ImageTexture" value="textures\compass.dds"/>
+ </Widget>
+
+ <Widget type="Button" skin="" position="0 0 1536 1536" name="MiniMapButton" align="Right Bottom">
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+ </Widget>
+
+ <!-- Crosshair -->
+ <Widget type="ImageBox" skin="ImageBox" position="0 0 32 32" align="Center Center" name="Crosshair">
+ <Property key="ImageTexture" value="textures\target.dds"/>
+ </Widget>
+
+ <!-- Basic FPSCounter box -->
+ <Widget type="Widget" skin="HUD_Box" position="12 12 32 26" align="Left Top" name="FPSBox">
+ <Property key="Visible" value="false"/>
+ <Widget type="TextBox" skin="NumFPS" position="3 3 25 17" align="Center" name="FPSCounter"/>
+ </Widget>
+
+ <!-- Advanced FPSCounter box -->
+ <Widget type="Widget" skin="HUD_Box" position="12 12 165 64" align="Left Top" name="FPSBoxAdv">
+ <Property key="Visible" value="false"/>
+
+ <Widget type="Widget" skin="" position="0 0 110 60" align="Left Top">
+
+ <Widget type="TextBox" skin="NumFPS" position="0 0 110 32" align="Left Top">
+ <Property key="Caption" value="FPS: "/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NumFPS" position="0 16 110 32" align="Left Top">
+ <Property key="Caption" value="Tri Count: "/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NumFPS" position="0 32 110 32" align="Left Top">
+ <Property key="Caption" value="Batch Count: "/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="Widget" skin="" position="110 0 55 60" align="Left Top">
+
+ <Widget type="TextBox" skin="NumFPS" position="0 0 55 32" align="Left Top" name="FPSCounterAdv">
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NumFPS" position="0 16 55 32" align="Left Top" name="TriangleCounter">
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NumFPS" position="0 32 55 32" align="Left Top" name="BatchCounter">
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml
new file mode 100644
index 0000000000..dd8172661b
--- /dev/null
+++ b/files/mygui/openmw_hud_box.skin.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <!-- The 'box' frame that surrounds various HUD items and other elements -->
+
+ <Skin name="HUD_Box" size="40 40">
+
+ <!-- Borders -->
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 40 40" align="Left Stretch" name="Client"/>
+
+ <!-- The interior of the box -->
+
+ <Child type="Widget" skin="BlackBG" offset="2 2 36 36" align="Stretch" name="Client"/>
+
+ </Skin>
+
+ <Skin name="HUD_Box_Transparent" size="40 40">
+ <Child type="Widget" skin="MW_Box" offset="0 0 40 40" align="Left Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="HUD_Box_NoTransp" size="40 40">
+
+ <!-- Borders -->
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 40 40" align="Left Stretch" name="Client"/>
+
+ <!-- The interior of the box -->
+
+ <Child type="Widget" skin="DialogBG" offset="2 2 36 36" align="Stretch" name="Client"/>
+
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_hud_energybar.skin.xml b/files/mygui/openmw_hud_energybar.skin.xml
new file mode 100644
index 0000000000..f10908d7b0
--- /dev/null
+++ b/files/mygui/openmw_hud_energybar.skin.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <!-- Energy bar frame graphics -->
+ <Skin name="HUD_Bar_Top" size="64 2" texture="textures\menu_small_energy_bar_top.dds">
+ <BasisSkin type="MainSkin" offset="0 0 64 2">
+ <State name="normal" offset="0 0 64 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="HUD_Bar_Bottom" size="64 2" texture="textures\menu_small_energy_bar_bottom.dds">
+ <BasisSkin type="MainSkin" offset="0 0 64 2">
+ <State name="normal" offset="0 0 64 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="HUD_Bar_Side" size="2 8" texture="textures\menu_small_energy_bar_vert.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 8">
+ <State name="normal" offset="0 0 2 8"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Progress bar track, various colors -->
+ <Skin name="MW_BarTrack_Red" size="4 8" texture="smallbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 4 8" align="Stretch">
+ <State name="normal" offset="0 0 4 8"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BarTrack_Green" size="4 8" texture="smallbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 4 8" align="Stretch">
+ <State name="normal" offset="0 16 4 8"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BarTrack_Blue" size="4 8" texture="smallbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 4 8" align="Stretch">
+ <State name="normal" offset="0 8 4 8"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BarTrack_Yellow" size="4 8" texture="smallbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 4 8" align="Stretch">
+ <State name="normal" offset="0 32 4 8"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Main energy bar widget definitions. There's one for each color.-->
+
+ <Skin name="MW_EnergyBar_Red" size="64 12">
+ <Property key="TrackSkin" value="MW_BarTrack_Red"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_EnergyBar_Green" size="64 12">
+ <Property key="TrackSkin" value="MW_BarTrack_Green"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_EnergyBar_Blue" size="64 12">
+ <Property key="TrackSkin" value="MW_BarTrack_Blue"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_EnergyBar_Yellow" size="64 12">
+ <Property key="TrackSkin" value="MW_BarTrack_Yellow"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_infobox.layout b/files/mygui/openmw_infobox.layout
new file mode 100644
index 0000000000..4e70cd2c4f
--- /dev/null
+++ b/files/mygui/openmw_infobox.layout
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 545 265" name="_Main">
+
+ <!-- Edit box -->
+ <Widget type="Widget" skin="" position="14 14 516 70" name="TextBox" align="Top HCenter">
+ <Widget type="TextBox" skin="SandText" position="4 4 508 62" name="Text" align="Top HCenter">
+ <Property key="WordWrap" value="1" />
+ </Widget>
+ </Widget>
+
+ <!-- Button bar, buttons are created as children -->
+ <Widget type="Widget" skin="" position="72 98 400 150" name="ButtonBar" align="Top HCenter" />
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_interactive_messagebox.layout b/files/mygui/openmw_interactive_messagebox.layout
new file mode 100644
index 0000000000..d3ac1f8b56
--- /dev/null
+++ b/files/mygui/openmw_interactive_messagebox.layout
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 500 400" name="_Main">
+ <Widget type="EditBox" skin="MW_TextEditClient" position="10 10 490 20" align="Left Top Stretch" name="message">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+ <Property key="Static" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="MultiLine" value="1"/>
+ <Property key="VisibleVScroll" value="1"/>
+ </Widget>
+ <Widget type="Widget" skin="" position="0 0 500 400" align="Stretch" name="buttons">
+ <!-- Widget type="Button" skin="MW_Button" position="0 0 30 18" name="somefunnybutton" / -->
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout
new file mode 100644
index 0000000000..ecccd995bc
--- /dev/null
+++ b/files/mygui/openmw_inventory_window.layout
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="ExposedWindow" skin="MW_Window_Pinnable" layer="Windows" position="0 0 600 300" name="_Main">
+ <Property key="MinSize" value="470 235"/>
+
+ <Widget type="Widget" skin="" position="0 0 224 223" align="Left Top" name="LeftPane">
+
+ <!-- Player encumbrance -->
+ <Widget type="MWDynamicStat" skin="MW_ChargeBar_Blue" position="8 8 212 24" name="EncumbranceBar" align="Left Top HStretch">
+ </Widget>
+
+ <!-- Avatar -->
+ <Widget type="Widget" skin="MW_Box" position="8 38 212 185" name="Avatar" align="Left Top Stretch">
+ <UserString key="ToolTipType" value="AvatarItemSelection"/>
+ <Widget type="ImageBox" skin="ImageBox" position="0 0 212 185" align="Stretch" name="AvatarImage">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="ProgressText" position="0 150 212 24" align="HCenter Bottom" name="ArmorRating">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="Widget" skin="" position="228 0 350 223" align="Left Top" name="RightPane">
+
+ <!-- Items in inventory -->
+ <Widget type="ItemView" skin="MW_ItemView" position="0 38 350 185" name="ItemView" align="Left Top Stretch">
+ </Widget>
+
+ <!-- Categories -->
+ <Widget type="HBox" position="0 8 350 24" align="Left Top HStretch" name="Categories">
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 60 24" name="AllButton">
+ <Property key="Caption" value="#{sAllTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 60 24" name="WeaponButton">
+ <Property key="Caption" value="#{sWeaponTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 60 24" name="ApparelButton">
+ <Property key="Caption" value="#{sApparelTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 60 24" name="MagicButton">
+ <Property key="Caption" value="#{sMagicTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 60 24" name="MiscButton">
+ <Property key="Caption" value="#{sMiscTab}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout
new file mode 100644
index 0000000000..003eb1c6ac
--- /dev/null
+++ b/files/mygui/openmw_itemselection_dialog.layout
@@ -0,0 +1,17 @@
+<?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="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>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="340 110 24 24" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout
new file mode 100644
index 0000000000..34421d4313
--- /dev/null
+++ b/files/mygui/openmw_journal.layout
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+
+ <Widget type="Window" skin="" layer="Windows" align="Left|Top" position="0 0 565 390" name="_Main">
+ <!-- pages -->
+ <Widget type="ImageBox" skin="ImageBox" position="-70 0 705 390" align="Top|Right" name="JImage">
+ <Property key="ImageTexture" value="textures\tx_menubook.dds"/>
+ <Widget type="Widget" position="70 0 565 390" align="Top|Right">
+
+
+ <Widget type="Widget" position="0 0 282 390">
+ <Widget type="ImageButton" skin="ImageBox" position="205 350 48 32" name="PrevPageBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_prev_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_prev_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_prev_pressed.dds"/>
+ </Widget>
+ </Widget>
+ <Widget type="Widget" position="282 0 282 390">
+ <Widget type="ImageButton" skin="ImageBox" position="18 350 48 32" name="NextPageBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_next_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_next_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_next_pressed.dds"/>
+ </Widget>
+ </Widget>
+
+ <!-- buttons -->
+ <Widget type="ImageButton" skin="ImageBox" position="40 350 64 32" name="OptionsBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_options_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_options_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_options_pressed.dds"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="150 350 32 16" name="PageOneNum">
+ <Property key="TextColour" value="0 0 0"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="410 350 32 16" name="PageTwoNum">
+ <Property key="TextColour" value="0 0 0"/>
+ </Widget>
+ <Widget type="ImageButton" skin="ImageBox" position="460 350 48 32" name="CloseBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_close_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_close_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_close_pressed.dds"/>
+ </Widget>
+ <Widget type="ImageButton" skin="ImageBox" position="460 350 64 32" name="JournalBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_journal_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_journal_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_journal_pressed.dds"/>
+ </Widget>
+
+
+ <!-- text pages -->
+ <Widget type="BookPage" skin="MW_BookPage" position="30 22 240 300" name="LeftBookPage"/>
+ <Widget type="BookPage" skin="MW_BookPage" position="300 22 240 300" name="RightBookPage"/>
+
+ <!-- options overlay -->
+ </Widget>
+ </Widget>
+
+ <!-- "options" -->
+ <Widget type="ImageBox" skin="ImageBox" position="300 0 224 350" name="OptionsOverlay">
+ <Property key="ImageTexture" value="textures\tx_menubook_bookmark.dds"/>
+ <Property key="ImageCoord" value="0 0 164 256"/>
+
+ <Widget type="BookPage" skin="MW_BookPage" position="20 15 92 250" name="LeftTopicIndex"/>
+ <Widget type="BookPage" skin="MW_BookPage" position="112 15 92 250" name="RightTopicIndex"/>
+
+ <Widget type="ImageButton" skin="ImageBox" position="62 15 100 20" Align="Top|Left" name="ShowActiveBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_quests_active_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_quests_active_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_quests_active_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="76 15 72 20" Align="Top|Left" name="ShowAllBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_quests_all_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_quests_all_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_quests_all_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ScrollView" skin="MW_ScrollView" position="20 15 184 245" name="TopicsList" align="Right VStretch">
+ <Property key="CanvasAlign" value="Left Top"/>
+ <Widget type="BookPage" skin="MW_BookPage" position="0 0 30000 30000" name="TopicsPage"/>
+ </Widget>
+
+ <Widget type="ScrollView" skin="MW_ScrollView" position="20 35 184 225" name="QuestsList" align="Right VStretch">
+ <Property key="CanvasAlign" value="Left Top"/>
+ <Widget type="BookPage" skin="MW_BookPage" position="0 0 30000 30000" name="QuestsPage"/>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="20 265 56 32" Align="Top|Left" name="TopicsBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_topics_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_topics_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_topics_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="130 265 56 32" Align="Top|Left" name="QuestsBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_quests_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_quests_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_quests_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="85 290 56 32" Align="Top|Left" name="CancelBTN">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_cancel_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_cancel_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_cancel_pressed.dds"/>
+ </Widget>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml
new file mode 100644
index 0000000000..ca6d309d79
--- /dev/null
+++ b/files/mygui/openmw_journal_skin.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <Skin name="MW_BookClient" size="10 10">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <Property key="TextColour" value="0 0 0"/>
+ <!--Property key="Pointer" value="beam" /-->
+ <BasisSkin type="EditText" offset="0 0 10 10" align="Stretch"/>
+ </Skin>
+
+ <Skin name="MW_BookPage" size="0 0 50 50">
+ <BasisSkin type="PageDisplay"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml
new file mode 100644
index 0000000000..84ec6f7c50
--- /dev/null
+++ b/files/mygui/openmw_layers.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layer">
+ <Layer name="Scene" overlapped="false" peek="true"/>
+ <Layer name="HUD" overlapped="false" peek="true"/>
+ <Layer name="Windows" overlapped="true" peek="true"/>
+ <Layer name="Console" overlapped="false" peek="true"/>
+ <Layer name="Notification" overlapped="false" peek="false"/>
+ <Layer name="Popup" overlapped="true" peek="true"/>
+ <Layer name="DragAndDrop" overlapped="false" peek="false"/>
+ <Layer name="LoadingScreen" overlapped="false" peek="true"/>
+ <Layer name="Pointer" overlapped="false" peek="false"/>
+</MyGUI>
diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout
new file mode 100644
index 0000000000..765bf88a83
--- /dev/null
+++ b/files/mygui/openmw_levelup_dialog.layout
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="VBox" skin="MW_Dialog" layer="Windows" position="0 0 0 0" name="_Main">
+ <Property key="Padding" value="12"/>
+ <Property key="Spacing" value="8"/>
+ <Property key="AutoResize" value="true"/>
+
+ <Widget type="Widget" skin="MW_Box" position="28 14 391 198">
+ <UserString key="HStretch" value="false"/>
+ <UserString key="VStretch" value="false"/>
+ <Widget type="ImageBox" skin="ImageBox" name="ClassImage" position="4 4 383 190">
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="28 218 391 24" name="LevelText">
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="36 280 330 24" name="LevelDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="Static" value="true"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="0 0 100 16" name="Coins">
+ <UserString key="HStretch" value="false"/>
+ <UserString key="VStretch" value="false"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="0 280 350 100">
+ <UserString key="HStretch" value="false"/>
+ <UserString key="VStretch" value="false"/>
+
+ <Widget type="TextBox" skin="SandText" position="0 0 100 24" name="AttribMultiplier1"/>
+ <Widget type="TextBox" skin="SandText" position="0 24 100 24" name="AttribMultiplier2"/>
+ <Widget type="TextBox" skin="SandText" position="0 48 100 24" name="AttribMultiplier3"/>
+ <Widget type="TextBox" skin="SandText" position="0 72 100 24" name="AttribMultiplier4"/>
+ <Widget type="TextBox" skin="SandText" position="200 0 100 24" name="AttribMultiplier5"/>
+ <Widget type="TextBox" skin="SandText" position="200 24 100 24" name="AttribMultiplier6"/>
+ <Widget type="TextBox" skin="SandText" position="200 48 100 24" name="AttribMultiplier7"/>
+ <Widget type="TextBox" skin="SandText" position="200 72 100 24" name="AttribMultiplier8"/>
+
+ <Widget type="HBox" position="22 0 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib1">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeStrength}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal1">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="22 24 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib2">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeIntelligence}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal2">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="22 48 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib3">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeWillpower}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal3">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="22 72 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib4">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeAgility}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal4">
+ </Widget>
+ </Widget>
+
+
+ <Widget type="HBox" position="222 0 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib5">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeSpeed}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal5">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="222 24 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib6">
+ <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"/>
+ <Property key="Caption" value="#{sAttributeEndurance}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal6">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="222 48 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib7">
+ <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"/>
+ <Property key="Caption" value="#{sAttributePersonality}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal7">
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="222 72 200 24">
+ <Widget type="AutoSizedButton" skin="SandTextButton" name="Attrib8">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="AttributeToolTip"/>
+ <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
+ <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
+ <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
+ <Property key="Caption" value="#{sAttributeLuck}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="AttribVal8">
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" skin="" position="0 0 330 24">
+ <UserString key="HStretch" value="true"/>
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 0 0 24" name="OkButton">
+ <Property key="Caption" value="#{sOk}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+
+</MyGUI>
diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml
new file mode 100644
index 0000000000..02c11c3549
--- /dev/null
+++ b/files/mygui/openmw_list.skin.xml
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <!-- Horizontal Scrollbar -->
+
+ <Skin name="MW_HScroll" size="90 14">
+ <Property key="TrackRangeMargins" value="14 14"/>
+ <Property key="MinTrackSize" value="14"/>
+ <Property key="VerticalAlignment" value="false"/>
+ <Property key="MoveToClick" value="true"/>
+
+ <!-- Tracker must be last to be on top and receive mouse events -->
+
+ <Child type="Button" skin="MW_Box" offset="18 0 54 14" align="Stretch" name="Background"/>
+
+ <!-- Arrows -->
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 15 14" align="Left VStretch"/>
+ <Child type="Button" skin="MW_ArrowLeft" offset="3 2 10 10" align="Left VStretch" name="Start"/>
+
+ <Child type="Widget" skin="MW_Box" offset="75 0 15 14" align="Right VStretch"/>
+ <Child type="Button" skin="MW_ArrowRight" offset="76 2 10 10" align="Right VStretch" name="End"/>
+
+ <!-- These are only provided to get mouse input, they should have no skin and be transparent -->
+
+ <Child type="Button" skin="MW_ScrollEmptyPart" offset="14 0 24 14" align="Top HStretch" name="FirstPart"/>
+ <Child type="Button" skin="MW_ScrollEmptyPart" offset="52 0 24 14" align="Top HStretch" name="SecondPart"/>
+
+ <Child type="Button" skin="MW_ScrollTrackH" offset="38 2 30 9" align="Left VStretch" name="Track"/>
+ </Skin>
+
+ <Skin name="MW_ScrollTrackH" size="16 16" texture="textures\tx_menubook_bookmark.dds">
+ <BasisSkin type="TileRect" offset="6 0 3 16" align="Stretch">
+ <State name="normal" offset="70 22 16 16">
+ <Property key="TileSize" value="16 16"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Vertical Scrollbar -->
+
+ <Skin name="MW_VScroll" size="14 90">
+ <Property key="TrackRangeMargins" value="14 14"/>
+ <Property key="MinTrackSize" value="14"/>
+ <Property key="MoveToClick" value="true"/>
+
+ <!-- Background widget trick that must go first to be placed behind Track and FirstPart/SecondPart widgets, provides the bar texture -->
+
+ <Child type="Button" skin="MW_Box" offset="0 18 14 55" align="Stretch" name="Background"/>
+
+ <!-- Arrows -->
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 14 15" align="Top HStretch"/>
+ <Child type="Button" skin="MW_ArrowUp" offset="2 3 10 10" align="Top HStretch" name="Start"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 76 14 15" align="Bottom HStretch"/>
+ <Child type="Button" skin="MW_ArrowDown" offset="2 77 10 10" align="Bottom HStretch" name="End"/>
+
+ <!-- These are only provided to get mouse input, they should have no skin and be transparent -->
+
+ <Child type="Button" skin="MW_ScrollEmptyPart" offset="0 14 24 14" align="Left VStretch" name="FirstPart"/>
+ <Child type="Button" skin="MW_ScrollEmptyPart" offset="0 52 24 14" align="Left VStretch" name="SecondPart"/>
+
+ <!-- Tracker must be last to be on top and receive mouse events -->
+
+ <Child type="Button" skin="MW_ScrollTrackV" offset="2 40 9 30" align="Top HStretch" name="Track"/>
+
+ </Skin>
+
+ <Skin name="MW_ScrollTrackV" size="16 16" texture="textures\tx_menubook_bookmark.dds">
+ <BasisSkin type="TileRect" offset="0 7 16 3" align="Stretch">
+ <State name="normal" offset="68 19 16 16">
+ <Property key="TileSize" value="16 16"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Empty space in scrollbar -->
+
+ <Skin name="MW_ScrollEmptyPart" size="16 16" >
+ </Skin>
+
+ <!-- Header text -->
+
+ <Skin name="HeaderText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.82 0.74 0.58"/>
+
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <!-- list and multilist skins -->
+
+ <Skin name="MW_ListLine" size="5 5">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left VCenter"/>
+
+ <BasisSkin type="SimpleText" offset="2 0 1 5" align="Stretch">
+ <State name="disabled" colour="0.70 0.57 0.33" shift="0"/>
+ <State name="normal" colour="0.70 0.57 0.33" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="MW_List" 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="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"/>
+
+ <Child type="ScrollView" skin="MW_ScrollViewH" offset="3 3 509 509" align="Stretch" name="ScrollView">
+ <Property key="CanvasAlign" value="Left Top"/>
+ <Property key="NeedMouse" value="true"/>
+ </Child>
+ </Skin>
+
+ <Skin name="MW_SimpleList" size="516 516" align="Left Top">
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 516 516" align="Stretch"/>
+
+ <Child type="Widget" skin="" offset="3 3 510 510" align="Top Left Stretch" name="Client"/>
+
+ </Skin>
+
+ <Skin name="MW_MultiSubList" 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="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="Default" offset="3 3 493 509" align="Stretch" name="Client"/>
+
+ </Skin>
+
+ <Skin name="MW_MultiList" size="516 516" align="Left Top">
+ <Property key="NeedKey" value="true"/>
+ <Property key="SkinButton" value="ButtonSmall"/>
+ <Property key="_SkinButtonEmpty" value="EditBox"/>
+ <Property key="HeightButton" value="20"/>
+
+ <Property key="SkinList" value="MW_MultiSubList"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 516 516" align="Stretch"/>
+
+ <Child type="Widget" skin="" offset="3 3 516 516" align="Stretch" name="Client"/>
+ </Skin>
+
+ <!-- Horizontal line -->
+
+ <Skin name="MW_HLine" size="512 10" texture="textures\menu_thin_border_top.dds">
+ <BasisSkin type="SubSkin" offset="0 0 512 2" align="Bottom HStretch">
+ <State name="normal" offset="0 0 512 2"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Arrows -->
+
+ <Skin name="MW_ArrowLeft" size="20 20" texture="textures\menu_scroll_left.dds">
+ <BasisSkin type="SubSkin" offset="0 0 19 20" align="Stretch">
+ <State name="normal" offset="0 0 19 20"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="MW_ArrowRight" size="20 20" texture="textures\menu_scroll_right.dds">
+ <BasisSkin type="SubSkin" offset="1 0 19 20" align="Stretch">
+ <State name="normal" offset="1 0 19 20"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="MW_ArrowUp" size="20 20" texture="textures\menu_scroll_up.dds">
+ <BasisSkin type="SubSkin" offset="0 0 20 19" align="Stretch">
+ <State name="normal" offset="0 0 20 19"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="MW_ArrowDown" size="20 20" texture="textures\menu_scroll_down.dds">
+ <BasisSkin type="SubSkin" offset="0 1 20 20" align="Stretch">
+ <State name="normal" offset="0 1 20 20"/>
+ </BasisSkin>
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout
new file mode 100644
index 0000000000..5fd3440f91
--- /dev/null
+++ b/files/mygui/openmw_loading_screen.layout
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <!-- The entire screen -->
+ <Widget type="Widget" layer="LoadingScreen" position="0 0 300 300" name="_Main">
+
+ <Widget type="ImageBox" skin="ImageBox" position="0 0 300 300" align="Stretch" name="BackgroundImage">
+
+ <Widget type="Widget" skin="HUD_Box" position="0 200 300 60" align="Bottom HCenter">
+
+ <Widget type="TextBox" skin="SandText" position="20 12 260 20" name="LoadingText">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="ScrollBar" skin="MW_ProgressScroll_Loading" position="20 36 260 8" name="ProgressBar">
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_magicselection_dialog.layout b/files/mygui/openmw_magicselection_dialog.layout
new file mode 100644
index 0000000000..a89795473f
--- /dev/null
+++ b/files/mygui/openmw_magicselection_dialog.layout
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" position="0 0 330 370" layer="Windows" name="_Main">
+
+ <Widget type="TextBox" skin="SandText" position="8 8 292 24">
+ <Property key="Caption" value="Select a magic to quick key."/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="8 38 306 285" name="box" align="Left Top Stretch">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 298 277" name="MagicList" align="Left Top Stretch">
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton" position="284 330 32 24" align="Right Bottom">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout
new file mode 100644
index 0000000000..4479a121f6
--- /dev/null
+++ b/files/mygui/openmw_mainmenu.layout
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <!-- The entire screen -->
+ <Widget type="Widget" layer="Windows" position="0 0 300 300" name="_Main" />
+</MyGUI>
diff --git a/files/mygui/openmw_mainmenu_skin.xml b/files/mygui/openmw_mainmenu_skin.xml
new file mode 100644
index 0000000000..c7f2fbce34
--- /dev/null
+++ b/files/mygui/openmw_mainmenu_skin.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+</MyGUI>
diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout
new file mode 100644
index 0000000000..232f31b754
--- /dev/null
+++ b/files/mygui/openmw_map_window.layout
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="ExposedWindow" skin="MW_Window_Pinnable" layer="Windows" position="0 0 300 300" name="_Main">
+ <Property key="MinSize" value="230 180"/>
+
+ <!-- Local map -->
+ <Widget type="ScrollView" skin="MW_MapView" position="0 0 284 264" align="Stretch" name="LocalMap">
+ <Property key="CanvasSize" value="1536 1536"/>
+
+ <Widget type="ImageBox" skin="RotatingSkin" position="0 0 32 32" align="Top Left" name="CompassLocal">
+ <Property key="ImageTexture" value="textures\compass.dds"/>
+ </Widget>
+
+ <Widget type="Button" skin="" position_real="0 0 1 1" name="EventBoxLocal" align="Stretch"/>
+ </Widget>
+
+ <!-- Global map -->
+ <Widget type="ScrollView" skin="MW_MapView" position="0 0 284 264" align="Stretch" name="GlobalMap">
+ <Property key="CanvasSize" value="1536 1536"/>
+
+ <Widget type="ImageBox" skin="ImageBox" position_real="0 0 1 1" align="Stretch" name="GlobalMapImage">
+ <Widget type="ImageBox" skin="ImageBox" position_real="0 0 1 1" align="Stretch" name="GlobalMapOverlay"/>
+ </Widget>
+
+ <Widget type="ImageBox" skin="RotatingSkin" position="0 0 32 32" align="Top Left" name="CompassGlobal">
+ <Property key="ImageTexture" value="textures\compass.dds"/>
+ </Widget>
+
+ <Widget type="Button" skin="" position_real="0 0 1 1" name="EventBoxGlobal" align="Stretch"/>
+ </Widget>
+
+
+ <!-- World button -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="213 233 61 22" align="Bottom Right" name="WorldButton">
+ <Property key="ExpandDirection" value="Left"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_map_window_skin.xml b/files/mygui/openmw_map_window_skin.xml
new file mode 100644
index 0000000000..887e33293c
--- /dev/null
+++ b/files/mygui/openmw_map_window_skin.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <Skin name="MW_MapView" size="516 516">
+ <Child type="Widget" skin="" offset="0 0 516 516" align="Stretch" name="Client"/>
+
+ <!-- invisible scroll bars, needed for setting the view offset -->
+ <Child type="MWScrollBar" skin="" offset="0 0 0 0" align="Default" name="VScroll"/>
+ <Child type="MWScrollBar" skin="" offset="0 0 0 0" align="Default" name="HScroll"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_merchantrepair.layout b/files/mygui/openmw_merchantrepair.layout
new file mode 100644
index 0000000000..360f5f4f09
--- /dev/null
+++ b/files/mygui/openmw_merchantrepair.layout
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 418 248" name="_Main">
+ <Property key="Visible" value="false"/>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 18 418 24" align="Right Top">
+ <Property key="TextAlign" value="Left"/>
+ <Property key="Caption" value="#{sRepairServiceTitle}"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="0 0 418 24" align="Right Top">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="#{sServiceRepairTitle}"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="10 46 389 156" align="Left Stretch">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 381 147" align="Left Top Stretch" name="RepairView">
+ <Property key="CanvasAlign" value="Left"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="10 210 300 24" name="PlayerGold" align="Right Top">
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="343 210 60 24" name="OkButton" align="Right Top">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout
new file mode 100644
index 0000000000..dfdb57648d
--- /dev/null
+++ b/files/mygui/openmw_messagebox.layout
@@ -0,0 +1,18 @@
+<?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">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+ <Property key="Static" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="MultiLine" value="1"/>
+ <Property key="VisibleVScroll" value="1"/>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_persuasion_dialog.layout b/files/mygui/openmw_persuasion_dialog.layout
new file mode 100644
index 0000000000..87851b479b
--- /dev/null
+++ b/files/mygui/openmw_persuasion_dialog.layout
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 220 194" name="_Main">
+
+ <Widget type="TextBox" skin="NormalText" position="0 6 220 24">
+ <Property key="Caption" value="#{sPersuasionMenuTitle}"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="8 156 208 24" name="GoldLabel">
+ <Property key="TextAlign" value="Left VCenter"/>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="8 32 196 115">
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 0 0 18" name="AdmireButton">
+ <Property key="Caption" value="#{sAdmire}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 18 0 18" name="IntimidateButton">
+ <Property key="Caption" value="#{sIntimidate}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 36 0 18" name="TauntButton">
+ <Property key="Caption" value="#{sTaunt}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 54 0 18" name="Bribe10Button">
+ <Property key="Caption" value="#{sBribe 10 Gold}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 72 0 18" name="Bribe100Button">
+ <Property key="Caption" value="#{sBribe 100 Gold}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="SandTextButton" position="4 90 0 18" name="Bribe1000Button">
+ <Property key="Caption" value="#{sBribe 1000 Gold}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="204 156 0 24" name="CancelButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_pointer.xml b/files/mygui/openmw_pointer.xml
new file mode 100644
index 0000000000..a55a5453c0
--- /dev/null
+++ b/files/mygui/openmw_pointer.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Resource">
+ <Resource type="ResourceImageSetPointer" name="arrow">
+ <Property key="Point" value="7 0"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="ArrowPointerImage"/>
+ <Property key="Rotation" value="0"/>
+ </Resource>
+ <Resource type="ResourceImageSetPointer" name="hresize">
+ <Property key="Point" value="16 14"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="HResizePointerImage"/>
+ <Property key="Rotation" value="0"/>
+ </Resource>
+ <Resource type="ResourceImageSetPointer" name="vresize">
+ <Property key="Point" value="17 16"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="HResizePointerImage"/>
+ <Property key="Rotation" value="90"/>
+ </Resource>
+ <Resource type="ResourceImageSetPointer" name="dresize">
+ <Property key="Point" value="17 15"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="HResizePointerImage"/>
+ <Property key="Rotation" value="45"/>
+ </Resource>
+ <Resource type="ResourceImageSetPointer" name="dresize2">
+ <Property key="Point" value="15 15"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="HResizePointerImage"/>
+ <Property key="Rotation" value="-45"/>
+ </Resource>
+ <Resource type="ResourceImageSetPointer" name="drop_ground">
+ <Property key="Point" value="0 24"/>
+ <Property key="Size" value="32 32"/>
+ <Property key="Resource" value="DropGroundPointerImage"/>
+ </Resource>
+</MyGUI>
diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml
new file mode 100644
index 0000000000..35114ffebb
--- /dev/null
+++ b/files/mygui/openmw_progress.skin.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <!-- Progress bar track, various colors -->
+ <Skin name="MW_BigTrack_Red" size="2 14" texture="bigbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 2 14" align="Stretch">
+ <State name="normal" offset="0 0 2 14"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BigTrack_Blue" size="2 14" texture="bigbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 2 14" align="Stretch">
+ <State name="normal" offset="0 14 2 14"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BigTrack_Green" size="2 14" texture="bigbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 2 14" align="Stretch">
+ <State name="normal" offset="0 28 2 14"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="MW_BigTrack_Progress_Blue_Small" size="2 6" texture="smallbars.png" >
+ <BasisSkin type="MainSkin" offset="0 0 2 6" align="Stretch">
+ <State name="normal" offset="0 26 2 6"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="ProgressText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <!-- Main energy bar widget definitions. There's one for each color.-->
+
+ <Skin name="MW_Progress_Red" size="64 12">
+ <Property key="TrackSkin" value="MW_BigTrack_Red"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_Progress_Green" size="64 12">
+ <Property key="TrackSkin" value="MW_BigTrack_Green"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_Progress_Blue" size="64 12">
+ <Property key="TrackSkin" value="MW_BigTrack_Blue"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 12" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 8" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_Progress_Loading" size="64 6">
+ <Property key="TrackSkin" value="MW_BigTrack_Progress_Blue_Small"/>
+ <Property key="TrackWidth" value="1"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 6" align="Stretch"/>
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 2" align="Stretch" name="Client"/>
+ </Skin>
+
+ <Skin name="MW_ProgressScroll_Loading" size="64 6">
+ <Property key="TrackWidth" value="1"/>
+ <Property key="TrackRangeMargins" value="0 0"/>
+ <Property key="MinTrackSize" value="1"/>
+ <Property key="VerticalAlignment" value="false"/>
+ <Property key="MoveToClick" value="false"/>
+
+ <Child type="Widget" skin="BlackBG" offset="2 2 60 2" align="Stretch" name="Client"/>
+ <Child type="Button" skin="MW_BigTrack_Progress_Blue_Small" offset="0 0 1 6" align="Left VStretch" name="Track"/>
+
+ <Child type="Widget" skin="MW_Box" offset="0 0 64 6" align="Stretch"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_quickkeys_menu.layout b/files/mygui/openmw_quickkeys_menu.layout
new file mode 100644
index 0000000000..dcb10404df
--- /dev/null
+++ b/files/mygui/openmw_quickkeys_menu.layout
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" position="0 0 370 230" layer="Windows" name="_Main">
+
+ <Widget type="TextBox" skin="SandText" position="8 8 354 18">
+ <Property key="Caption" value="#{sQuickMenuTitle}"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="EditBox" skin="SandText" position="8 26 354 18" name="InstructionLabel" align="Left Top VStretch">
+ <Property key="Caption" value="#{sQuickMenuInstruc}"/>
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="Static" value="true"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="15 55 332 128" align="Left Bottom HCenter">
+
+ <Widget type="Button" skin="MW_Box" position="0 0 60 59" name="QuickKey1"/>
+ <Widget type="Button" skin="MW_Box" position="68 0 60 59" name="QuickKey2"/>
+ <Widget type="Button" skin="MW_Box" position="136 0 60 59" name="QuickKey3"/>
+ <Widget type="Button" skin="MW_Box" position="204 0 60 59" name="QuickKey4"/>
+ <Widget type="Button" skin="MW_Box" position="272 0 60 59" name="QuickKey5"/>
+ <Widget type="Button" skin="MW_Box" position="0 67 60 59" name="QuickKey6"/>
+ <Widget type="Button" skin="MW_Box" position="68 67 60 59" name="QuickKey7"/>
+ <Widget type="Button" skin="MW_Box" position="136 67 60 59" name="QuickKey8"/>
+ <Widget type="Button" skin="MW_Box" position="204 67 60 59" name="QuickKey9"/>
+ <Widget type="Button" skin="MW_Box" position="272 67 60 59" name="QuickKey10"/>
+
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OKButton" position="315 190 32 24" align="Right Bottom">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_quickkeys_menu_assign.layout b/files/mygui/openmw_quickkeys_menu_assign.layout
new file mode 100644
index 0000000000..4bbd9f1fa5
--- /dev/null
+++ b/files/mygui/openmw_quickkeys_menu_assign.layout
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" position="0 0 16 154" layer="Windows" name="_Main">
+
+ <Widget type="TextBox" skin="SandText" position="8 8 0 18" name="Label">
+ <Property key="Caption" value="#{sQuickMenu1}"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="Button" skin="MW_Button" position="8 28 0 24" name="ItemButton">
+ <Property key="Caption" value="#{sQuickMenu2}"/>
+ </Widget>
+
+ <Widget type="Button" skin="MW_Button" position="8 56 0 24" name="MagicButton">
+ <Property key="Caption" value="#{sQuickMenu3}"/>
+ </Widget>
+
+ <Widget type="Button" skin="MW_Button" position="8 84 0 24" name="UnassignButton">
+ <Property key="Caption" value="#{sQuickMenu4}"/>
+ </Widget>
+
+
+ <Widget type="Button" skin="MW_Button" position="8 112 0 24" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_repair.layout b/files/mygui/openmw_repair.layout
new file mode 100644
index 0000000000..2881a5853a
--- /dev/null
+++ b/files/mygui/openmw_repair.layout
@@ -0,0 +1,33 @@
+<?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="ToolBox">
+ <Widget type="ImageBox" skin="ImageBox" position="5 6 32 32" name="ToolIcon"/>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="55 13 300 18" name="UsesLabel">
+ <Property key="Caption" value="#{sUses}"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="55 13 250 18" name="QualityLabel">
+ <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="RepairBox">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 301 152" align="Left Top Stretch" name="RepairView">
+ <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
new file mode 100644
index 0000000000..2c3908a1b9
--- /dev/null
+++ b/files/mygui/openmw_resources.xml
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Resource">
+
+ <!-- Cursors -->
+ <Resource type="ResourceImageSet" name="ArrowPointerImage">
+ <Group name="Pointer" texture="textures\tx_cursor.dds" size="32 32">
+ <Index name="Pointer" >
+ <Frame point="0 0"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="HResizePointerImage">
+ <Group name="Pointer" texture="textures\tx_cursormove.dds" size="32 32">
+ <Index name="Pointer" >
+ <Frame point="0 0"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="DropGroundPointerImage">
+ <Group name="Pointer" texture="textures\cursor_drop_ground.dds" size="32 32">
+ <Index name="Pointer" >
+ <Frame point="0 0"/>
+ </Index>
+ </Group>
+ </Resource>
+
+
+
+
+ <!-- Journal, book, scroll -->
+ <Resource type="ResourceImageSet" name="MenuBook_Close">
+ <Group name="States" texture="mwgui1" size="96 24">
+ <Index name="disabled">
+ <Frame point="0 0"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="0 0"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="128 0"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="256 0"/>
+ </Index>
+ </Group>
+ </Resource>
+
+ <Resource type="ResourceImageSet" name="MenuBook_Take">
+ <Group name="States" texture="mwgui1" size="96 24">
+ <Index name="disabled">
+ <Frame point="384 0"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="384 0"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="0 32"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="128 32"/>
+ </Index>
+ </Group>
+ </Resource>
+
+ <Resource type="ResourceImageSet" name="MenuBook_Next">
+ <Group name="States" texture="mwgui1" size="57 24">
+ <Index name="disabled">
+ <Frame point="256 32"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="256 32"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="384 32"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="0 64"/>
+ </Index>
+ </Group>
+ </Resource>
+
+ <Resource type="ResourceImageSet" name="MenuBook_Prev">
+ <Group name="States" texture="mwgui1" size="96 24">
+ <Index name="disabled">
+ <Frame point="128 64"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="128 64"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="256 64"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="384 64"/>
+ </Index>
+ </Group>
+ </Resource>
+
+
+
+
+ <!-- Main menu -->
+ <Resource type="ResourceImageSet" name="Menu_NewGame">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="0 0"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="0 0"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="256 0"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="128 0"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_LoadGame">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="384 0"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="384 0"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="128 64"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="0 64"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_Options">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="256 64"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="256 64"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="0 128"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="384 64"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_Credits">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="128 128"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="128 128"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="384 128"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="256 128"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_ExitGame">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="0 192"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="0 192"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="256 192"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="128 192"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_SaveGame">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="384 192"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="384 192"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="128 256"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="0 256"/>
+ </Index>
+ </Group>
+ </Resource>
+ <Resource type="ResourceImageSet" name="Menu_Return">
+ <Group name="States" texture="mwgui2" size="128 64">
+ <Index name="disabled">
+ <Frame point="256 256"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="256 256"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="0 320"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="384 256"/>
+ </Index>
+ </Group>
+ </Resource>
+
+
+ <Resource type="ResourceImageSet" name="DoorMarker">
+ <Group name="States" texture="markers.png" size="8 8">
+ <Index name="disabled">
+ <Frame point="8 0"/>
+ </Index>
+ <Index name="normal">
+ <Frame point="8 0"/>
+ </Index>
+ <Index name="highlighted">
+ <Frame point="0 0"/>
+ </Index>
+ <Index name="pushed">
+ <Frame point="0 0"/>
+ </Index>
+ </Group>
+ </Resource>
+
+
+
+ <!-- Skins -->
+
+ <Resource type="ResourceLayout" name="ButtonImage" version="3.2.0">
+ <Widget type="Widget" skin="Default" position="20 20 16 16" name="Root">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="FontName" value="Default"/>
+ <Property key="ModeImage" value="true"/>
+ <Widget type="ImageBox" skin="ImageBox" position="0 0 16 16" align="Stretch" name="Image">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ </Resource>
+
+ <Resource type="ResourceLayout" name="TabControl" version="3.2.0">
+ <Widget type="Widget" skin="" position="5 5 89 60" name="Root">
+ <UserString key="ButtonSkin" value="MW_Button"/>
+ <Widget type="Widget" skin="MW_Box" position="0 28 89 32" align="Left Top Stretch">
+ <Widget type="Widget" skin="" position="4 4 81 28" align="Left Top Stretch" name="TabItem"/>
+ </Widget>
+ <Widget type="Widget" skin="" position="0 0 89 23" align="HStretch Top" name="HeaderPlace">
+ <Widget type="Widget" skin="" position="52 0 37 23" name="Controls">
+ </Widget>
+ </Widget>
+ </Widget>
+ </Resource>
+
+ <Resource type="ResourceLayout" name="TabControlInner" version="3.2.0">
+ <Widget type="Widget" skin="" position="0 5 89 60" name="Root">
+ <UserString key="ButtonSkin" value="MW_Button"/>
+ <Widget type="Widget" skin="" position="0 28 89 32" align="Left Top Stretch" name="TabItem"/>
+
+ <Widget type="Widget" skin="" position="0 0 89 23" align="HStretch Top" name="HeaderPlace">
+ <Widget type="Widget" skin="" position="52 0 37 23" name="Controls">
+ </Widget>
+ </Widget>
+ </Widget>
+ </Resource>
+
+ <Resource type="ResourceLayout" name="MW_StatNameValue" version="3.2.0">
+ <Widget type="Widget" skin="" position="0 0 200 18" name="Root">
+ <Property key="NeedMouse" value="true"/>
+ <Widget type="TextBox" skin="SandText" position="0 0 160 18" align="Left HStretch" name="StatName">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 40 18" align="Right Top" name="StatValue">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ </Resource>
+
+ <Resource type="ResourceLayout" name="MW_StatName" version="3.2.0">
+ <Widget type="Widget" skin="" position="0 0 200 18" name="Root">
+ <Property key="NeedMouse" value="true"/>
+ <Widget type="TextBox" skin="SandText" position="0 0 200 18" align="Left HStretch" name="StatName">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ </Resource>
+
+ <Resource type="ResourceLayout" name="MW_StatNameButton" version="3.2.0">
+ <Widget type="Widget" skin="" position="0 0 200 18" name="Root">
+ <Widget type="Button" skin="SandTextButton" position="0 0 200 18" align="Left HStretch" name="StatNameButton">
+ <Property key="NeedMouse" value="true"/>
+ </Widget>
+ </Widget>
+ </Resource>
+
+</MyGUI>
diff --git a/files/mygui/openmw_scroll.layout b/files/mygui/openmw_scroll.layout
new file mode 100644
index 0000000000..194700f36e
--- /dev/null
+++ b/files/mygui/openmw_scroll.layout
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+
+<Widget type="Window" skin="" layer="Windows" position="0 0 512 512" name="_Main">
+
+ <Widget type="ImageBox" skin="ImageBox" position_real="0 0 1 1" name="ScrollImage">
+ <Property key="ImageTexture" value="textures\scroll.dds"/>
+
+ <Widget type="ImageButton" skin="ImageBox" position="12 18 128 32" name="TakeButton">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_take_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_take_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_take_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ImageButton" skin="ImageBox" position="415 24 128 32" name="CloseButton">
+ <Property key="ImageHighlighted" value="textures\tx_menubook_close_over.dds"/>
+ <Property key="ImageNormal" value="textures\tx_menubook_close_idle.dds"/>
+ <Property key="ImagePushed" value="textures\tx_menubook_close_pressed.dds"/>
+ </Widget>
+
+ <Widget type="ScrollView" skin="MW_ScrollView" position="60 130 410 300" name="TextView">
+ <Property key="CanvasSize" value="410 900"/>
+ </Widget>
+
+ </Widget>
+</Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_scroll_skin.xml b/files/mygui/openmw_scroll_skin.xml
new file mode 100644
index 0000000000..b6ed9155f1
--- /dev/null
+++ b/files/mygui/openmw_scroll_skin.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <Skin name="MW_ScrollView" size="516 516">
+ <Child type="Widget" skin="" offset="0 0 516 516" align="Stretch" name="Client"/>
+ <Child type="MWScrollBar" skin="MW_VScroll" offset="498 3 14 509" align="Right Top VStretch" name="VScroll"/>
+ </Skin>
+
+ <Skin name="MW_ScrollViewH" size="516 516">
+ <Child type="Widget" skin="" offset="0 0 516 516" align="Stretch" name="Client"/>
+ <Child type="MWScrollBar" skin="MW_HScroll" offset="3 498 509 14" align="Left Bottom HStretch" name="HScroll"/>
+ </Skin>
+
+</MyGUI>
diff --git a/files/mygui/openmw_settings.xml b/files/mygui/openmw_settings.xml
new file mode 100644
index 0000000000..37d2359683
--- /dev/null
+++ b/files/mygui/openmw_settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MyGUI>
+ <MyGUI type="Font">
+ <Property key="Default" value="Magic Cards"/>
+
+ </MyGUI>
+ <MyGUI type="Pointer">
+ <Property key="Default" value="arrow"/>
+ <Property key="Layer" value="Pointer"/>
+ </MyGUI>
+</MyGUI>
diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout
new file mode 100644
index 0000000000..ebfaf678a7
--- /dev/null
+++ b/files/mygui/openmw_settings_window.layout
@@ -0,0 +1,359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window_NoCaption" layer="Windows" position="0 0 400 426" name="_Main">
+
+ <Property key="MinSize" value="400 426"/>
+ <Property key="MaxSize" value="400 426"/>
+
+ <Widget type="TabControl" skin="TabControl" position="8 8 368 340" align="Left Top" name="SettingsTab">
+ <Property key="ButtonAutoWidth" value="true"/>
+
+ <Widget type="TabItem" skin="" position="4 28 360 312">
+ <Property key="Caption" value=" #{sPrefs} "/>
+
+ <Widget type="TextBox" skin="NormalText" position="4 4 352 18" align="Left Top">
+ <Property key="Caption" value="#{sTransparency_Menu}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 28 352 18" align="Left Top" name="MenuTransparencySlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 52 352 18" align="Left Top">
+ <Property key="Caption" value="#{sFull}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 52 352 18" align="Left Top">
+ <Property key="Caption" value="#{sNone}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 78 352 18" align="Left Top">
+ <Property key="Caption" value="#{sMenu_Help_Delay}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 102 352 18" align="Left Top" name="ToolTipDelaySlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 126 352 18" align="Left Top">
+ <Property key="Caption" value="#{sFast}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 126 352 18" align="Left Top">
+ <Property key="Caption" value="#{sSlow}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="HBox" skin="" position="4 170 260 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="BestAttackButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="#{sBestAttack}"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" skin="" position="4 200 260 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="SubtitlesButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="#{sSubtitles}"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" skin="" position="4 230 260 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="CrosshairButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="#{sCursorOff}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+ <Widget type="TabItem" skin="" position="4 28 360 312">
+ <Property key="Caption" value=" #{sAudio} "/>
+
+ <Widget type="TextBox" skin="NormalText" position="4 4 352 18" align="Left Top">
+ <Property key="Caption" value="#{sMaster}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 28 352 18" align="Left Top" name="MasterVolume">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 54 352 18" align="Left Top">
+ <Property key="Caption" value="#{sVoice}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 78 352 18" align="Left Top" name="VoiceVolume">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 104 352 18" align="Left Top">
+ <Property key="Caption" value="#{sEffects}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 128 352 18" align="Left Top" name="EffectsVolume">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 154 352 18" align="Left Top">
+ <Property key="Caption" value="#{sFootsteps}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 178 352 18" align="Left Top" name="FootstepsVolume">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 204 352 18" align="Left Top">
+ <Property key="Caption" value="#{sMusic}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 228 352 18" align="Left Top" name="MusicVolume">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ </Widget>
+ <Widget type="TabItem" skin="" position="4 28 360 312">
+ <Property key="Caption" value=" #{sControls} "/>
+
+ <Widget type="Widget" skin="MW_Box" position="4 4 352 154">
+ <Widget type="ScrollView" skin="MW_ScrollView" name="ControlsBox" position="4 4 344 146"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="ResetControlsButton" position="4 162 100 24">
+ <Property key="Caption" value="#{sControlsMenu1}"/>
+ </Widget>
+
+ <Widget type="HBox" position="4 192 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" name="InvertYButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText">
+ <Property key="Caption" value="#{sMouseFlip}"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 228 336 18" align="Left Top">
+ <Property key="Caption" value="Camera sensitivity"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 252 336 18" align="Left Top" name="CameraSensitivitySlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 276 336 18" align="Left Top">
+ <Property key="Caption" value="#{sLow}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 276 336 18" align="Left Top">
+ <Property key="Caption" value="#{sHigh}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+
+
+ </Widget>
+ <Widget type="TabItem" skin="" position="4 28 360 312">
+ <Property key="Caption" value=" #{sVideo} "/>
+
+ <Widget type="TabControl" skin="TabControlInner" position="4 4 352 296" align="Left Top">
+ <Property key="ButtonAutoWidth" value="true"/>
+
+ <Widget type="TabItem" skin="" position="4 28 344 272">
+ <Property key="Caption" value=" Video "/>
+
+ <Widget type="ListBox" skin="MW_List" position="0 4 170 170" align="Left Top" name="ResolutionList"/>
+
+
+ <Widget type="HBox" position="182 4 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="FullscreenButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Fullscreen"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="182 34 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="VSyncButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="VSync"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="182 64 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="FPSButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="FPS"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="182 94 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadersButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Object shaders"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="182 124 300 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShaderModeButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Shader mode"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="0 198 329 18" align="Left Top" name="FovText">
+ <Property key="Caption" value="Field of View"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="0 222 329 18" align="Left Top" name="FOVSlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="0 246 329 18" align="Left Top">
+ <Property key="Caption" value="#{sLow}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="0 246 329 18" align="Left Top">
+ <Property key="Caption" value="#{sHigh}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TabItem" skin="" position="4 28 344 272">
+ <Property key="Caption" value=" Detail "/>
+
+ <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="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">
+ <Property key="Caption" value="Anisotropy"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="0 28 150 18" align="Left Top" name="AnisotropySlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="4 130 322 18" align="Left Top">
+ <Property key="Caption" value="#{sRender_Distance}"/>
+ </Widget>
+ <Widget type="MWScrollBar" skin="MW_HScroll" position="4 154 322 18" align="Left Top" name="ViewDistanceSlider">
+ <Property key="Range" value="10000"/>
+ <Property key="Page" value="300"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 178 332 18" align="Left Top">
+ <Property key="Caption" value="#{sNear}"/>
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="4 178 332 18" align="Left Top">
+ <Property key="Caption" value="#{sFar}"/>
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="TabItem" skin="" position="4 28 344 272">
+ <Property key="Caption" value=" Water "/>
+
+
+ <Widget type="HBox" position="4 4 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" position="4 4 34 24" align="Left Top" name="WaterShaderButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" position="42 4 120 24" align="Left Top">
+ <Property key="Caption" value="Reflection"/>
+ </Widget>
+ </Widget>
+
+
+ <Widget type="Widget" skin="" position="24 32 300 230">
+ <Widget type="HBox" position="4 0 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ReflectActorsButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Reflect actors"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 28 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ReflectObjectsButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Reflect objects"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 56 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ReflectTerrainButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Reflect terrain"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 135 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="RefractionButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Refraction"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="TabItem" skin="" position="4 28 344 272">
+ <Property key="Caption" value=" Shadows "/>
+
+ <Widget type="HBox" position="4 4 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsEnabledButton"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Enabled"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Widget" skin="" position="24 32 300 230">
+
+ <Widget type="HBox" position="4 0 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsLargeDistance"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Large distance (PSSM3)"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 28 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsTextureSize"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Texture size"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 56 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ActorShadows"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Actor shadows"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 84 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="StaticsShadows"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="World shadows"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 112 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="MiscShadows"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Misc shadows"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="4 140 350 24">
+ <Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="TerrainShadows"/>
+ <Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
+ <Property key="Caption" value="Terrain shadows"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="317 355 60 24" name="OkButton" align="Right Bottom">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_spell_buying_window.layout b/files/mygui/openmw_spell_buying_window.layout
new file mode 100644
index 0000000000..1510372ddb
--- /dev/null
+++ b/files/mygui/openmw_spell_buying_window.layout
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 450 300" name="_Main">
+ <Property key="Visible" value="false"/>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 10 450 18" align="Right Top">
+ <Property key="TextAlign" value="Left"/>
+ <Property key="Caption" value="#{sSpellServiceTitle}"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="0 0 450 18" align="Right Top">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="#{sSpells}"/>
+ </Widget>
+
+
+ <Widget type="Widget" skin="MW_Box" position="6 31 430 225" align="Left Stretch">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 422 217" align="Left Top Stretch" name="SpellsView">
+ <Property key="CanvasAlign" value="Left"/>
+ </Widget>
+ </Widget>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 255 24 24" name="PlayerGold" align="Right Top">
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="375 260 60 24" name="CancelButton" align="Right Top">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout
new file mode 100644
index 0000000000..ab924da6d5
--- /dev/null
+++ b/files/mygui/openmw_spell_window.layout
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="ExposedWindow" skin="MW_Window_Pinnable" layer="Windows" position="0 0 300 600" name="_Main">
+ <Property key="MinSize" value="260 250"/>
+
+ <!-- Effect box-->
+ <Widget type="Widget" skin="MW_Box" position="8 8 268 23" align="Left Top HStretch">
+ <Widget type="Widget" skin="" position="2 1 264 20" align="Left Top Stretch" name="EffectsBox"/>
+ </Widget>
+
+ <!-- Spell list -->
+ <Widget type="Widget" skin="MW_Box" position="8 38 268 518" align="Left Top Stretch">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 260 510" align="Left Top Stretch" name="SpellView">
+ <Property key="CanvasAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_spellcreation_dialog.layout b/files/mygui/openmw_spellcreation_dialog.layout
new file mode 100644
index 0000000000..78d3f3de73
--- /dev/null
+++ b/files/mygui/openmw_spellcreation_dialog.layout
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 560 400" name="_Main">
+
+ <Widget type="HBox" position="12 12 250 30">
+
+ <Widget type="AutoSizedTextBox" skin="NormalText">
+ <Property key="Caption" value="#{sName}"/>
+ </Widget>
+
+ <Widget type="EditBox" skin="MW_TextEdit" position="0 0 30 30" name="NameEdit">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="Widget">
+ </Widget>
+
+ </Widget>
+
+ <Widget type="TextBox" skin="NormalText" position="280 0 300 24">
+ <Property key="Caption" value="#{sEnchantmentMenu4}"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="280 0 258 24" name="MagickaCost">
+ <Property key="TextAlign" value="Right HCenter"/>
+ </Widget>
+
+
+ <Widget type="TextBox" skin="NormalText" position="280 24 300 24">
+ <Property key="Caption" value="#{sSpellmakingMenu1}"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="280 24 258 24" name="SuccessChance">
+ <Property key="TextAlign" value="Right HCenter"/>
+ </Widget>
+
+
+ <!-- Available effects -->
+ <Widget type="TextBox" skin="NormalText" position="12 48 300 24">
+ <Property key="Caption" value="#{sMagicEffects}"/>
+ </Widget>
+ <Widget type="MWList" skin="MW_SimpleList" position="12 76 202 269" name="AvailableEffects">
+ </Widget>
+
+ <!-- Used effects -->
+ <Widget type="TextBox" skin="NormalText" position="226 48 300 24">
+ <Property key="Caption" value="#{sEffects}"/>
+ </Widget>
+ <Widget type="Widget" skin="MW_Box" position="226 76 316 269">
+ <Widget type="ScrollView" skin="MW_ScrollViewH" position="4 4 308 261" name="UsedEffects">
+ <Property key="CanvasAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="HBox" position="0 340 560 60">
+ <Property key="Padding" value="16"/>
+
+ <Widget type="Widget" position="0 0 0 0">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText">
+ <Property key="Caption" value="#{sBarterDialog7}"/>
+ </Widget>
+ <Widget type="AutoSizedTextBox" skin="SandText" name="PriceLabel">
+ </Widget>
+
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="BuyButton">
+ <Property key="Caption" value="#{sBuy}"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout
new file mode 100644
index 0000000000..5ae3f96caf
--- /dev/null
+++ b/files/mygui/openmw_stats_window.layout
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<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"/>
+
+ <Widget type="Widget" skin="" name="LeftPane" position="0 0 220 342">
+
+ <!-- Player health stats -->
+ <Widget type="Widget" skin="MW_Box" position="8 8 212 62" align="Left Top HStretch">
+ <!-- Health -->
+ <Widget type="Widget" skin="" position="4 4 204 18" name="Health" align="Left Top HStretch">
+ <Property key="NeedMouse" value="true"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\health.dds"/>
+ <Widget type="TextBox" skin="NormalText" position="0 0 70 18" name="Health_str" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ <Property key="Caption" value="#{sHealth}"/>
+ </Widget>
+ <Widget type="ProgressBar" skin="MW_Progress_Red" position="74 0 130 18" name="HBar" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="ProgressText" position="74 0 130 18" name="HBarT" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Magicka -->
+ <Widget type="Widget" skin="" position="4 22 204 18" name="Magicka" align="Left Top HStretch">
+ <Property key="NeedMouse" value="true"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\magicka.dds"/>
+
+ <Widget type="TextBox" skin="NormalText" position="0 0 70 18" name="Magicka_str" align="Left Top HStretch">
+ <Property key="Caption" value="#{sMagic}"/>
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+
+ <Widget type="ProgressBar" skin="MW_Progress_Blue" position="74 0 130 18" name="MBar" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="ProgressText" position="74 0 130 18" name="MBarT" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <!-- Fatigue -->
+ <Widget type="Widget" skin="" position="4 40 204 18" name="Fatigue" align="Left Top HStretch">
+ <Property key="NeedMouse" value="true"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="HealthToolTip"/>
+ <UserString key="ImageTexture_HealthImage" value="icons\k\fatigue.dds"/>
+
+ <Widget type="TextBox" skin="NormalText" position="0 0 70 18" name="Fatigue_str" align="Left Top HStretch">
+ <Property key="Caption" value="#{sFatigue}"/>
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+
+ <Widget type="ProgressBar" skin="MW_Progress_Green" position="74 0 130 18" name="FBar" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="ProgressText" position="74 0 130 18" name="FBarT" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <!-- Player level, race and class -->
+ <Widget type="Widget" skin="MW_Box" position="8 78 212 62" align="Left Top HStretch">
+ <Widget type="TextBox" skin="NormalText" position="4 4 100 18" name="Level_str" align="Left Top HStretch">
+ <Property key="Caption" value="#{sLevel}"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="LevelToolTip"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="4 22 100 18" name="Race_str" align="Left Top HStretch">
+ <Property key="Caption" value="#{sRace}"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextWithCenteredCaptionToolTip"/>
+ </Widget>
+ <Widget type="TextBox" skin="NormalText" position="4 40 100 18" name="Class_str" align="Left Top HStretch">
+ <Property key="Caption" value="#{sClass}"/>
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="ClassToolTip"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="104 4 104 18" name="LevelText" align="Right Top">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="LevelToolTip"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="104 22 104 18" name="RaceText" align="Right Top">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="TextWithCenteredCaptionToolTip"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="104 40 104 18" name="ClassText" align="Right Top">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="ClassToolTip"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Widget" skin="MW_Box" position="8 148 212 152" align="Left Top Stretch">
+ <Widget type="Button" skin="" position="4 4 204 18" name="Attrib1Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib1" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal1" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 22 204 18" name="Attrib2Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib2" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal2" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 40 204 18" name="Attrib3Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib3" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal3" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 58 204 18" name="Attrib4Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib4" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal4" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 76 204 18" name="Attrib5Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib5" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal5" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 94 204 18" name="Attrib6Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib6" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal6" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 112 204 18" name="Attrib7Box" align="Left Top HStretch">
+ <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 type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib7" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal7" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="Button" skin="" position="4 130 204 18" name="Attrib8Box" align="Left Top HStretch">
+ <UserString key="ToolTipType" value="Layout"/>
+ <UserString key="ToolTipLayout" value="AttributeToolTip"/>
+ <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
+ <UserString key="Caption_AttributeDescription" value="#{sLucDesc}"/>
+ <UserString key="ImageTexture_AttributeImage" value="icons\k\attribute_luck.dds"/>
+ <Widget type="TextBox" skin="SandText" position="0 0 160 18" name="Attrib8" align="Left Top HStretch">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandTextRight" position="160 0 44 18" name="AttribVal8" align="Right Top">
+ <Property key="NeedMouse" value="false"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="Widget" skin="" name="RightPane" position="220 0 280 342">
+
+ <!-- Player skills, factions, birthsign and reputation -->
+ <Widget type="Widget" skin="MW_Box" position="8 8 248 292" align="Left Stretch" name="Skills">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 240 284" align="Left Top Stretch" name="SkillView"/>
+ </Widget>
+
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml
new file mode 100644
index 0000000000..6a1dea60bf
--- /dev/null
+++ b/files/mygui/openmw_text.skin.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+
+ <!-- HTML colour: #DDC79E -->
+ <Skin name="NormalText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <Property key="TextColour" value="0.87 0.78 0.62"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="NumFPS" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="HCenter Bottom"/>
+ <Property key="TextColour" value="1 1 1"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <!-- HTML colour: #9A9074 -->
+ <Skin name="SandTextGreyedOut" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <Property key="TextColour" value="0.6 0.56 0.45"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <!-- HTML colour: #BF9959 -->
+ <Skin name="SandText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="SandTextC" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Top HCenter"/>
+ <Property key="TextColour" value="0.75 0.6 0.35"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="SandTextRight" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Right Bottom"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch">
+ <State name="normal" colour="0.75 0.6 0.35"/>
+ <State name="increased" colour="0.757 0.679 0.539"/>
+ <State name="decreased" colour="0.785 0.363 0.308"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="SandBrightText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <Property key="TextColour" value="0.85 0.76 0.60"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="DaedricText" size="16 16">
+ <Property key="FontName" value="daedric36"/>
+ <Property key="FontHeight" value="36"/>
+ <Property key="TextAlign" value="Default"/>
+ <Property key="TextColour" value="1 1 1"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="DaedricText_orig" size="16 16">
+ <Property key="FontName" value="daedric_orig36"/>
+ <Property key="FontHeight" value="36"/>
+ <Property key="TextAlign" value="Default"/>
+ <Property key="TextColour" value="1 1 1"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch"/>
+ </Skin>
+
+ <Skin name="MW_StatNameC" size="200 18">
+ <Child type="TextBoxC" skin="SandText" offset="0 0 200 18" align="Left HStretch" name="StatName"/>
+ </Skin>
+
+ <Skin name="SandTextButtonC" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Top HCenter"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch">
+ <State name="disabled" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="normal" colour="0.75 0.6 0.35" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="1 1 1" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="SandTextButton" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch">
+ <State name="disabled" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="normal" colour="0.75 0.6 0.35" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="1 1 1" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="SpellText" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch">
+ <State name="disabled" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="normal" colour="0.75 0.6 0.35" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="1 1 1" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="SpellTextUnequipped" size="16 16">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Left Bottom"/>
+ <BasisSkin type="SimpleText" offset="0 0 16 16" align="Stretch">
+ <State name="disabled" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="normal" colour="0.6 0.56 0.45" shift="0"/>
+ <State name="highlighted" colour="0.85 0.76 0.60" shift="0"/>
+ <State name="pushed" colour="1 1 1" shift="0"/>
+ <State name="disabled_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="normal_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="highlighted_checked" colour="0.33 0.38 0.67" shift="0"/>
+ <State name="pushed_checked" colour="0.33 0.38 0.67" shift="0"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="MW_StatNameButtonC" size="200 18">
+ <Child type="Button" skin="SandTextButtonC" offset="0 0 200 18" align="Left HStretch" name="StatNameButton"/>
+ </Skin>
+
+
+
+ <Skin name="MW_StatNameValueButton" size="200 18">
+ <Child type="Button" skin="SandText" offset="0 0 160 18" align="Left HStretch" name="StatNameButton"/>
+ <Child type="Button" skin="SandTextRight" offset="160 0 40 18" align="Right Top" name="StatValueButton"/>
+ </Skin>
+
+ <Skin name="MW_EffectImage" size="200 24">
+ <Child type="ImageBox" skin="ImageBox" offset="4 4 16 16" align="Left Top" name="Image"/>
+ <Child type="TextBox" skin="SandText" offset="24 0 176 20" align="VCenter HStretch" name="Text"/>
+ </Skin>
+
+ <Skin name="MW_ChargeBar" size="204 18">
+ <Child type="ProgressBar" skin="MW_Progress_Red" offset="0 0 204 18" align="Right Top Stretch" name="Bar"/>
+ <Child type="TextBox" skin="SandTextC" offset="0 0 204 18" align="Right Top Stretch" name="BarText"/>
+ </Skin>
+
+ <Skin name="MW_ChargeBar_Blue" size="204 18">
+ <Child type="ProgressBar" skin="MW_Progress_Blue" offset="0 0 204 18" align="Right Top Stretch" name="Bar"/>
+ <Child type="TextBox" skin="SandTextC" offset="0 0 204 18" align="Right Top Stretch" name="BarText"/>
+ </Skin>
+
+ <Skin name="MW_DynamicStat_Red" size="204 18">
+ <Child type="TextBox" skin="SandText" offset="0 0 100 18" align="Left Top" name="Text"/>
+ <Child type="ProgressBar" skin="MW_Progress_Red" offset="74 0 130 18" align="Right Top" name="Bar"/>
+ <Child type="TextBox" skin="SandTextC" offset="74 0 130 18" align="Right Top" name="BarText"/>
+ </Skin>
+
+ <Skin name="MW_DynamicStat_Blue" size="204 18">
+ <Child type="TextBox" skin="SandText" offset="0 0 100 18" align="Left Top" name="Text"/>
+ <Child type="ProgressBar" skin="MW_Progress_Blue" offset="74 0 130 18" align="Right Top" name="Bar"/>
+ <Child type="TextBox" skin="SandTextC" offset="74 0 130 18" align="Right Top" name="BarText"/>
+ </Skin>
+
+ <Skin name="MW_DynamicStat_Green" size="204 18">
+ <Child type="TextBox" skin="SandText" offset="0 0 100 18" align="Left Top" name="Text"/>
+ <Child type="ProgressBar" skin="MW_Progress_Green" offset="74 0 130 18" align="Right Top" name="Bar"/>
+ <Child type="TextBox" skin="SandTextC" offset="74 0 130 18" align="Right Top" name="BarText"/>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/openmw_text_input.layout b/files/mygui/openmw_text_input.layout
new file mode 100644
index 0000000000..012b0a82b3
--- /dev/null
+++ b/files/mygui/openmw_text_input.layout
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 320 97" name="_Main">
+
+ <!-- Appearance -->
+ <Widget type="TextBox" skin="ProgressText" position="6 6 300 18" name="LabelT" align="Left Top">
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ <Widget type="EditBox" skin="MW_TextEdit" position="6 28 300 30" name="TextEdit" align="Left Top"/>
+
+ <!-- Dialog buttons -->
+ <Widget type="AutoSizedButton" skin="MW_Button" position="264 60 42 23" name="OKButton">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="OK"/>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout
new file mode 100644
index 0000000000..624c133f26
--- /dev/null
+++ b/files/mygui/openmw_tooltips.layout
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Widget" layer="Popup" position="0 0 300 300" name="_Main">
+
+ <!-- Dynamically constructed tooltip goes here -->
+ <Widget type="Widget" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="DynamicToolTipBox">
+ </Widget>
+
+ <!-- Text tooltip, one line -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="TextToolTipOneLine">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="8 8 284 284" align="Left Top" name="TextOneLine">
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Text tooltip, multiline -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="TextToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 8 284 284" align="Left Top" name="Text">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Text with centered caption tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 56" align="Stretch" name="TextWithCenteredCaptionToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="8 8 284 18" align="Left Top" name="CenteredCaption">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 30 330 18" align="Left Top" name="CenteredCaptionText">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Class tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 78" align="Stretch" name="ClassToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="8 8 284 18" align="Left Top" name="ClassName">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 30 320 18" align="Left Top" name="ClassDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 52 284 18" align="Left Bottom" name="ClassSpecialisation">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+ </Widget>
+
+ <!-- Health tooltip -->
+ <Widget type="HBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="HealthToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="VBox">
+ <UserString key="VStretch" value="true"/>
+ <Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="HealthImage"/>
+ <Widget type="Widget">
+ <UserString key="VStretch" value="true"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="44 8 248 284" align="Left Top" name="HealthDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Attribute tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="AttributeToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="HBox">
+ <Property key="Spacing" value="4"/>
+ <Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="AttributeImage"/>
+
+ <Widget type="AutoSizedEditBox" skin="NormalText" position="44 8 252 32" align="Left Top" name="AttributeName">
+ <Property key="TextAlign" value="Left VCenter"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 44 284 248" align="Left Top" name="AttributeDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Skill tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 98" align="Stretch" name="SkillToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="HBox">
+ <UserString key="HStretch" value="true"/>
+ <Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="SkillImage"/>
+
+ <Widget type="VBox">
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="44 8 252 16" align="Left Top" name="SkillName">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="44 24 252 16" align="Left Top" name="SkillAttribute">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 44 320 0" align="Left Top" name="SkillDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="8 48 284 18" align="Left Bottom">
+ <Property key="Caption" value="#{sSkillProgress}"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="ProgressBar" skin="MW_Progress_Red" position="50 70 200 20" align="HCenter Bottom" name="SkillProgress">
+ <Widget type="TextBox" skin="ProgressText" position="0 0 200 20" align="Stretch" name="SkillProgressText">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <!-- Skill tooltip (without progress bar) -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 52" align="Stretch" name="SkillNoProgressToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="HBox">
+ <UserString key="HStretch" value="true"/>
+
+ <Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="SkillNoProgressImage"/>
+
+ <Widget type="VBox">
+ <Widget type="AutoSizedEditBox" skin="NormalText" position="44 8 252 16" align="Left Top" name="SkillNoProgressName">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="44 24 252 16" align="Left Top" name="SkillNoProgressAttribute">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 44 320 0" align="Left Top" name="SkillNoProgressDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ <!-- Level tooltip -->
+ <Widget type="Widget" skin="HUD_Box_NoTransp" position="0 0 300 58" align="Left Top" name="LevelToolTip">
+ <Widget type="TextBox" skin="NormalText" position="8 8 284 18" align="Left Top HStretch">
+ <Property key="Caption" value="#{sLevelProgress}"/>
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="ProgressBar" skin="MW_Progress_Red" position="50 30 200 20" align="HCenter Bottom" name="LevelProgress">
+ <Property key="Range" value="10"/>
+ <Property key="RangePosition" value="0"/>
+ <Widget type="TextBox" skin="ProgressText" position="0 0 200 20" align="Stretch" name="LevelProgressText">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="0/10"/>
+ </Widget>
+ </Widget>
+ </Widget>
+
+ <!-- Birthsign tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 300" align="Stretch" name="BirthSignToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <!-- Birthsign image -->
+ <Widget type="Widget" skin="MW_Box" position="18 13 263 137" align="Top HCenter">
+ <Widget type="ImageBox" skin="ImageBox" position="2 2 259 133" name="BirthSignImage" align="Left Top"/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="8 154 284 138" align="Top" name="BirthSignText">
+ <Property key="TextAlign" value="Top HCenter"/>
+ </Widget>
+ </Widget>
+
+ <!-- Magic effect tooltip -->
+ <Widget type="VBox" skin="HUD_Box_NoTransp" position="0 0 300 52" align="Stretch" name="MagicEffectToolTip">
+ <Property key="AutoResize" value="true"/>
+ <Property key="Padding" value="8"/>
+
+ <Widget type="HBox">
+ <UserString key="HStretch" value="true"/>
+
+ <Widget type="ImageBox" skin="ImageBox" position="8 8 32 32" align="Left Top" name="MagicEffectImage"/>
+
+ <Widget type="VBox">
+ <Widget type="AutoSizedTextBox" skin="NormalText" position="44 8 252 16" align="Left Top" name="MagicEffectName">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+
+ <Widget type="AutoSizedTextBox" skin="SandText" position="44 24 252 16" align="Left Top" name="MagicEffectSchool">
+ <Property key="TextAlign" value="Left"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+ <Widget type="AutoSizedEditBox" skin="SandText" position="8 44 284 0" align="Left Top" name="MagicEffectDescription">
+ <Property key="MultiLine" value="true"/>
+ <Property key="WordWrap" value="true"/>
+ <Property key="TextAlign" value="Left Top"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
+
diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout
new file mode 100644
index 0000000000..8a1be496f4
--- /dev/null
+++ b/files/mygui/openmw_trade_window.layout
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 600 360" name="_Main">
+ <Property key="Visible" value="false"/>
+ <Property key="MinSize" value="380 245"/>
+
+ <!-- Categories -->
+ <Widget type="HBox" position="8 8 566 24" align="Left Top HStretch" name="Categories">
+ <Widget type="AutoSizedButton" skin="MW_Button" name="AllButton">
+ <Property key="Caption" value="#{sAllTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="WeaponButton">
+ <Property key="Caption" value="#{sWeaponTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="ApparelButton">
+ <Property key="Caption" value="#{sApparelTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="MagicButton">
+ <Property key="Caption" value="#{sMagicTab}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="MiscButton">
+ <Property key="Caption" value="#{sMiscTab}"/>
+ </Widget>
+ </Widget>
+
+ <!-- Items -->
+ <Widget type="ItemView" skin="MW_ItemView" position="8 38 566 185" name="ItemView" align="Left Top Stretch">
+ </Widget>
+
+ <Widget type="Widget" skin="" position="8 231 566 92" name="BottomPane" align="Left Bottom HStretch">
+ <Widget type="TextBox" skin="SandText" position="192 0 374 24" name="PlayerGold" align="Left Top HStretch">
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="192 28 374 24" name="MerchantGold" align="Left Top HStretch">
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+
+ <Widget type="Button" skin="MW_Button" position="0 0 40 24" name="IncreaseButton" align="Left Top">
+ <Property key="Caption" value="+"/>
+ </Widget>
+ <Widget type="Button" skin="MW_Button" position="0 28 40 24" name="DecreaseButton" align="Left Top">
+ <Property key="Caption" value="-"/>
+ </Widget>
+
+ <Widget type="TextBox" skin="SandText" position="48 0 140 24" name="TotalBalanceLabel" align="Left Top "/>
+ <Widget type="TextBox" skin="SandText" position="48 28 140 24" name="TotalBalance" align="Left Top">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+
+ <Widget type="HBox" position="0 60 566 24" align="Right Bottom">
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="OfferButton" align="Right Top">
+ <Property key="Caption" value="#{sBarterDialog8}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton" align="Right Top">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" position="0 60 60 24" name="MaxSaleButton" align="Left Top">
+ <Property key="Caption" value="Max. Sale"/> <!-- GMST sMaxSale doesn't work -->
+ </Widget>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_trainingwindow.layout b/files/mygui/openmw_trainingwindow.layout
new file mode 100644
index 0000000000..c58bd0ab3a
--- /dev/null
+++ b/files/mygui/openmw_trainingwindow.layout
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 319 200" name="_Main">
+
+ <Widget type="TextBox" skin="NormalText" position="0 5 319 24" name="Select" align="Right Top">
+ <Property key="TextAlign" value="Center"/>
+ <Property key="Caption" value="#{sServiceTrainingTitle}"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="5 30 319 24" name="Travel" align="Right Top">
+ <Property key="TextAlign" value="Left"/>
+ <Property key="Caption" value="#{sTrainingServiceTitle}"/>
+ </Widget>
+
+
+ <Widget type="Widget" skin="MW_Box" position="6 54 299 100" align="Left Top" name="TrainingOptions">
+ </Widget>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 161 200 24" name="PlayerGold" align="Right Top">
+ <Property key="TextAlign" value="Left"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="244 161 60 24" name="CancelButton" align="Right Top">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI>
diff --git a/files/mygui/openmw_travel_window.layout b/files/mygui/openmw_travel_window.layout
new file mode 100644
index 0000000000..db3fa24a08
--- /dev/null
+++ b/files/mygui/openmw_travel_window.layout
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 450 300" name="_Main">
+ <Property key="Visible" value="false"/>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 10 24 24" name="Select" align="Right Top">
+ <Property key="TextAlign" value="Right"/>
+ <Property key="Caption" value="#{sTravelServiceTitle}"/>
+ </Widget>
+ <Widget type="TextBox" skin="SandText" position="0 0 24 24" name="Travel" align="Right Top">
+ <Property key="TextAlign" value="Right"/>
+ <Property key="Caption" value="#D8C09A#{sTravel}"/>
+ </Widget>
+
+
+ <Widget type="Widget" skin="MW_Box" position="6 31 430 225" align="Left Stretch">
+ <Widget type="ScrollView" skin="MW_ScrollView" position="4 4 422 217" align="Left Top Stretch" name="DestinationsView">
+ <Property key="CanvasAlign" value="Left"/>
+ </Widget>
+ </Widget>
+
+
+ <Widget type="TextBox" skin="SandText" position="8 255 24 24" name="PlayerGold" align="Right Top">
+ <Property key="TextAlign" value="Right"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" position="375 260 60 24" name="CancelButton" align="Right Top">
+ <Property key="ExpandDirection" value="Left"/>
+ <Property key="Caption" value="#{sOK}"/>
+ </Widget>
+
+ </Widget>
+
+</MyGUI> \ No newline at end of file
diff --git a/files/mygui/openmw_wait_dialog.layout b/files/mygui/openmw_wait_dialog.layout
new file mode 100644
index 0000000000..eeb7012eb0
--- /dev/null
+++ b/files/mygui/openmw_wait_dialog.layout
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="VBox" skin="MW_Dialog" layer="Windows" position="0 0 600 200" name="_Main">
+ <Property key="Padding" value="12"/>
+ <Property key="Spacing" value="8"/>
+ <Property key="AutoResize" value="true"/>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" name="DateTimeText">
+ <Property key="Caption" value="24 Herzfeuer (Tag 24) 2 a.m."/>
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" name="RestText">
+ </Widget>
+
+ <Widget type="AutoSizedTextBox" skin="SandText" name="HourText">
+ </Widget>
+
+ <Widget type="MWScrollBar" skin="MW_HScroll" name="HourSlider" position="0 0 0 18">
+ <Property key="MoveToClick" value="true"/>
+ <Property key="Range" value="24"/>
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="HBox">
+ <UserString key="HStretch" value="true"/>
+ <Widget type="Widget">
+ <UserString key="HStretch" value="true"/>
+ </Widget>
+
+ <Widget type="AutoSizedButton" skin="MW_Button" name="UntilHealedButton">
+ <Property key="Caption" value="#{sUntilHealed}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="WaitButton">
+ <Property key="Caption" value="#{sRest}"/>
+ </Widget>
+ <Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton">
+ <Property key="Caption" value="#{sCancel}"/>
+ </Widget>
+ </Widget>
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_wait_dialog_progressbar.layout b/files/mygui/openmw_wait_dialog_progressbar.layout
new file mode 100644
index 0000000000..93793fd8e0
--- /dev/null
+++ b/files/mygui/openmw_wait_dialog_progressbar.layout
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Layout">
+ <Widget type="Window" skin="MW_Dialog" layer="Windows" position="0 0 219 40" name="_Main">
+
+ <Widget type="ProgressBar" skin="MW_Progress_Blue" position="5 6 199 20" name="ProgressBar">
+ <Widget type="TextBox" skin="SandText" position="0 0 199 20" name="ProgressText">
+ <Property key="TextAlign" value="Center"/>
+ </Widget>
+ </Widget>
+
+ </Widget>
+</MyGUI>
diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml
new file mode 100644
index 0000000000..22586716cd
--- /dev/null
+++ b/files/mygui/openmw_windows.skin.xml
@@ -0,0 +1,871 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<MyGUI type="Skin">
+ <!-- Defines a transparent background -->
+ <Skin name="BlackBG" size="8 8" texture="transparent.png">
+ <BasisSkin type="MainSkin" offset="0 0 8 8">
+ <State name="normal" offset="0 0 8 8"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Define the borders for pin button (up) -->
+ <Skin name="PU_B" size="12 2" texture="textures\menu_rightbuttonup_bottom.dds">
+ <BasisSkin type="MainSkin" offset="0 0 12 2">
+ <State name="normal" offset="0 0 12 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_BR" size="2 2" texture="textures\menu_rightbuttonup_bottom_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_R" size="2 12" texture="textures\menu_rightbuttonup_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 12">
+ <State name="normal" offset="0 0 2 12"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_TR" size="2 2" texture="textures\menu_rightbuttonup_top_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_T" size="12 2" texture="textures\menu_rightbuttonup_top.dds">
+ <BasisSkin type="MainSkin" offset="0 0 12 2">
+ <State name="normal" offset="0 0 12 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_TL" size="2 2" texture="textures\menu_rightbuttonup_top_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_L" size="2 12" texture="textures\menu_rightbuttonup_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 12">
+ <State name="normal" offset="0 0 2 12"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PU_BL" size="2 2" texture="textures\menu_rightbuttonup_bottom_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Define the borders for pin button (down) -->
+ <Skin name="PD_B" size="12 2" texture="textures\menu_rightbuttondown_bottom.dds">
+ <BasisSkin type="MainSkin" offset="0 0 12 2">
+ <State name="normal" offset="0 0 12 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_BR" size="2 2" texture="textures\menu_rightbuttondown_bottom_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_R" size="2 12" texture="textures\menu_rightbuttondown_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 12">
+ <State name="normal" offset="0 0 2 12"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_TR" size="2 2" texture="textures\menu_rightbuttondown_top_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_T" size="12 2" texture="textures\menu_rightbuttondown_top.dds">
+ <BasisSkin type="MainSkin" offset="0 0 12 2">
+ <State name="normal" offset="0 0 12 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_TL" size="2 2" texture="textures\menu_rightbuttondown_top_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_L" size="2 12" texture="textures\menu_rightbuttondown_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 12">
+ <State name="normal" offset="0 0 2 12"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="PD_BL" size="2 2" texture="textures\menu_rightbuttondown_bottom_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Define the pin button skin -->
+ <Skin name="PinUp" size="19 19" texture="textures\menu_rightbuttonup_center.dds">
+ <BasisSkin type="MainSkin" offset="0 0 19 19">
+ <State name="normal" offset="0 0 19 19"/>
+ </BasisSkin>
+ <Child type="Widget" skin="PU_B" offset="2 17 15 2" align="Stretch"/>
+ <Child type="Widget" skin="PU_BR" offset="17 17 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PU_R" offset="17 2 2 15" align="Stretch"/>
+ <Child type="Widget" skin="PU_TR" offset="17 0 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PU_T" offset="2 0 15 2" align="Stretch"/>
+ <Child type="Widget" skin="PU_TL" offset="0 0 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PU_L" offset="0 2 2 15" align="Stretch"/>
+ <Child type="Widget" skin="PU_BL" offset="0 17 2 2" align="Stretch"/>
+ </Skin>
+ <Skin name="PinDown" size="19 19" texture="textures\menu_rightbuttondown_center.dds">
+ <BasisSkin type="MainSkin" offset="0 0 19 19">
+ <State name="normal" offset="0 0 19 19"/>
+ </BasisSkin>
+ <Child type="Widget" skin="PD_B" offset="2 17 15 2" align="Stretch"/>
+ <Child type="Widget" skin="PD_BR" offset="17 17 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PD_R" offset="17 2 2 15" align="Stretch"/>
+ <Child type="Widget" skin="PD_TR" offset="17 0 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PD_T" offset="2 0 15 2" align="Stretch"/>
+ <Child type="Widget" skin="PD_TL" offset="0 0 2 2" align="Stretch"/>
+ <Child type="Widget" skin="PD_L" offset="0 2 2 15" align="Stretch"/>
+ <Child type="Widget" skin="PD_BL" offset="0 17 2 2" align="Stretch"/>
+ </Skin>
+
+ <!-- Defines a pure black background -->
+ <Skin name="DialogBG" size="8 8" texture="black.png">
+ <BasisSkin type="MainSkin" offset="0 0 8 8">
+ <State name="normal" offset="0 0 8 8"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- These define the dialog borders -->
+ <Skin name="DB_B" size="512 4" texture="textures\menu_thick_border_bottom.dds">
+ <BasisSkin type="TileRect" offset="0 0 512 4" align="Stretch">
+ <State name="normal" offset="0 0 512 4">
+ <Property key="TileSize" value="512 4"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="DB_R" size="4 512" texture="textures\menu_thick_border_right.dds">
+ <BasisSkin type="TileRect" offset="0 0 4 512" align="Stretch">
+ <State name="normal" offset="0 0 4 512">
+ <Property key="TileSize" value="4 512"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="DB_T" size="512 4" texture="textures\menu_thick_border_top.dds">
+ <BasisSkin type="MainSkin" offset="0 0 512 4">
+ <State name="normal" offset="0 0 512 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="DB_L" size="4 512" texture="textures\menu_thick_border_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 512">
+ <State name="normal" offset="0 0 4 512"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Dialog border corners -->
+ <Skin name="DB_BR" size="4 4" texture="textures\menu_thick_border_bottom_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="DB_BL" size="4 4" texture="textures\menu_thick_border_bottom_left_corner.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="DB_TR" size="4 4" texture="textures\menu_thick_border_top_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="DB_TL" size="4 4" texture="textures\menu_thick_border_top_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- These define the window borders -->
+ <Skin name="TB_B" size="512 4" texture="textures\menu_thick_border_bottom.dds">
+ <Property key="Pointer" value="vresize"/>
+ <BasisSkin type="TileRect" offset="0 0 512 4" align="Stretch">
+ <State name="normal" offset="0 0 512 4">
+ <Property key="TileSize" value="512 4"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_R" size="4 512" texture="textures\menu_thick_border_right.dds">
+ <Property key="Pointer" value="hresize"/>
+ <BasisSkin type="TileRect" offset="0 0 4 512" align="Stretch">
+ <State name="normal" offset="0 0 4 512">
+ <Property key="TileSize" value="4 512"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_T" size="512 4" texture="textures\menu_thick_border_top.dds">
+ <Property key="Pointer" value="vresize"/>
+ <BasisSkin type="TileRect" offset="0 0 512 4" align="Stretch">
+ <State name="normal" offset="0 0 512 4">
+ <Property key="TileSize" value="512 4"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_L" size="4 512" texture="textures\menu_thick_border_left.dds">
+ <Property key="Pointer" value="hresize"/>
+ <BasisSkin type="TileRect" offset="0 0 4 512" align="Stretch">
+ <State name="normal" offset="0 0 4 512">
+ <Property key="TileSize" value="4 512"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Window border corners -->
+ <Skin name="TB_BR" size="4 4" texture="textures\menu_thick_border_bottom_right_corner.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="TB_BL" size="4 4" texture="textures\menu_thick_border_bottom_left_corner.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="TB_TR" size="4 4" texture="textures\menu_thick_border_top_right_corner.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="TB_TL" size="4 4" texture="textures\menu_thick_border_top_left_corner.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 4">
+ <State name="normal" offset="0 0 4 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- Expanded border corners, to get larger diagonal corner pointer area -->
+ <Skin name="TB_TL_T" size="10 4" texture="textures\menu_thick_border_top.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 10 4">
+ <State name="normal" offset="0 0 10 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_TL_B" size="4 10" texture="textures\menu_thick_border_left.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 10">
+ <State name="normal" offset="0 0 4 10"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_TR_T" size="10 4" texture="textures\menu_thick_border_top.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 10 4">
+ <State name="normal" offset="0 0 10 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_TR_B" size="4 10" texture="textures\menu_thick_border_right.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 10">
+ <State name="normal" offset="0 0 4 10"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_BL_T" size="4 10" texture="textures\menu_thick_border_left.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 10">
+ <State name="normal" offset="0 0 4 10"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_BL_B" size="10 4" texture="textures\menu_thick_border_bottom.dds">
+ <Property key="Pointer" value="dresize2"/>
+ <BasisSkin type="MainSkin" offset="0 0 10 4">
+ <State name="normal" offset="0 0 10 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_BR_T" size="4 10" texture="textures\menu_thick_border_right.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 4 10">
+ <State name="normal" offset="0 0 4 10"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="TB_BR_B" size="10 4" texture="textures\menu_thick_border_bottom.dds">
+ <Property key="Pointer" value="dresize"/>
+ <BasisSkin type="MainSkin" offset="0 0 10 4">
+ <State name="normal" offset="0 0 10 4"/>
+ </BasisSkin>
+ </Skin>
+
+ <!-- These parts defines the 'blocks' to the left and right of the caption -->
+ <Skin name="HB_MID" size="256 16" texture="textures\menu_head_block_middle.dds">
+ <BasisSkin type="TileRect" offset="0 0 256 16" align="Stretch">
+ <State name="normal" offset="0 0 256 16">
+ <Property key="TileSize" value="256 16"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_TOP" size="256 2" texture="textures\menu_head_block_top.dds">
+ <BasisSkin type="TileRect" offset="0 0 256 2" align="Stretch">
+ <State name="normal" offset="0 0 256 2">
+ <Property key="TileSize" value="256 2"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_BOTTOM" size="256 2" texture="textures\menu_head_block_bottom.dds">
+ <BasisSkin type="TileRect" offset="0 0 256 2" align="Stretch">
+ <State name="normal" offset="0 0 256 2">
+ <Property key="TileSize" value="256 2"/>
+ <Property key="TileH" value="true"/>
+ <Property key="TileV" value="true"/>
+ </State>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_LEFT" size="2 16" texture="textures\menu_head_block_left.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 16">
+ <State name="normal" offset="0 0 2 16"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_RIGHT" size="2 16" texture="textures\menu_head_block_right.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 16">
+ <State name="normal" offset="0 0 2 16"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_TR" size="2 2" texture="textures\menu_head_block_top_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="HB_TL" size="2 2" texture="textures\menu_head_block_top_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="HB_BR" size="2 2" texture="textures\menu_head_block_bottom_right_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+ <Skin name="HB_BL" size="2 2" texture="textures\menu_head_block_bottom_left_corner.dds">
+ <BasisSkin type="MainSkin" offset="0 0 2 2">
+ <State name="normal" offset="0 0 2 2"/>
+ </BasisSkin>
+ </Skin>
+
+ <Skin name="HB_ALL" size="260 20">
+ <Child type="Widget" skin="HB_MID" offset="2 2 256 16" align="Top Left HStretch"/>
+ <Child type="Widget" skin="HB_TOP" offset="2 0 256 2" align="Top HStretch HStretch"/>
+ <Child type="Widget" skin="HB_BOTTOM" offset="2 18 256 2" align="Bottom HStretch"/>
+ <Child type="Widget" skin="HB_LEFT" offset="0 2 2 16" align="Top Left"/>
+ <Child type="Widget" skin="HB_RIGHT" offset="258 2 2 16" align="Top Right"/>
+ <Child type="Widget" skin="HB_TR" offset="258 0 2 2" align="Top Right"/>
+ <Child type="Widget" skin="HB_TL" offset="0 0 2 2" align="Top Left"/>
+ <Child type="Widget" skin="HB_BR" offset="258 18 2 2" align="Bottom Right"/>
+ <Child type="Widget" skin="HB_BL" offset="0 18 2 2" align="Bottom Left"/>
+ </Skin>
+
+ <!-- The actual caption. It contains the edges of the blocks on
+ its sides as well -->
+ <Skin name="MW_Caption" size="88 20" texture="black.png" >
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.75 0.62 0.36"/>
+
+ <BasisSkin type="SubSkin" offset="0 0 88 20" align="Stretch">
+ <State name="normal" offset="0 0 8 8"/>
+ </BasisSkin>
+ <BasisSkin type="SimpleText" offset="2 0 84 20" align="Stretch"/>
+
+ <!-- Add the borders of the surrounding blocks -->
+ <Child type="Widget" skin="HB_LEFT" offset="86 2 2 16" align="Top Right"/>
+ <Child type="Widget" skin="HB_RIGHT" offset="0 2 2 16" align="Top Left"/>
+ <Child type="Widget" skin="HB_TR" offset="0 0 2 2" align="Top Left"/>
+ <Child type="Widget" skin="HB_TL" offset="86 0 2 2" align="Top Right"/>
+ <Child type="Widget" skin="HB_BR" offset="0 18 2 2" align="Bottom Left"/>
+ <Child type="Widget" skin="HB_BL" offset="86 18 2 2" align="Bottom Right"/>
+ </Skin>
+
+<!-- ----------------------------------------------------
+
+ WINDOW DEFINITION
+
+------------------------------------------------------ -->
+
+ <Skin name="MW_Window" size="256 256">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.8 0.8 0.8"/>
+ <Property key="Snap" value="true"/>
+ <Property key="MinSize" value="64 64"/>
+
+ <Child type="Widget" skin="BlackBG" offset="8 28 240 220" align="Stretch" name="Client"/>
+
+ <!-- Outer Borders -->
+ <Child type="Widget" skin="TB_T" offset="14 0 228 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="0 14 4 228" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="252 14 4 228" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="14 252 228 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- OB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="4 0 10 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="0 0 4 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="0 4 4 10" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- OB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="242 0 10 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="252 0 4 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="252 4 4 10" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- OB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="0 242 4 10" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="0 252 4 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="4 252 10 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- OB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="252 242 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="252 252 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="242 252 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Inner Borders -->
+ <Child type="Widget" skin="TB_T" offset="18 24 220 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="4 38 4 200" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="248 38 4 200" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="18 248 220 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- IB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="8 24 10 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="4 24 4 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="4 28 4 10" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- IB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="238 24 10 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="248 24 4 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="248 28 4 10" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- IB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="4 238 4 10" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="4 248 4 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="8 248 10 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- IB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="248 238 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="248 248 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="238 248 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Caption -->
+ <Child type="Widget" skin="HB_ALL" offset="4 4 248 20" align="Top HStretch">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+
+ <Child type="TextBox" skin="MW_Caption" offset="80 4 88 20" align="HCenter Top" name="Caption">
+ </Child>
+
+ <!-- This invisible button makes it possible to move the
+ window by dragging the caption. -->
+ <Child type="Button" offset="4 4 248 20" align="HStretch Top" name="Action">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+ </Skin>
+
+ <Skin name="MW_Window_NoCaption" size="256 256">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.8 0.8 0.8"/>
+ <Property key="Snap" value="true"/>
+ <Property key="MinSize" value="64 64"/>
+
+ <Child type="Widget" skin="BlackBG" offset="8 28 240 220" align="Stretch" name="Client"/>
+
+ <!-- Outer Borders -->
+ <Child type="Widget" skin="TB_T" offset="14 0 228 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="0 14 4 228" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="252 14 4 228" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="14 252 228 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- OB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="4 0 10 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="0 0 4 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="0 4 4 10" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- OB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="242 0 10 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="252 0 4 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="252 4 4 10" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- OB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="0 242 4 10" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="0 252 4 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="4 252 10 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- OB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="252 242 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="252 252 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="242 252 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Inner Borders -->
+ <Child type="Widget" skin="TB_T" offset="18 24 220 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="4 38 4 200" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="248 38 4 200" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="18 248 220 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- IB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="8 24 10 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="4 24 4 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="4 28 4 10" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- IB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="238 24 10 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="248 24 4 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="248 28 4 10" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- IB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="4 238 4 10" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="4 248 4 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="8 248 10 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- IB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="248 238 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="248 248 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="238 248 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Caption -->
+ <Child type="Widget" skin="HB_ALL" offset="4 4 248 20" align="Top HStretch">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+
+ <!-- This invisible button makes it possible to move the
+ window by dragging the caption. -->
+ <Child type="Button" offset="4 4 248 20" align="HStretch Top" name="Action">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+ </Skin>
+
+ <Skin name="MW_Window_Pinnable" size="256 256">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.8 0.8 0.8"/>
+ <Property key="Snap" value="true"/>
+ <Property key="MinSize" value="64 64"/>
+
+ <Child type="Widget" skin="BlackBG" offset="8 28 240 220" align="Stretch" name="Client"/>
+
+ <!-- Outer Borders -->
+ <Child type="Widget" skin="TB_T" offset="14 0 228 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="0 14 4 228" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="252 14 4 228" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="14 252 228 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- OB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="4 0 10 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="0 0 4 4" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="0 4 4 10" align="Left Top" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- OB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="242 0 10 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="252 0 4 4" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="252 4 4 10" align="Right Top" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- OB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="0 242 4 10" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="0 252 4 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="4 252 10 4" align="Left Bottom" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- OB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="252 242 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="252 252 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="242 252 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Inner Borders -->
+ <Child type="Widget" skin="TB_T" offset="18 24 220 4" align="Top HStretch" name="Action">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_L" offset="4 38 4 200" align="Left VStretch" name="Action">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_R" offset="248 38 4 200" align="Right VStretch" name="Action">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+ <Child type="Widget" skin="TB_B" offset="18 248 220 4" align="Bottom HStretch" name="Action">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+
+ <!-- IB: Top Left -->
+ <Child type="Widget" skin="TB_TL_T" offset="8 24 10 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL" offset="4 24 4 4" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TL_B" offset="4 28 4 10" align="Top Left" name="Action">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+
+ <!-- IB: Top Right -->
+ <Child type="Widget" skin="TB_TR_T" offset="238 24 10 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR" offset="248 24 4 4" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="TB_TR_B" offset="248 28 4 10" align="Top Right" name="Action">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+
+ <!-- IB: Bottom Left -->
+ <Child type="Widget" skin="TB_BL_T" offset="4 238 4 10" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL" offset="4 248 4 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BL_B" offset="8 248 10 4" align="Bottom Left" name="Action">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+
+ <!-- IB: Bottom Right -->
+ <Child type="Widget" skin="TB_BR_T" offset="248 238 4 10" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR" offset="248 248 4 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="TB_BR_B" offset="238 248 10 4" align="Right Bottom" name="Action">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+
+ <!-- Caption -->
+
+ <Child type="Widget" skin="HB_ALL" offset="4 4 248 20" align="Top HStretch">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+
+ <Child type="TextBox" skin="MW_Caption" offset="80 4 88 20" align="HCenter Top" name="Caption">
+ </Child>
+
+ <!-- This invisible button makes it possible to move the
+ window by dragging the caption. -->
+ <Child type="Button" offset="4 4 248 20" align="HStretch Top" name="Action">
+ <Property key="Scale" value="1 1 0 0"/>
+ </Child>
+
+ <Child type="Button" skin="PinUp" offset="232 4 19 19" align="Right Top" name="Button"/>
+ </Skin>
+
+ <Skin name="MW_Dialog" size="256 54">
+ <Property key="FontName" value="Default"/>
+ <Property key="TextAlign" value="Center"/>
+ <Property key="TextColour" value="0.8 0.8 0.8"/>
+
+ <Child type="Widget" skin="BlackBG" offset="4 4 248 46" align="Stretch" name="Client"/>
+
+ <!-- Outer borders -->
+ <Child type="Widget" skin="DB_T" offset="4 0 248 4" align="Top HStretch" name="Border">
+ <Property key="Scale" value="0 1 0 -1"/>
+ </Child>
+ <Child type="Widget" skin="DB_L" offset="0 4 4 46" align="Left VStretch" name="Border">
+ <Property key="Scale" value="1 0 -1 0"/>
+ </Child>
+ <Child type="Widget" skin="DB_B" offset="4 50 248 4" align="Bottom HStretch" name="Border">
+ <Property key="Scale" value="0 0 0 1"/>
+ </Child>
+ <Child type="Widget" skin="DB_R" offset="252 4 4 46" align="Right VStretch" name="Border">
+ <Property key="Scale" value="0 0 1 0"/>
+ </Child>
+
+ <Child type="Widget" skin="DB_BR" offset="252 50 4 4" align="Right Bottom" name="Border">
+ <Property key="Scale" value="0 0 1 1"/>
+ </Child>
+ <Child type="Widget" skin="DB_BL" offset="0 50 4 4" align="Left Bottom" name="Border">
+ <Property key="Scale" value="1 0 -1 1"/>
+ </Child>
+ <Child type="Widget" skin="DB_TR" offset="252 0 4 4" align="Right Top" name="Border">
+ <Property key="Scale" value="0 1 1 -1"/>
+ </Child>
+ <Child type="Widget" skin="DB_TL" offset="0 0 4 4" align="Left Top" name="Border">
+ <Property key="Scale" value="1 1 -1 -1"/>
+ </Child>
+ </Skin>
+</MyGUI>
diff --git a/files/mygui/smallbars.png b/files/mygui/smallbars.png
new file mode 100644
index 0000000000..3c007a55ce
--- /dev/null
+++ b/files/mygui/smallbars.png
Binary files differ
diff --git a/files/opencs.cfg b/files/opencs.cfg
new file mode 100644
index 0000000000..3faac7c8e8
--- /dev/null
+++ b/files/opencs.cfg
@@ -0,0 +1,5 @@
+[Editor]
+Record Status Display = Icon and Text
+[Window Size]
+Width = 640
+Height = 480
diff --git a/files/opencs.desktop b/files/opencs.desktop
new file mode 100644
index 0000000000..f6aad5b097
--- /dev/null
+++ b/files/opencs.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Name=OpenMW Content Editor
+GenericName=Content Editor
+Comment=A replacement for the Morrowind Construction Set.
+TryExec=opencs
+Exec=opencs
+Icon=opencs
+Categories=Game;RolePlaying;
diff --git a/files/opencs/activator.png b/files/opencs/activator.png
new file mode 100755
index 0000000000..0446af22cf
--- /dev/null
+++ b/files/opencs/activator.png
Binary files differ
diff --git a/files/opencs/added.png b/files/opencs/added.png
new file mode 100644
index 0000000000..aff7e25d46
--- /dev/null
+++ b/files/opencs/added.png
Binary files differ
diff --git a/files/opencs/apparatus.png b/files/opencs/apparatus.png
new file mode 100755
index 0000000000..3cef537e14
--- /dev/null
+++ b/files/opencs/apparatus.png
Binary files differ
diff --git a/files/opencs/armor.png b/files/opencs/armor.png
new file mode 100755
index 0000000000..fc534c7d1c
--- /dev/null
+++ b/files/opencs/armor.png
Binary files differ
diff --git a/files/opencs/base.png b/files/opencs/base.png
new file mode 100644
index 0000000000..4398e2d687
--- /dev/null
+++ b/files/opencs/base.png
Binary files differ
diff --git a/files/opencs/book.png b/files/opencs/book.png
new file mode 100755
index 0000000000..3afa9e8aae
--- /dev/null
+++ b/files/opencs/book.png
Binary files differ
diff --git a/files/opencs/clothing.png b/files/opencs/clothing.png
new file mode 100755
index 0000000000..88c9b6ab8b
--- /dev/null
+++ b/files/opencs/clothing.png
Binary files differ
diff --git a/files/opencs/container.png b/files/opencs/container.png
new file mode 100755
index 0000000000..2a6ed01eb9
--- /dev/null
+++ b/files/opencs/container.png
Binary files differ
diff --git a/files/opencs/creature.png b/files/opencs/creature.png
new file mode 100755
index 0000000000..99cf9c87ca
--- /dev/null
+++ b/files/opencs/creature.png
Binary files differ
diff --git a/files/opencs/door.png b/files/opencs/door.png
new file mode 100755
index 0000000000..aa48858efa
--- /dev/null
+++ b/files/opencs/door.png
Binary files differ
diff --git a/files/opencs/ingredient.png b/files/opencs/ingredient.png
new file mode 100755
index 0000000000..6b36d008d2
--- /dev/null
+++ b/files/opencs/ingredient.png
Binary files differ
diff --git a/files/opencs/leveled-creature.png b/files/opencs/leveled-creature.png
new file mode 100755
index 0000000000..ad4a7c6f83
--- /dev/null
+++ b/files/opencs/leveled-creature.png
Binary files differ
diff --git a/files/opencs/leveled-item.png b/files/opencs/leveled-item.png
new file mode 100755
index 0000000000..7b8e68e605
--- /dev/null
+++ b/files/opencs/leveled-item.png
Binary files differ
diff --git a/files/opencs/light.png b/files/opencs/light.png
new file mode 100755
index 0000000000..c606fcd987
--- /dev/null
+++ b/files/opencs/light.png
Binary files differ
diff --git a/files/opencs/lockpick.png b/files/opencs/lockpick.png
new file mode 100755
index 0000000000..d9bd27f5e9
--- /dev/null
+++ b/files/opencs/lockpick.png
Binary files differ
diff --git a/files/opencs/miscellaneous.png b/files/opencs/miscellaneous.png
new file mode 100755
index 0000000000..744bcd9dbc
--- /dev/null
+++ b/files/opencs/miscellaneous.png
Binary files differ
diff --git a/files/opencs/modified.png b/files/opencs/modified.png
new file mode 100644
index 0000000000..d15ad827c3
--- /dev/null
+++ b/files/opencs/modified.png
Binary files differ
diff --git a/files/opencs/npc.png b/files/opencs/npc.png
new file mode 100755
index 0000000000..7a07f26dfe
--- /dev/null
+++ b/files/opencs/npc.png
Binary files differ
diff --git a/files/opencs/opencs.png b/files/opencs/opencs.png
new file mode 100644
index 0000000000..dddf220a3c
--- /dev/null
+++ b/files/opencs/opencs.png
Binary files differ
diff --git a/files/opencs/potion.png b/files/opencs/potion.png
new file mode 100755
index 0000000000..678f61fbf3
--- /dev/null
+++ b/files/opencs/potion.png
Binary files differ
diff --git a/files/opencs/probe.png b/files/opencs/probe.png
new file mode 100755
index 0000000000..01536186de
--- /dev/null
+++ b/files/opencs/probe.png
Binary files differ
diff --git a/files/opencs/raster/Info.png b/files/opencs/raster/Info.png
new file mode 100644
index 0000000000..d7bdad6cb1
--- /dev/null
+++ b/files/opencs/raster/Info.png
Binary files differ
diff --git a/files/opencs/raster/LandTexture.png b/files/opencs/raster/LandTexture.png
new file mode 100644
index 0000000000..84f7290981
--- /dev/null
+++ b/files/opencs/raster/LandTexture.png
Binary files differ
diff --git a/files/opencs/raster/PathGrid.png b/files/opencs/raster/PathGrid.png
new file mode 100644
index 0000000000..23b6b84d74
--- /dev/null
+++ b/files/opencs/raster/PathGrid.png
Binary files differ
diff --git a/files/opencs/raster/activator.png b/files/opencs/raster/activator.png
new file mode 100644
index 0000000000..0446af22cf
--- /dev/null
+++ b/files/opencs/raster/activator.png
Binary files differ
diff --git a/files/opencs/raster/added.png b/files/opencs/raster/added.png
new file mode 100644
index 0000000000..ddd9c2108e
--- /dev/null
+++ b/files/opencs/raster/added.png
Binary files differ
diff --git a/files/opencs/raster/apparatus.png b/files/opencs/raster/apparatus.png
new file mode 100644
index 0000000000..3cef537e14
--- /dev/null
+++ b/files/opencs/raster/apparatus.png
Binary files differ
diff --git a/files/opencs/raster/armor.png b/files/opencs/raster/armor.png
new file mode 100644
index 0000000000..fc534c7d1c
--- /dev/null
+++ b/files/opencs/raster/armor.png
Binary files differ
diff --git a/files/opencs/raster/base.png b/files/opencs/raster/base.png
new file mode 100644
index 0000000000..4398e2d687
--- /dev/null
+++ b/files/opencs/raster/base.png
Binary files differ
diff --git a/files/opencs/raster/birthsign.png b/files/opencs/raster/birthsign.png
new file mode 100644
index 0000000000..8192d2ebf8
--- /dev/null
+++ b/files/opencs/raster/birthsign.png
Binary files differ
diff --git a/files/opencs/raster/body-part.png b/files/opencs/raster/body-part.png
new file mode 100644
index 0000000000..4aa5dc02e5
--- /dev/null
+++ b/files/opencs/raster/body-part.png
Binary files differ
diff --git a/files/opencs/raster/book.png b/files/opencs/raster/book.png
new file mode 100644
index 0000000000..3afa9e8aae
--- /dev/null
+++ b/files/opencs/raster/book.png
Binary files differ
diff --git a/files/opencs/raster/cell.png b/files/opencs/raster/cell.png
new file mode 100644
index 0000000000..c4f00c1f07
--- /dev/null
+++ b/files/opencs/raster/cell.png
Binary files differ
diff --git a/files/opencs/raster/class.png b/files/opencs/raster/class.png
new file mode 100644
index 0000000000..316380363a
--- /dev/null
+++ b/files/opencs/raster/class.png
Binary files differ
diff --git a/files/opencs/raster/clothing.png b/files/opencs/raster/clothing.png
new file mode 100644
index 0000000000..88c9b6ab8b
--- /dev/null
+++ b/files/opencs/raster/clothing.png
Binary files differ
diff --git a/files/opencs/raster/container.png b/files/opencs/raster/container.png
new file mode 100644
index 0000000000..2a6ed01eb9
--- /dev/null
+++ b/files/opencs/raster/container.png
Binary files differ
diff --git a/files/opencs/raster/creature.png b/files/opencs/raster/creature.png
new file mode 100644
index 0000000000..99cf9c87ca
--- /dev/null
+++ b/files/opencs/raster/creature.png
Binary files differ
diff --git a/files/opencs/raster/dialogoue-info.png b/files/opencs/raster/dialogoue-info.png
new file mode 100644
index 0000000000..f6743d43c9
--- /dev/null
+++ b/files/opencs/raster/dialogoue-info.png
Binary files differ
diff --git a/files/opencs/raster/dialogoue-journal.png b/files/opencs/raster/dialogoue-journal.png
new file mode 100644
index 0000000000..b6a95c5384
--- /dev/null
+++ b/files/opencs/raster/dialogoue-journal.png
Binary files differ
diff --git a/files/opencs/raster/dialogoue-regular.png b/files/opencs/raster/dialogoue-regular.png
new file mode 100644
index 0000000000..f9b8d252d3
--- /dev/null
+++ b/files/opencs/raster/dialogoue-regular.png
Binary files differ
diff --git a/files/opencs/raster/dialogue-greeting.png b/files/opencs/raster/dialogue-greeting.png
new file mode 100644
index 0000000000..a35e1fe6d1
--- /dev/null
+++ b/files/opencs/raster/dialogue-greeting.png
Binary files differ
diff --git a/files/opencs/raster/dialogue-persuasion.png b/files/opencs/raster/dialogue-persuasion.png
new file mode 100644
index 0000000000..5bc5d61136
--- /dev/null
+++ b/files/opencs/raster/dialogue-persuasion.png
Binary files differ
diff --git a/files/opencs/raster/dialogue-speech.png b/files/opencs/raster/dialogue-speech.png
new file mode 100644
index 0000000000..11eb9f1ca8
--- /dev/null
+++ b/files/opencs/raster/dialogue-speech.png
Binary files differ
diff --git a/files/opencs/raster/door.png b/files/opencs/raster/door.png
new file mode 100644
index 0000000000..aa48858efa
--- /dev/null
+++ b/files/opencs/raster/door.png
Binary files differ
diff --git a/files/opencs/raster/enchantment.png b/files/opencs/raster/enchantment.png
new file mode 100644
index 0000000000..c90fb27ce3
--- /dev/null
+++ b/files/opencs/raster/enchantment.png
Binary files differ
diff --git a/files/opencs/raster/faction.png b/files/opencs/raster/faction.png
new file mode 100644
index 0000000000..8ac1f5200c
--- /dev/null
+++ b/files/opencs/raster/faction.png
Binary files differ
diff --git a/files/opencs/raster/globvar.png b/files/opencs/raster/globvar.png
new file mode 100644
index 0000000000..646145f0f4
--- /dev/null
+++ b/files/opencs/raster/globvar.png
Binary files differ
diff --git a/files/opencs/raster/ingredient.png b/files/opencs/raster/ingredient.png
new file mode 100644
index 0000000000..6b36d008d2
--- /dev/null
+++ b/files/opencs/raster/ingredient.png
Binary files differ
diff --git a/files/opencs/raster/land.png b/files/opencs/raster/land.png
new file mode 100644
index 0000000000..20dd321dda
--- /dev/null
+++ b/files/opencs/raster/land.png
Binary files differ
diff --git a/files/opencs/raster/landpaint.png b/files/opencs/raster/landpaint.png
new file mode 100644
index 0000000000..711c0d8f5f
--- /dev/null
+++ b/files/opencs/raster/landpaint.png
Binary files differ
diff --git a/files/opencs/raster/leveled-creature.png b/files/opencs/raster/leveled-creature.png
new file mode 100644
index 0000000000..ad4a7c6f83
--- /dev/null
+++ b/files/opencs/raster/leveled-creature.png
Binary files differ
diff --git a/files/opencs/raster/light.png b/files/opencs/raster/light.png
new file mode 100644
index 0000000000..c606fcd987
--- /dev/null
+++ b/files/opencs/raster/light.png
Binary files differ
diff --git a/files/opencs/raster/lockpick.png b/files/opencs/raster/lockpick.png
new file mode 100644
index 0000000000..d9bd27f5e9
--- /dev/null
+++ b/files/opencs/raster/lockpick.png
Binary files differ
diff --git a/files/opencs/raster/magic-effect.png b/files/opencs/raster/magic-effect.png
new file mode 100644
index 0000000000..e672ffccb3
--- /dev/null
+++ b/files/opencs/raster/magic-effect.png
Binary files differ
diff --git a/files/opencs/raster/magicrabbit.png b/files/opencs/raster/magicrabbit.png
new file mode 100644
index 0000000000..d1d7c8270b
--- /dev/null
+++ b/files/opencs/raster/magicrabbit.png
Binary files differ
diff --git a/files/opencs/raster/map.png b/files/opencs/raster/map.png
new file mode 100644
index 0000000000..3653797cca
--- /dev/null
+++ b/files/opencs/raster/map.png
Binary files differ
diff --git a/files/opencs/raster/miscellaneous.png b/files/opencs/raster/miscellaneous.png
new file mode 100644
index 0000000000..744bcd9dbc
--- /dev/null
+++ b/files/opencs/raster/miscellaneous.png
Binary files differ
diff --git a/files/opencs/raster/modified.png b/files/opencs/raster/modified.png
new file mode 100644
index 0000000000..39bd182ac2
--- /dev/null
+++ b/files/opencs/raster/modified.png
Binary files differ
diff --git a/files/opencs/raster/npc.png b/files/opencs/raster/npc.png
new file mode 100644
index 0000000000..7a07f26dfe
--- /dev/null
+++ b/files/opencs/raster/npc.png
Binary files differ
diff --git a/files/opencs/raster/potion.png b/files/opencs/raster/potion.png
new file mode 100644
index 0000000000..678f61fbf3
--- /dev/null
+++ b/files/opencs/raster/potion.png
Binary files differ
diff --git a/files/opencs/raster/probe.png b/files/opencs/raster/probe.png
new file mode 100644
index 0000000000..01536186de
--- /dev/null
+++ b/files/opencs/raster/probe.png
Binary files differ
diff --git a/files/opencs/raster/race.png b/files/opencs/raster/race.png
new file mode 100644
index 0000000000..94a2de696a
--- /dev/null
+++ b/files/opencs/raster/race.png
Binary files differ
diff --git a/files/opencs/raster/random-item.png b/files/opencs/raster/random-item.png
new file mode 100644
index 0000000000..7b8e68e605
--- /dev/null
+++ b/files/opencs/raster/random-item.png
Binary files differ
diff --git a/files/opencs/raster/removed.png b/files/opencs/raster/removed.png
new file mode 100644
index 0000000000..2354bc7430
--- /dev/null
+++ b/files/opencs/raster/removed.png
Binary files differ
diff --git a/files/opencs/raster/repair.png b/files/opencs/raster/repair.png
new file mode 100644
index 0000000000..6cf1c0aacd
--- /dev/null
+++ b/files/opencs/raster/repair.png
Binary files differ
diff --git a/files/opencs/raster/script.png b/files/opencs/raster/script.png
new file mode 100644
index 0000000000..297da40210
--- /dev/null
+++ b/files/opencs/raster/script.png
Binary files differ
diff --git a/files/opencs/raster/skill.png b/files/opencs/raster/skill.png
new file mode 100644
index 0000000000..418f4f35c2
--- /dev/null
+++ b/files/opencs/raster/skill.png
Binary files differ
diff --git a/files/opencs/raster/sound.png b/files/opencs/raster/sound.png
new file mode 100644
index 0000000000..b072acf767
--- /dev/null
+++ b/files/opencs/raster/sound.png
Binary files differ
diff --git a/files/opencs/raster/soundgen.png b/files/opencs/raster/soundgen.png
new file mode 100644
index 0000000000..70ae43a1d7
--- /dev/null
+++ b/files/opencs/raster/soundgen.png
Binary files differ
diff --git a/files/opencs/raster/spell.png b/files/opencs/raster/spell.png
new file mode 100644
index 0000000000..69c8971805
--- /dev/null
+++ b/files/opencs/raster/spell.png
Binary files differ
diff --git a/files/opencs/raster/static.png b/files/opencs/raster/static.png
new file mode 100644
index 0000000000..b53be12d9a
--- /dev/null
+++ b/files/opencs/raster/static.png
Binary files differ
diff --git a/files/opencs/raster/weapon.png b/files/opencs/raster/weapon.png
new file mode 100644
index 0000000000..3d4b534661
--- /dev/null
+++ b/files/opencs/raster/weapon.png
Binary files differ
diff --git a/files/opencs/removed.png b/files/opencs/removed.png
new file mode 100644
index 0000000000..2ca9e094be
--- /dev/null
+++ b/files/opencs/removed.png
Binary files differ
diff --git a/files/opencs/repair.png b/files/opencs/repair.png
new file mode 100755
index 0000000000..6cf1c0aacd
--- /dev/null
+++ b/files/opencs/repair.png
Binary files differ
diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc
new file mode 100644
index 0000000000..321413763e
--- /dev/null
+++ b/files/opencs/resources.qrc
@@ -0,0 +1,29 @@
+<RCC>
+ <qresource prefix="/">
+ <file>opencs.png</file>
+ <file>added.png</file>
+ <file>modified.png</file>
+ <file>removed.png</file>
+ <file>base.png</file>
+ <file>activator.png</file>
+ <file>apparatus.png</file>
+ <file>armor.png</file>
+ <file>book.png</file>
+ <file>clothing.png</file>
+ <file>container.png</file>
+ <file>creature.png</file>
+ <file>door.png</file>
+ <file>ingredient.png</file>
+ <file>leveled-creature.png</file>
+ <file>leveled-item.png</file>
+ <file>light.png</file>
+ <file>lockpick.png</file>
+ <file>miscellaneous.png</file>
+ <file>npc.png</file>
+ <file>potion.png</file>
+ <file>probe.png</file>
+ <file>repair.png</file>
+ <file>static.png</file>
+ <file>weapon.png</file>
+ </qresource>
+</RCC>
diff --git a/files/opencs/scalable/Palette.svg b/files/opencs/scalable/Palette.svg
new file mode 100644
index 0000000000..f42475330d
--- /dev/null
+++ b/files/opencs/scalable/Palette.svg
@@ -0,0 +1,569 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 11 Build 196, SVG Export Plug-In -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1000"
+ height="1000"
+ viewBox="0 0 321 368"
+ xml:space="preserve"
+ id="svg1468"
+ sodipodi:version="0.32"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="Tango-Palette.svg"
+ inkscape:export-filename="/home/jimmac/gfx/ximian/tango-art-tools/palettes/Tango-Palette.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ version="1.0"><metadata
+ id="metadata1573">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+
+
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+
+
+ <dc:title>Tango Palette</dc:title>
+<dc:creator>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+</dc:creator>
+<dc:contributor>
+ <cc:Agent>
+ <dc:title>Garrett Le Sage
+Kenneth Wimer
+Jakub Steiner</dc:title>
+ </cc:Agent>
+</dc:contributor>
+<dc:source>http://www.tango-project.org/files/Tango-Palette.svg</dc:source>
+<dc:subject>
+ <rdf:Bag>
+ <rdf:li>unify</rdf:li>
+ <rdf:li>global</rdf:li>
+ <rdf:li>theme</rdf:li>
+ <rdf:li>color</rdf:li>
+ <rdf:li>palette</rdf:li>
+ </rdf:Bag>
+</dc:subject>
+<cc:license
+ rdf:resource="http://web.resource.org/cc/PublicDomain" />
+</cc:Work>
+
+
+
+<cc:License
+ rdf:about="http://web.resource.org/cc/PublicDomain"><cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" /><cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" /><cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" /></cc:License></rdf:RDF>
+
+
+</metadata>
+
+
+<sodipodi:namedview
+ fill="#788600"
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.15294118"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="910"
+ inkscape:window-height="972"
+ inkscape:cy="742.44911"
+ inkscape:cx="620.6461"
+ inkscape:zoom="0.35355339"
+ inkscape:window-x="366"
+ inkscape:window-y="30"
+ inkscape:current-layer="layer1"
+ inkscape:showpageshadow="false"
+ showgrid="false"
+ inkscape:window-maximized="0" />
+
+
+
+ <style
+ type="text/css"
+ id="style1470">
+
+@font-face{font-family:'TrebuchetMS-Bold';src:url(&quot;data:;base64,\
+T1RUTwADACAAAQAQQ0ZGINnFGF0AAADAAAAFYUdQT1OwB73vAAAGJAAAAGZjbWFwAtwCtwAAADwA\
+AACEAAAAAQAAAAMAAAAMAAQAeAAAABoAEAADAAoAIABDAFAAVQBhAGYAaQBsAG8AcgB0AHn//wAA\
+ACAAQwBQAFUAYQBlAGkAbABuAHIAdAB5////4f+//7P/r/+k/6H/n/+d/5z/mv+Z/5UAAQAAAAAA\
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAEAgABAQERVHJlYnVjaGV0TVMtQm9sZAABAQE7+BsMAPgU\
+BPgcDBX7Yvy8HAgUHAfYBR6gAEiCgSX/i4seoABIgoEl/4uLDAf3Pw/3XBD3XhGRHAVbEgACAQE/\
+TENvcHlyaWdodCAoYykgMTk5NiBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVz\
+ZXJ2ZWQuL0ZTVHlwZSA4IGRlZgAAAAABACQAMQA2AEIARgBHAEoATQBPAFAAUwBVAFoAAAAPAgAB\
+ABsAHgCXAPEBPgHaAlICmwLlAwgDTQO+A+8ENwRvIPcU9/IV+ZT5lP2UB/0U9xQV+JT4lPyUBg78\
+lg73DhwEghwFaxUg+2sFxVEuqPsVG/sOJ1glPR89JGT7FfsxGvsxr/sQ1C8eL9PrXfcNG/ce9wC8\
+7tkf9w37ZgX7BSH7L1P7YRv7Yfszzvcb+wUf+wX3G1L3S/d9GvdtyvdJ9xL3Jh73JfcR9zXU91gb\
+9zz3GmlG8B8O0vgu+K8V/K/7mBwFuAeS90Hxj6gb93v3PmhE9h/2RMD7A/srGvvk+1r7PPwgbmON\
+kFoe+VAE/GkHhreuiaQb9wvhn7PCH8KypszkGvcs+w7X+4lwcImIch4O95X3KhwFuRX3mP51Bjyj\
+S7taHlq7zXLfG+nUo7zAH7+7pc3fGvpw95j+hAf7K1n7CiY2HjYm+xtg+z0b+z77F7XeLx8u3l33\
+DPcvGg5j+W/2FXRmZGxTdAhzUlB/Thv7BzCoxUkfScRq3fUa9xC67OjRHtHo9xiu9z8bqK6GgbMf\
+9xI7yvszLTx7bEweVfdWBbTh8aD3Cxv3N/cMZkHXH9dAsfsh+2Ma+3kH+yOoMsRmHnZndHVygwiC\
+cm6Hahtna5imbh9upniogaoIc/ghFZRga492G/tZKEr7FSvDW/cD9yrW1vcqHw65+tf4XxX9kQaQ\
+NqhIwVwIXMHUc+Yb9wbiqcbGH+z7UwVEM/sXZ/tDG/s3+xW76ywfLOpb9xr3Pxr3Pb/3HPT0HvTz\
+9xG/9yYb9y/3EV0u6R/pLrr7CvsjGmyEXX1NHv2I91AV+KIG9zF6NNn7MBv7IzM9+zFoHw78Cvlw\
+HAUBFZ1UYZRuG1xjd2JqH2piellQGoKLg4yCHvdy+2L7bv31+4759fsw92L3MQaQ9xey9dTcCNzT\
+6LP3BxvG1H5x4B8O/Jz33xwFzhWzrX1vqB+nbplpYxpjfWlvbx5ubml9YxtjaZmobx9up32tsxqz\
+ma2oqB6np62Zsxv7FRz6MhX59vsd92H4Gv7DBw73RhwFvhX3jscFHPtMB/sYsjzach5CZElmLhv7\
+BVLa9zEfDtn5zBb4/wfmes5otR61aFGgPBtmZIF2Yh9hdmpydGwI/Zr7jvrD90gHuScF28/vs/cZ\
+G/cT8GU/1R/UPrAh+x0a/SUHDqfM+K4V9ze69xnq8h7y6fcQvvcvG/c39xJaKOUf5Si4+xv7Pxr7\
+P137GzAmHiYv+xJZ+zMb+zf7Er7xMR8w8F73G/c9GveYFvuA4PsK9z7ZyarIuR64yKLj9wUa9302\
+9wj7PT1NbE5dHl1OdDX7AhoO+5T5hvnaFaheWppWG1FYcVZeH15WdEs/Gvz2+476w/eOKQfa0eiy\
+9wgb4M1+cbgfDvvT90H5+hX7EPdd9xD3bgb3jucF+8r3uvtd+7r8aQc+l1WjbB5ro7V7xxvHw5us\
+vx/7egd3UTiBIBsgOanIUh9Sx27h9wMaDmX49ftAFXBEUlA0XAhcMyVz+wkb93EH91Xru+zLcO1W\
+9xgf++P50wX3lwb3uP1495v5eAX3lwYO+PAU+v8VAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAA\
+//8AAQAAAAFrZXJuAAgAAAABAAAAAQAEAAIAAAABAAgAAQAmAAQAAAACAA4AIAAEAAH/2wAF/6AA\
+Bv+gAAv/oAABAAX/wAABAAIAAwAMAAA=&quot;)}
+@font-face{font-family:'Arial-BoldMT';src:url(&quot;data:;base64,\
+T1RUTwADACAAAQAQQ0ZGILKMO8AAAACwAAAMSUdQT1OvL77EAAAM/AAAAGxjbWFwAnkCWAAAADwA\
+AAB0AAAAAQAAAAMAAAAMAAQAaAAAABYAEAADAAYAIABDAFAAUwBlAGkAbwByAHUAef//AAAAIABB\
+AE8AUgBhAGcAawByAHQAef///+H/wf+2/7X/qP+n/6b/pP+j/6AAAQAAAAAAAAAAAAAAAAAAAAAA\
+AAAAAAABAAQCAAEBAQ1BcmlhbC1Cb2xkTVQAAQEBOvgbAfgUBPgcDBX76/xDHAgAHAdNBR6gAEiC\
+gSX/i4seoABIgoEl/4uLDAf3YA/3fxD3gRGTHAxBEgACAQFlckFyaWFsKFIpIFRyYWRlbWFyayBv\
+ZiBUaGUgTW9ub3R5cGUgQ29ycG9yYXRpb24gcGxjIHJlZ2lzdGVyZWQgaW4gdGhlIFVTIFBhdCAm\
+IFRNIE9mZi4gYW5kIGVsc2V3aGVyZS4vRlNUeXBlIDAgZGVmAAABAAEAACICADABADMBAEIEAEgC\
+AEwEAFMAAFUBAFoAAAAAGgIAAQAbAB4ATQENAZsCOgKqAzsEBgTaBVIF0wZLBtEHmQf6CBcIRQhU\
+CO4JTAnMCg0KawrICxzK95QWHAUA+pQc+wAH/nSrFfpUHATA/lQGDv4cDpEcBb8W+9YG+xT34QX8\
+3gb7DfvhBfvOBvjPHAW6BffNBrX+ChX7Xvi0+1r8tAUOkfcqHAW6FfjeBvcI4oaCxB/Egb53uGy4\
+bLBjqVgIqViaUUwaRnlMZlIeZlJYYExu5HHQX7tMCLtMo0I2Gkh8S2xMHmxMYFhWZlVlSXQ8glqG\
++wuH+1GKCPyHBve8HATGFfvn91YH9wfTjY6nH76Rs52oqAioqJmxuhq4f7ByqB5yp2WcWpEIjm42\
+jfsfG/s+/NsV/Bz3pgf2zo6RqB+3k6+fp6oIpqqZtL8at4CwdqoedqpsoWOZCJliNJL7GxsOkfrT\
++K8V97MwX/s0QvsLJT4ZPST7FmT7MRv7V/s0zvcZ+xEf+xH3GUz3Sfd7GveIyvdS9xL3Gx73G/cS\
+9zrO92Eb90f3JlYh9wQfzky9Maz7Cfu5RRh612fHVLcIt1RIoTwb+wEzZD1IH0c9afsS+0Ma+02s\
++xjOPB48zuFk9hvazqS9xB/EvbPapPYIDvcM5PloFfcpofcSuPAerNa5zsXGxMbKt9CoCLLm9Z73\
+Cxv3bPdBSPsa9xYf9xX7Gsz7TvuDGvuBS/tN+xX7GR77GvsV+0BI+2sb+277Qc73GfsVH/sV9xlL\
+90v3fRr3xZUV+zqx+xLYNh412Oxg9wob9wrstuDXH9bgsfcT9z4a9zxm9xFC3h7eQSm0+w4b+w4p\
+YThAH0A3ZvsS+z0aDiD3KRYcBbr4bwf3SPcJhHzCH9910VvEQgjEQacs+wkaMXs/ak4eak5iW1lo\
+WGhYc1eACH1EJYT7Ghv7Vfy9BhwEwgT8NPc2B/cJ2ZOash+ymqqjoqwIoayWsbYawHu3bK4ebK5j\
+oFuUCJJoRI4gGw6R9yoWHAW6+QMH9zH3Bn5x0h/ScMNctkgItkigPjQa+wJrMEpEHkpDK177FXjL\
+ZsBitV60XsM80vsG90f7shj79gb7avfTP/cGV9NvqRlvqG2gbJYIlmxZkEcbT/z4BvniBPdvBvci\
+5JGXrh+ul6egn6gIn6iVsLcavH6zcaoecKlmnluTCI5zQ437DBv7ewYOINX4cRX3tKecKq9EwF4Z\
+XsDSdOUb6tOftLwfu7OjusEaroGod6QedqNooFidaJc8oPsPqvszsvsDvEvECDHcXu33CBrWoNG2\
+zB61zMi8260IrdrrnPcFG/dM9x9jOugf6Dq7IJD7G/u8fhh+1nDCYqwIrGFMmzgbNUh5aFofbHR7\
+bWUaaJpuqHIesGzmavckafckafZo0GfQZsFaskwIskyePS4aN3Q8XEIeXEJJVDZoCGc2IHn7Exv7\
+TfsjtuEoHyjgT/cRePc4CA774vf5+XYV+5O5qPK819G8GbzR86T3Hhv3Eel8bsgfyG23ZaReCKRd\
+lzf7DhqI+9wFLpBGlF8elF6bXKRYCPuqBoSegqaAsIaciJaJkFtcWGhUdAh0VFF/TRv7ATWpxkwf\
+TMZr1uYax5nBqLoeqLqzr7+kvqTWoOye9xek5qK+oAinB8F+snCiHqJwWZZBG1lkgXhvH293dGh6\
+Wgj4DPt4FWd/Un09ej16WHtzewhmcXlqYxpkmmmobh5uqLF9uBu+u5ysuR+tpKGqlrAIkqOPuc4a\
+DvcbFhwFuvet/KQH7uLxvPcLG/cV9lwu4B/gLbX7G/tDGvtJYPsgNSkeKTQiWvsPG05QmqpQH1Cp\
+WLhgxgj7MAed+L4V+wKcOq5WHkC8y2bcG8nApsC3H7bAod73Bhr3DXXjX8EewF9TpkYbSFNxV14f\
+XlZ1PCAaDvvi+sX5gBX7qVmCwna1aqcZp2pgmVYbRVNzW2IfYVp2OvsFGvsSoDK2Vx5XtcRx0hvA\
+t5qqrR+tqaO/mdX3qFwYbvsTVCw6SghKOvsBa/sdG/sv+xC87S8fLu1d9xz3QRr3Q7n3HejtHuzo\
+9xG89zIb9xXyb1TYH9dTwjas+wYIDvr1FvuZ9zAGYE5YXlBuCG1QT3xPG/sOI7zuNB807V/3HfdE\
+GvdItfcd4Oke6OD2uvcVG/cL8Voo4h/4pPetB/2C/iQV+wWbOapYHkK4y2bcG8zCp8K4H7jCot33\
+ARr3DnXjX8EewF9TpkYbSFRxVl4fXlZ0OyIaDvvi+Y735hX3rFxnJFI9PlYZVT0qcPsJG/tN+xzH\
+9w0yH0XsaPcO9yca90S59x7n7x7u5/cIvfchG/cy9xFXI+Yf5iK3+zSH+2sI/VQGjTiiSrZdCFy2\
+wXTMG7ewl6OpH6mjorKawAib97AVidx2yWO2CLVjWqBSG05YdV5jH2Ned0+MPggO9w1FFffVZJBm\
+mHGefRl3prWBxBvUwpahsB+kmp2imKwIlKKPtsoa9y8H+wc3IVL7FBv7I/sFx/cNOB9K6mr3C/ci\
+GvdGtvcc4eke6eD2uvcTG/cX9wFR+wfgH/cp95v+TQf7EYEtdk0edk1uWmZoZmhZb013CHdMPIEs\
+G/tI+xSqyUAfQMhl2eoalIuXjJge94/5AxX7BaE5t1ceVrbBccsb0MWmwbofusCj2/Qa9wJ03V7A\
+HsBeUaZGG0hTcVdgH19WdTsgGg74PxwFuhX8rwf15vcAwPcSG8zFf3O/H79zsmymZqVmnWGVXgiU\
+XpBELBr9A/ut+MUH9wOG0oCpHoCpeaNwnQiccGmUYhtcYoB0Zh9mdHFpel4Iel2CRzIa/Kj7rRwF\
+ugcO/hz3JxwEthX3mPet+5gH+60c+0oV+rr3rf66Bw774vcdFhwFuvet/Z4H9934CgX37gb7//wY\
++Bn9NgX7wwb7n/hx+xf7HQX76AcO/hz3JxYcBbr3rRz6RgcO9/D3Evq6FfeX+yUG9wXo9wLD9xQb\
+z8Z9b70fvW+0YatSusS9tcGnCKfBxZnIG9nNe2zBH8Frs1ymTgieXpVBJhr9O/ut+PMH9IHPeKoe\
+s3Fjn1UbZGZ/c2gfaHNyaHxdCHxcg0InGvyS+6342gfyhs6BqR6BqXyhdpoImnZukmcbYGR/dGgf\
+aHRzaXxfCHxfg0IlGvyY+60HDvrtFvut+LIG9weF1X+tHn+seKVwngiecGqUZRtaYH5wZB9kcHFo\
+fV8IfF+EOvsLGvx1+636uveZ+zAH9wzo9wjH9yEbycSAdb4fvnSyb6ZopWidZJZfCJVfkEw5Gg7d\
++LYV6KLmueIeueLMzuC5CLnf6aLzG/c19xdXI/If8iK++xj7Mxr7NVf7GSQiHiEj+xdW+zEbKi6h\
+tzMfMrdIzF3gCF3gdPL3Dhr3tHwVIqQ6vVMeU73Jb9Qb1Mmnw70fvMOk3PYa83LbWsMew1lNp0Ib\
+Qk1vU1kfWVNyOiIaDv04+DQW+636uveZ+ysGuNKzuq+iCKKutJa4G8vJeWjGHzT7iQWqXF+aYhtk\
+aYB2cB9wdXVkfFIIe1KD+wr7SRoO/av5Dvq6Fft0+1T8QAc0jVmPfR6OfJR/mIIIgpiahp4bpbGU\
+nbwfo/tuBW9KQX04G1helJxiH2KcbqF4pnimfa+EuQiFrIjN7hr4Y/sV93T3FfdnB/eu9zgF/AsH\
+DvniFvczB2RSWV9MaghqTEh7RRtES5uqUh9SqmK3csQIcsR+2e8a+TT3rfx8B/spkC+Wah6VaZ5w\
+pngId6augbUbu7aYprEfsaWlrJmyCJmykur3LBr4VPet/roHDvvimfq6Ffe/BveS/Yb3jPmGBfe3\
+BvwL/pJI+01yTXRcdWoZdGpycW53bnZne2GACIBgW4VWG1VWkZZXH3L3cAWCt7OHrhvMvJ6yqh+q\
+saO8nMYIDhwE4xQcBcEVAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAA//8AAQAAAAFrZXJuAAgA\
+AAABAAAAAQAEAAIAAAABAAgAAQAqAAQAAAADABAAFgAgAAEAAv+0AAIAAf+0ABn/tAACAAH/2wAC\
+/2gAAQADAAEAAgAG&quot;)}
+
+ </style>
+
+
+
+ <defs
+ id="defs1472">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</defs>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="hex triplets">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<rect
+ style="fill:#caf8f6;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1"
+ id="rect4206"
+ width="98.624001"
+ height="76.543999"
+ x="118.06892"
+ y="13.81922" /><rect
+ y="169.48727"
+ x="208.23248"
+ height="76.543999"
+ width="98.624001"
+ id="rect4716"
+ style="fill:#232323;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1" /><rect
+ y="240.53598"
+ x="149.55435"
+ height="76.543999"
+ width="98.624001"
+ id="rect4718"
+ style="fill:#5a0048;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1" /><rect
+ style="fill:#fa00c8;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1"
+ id="rect4720"
+ width="98.624001"
+ height="76.543999"
+ x="44.432781"
+ y="233.90982" /><rect
+ y="162.37894"
+ x="103.90397"
+ height="76.543999"
+ width="98.624001"
+ id="rect5770"
+ style="fill:#b3bbad;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1" /><rect
+ style="fill:#000e50;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1"
+ id="rect5772"
+ width="98.624001"
+ height="76.543999"
+ x="222.55765"
+ y="18.913427" /><rect
+ style="fill:#fcd63f;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1"
+ id="rect6787"
+ width="98.624001"
+ height="76.543999"
+ x="60.010452"
+ y="84.591927" /><rect
+ y="243.20786"
+ x="248.97467"
+ height="76.543999"
+ width="98.624001"
+ id="rect6789"
+ style="fill:#ffcb80;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1" /><rect
+ y="91.347267"
+ x="164.42346"
+ height="76.543999"
+ width="98.624001"
+ id="rect7822"
+ style="fill:#d07200;fill-opacity:1;stroke:#232323;stroke-width:0.368;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/files/opencs/scalable/referenceable-record/.directory b/files/opencs/scalable/referenceable-record/.directory
new file mode 100644
index 0000000000..c633c38a9a
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/.directory
@@ -0,0 +1,7 @@
+[Dolphin]
+GroupedSorting=true
+SortFoldersFirst=false
+Timestamp=2013,8,11,16,50,43
+Version=3
+ViewMode=2
+VisibleRoles=Compact_text,Compact_size
diff --git a/files/opencs/scalable/referenceable-record/activator.svg b/files/opencs/scalable/referenceable-record/activator.svg
new file mode 100644
index 0000000000..e917d93327
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/activator.svg
@@ -0,0 +1,1022 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="activator.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="-35.165989"
+ inkscape:cy="-65.142411"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3910"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3948"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-183.1468,-158.28118)" />
+ <linearGradient
+ id="linearGradient3942">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3944" />
+ <stop
+ style="stop-color:#343a30;stop-opacity:0;"
+ offset="1"
+ id="stop3946" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3846">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3848" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3850" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4012"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3892">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3894" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3896" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4014"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3901" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3903" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4018"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3888"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3920">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3922" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3924" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3876"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3886"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3934">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3936" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3938" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3941">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3943" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3945" />
+ </linearGradient>
+ <radialGradient
+ r="149.18433"
+ fy="260.15875"
+ fx="72.191498"
+ cy="260.15875"
+ cx="72.191498"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3964"
+ xlink:href="#linearGradient3846"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3955"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-445.4059,-219.9852)"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.90336573,0,0,0.90338687,22.936413,14.223841)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.40004875,0,0,0.40008471,208.47728,43.13925)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3967"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3969"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3971"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3973"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3975"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g3891"
+ transform="matrix(0.02092262,0,0,0.02092262,47.215663,982.03701)">
+ <g
+ id="g3910"
+ transform="matrix(10.683152,0,0,10.683152,-3645.2454,641.29516)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:url(#radialGradient3957);fill-opacity:1;stroke:#232323;stroke-width:3.33428001;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ d="m 228.65243,50.466564 -2.07197,21.916415 c -6.03313,0.861247 -11.83788,2.435667 -17.32366,4.63355 l -12.74905,-17.935734 -15.07038,8.700373 9.16735,20.02582 c -4.70501,3.708 -8.9606,7.965914 -12.66746,12.671992 l -20.03035,-9.167355 -8.70037,15.070375 17.94479,12.75359 c -2.19581,5.48421 -3.76873,11.28371 -4.629,17.3146 l -21.92549,2.07195 0,17.40529 21.92549,2.07195 c 0.86049,6.03003 2.43338,11.831 4.629,17.3146 l -17.94479,12.7536 8.70037,15.07037 20.03035,-9.17189 c 3.70758,4.70736 7.96589,8.96313 12.67199,12.67199 l -9.1719,20.03035 15.07038,8.70038 12.74905,-17.94027 c 5.48618,2.19834 11.29027,3.77225 17.32368,4.63355 l 2.07197,21.92095 17.40073,0 2.07195,-21.91642 c 6.03341,-0.86053 11.83737,-2.43598 17.32368,-4.63355 l 12.74905,17.93574 15.07038,-8.70038 -9.16737,-20.02582 c 4.70646,-3.70847 8.96404,-7.96494 12.672,-12.67199 l 20.02583,9.16736 8.70037,-15.07037 -17.94028,-12.74907 c 2.1968,-5.48521 3.77279,-11.2871 4.63356,-17.31913 l 21.91641,-2.07195 0,-17.40529 -21.91641,-2.07195 c -0.86055,-6.03288 -2.4366,-11.83332 -4.63356,-17.31913 L 325.49905,106.382 316.79868,91.311625 296.77739,100.47444 C 293.069,95.766874 288.80805,91.51116 284.10085,87.802454 l 9.16738,-20.021286 -15.07038,-8.700373 -12.74453,17.935734 c -5.48751,-2.198217 -11.29324,-3.772343 -17.32819,-4.63355 l -2.07195,-21.916415 -17.40075,0 z m 8.70491,68.129496 c 15.80859,0 28.62644,12.81639 28.62644,28.62647 0,15.81007 -12.81784,28.62644 -28.62643,28.62644 -15.80863,0 -28.62193,-12.81637 -28.62193,-28.62644 0,-15.81008 12.8133,-28.62647 28.62193,-28.62647 z"
+ id="path3912" />
+ <path
+ id="path3914"
+ d="m 237.3574,87.576156 c -32.93997,0 -59.64326,26.703824 -59.64326,59.646834 0,32.94302 26.70329,59.65043 59.64326,59.65043 32.93996,0 59.64326,-26.70741 59.64326,-59.65043 0,-32.94301 -26.7033,-59.646834 -59.64326,-59.646834 z m 0,37.061524 c 12.47247,0 22.58532,10.1117 22.58532,22.58531 0,12.47363 -10.11286,22.58532 -22.58532,22.58532 -12.47247,0 -22.58174,-10.11169 -22.58174,-22.58532 0,-12.47361 10.10927,-22.58531 22.58174,-22.58531 z"
+ style="fill:url(#radialGradient3959);fill-opacity:1;stroke:#232323;stroke-width:2.12434816;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/apparatus.svg b/files/opencs/scalable/referenceable-record/apparatus.svg
new file mode 100644
index 0000000000..37cef0e890
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/apparatus.svg
@@ -0,0 +1,1058 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="apparatus.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.8101934"
+ inkscape:cx="-63.738285"
+ inkscape:cy="32.253001"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5597"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="false"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3985">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3987" />
+ <stop
+ id="stop3993"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.5"
+ offset="1"
+ id="stop3989" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3954">
+ <stop
+ style="stop-color:#b89f9f;stop-opacity:1;"
+ offset="0"
+ id="stop3956" />
+ <stop
+ style="stop-color:#443232;stop-opacity:1;"
+ offset="1"
+ id="stop3958" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3944">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3946" />
+ <stop
+ style="stop-color:#b49797;stop-opacity:1;"
+ offset="1"
+ id="stop3948" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3918">
+ <stop
+ style="stop-color:#737373;stop-opacity:1;"
+ offset="0"
+ id="stop3920" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3922" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#493535;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3944"
+ id="radialGradient3942"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.92976036,0,0,2.6869958,-56.971163,888.30913)"
+ cx="-15.5"
+ cy="30.53125"
+ fx="-15.5"
+ fy="30.53125"
+ r="15.34375" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3944"
+ id="radialGradient3952"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="20.249271"
+ gradientTransform="matrix(1,0,0,0.24294293,0,-16.77506)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3954"
+ id="linearGradient3960"
+ x1="-126.17325"
+ y1="953.44806"
+ x2="-137.5746"
+ y2="991.67816"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.98345881,0,0,1.0246049,42.42393,-31.855387)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3962"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3985"
+ id="linearGradient3991"
+ x1="-102.68832"
+ y1="973.31067"
+ x2="-78.542328"
+ y2="980.34857"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ id="g5597"
+ transform="matrix(0.85483505,0,0,1,-20.035433,-2.2155805e-6)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:url(#radialGradient3942);fill-opacity:1;stroke:#966e6e;stroke-width:1.10622696000000009;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m -89.195028,954.65568 c 0,7.61583 2.754963,14.3738 7.001688,18.67942 l -6.203574,7.51572 -0.181391,0 c 0,2.87251 7.82372,5.1863 17.48608,5.1863 9.662371,0 17.522365,-2.31379 17.522365,-5.1863 l -6.566353,-7.95524 c 3.994497,-4.31983 6.566353,-10.89096 6.566353,-18.2399 l -35.625168,0 z"
+ id="path3935" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient3962);fill-opacity:1;stroke:url(#radialGradient3952);stroke-opacity:1"
+ id="path4311"
+ sodipodi:cx="-152.88422"
+ sodipodi:cy="-22.158251"
+ sodipodi:rx="19.749271"
+ sodipodi:ry="4.4194174"
+ d="m -133.13494,-22.158251 a 19.749271,4.4194174 0 1 1 -39.49855,0 19.749271,4.4194174 0 1 1 39.49855,0 z"
+ transform="matrix(0.90165016,0,0,1.1768756,66.452928,980.73826)" />
+ <path
+ style="fill:url(#linearGradient3960);fill-opacity:1;stroke:#232323;stroke-width:0.33186809000000000;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m -85.419119,957.25801 c -0.74559,-0.16329 -1.629975,0.14268 -2.346661,0.97197 -2.659754,3.07769 -5.034552,11.552 -6.008009,17.16572 -0.904761,0.68879 -1.613264,1.70279 -1.948458,2.91662 -0.719989,2.60728 0.593312,5.14508 2.92708,5.65624 2.333766,0.51118 4.799337,-1.19893 5.519327,-3.8062 0.333599,-1.20803 0.224209,-2.38563 -0.213283,-3.36691 2.063483,-5.37412 4.377818,-13.88029 3.62318,-17.71127 -0.203035,-1.03075 -0.807588,-1.66285 -1.553176,-1.82617 z"
+ id="rect3930"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3983"
+ d="m -85.419119,957.25801 c -0.74559,-0.16329 -1.629975,0.14268 -2.346661,0.97197 -2.659754,3.07769 -5.034552,11.552 -6.008009,17.16572 -0.904761,0.68879 -1.613264,1.70279 -1.948458,2.91662 -0.719989,2.60728 0.593312,5.14508 2.92708,5.65624 2.333766,0.51118 4.799337,-1.19893 5.519327,-3.8062 0.333599,-1.20803 0.224209,-2.38563 -0.213283,-3.36691 2.063483,-5.37412 4.377818,-13.88029 3.62318,-17.71127 -0.203035,-1.03075 -0.807588,-1.66285 -1.553176,-1.82617 z"
+ style="fill:url(#linearGradient3991);fill-opacity:1;stroke:none;stroke-width:0.33186809000000000;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;opacity:0.50000000000000000" />
+ </g>
+ <path
+ style="fill:#fa0000;fill-opacity:1;stroke:none"
+ d="m -69.333164,982.81211 0,3.65905 -6.823881,0 11.138221,11.04106 11.138216,-11.04106 -6.823877,0 0,-3.65905 -8.628679,0 z"
+ id="path5146"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/book.svg b/files/opencs/scalable/referenceable-record/book.svg
new file mode 100644
index 0000000000..56c8e6c6fb
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/book.svg
@@ -0,0 +1,687 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="book.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8.0000002"
+ inkscape:cx="52.294682"
+ inkscape:cy="4.9257181"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3891"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-1.6900304,-2.3597835)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-1.6900304,-2.3597835)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-1.6900304,-2.3597835)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g3891"
+ transform="matrix(0.02092262,0,0,0.02092262,47.215663,982.03701)">
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ d="m -2205.1104,2996.825 49.0856,-84.9175 c -95.8266,-280.635 -2.1625,-466.3113 -2.1625,-466.3113 l -46.9231,86.3481 c 0,0 -95.8266,184.2457 0,464.8807 z"
+ id="path3189"
+ style="fill:url(#linearGradient8509);fill-opacity:1;fill-rule:evenodd;stroke:#232323;stroke-width:9.55903244;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ style="fill:#000e50;fill-opacity:1;fill-rule:evenodd;stroke:#232323;stroke-width:14.33854866;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path8511"
+ d="m -2205.1104,2996.825 2231.227886,0 -638.116266,-1105.2497 -954.23512,0 -638.8765,1105.2497 z"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ d="m -2156.0248,2911.9075 c -95.8267,-280.6349 -2.1624,-462.8889 -2.1624,-462.8889 l 2041.44844,1.7112 0,460.31 z"
+ id="path4652"
+ style="fill:url(#linearGradient9254);fill-opacity:1;fill-rule:evenodd;stroke:#232323;stroke-width:14.33854866;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m -2205.1104,2531.9443 2231.227846,0 -638.116256,-1105.2497 -954.23509,0 -638.8765,1105.2497 z"
+ id="path2415"
+ style="fill:url(#linearGradient6042);fill-opacity:1;fill-rule:evenodd;stroke:#232323;stroke-width:14.33854866;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(22.19212,0,0,22.19212,-1374.3504,1553.9394)"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#5a0048;fill-opacity:1;stroke:#000000;stroke-width:0.64610988;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans"
+ id="text7930">
+ <path
+ d="m -9.9483287,3.3515314 c 0.502131,-0.032483 1.2334738,-0.025076 1.7206493,-0.032648 1.1695202,0.011845 2.0464005,0.1084151 3.0507321,0.1185805 2.9493189,0.029852 5.57811129,-0.6462625 6.13605785,-2.3885307 0.42249075,-1.31928818 -0.89007164,-1.79067597 -3.09735305,-1.81374225 0,0 0.012685,-0.0299781 0.012685,-0.0299781 C -0.59087915,-1.126304 0.71093478,-1.5016854 0.97402754,-2.2519624 1.2137294,-2.9355342 0.5343313,-3.5167955 -1.6358041,-3.471392 c -1.1793884,0.030419 -1.7591718,0.075979 -2.9564485,0.063225 -0.329724,-0.00351 -1.1183709,-0.029182 -1.4479812,-0.032694 0,0 -0.032178,0.025566 -0.032178,0.025566 0.3319588,0.186026 0.2270562,0.4668657 0.072154,0.7429275 0,0 -2.77776,4.9504224 -2.77776,4.9504224 -0.2071667,0.3692049 -0.5012792,0.7446644 -1.1834494,1.0232682 0,0 0.013139,0.050208 0.013139,0.050208 m 6.1536048,-3.82970524 c 2.1257227,-0.008457 3.19056282,0.33193308 2.7268173,1.57271104 -0.4655547,1.2456182 -2.148481,1.9159431 -4.4275521,1.8560024 -0.5788187,-0.00588 -1.13344,-0.060472 -1.6474964,-0.1510845 0,0 1.6559969,-3.23385804 1.6559969,-3.23385804 0.5155975,-0.004876 1.1022734,-0.0397016 1.6922343,-0.0437709 M -4.1453579,-3.054302 c 0.4952078,-0.038774 1.1548089,-0.093216 1.7355118,-0.095801 1.3385837,-0.012015 1.94525982,0.2676829 1.6965154,0.8895237 -0.3027573,0.8953473 -1.8288112,1.30169076 -3.5997134,1.44292756 -0.4874014,0.0350725 -0.8134777,0.23386055 -1.1739141,0.3832489 0,0 1.3416003,-2.61989906 1.3416003,-2.61989906"
+ style="font-variant:normal;font-stretch:normal;fill:#5a0048;fill-opacity:1;stroke:#000000;stroke-width:0.64610988;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Apolonia;-inkscape-font-specification:Apolonia"
+ id="path7935"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 8.3160092,-1.5844315 C 5.8331387,-1.5724091 3.9816949,-0.75098647 3.5687494,0.88874049 3.1130925,2.6980661 4.6796944,3.6504397 7.6152315,3.6549184 10.587566,3.6598124 12.401697,2.5654724 12.428244,0.79509868 12.452935,-0.85153854 10.865776,-1.6051517 8.3160092,-1.5844315 m -0.037187,0.2780244 C 9.8584867,-1.318823 10.756032,-0.41807786 10.680129,0.80956304 10.598162,2.1352847 9.4064313,3.2987265 7.5806321,3.2925725 5.8754735,3.3000367 5.0663857,2.1720625 5.3321619,0.87412899 5.5806824,-0.33953513 6.7328869,-1.2935821 8.2788227,-1.3064071"
+ style="font-variant:normal;font-stretch:normal;fill:#5a0048;fill-opacity:1;stroke:#000000;stroke-width:0.64610988;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Apolonia;-inkscape-font-specification:Apolonia"
+ id="path7937"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 18.578388,-1.4764483 c -2.467731,0.012151 -4.069704,0.83551072 -4.000021,2.4788775 0.07688,1.8131989 1.915129,2.7674802 4.837896,2.7718529 2.959515,0.00479 4.444405,-1.0918926 3.95223,-2.86605365 C 22.910682,-0.74205982 21.110158,-1.49732 18.578388,-1.4764483 m 0.04447,0.2786599 c 1.568628,-0.012508 2.725891,0.8902308 3.010029,2.12058282 C 21.939705,2.2513665 21.094518,3.4172876 19.275657,3.4111928 17.580851,3.4187395 16.44505,2.2884416 16.329146,0.98771526 16.220761,-0.2286302 17.087919,-1.1848713 18.622854,-1.1977884"
+ style="font-variant:normal;font-stretch:normal;fill:#5a0048;fill-opacity:1;stroke:#000000;stroke-width:0.64610988;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Apolonia;-inkscape-font-specification:Apolonia"
+ id="path7939"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 36.179516,3.7686534 C 35.424775,3.5626503 34.6437,2.8518432 34.030143,2.3518883 33.46376,1.887341 32.936046,1.4450023 32.207796,1.0863901 31.566579,0.76716017 30.69279,0.61965923 29.567457,0.57618475 c 0,0 -0.0095,-0.0212721 -0.0095,-0.0212721 0.876969,-0.35751599 1.49946,-0.65950878 1.991498,-0.93760042 0.505466,-0.30314386 0.858842,-0.56377695 1.130926,-0.85960563 0,0 -0.02679,-0.047984 -0.02679,-0.047984 -0.513151,0.051863 -1.235282,0.053832 -1.853595,0.047331 0.112408,0.2225716 0.0052,0.44565489 -0.214532,0.67043333 -0.555149,0.54920693 -1.896311,1.05503487 -2.610379,1.31316668 0,0 -1.639791,-4.07585061 -1.639791,-4.07585061 -0.706809,0.02643 -1.480805,0.06074 -2.213886,0.069979 0,0 0.0086,0.025714 0.0086,0.025714 0.546257,0.091514 0.868435,0.3198242 0.956585,0.5663651 0,0 1.930611,5.3996161 1.930611,5.3996161 0.146495,0.4097224 0.126645,0.6413886 -0.220162,0.947148 0,0 0.05882,0.050528 0.05882,0.050528 0.880484,-0.028544 1.919216,-0.018025 2.827986,0.028632 0,0 0.02074,-0.049711 0.02074,-0.049711 -0.561764,-0.3149136 -0.758443,-0.5487459 -0.923335,-0.9585994 0,0 -0.801851,-1.99307414 -0.801851,-1.99307414 0.972234,-0.0113261 2.278288,0.20643848 3.039091,0.67383964 0.580223,0.352485 1.003169,0.7327502 1.440406,1.132377 0.446295,0.4079049 0.966618,0.9108648 1.485371,1.2383027 0.609802,-0.018793 1.281024,-0.03693 2.201842,0.022292 0,0 0.03341,-0.049558 0.03341,-0.049558"
+ style="font-variant:normal;font-stretch:normal;fill:#5a0048;fill-opacity:1;stroke:#000000;stroke-width:0.64610988;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Apolonia;-inkscape-font-specification:Apolonia"
+ id="path7941"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:none;stroke:#b3b3b3;stroke-width:0.15362099px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m -1163.8471,2265.385 c -5.4387,6.3038 -5.8955,15.8907 2.894,19.4065 7.8828,3.1531 14.2275,-15.2718 2.5535,-21.1088 -1.5632,-0.7816 -1.5686,2.8209 -1.1916,3.5749 2.0776,4.1553 7.1286,1.66 9.533,6.4688 0.304,0.6079 2.7606,7.32 2.5535,7.32 -3.4046,0 4.1451,-11.3758 8.1711,-9.3628 5.2834,2.6416 2.5792,13.1022 11.7461,11.0651 5.973,-1.3273 5.575,-15.2996 0.5107,-12.7674 -5.298,2.6489 0.072,9.2918 3.5748,10.7246 2.9815,1.2197 7.345,1.0214 9.8736,1.0214"
+ id="path4797"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/container.svg b/files/opencs/scalable/referenceable-record/container.svg
new file mode 100644
index 0000000000..8c9465b668
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/container.svg
@@ -0,0 +1,1899 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="container.svg"
+ inkscape:version="0.48.4 r9939"
+ version="1.1"
+ id="svg2"
+ height="48"
+ width="48">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313709"
+ inkscape:cx="13.572484"
+ inkscape:cy="13.658831"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3302"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true"
+ inkscape:snap-midpoints="false">
+ <inkscape:grid
+ id="grid3225"
+ type="xygrid" />
+ <sodipodi:guide
+ position="15.974848,36.682844"
+ orientation="13.546292,0"
+ id="guide5067" />
+ <sodipodi:guide
+ position="15.974848,23.136552"
+ orientation="0,2.6662554"
+ id="guide5069" />
+ <sodipodi:guide
+ position="18.641104,23.136552"
+ orientation="-13.546292,0"
+ id="guide5071" />
+ <sodipodi:guide
+ position="24.041629,-85.206363"
+ orientation="0,-2.6662554"
+ id="guide5073" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient4932">
+ <stop
+ id="stop4934"
+ offset="0"
+ style="stop-color:#000e50;stop-opacity:1;" />
+ <stop
+ id="stop4936"
+ offset="1"
+ style="stop-color:#000e50;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4922">
+ <stop
+ id="stop4924"
+ offset="0"
+ style="stop-color:#000e50;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0.5"
+ id="stop4930" />
+ <stop
+ id="stop4926"
+ offset="1"
+ style="stop-color:#000e50;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4885">
+ <stop
+ id="stop4887"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0.5"
+ id="stop4895" />
+ <stop
+ id="stop4889"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4877"
+ inkscape:collect="always">
+ <stop
+ id="stop4879"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop4881"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4853">
+ <stop
+ id="stop4855"
+ offset="0"
+ style="stop-color:#eeeeee;stop-opacity:1;" />
+ <stop
+ id="stop4857"
+ offset="1"
+ style="stop-color:#e1fbfa;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4843">
+ <stop
+ id="stop4845"
+ offset="0"
+ style="stop-color:#dcfaf9;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.5"
+ id="stop4851" />
+ <stop
+ id="stop4847"
+ offset="1"
+ style="stop-color:#dcfaf9;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4817">
+ <stop
+ id="stop4819"
+ offset="0"
+ style="stop-color:#caf8f6;stop-opacity:1;" />
+ <stop
+ style="stop-color:#9ef2ee;stop-opacity:1;"
+ offset="0.5"
+ id="stop4825" />
+ <stop
+ id="stop4821"
+ offset="1"
+ style="stop-color:#caf8f6;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4801">
+ <stop
+ id="stop4803"
+ offset="0"
+ style="stop-color:#c8c8c8;stop-opacity:1;" />
+ <stop
+ id="stop4805"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4729">
+ <stop
+ id="stop4731"
+ offset="0"
+ style="stop-color:#b3bbad;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.5"
+ id="stop4737" />
+ <stop
+ id="stop4733"
+ offset="1"
+ style="stop-color:#b3bbad;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7689"
+ inkscape:collect="always">
+ <stop
+ id="stop7691"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop7693"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient6181">
+ <stop
+ style="stop-color:#232323;stop-opacity:1;"
+ offset="0"
+ id="stop6183" />
+ <stop
+ style="stop-color:#232323;stop-opacity:0;"
+ offset="1"
+ id="stop6185" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6173">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6175" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6177" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6165">
+ <stop
+ style="stop-color:#321b00;stop-opacity:1;"
+ offset="0"
+ id="stop6167" />
+ <stop
+ style="stop-color:#502b00;stop-opacity:1;"
+ offset="1"
+ id="stop6169" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6109">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6111" />
+ <stop
+ id="stop6117"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop6113" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5099">
+ <stop
+ style="stop-color:#321b00;stop-opacity:1;"
+ offset="0"
+ id="stop5101" />
+ <stop
+ style="stop-color:#502b00;stop-opacity:1;"
+ offset="1"
+ id="stop5103" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5089">
+ <stop
+ style="stop-color:#321b00;stop-opacity:1;"
+ offset="0"
+ id="stop5091" />
+ <stop
+ style="stop-color:#502b00;stop-opacity:1;"
+ offset="1"
+ id="stop5093" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5025">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop5027" />
+ <stop
+ id="stop5033"
+ offset="0.5"
+ style="stop-color:#502c00;stop-opacity:1;" />
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="1"
+ id="stop5029" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3974">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3976" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3978" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9305">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop9307" />
+ <stop
+ style="stop-color:#002bf4;stop-opacity:1;"
+ offset="1"
+ id="stop9309" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9289">
+ <stop
+ style="stop-color:#ffcb80;stop-opacity:1;"
+ offset="0"
+ id="stop9291" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop9293" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8860">
+ <stop
+ id="stop8862"
+ offset="0"
+ style="stop-color:#ff0000;stop-opacity:1;" />
+ <stop
+ id="stop8864"
+ offset="1"
+ style="stop-color:#ff0000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8850">
+ <stop
+ id="stop8852"
+ offset="0"
+ style="stop-color:#b3bbad;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop8858" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8832">
+ <stop
+ id="stop8834"
+ offset="0"
+ style="stop-color:#d07200;stop-opacity:1;" />
+ <stop
+ id="stop8836"
+ offset="1"
+ style="stop-color:#965200;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ id="stop4482"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ id="stop4484"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3882"
+ inkscape:collect="always">
+ <stop
+ id="stop3884"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3886"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ id="stop3941"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:0.784" />
+ <stop
+ id="stop3943"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ id="stop3881"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3883"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ id="stop3869"
+ offset="0"
+ style="stop-color:#5a0048;stop-opacity:1;" />
+ <stop
+ id="stop3871"
+ offset="1"
+ style="stop-color:#960078;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ id="stop3855"
+ offset="0"
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;" />
+ <stop
+ style="stop-color:#caf8db;stop-opacity:0.58823532;"
+ offset="0.33690071"
+ id="stop3861" />
+ <stop
+ id="stop3857"
+ offset="1"
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ id="stop3847"
+ offset="0"
+ style="stop-color:#aa0088;stop-opacity:1;" />
+ <stop
+ id="stop3849"
+ offset="1"
+ style="stop-color:#fa00c8;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ id="stop3839"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3841"
+ offset="1"
+ style="stop-color:#caf8f6;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5958"
+ inkscape:collect="always">
+ <stop
+ id="stop5960"
+ offset="0"
+ style="stop-color:#caf8f6;stop-opacity:1;" />
+ <stop
+ id="stop5962"
+ offset="1"
+ style="stop-color:#caf8f6;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ id="stop5924"
+ offset="0"
+ style="stop-color:#c84900;stop-opacity:1;" />
+ <stop
+ id="stop5926"
+ offset="1"
+ style="stop-color:#c84900;stop-opacity:0.19607843;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6817" />
+ <stop
+ id="stop6825"
+ offset="0.40229002"
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0;"
+ offset="1"
+ id="stop6819" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ style="stop-color:#204a87;stop-opacity:1;"
+ offset="0"
+ id="stop4024" />
+ <stop
+ style="stop-color:#729fcf;stop-opacity:1;"
+ offset="1"
+ id="stop4026" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3725" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3727" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ style="stop-color:#99bbd4;stop-opacity:1;"
+ offset="0"
+ id="stop3717" />
+ <stop
+ style="stop-color:#5d93ba;stop-opacity:1;"
+ offset="1"
+ id="stop3719" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3709" />
+ <stop
+ style="stop-color:#75a3c3;stop-opacity:1;"
+ offset="1"
+ id="stop3711" />
+ </linearGradient>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective10" />
+ <inkscape:perspective
+ id="perspective2920"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3707"
+ id="linearGradient3713"
+ x1="63.765881"
+ y1="949.3266"
+ x2="63.765881"
+ y2="1029.8864"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341189,5.74082)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715"
+ id="linearGradient3721"
+ x1="63.765881"
+ y1="936.95227"
+ x2="63.765881"
+ y2="983.41931"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341196,6.0908086)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3723"
+ id="linearGradient3729"
+ x1="37.375645"
+ y1="948.8266"
+ x2="37.375645"
+ y2="1030.7611"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341189,5.74082)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715"
+ id="linearGradient3735"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ x1="63.765881"
+ y1="936.95227"
+ x2="63.765881"
+ y2="983.41931" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3707"
+ id="linearGradient3737"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341189,5.74082)"
+ x1="63.765881"
+ y1="949.3266"
+ x2="63.765881"
+ y2="1029.8864" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3723"
+ id="linearGradient3739"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.2341189,5.74082)"
+ x1="37.375645"
+ y1="948.8266"
+ x2="37.375645"
+ y2="1030.7611" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3707"
+ id="linearGradient3742"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ x1="63.765881"
+ y1="949.3266"
+ x2="63.765881"
+ y2="1029.8864" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3723"
+ id="linearGradient3744"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ x1="37.375645"
+ y1="948.8266"
+ x2="37.375645"
+ y2="1030.7611" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715"
+ id="linearGradient3747"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ x1="63.765881"
+ y1="936.95227"
+ x2="63.765881"
+ y2="983.41931" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715"
+ id="linearGradient3753"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ x1="63.765881"
+ y1="936.95227"
+ x2="63.765881"
+ y2="983.41931" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3707"
+ id="linearGradient3755"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ x1="63.765881"
+ y1="949.3266"
+ x2="63.765881"
+ y2="1029.8864" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3723"
+ id="linearGradient3757"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ x1="37.375645"
+ y1="948.8266"
+ x2="37.375645"
+ y2="1030.7611" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4022"
+ id="linearGradient4028"
+ x1="14.991861"
+ y1="1048.3364"
+ x2="47.902649"
+ y2="1008.9376"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)" />
+ <linearGradient
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)"
+ gradientUnits="userSpaceOnUse"
+ y2="164.65698"
+ x2="-15.495684"
+ y1="166.73387"
+ x1="-14.939182"
+ id="linearGradient5433"
+ xlink:href="#linearGradient3818"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ id="stop3820"
+ offset="0"
+ style="stop-color:#1e1e1e;stop-opacity:1;" />
+ <stop
+ id="stop3822"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-2"
+ id="radialGradient5639"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ cx="-171.01654"
+ cy="123.88583"
+ fx="-171.01654"
+ fy="123.88583"
+ r="29.000444" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ id="stop3717-4"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop3719-9"
+ offset="1"
+ style="stop-color:#b3bbad;stop-opacity:1;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5299"
+ id="radialGradient5641"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ cx="-182.4375"
+ cy="116.19179"
+ fx="-182.4375"
+ fy="116.19179"
+ r="28.421875" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ id="stop5301"
+ offset="0"
+ style="stop-color:#000e50;stop-opacity:1;" />
+ <stop
+ id="stop5303"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-2"
+ id="radialGradient6812"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ cx="-172.875"
+ cy="119.59179"
+ fx="-172.875"
+ fy="119.59179"
+ r="29.000444" />
+ <linearGradient
+ gradientTransform="translate(8.4245144,951.32919)"
+ inkscape:collect="always"
+ xlink:href="#linearGradient6815"
+ id="linearGradient6823"
+ x1="27.183681"
+ y1="39.630547"
+ x2="26.643335"
+ y2="37.613949"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient2843"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ style="stop-color:#99bbd4;stop-opacity:1;"
+ offset="0"
+ id="stop3717-6" />
+ <stop
+ style="stop-color:#5d93ba;stop-opacity:1;"
+ offset="1"
+ id="stop3719-4" />
+ </linearGradient>
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3184"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3321"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6815-6"
+ id="linearGradient6823-6"
+ x1="27.183681"
+ y1="39.630547"
+ x2="26.643335"
+ y2="37.613949"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-82.281067,1085.8147)" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6817-4" />
+ <stop
+ id="stop6825-9"
+ offset="0.40229002"
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0;"
+ offset="1"
+ id="stop6819-5" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-2-4"
+ id="radialGradient5639-0"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ cx="-172.875"
+ cy="119.59179"
+ fx="-172.875"
+ fy="119.59179"
+ r="29.000444" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ id="stop3717-4-8"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop3719-9-7"
+ offset="1"
+ style="stop-color:#b3bbad;stop-opacity:1;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5299-7"
+ id="radialGradient5641-1"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ cx="-182.4375"
+ cy="116.19179"
+ fx="-182.4375"
+ fy="116.19179"
+ r="28.421875" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ id="stop5301-2"
+ offset="0"
+ style="stop-color:#000e50;stop-opacity:1;" />
+ <stop
+ id="stop5303-7"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5299-7"
+ id="radialGradient3063"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ cx="-182.4375"
+ cy="116.19179"
+ fx="-182.4375"
+ fy="116.19179"
+ r="28.421875" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="-45.944225"
+ x2="-72.843758"
+ y1="-45.656265"
+ x1="-72.843758"
+ id="linearGradient5928"
+ xlink:href="#linearGradient5922"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ r="20.15"
+ fy="-39.275696"
+ fx="-103.70541"
+ cy="-39.275696"
+ cx="-103.70541"
+ id="radialGradient5964"
+ xlink:href="#linearGradient5958"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ y2="-45.944225"
+ x2="-72.843758"
+ y1="-45.656265"
+ x1="-72.843758"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5973"
+ xlink:href="#linearGradient5922"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ r="20"
+ fy="-33.913769"
+ fx="-97.089668"
+ cy="-33.913769"
+ cx="-97.089668"
+ id="radialGradient3843"
+ xlink:href="#linearGradient3837"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ r="13.49375"
+ fy="54.418995"
+ fx="34.5625"
+ cy="54.418995"
+ cx="34.5625"
+ id="radialGradient3851"
+ xlink:href="#linearGradient3867"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(44.104424,-40.169184)"
+ gradientUnits="userSpaceOnUse"
+ y2="1017.9076"
+ x2="-104.17097"
+ y1="1017.7004"
+ x1="-140.89291"
+ id="linearGradient3859"
+ xlink:href="#linearGradient3853"
+ inkscape:collect="always" />
+ <radialGradient
+ r="20"
+ fy="-37.504356"
+ fx="-116.29909"
+ cy="-37.504356"
+ cx="-116.29909"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3865"
+ xlink:href="#linearGradient3837"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ r="19.749271"
+ fy="-33.680485"
+ fx="-150.6485"
+ cy="-33.680485"
+ cx="-150.6485"
+ id="radialGradient3875"
+ xlink:href="#linearGradient3845"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ id="radialGradient3885"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3889"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3893"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3897"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3901"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3905"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3909"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3913"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3917"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3921"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ gradientUnits="userSpaceOnUse"
+ y2="996.9176"
+ x2="-79.476425"
+ y1="982.83502"
+ x1="-79.476425"
+ id="linearGradient3945"
+ xlink:href="#linearGradient3939"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3947"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3949"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3951"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3953"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3955"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3957"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3959"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3961"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3963"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ r="3.4179688"
+ fy="53.175781"
+ fx="30.273438"
+ cy="53.175781"
+ cx="30.273438"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3965"
+ xlink:href="#linearGradient3879"
+ inkscape:collect="always" />
+ <radialGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ r="19.749271"
+ fy="-22.158251"
+ fx="-152.88422"
+ cy="-22.158251"
+ cx="-152.88422"
+ id="radialGradient3888"
+ xlink:href="#linearGradient3882"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="-41.022785"
+ x2="-152.88422"
+ y1="-17.455395"
+ x1="-152.88422"
+ id="linearGradient4486"
+ xlink:href="#linearGradient4480"
+ inkscape:collect="always" />
+ <radialGradient
+ r="13.49375"
+ fy="54.418995"
+ fx="34.5625"
+ cy="54.418995"
+ cx="34.5625"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5606"
+ xlink:href="#linearGradient3867"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="996.9176"
+ x2="-79.476425"
+ y1="982.83502"
+ x1="-79.476425"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5608"
+ xlink:href="#linearGradient3939"
+ inkscape:collect="always" />
+ <radialGradient
+ r="19.749271"
+ fy="-33.680485"
+ fx="-150.6485"
+ cy="-33.680485"
+ cx="-150.6485"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5610"
+ xlink:href="#linearGradient3845"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1017.9076"
+ x2="-104.17097"
+ y1="1017.7004"
+ x1="-140.89291"
+ gradientTransform="translate(44.104424,-40.169184)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5612"
+ xlink:href="#linearGradient3853"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="-45.944225"
+ x2="-72.843758"
+ y1="-45.656265"
+ x1="-72.843758"
+ spreadMethod="reflect"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5614"
+ xlink:href="#linearGradient5922"
+ inkscape:collect="always" />
+ <radialGradient
+ r="20"
+ fy="-37.504356"
+ fx="-116.29909"
+ cy="-37.504356"
+ cx="-116.29909"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5616"
+ xlink:href="#linearGradient3837"
+ inkscape:collect="always" />
+ <radialGradient
+ r="20"
+ fy="-33.913769"
+ fx="-97.089668"
+ cy="-33.913769"
+ cx="-97.089668"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5618"
+ xlink:href="#linearGradient3837"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4676">
+ <stop
+ id="stop4678"
+ offset="0"
+ style="stop-color:#d07200;stop-opacity:1;" />
+ <stop
+ id="stop4680"
+ offset="1"
+ style="stop-color:#603500;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4691">
+ <stop
+ id="stop4693"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop4695"
+ offset="1"
+ style="stop-color:#cccccc;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4713">
+ <stop
+ id="stop4715"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1" />
+ <stop
+ style="stop-color:#707070;stop-opacity:0;"
+ offset="0.5"
+ id="stop4721" />
+ <stop
+ id="stop4717"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4723">
+ <stop
+ id="stop4725"
+ offset="0"
+ style="stop-color:#969696;stop-opacity:1;" />
+ <stop
+ id="stop4727"
+ offset="1"
+ style="stop-color:#cccccc;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4653">
+ <stop
+ id="stop4655"
+ offset="0"
+ style="stop-color:#fa00c8;stop-opacity:1;" />
+ <stop
+ id="stop4657"
+ offset="1"
+ style="stop-color:#aa0088;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9289"
+ id="linearGradient9295"
+ x1="203.09436"
+ y1="1139.3492"
+ x2="186.81903"
+ y2="1093.0337"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(4.3622682,6.7170197)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9305"
+ id="linearGradient9311"
+ x1="124.46073"
+ y1="1154.9745"
+ x2="127.51315"
+ y2="1151.9221"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8850"
+ id="linearGradient9313"
+ gradientUnits="userSpaceOnUse"
+ x1="199.76904"
+ y1="110.00295"
+ x2="236.769"
+ y2="78.409447" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8832"
+ id="linearGradient9315"
+ gradientUnits="userSpaceOnUse"
+ x1="143.30029"
+ y1="1219.9902"
+ x2="72.589622"
+ y2="1149.2795" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8850"
+ id="linearGradient9317"
+ gradientUnits="userSpaceOnUse"
+ x1="158.40988"
+ y1="1098.7366"
+ x2="147.26907"
+ y2="1109.0519" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8832"
+ id="linearGradient9319"
+ gradientUnits="userSpaceOnUse"
+ x1="235.71814"
+ y1="1196.6267"
+ x2="292.90564"
+ y2="1135.908"
+ gradientTransform="translate(5.6098624,8.1772778)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8850"
+ id="linearGradient9355"
+ gradientUnits="userSpaceOnUse"
+ x1="199.76904"
+ y1="110.00295"
+ x2="236.769"
+ y2="78.409447" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9305"
+ id="linearGradient9357"
+ gradientUnits="userSpaceOnUse"
+ x1="124.46073"
+ y1="1154.9745"
+ x2="127.51315"
+ y2="1151.9221" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8832"
+ id="linearGradient9359"
+ gradientUnits="userSpaceOnUse"
+ x1="143.30029"
+ y1="1219.9902"
+ x2="72.589622"
+ y2="1149.2795" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8850"
+ id="linearGradient9361"
+ gradientUnits="userSpaceOnUse"
+ x1="158.40988"
+ y1="1098.7366"
+ x2="147.26907"
+ y2="1109.0519" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-1.6900304,-354.84909)" />
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient4261"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.05826577,0,0,0.05826577,458.90519,929.78821)"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient5031"
+ x1="5.8733339"
+ y1="42.337013"
+ x2="5.8733339"
+ y2="40.483025"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient5041"
+ x1="15.650404"
+ y1="25.302221"
+ x2="16.901522"
+ y2="26.024555"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient5049"
+ x1="33.373379"
+ y1="26.024555"
+ x2="34.729317"
+ y2="25.241705"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient5057"
+ x1="16.90152"
+ y1="23.258371"
+ x2="16.90152"
+ y2="21.523092"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5089"
+ id="linearGradient5095"
+ x1="5.8733373"
+ y1="28.879158"
+ x2="15.974848"
+ y2="24.861507"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5099"
+ id="linearGradient5105"
+ x1="44.388447"
+ y1="29.089437"
+ x2="34.262527"
+ y2="24.798422"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6109"
+ id="linearGradient6115"
+ x1="16.901521"
+ y1="16.485245"
+ x2="33.37338"
+ y2="16.485245"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(3.3754082,0,0,3.3754082,149.42695,1041.9491)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient6157"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="5.8733339"
+ y1="42.337013"
+ x2="5.8733373"
+ y2="28.879173"
+ gradientTransform="matrix(3.3754082,0,0,3.3754082,149.42695,1041.9491)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient6159"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="33.373379"
+ y1="26.024555"
+ x2="34.729317"
+ y2="25.241705"
+ gradientTransform="matrix(3.3754082,0,0,3.3754082,149.42695,1041.9491)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5025"
+ id="linearGradient6161"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="15.650404"
+ y1="25.302221"
+ x2="16.901522"
+ y2="26.024555"
+ gradientTransform="matrix(3.3754082,0,0,3.3754082,149.42695,1041.9491)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6165"
+ id="linearGradient6171"
+ x1="227.6591"
+ y1="1120.4557"
+ x2="227.6591"
+ y2="1074.7314"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6173"
+ id="radialGradient6179"
+ cx="25.968748"
+ cy="34.65625"
+ fx="25.968748"
+ fy="34.65625"
+ r="3.5312498"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6181"
+ id="radialGradient6187"
+ cx="25.968748"
+ cy="34.65625"
+ fx="25.968748"
+ fy="34.65625"
+ r="4.0312498"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6165-696"
+ id="linearGradient6171-857"
+ x1="227.6591"
+ y1="1120.4557"
+ x2="227.6591"
+ y2="1074.7314"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient6165-696">
+ <stop
+ style="stop-color:#381e00;stop-opacity:1;"
+ offset="0"
+ id="stop6426" />
+ <stop
+ style="stop-color:#593000;stop-opacity:1;"
+ offset="1"
+ id="stop6428" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6165-696-389"
+ id="linearGradient6171-857-364"
+ x1="227.6591"
+ y1="1120.4557"
+ x2="227.6591"
+ y2="1074.7314"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient6165-696-389">
+ <stop
+ style="stop-color:#3e2100;stop-opacity:1;"
+ offset="0"
+ id="stop6916" />
+ <stop
+ style="stop-color:#633500;stop-opacity:1;"
+ offset="1"
+ id="stop6918" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.87072097,0,0,1.0005854,30.284126,-0.66139717)"
+ inkscape:collect="always"
+ xlink:href="#linearGradient6165-696-389-794"
+ id="linearGradient6171-857-364-729"
+ x1="227.6591"
+ y1="1120.4557"
+ x2="227.6591"
+ y2="1074.7314"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient6165-696-389-794">
+ <stop
+ style="stop-color:#452500;stop-opacity:1;"
+ offset="0"
+ id="stop7414" />
+ <stop
+ style="stop-color:#6e3b00;stop-opacity:1;"
+ offset="1"
+ id="stop7416" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="1139.5773"
+ x2="223.25838"
+ y1="1184.9249"
+ x1="223.25838"
+ id="linearGradient7695"
+ xlink:href="#linearGradient7689"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="28.879173"
+ x2="5.8733373"
+ y1="42.337013"
+ x1="5.8733339"
+ spreadMethod="pad"
+ gradientTransform="matrix(3.3754082,0,0,3.3754082,149.42695,1041.9491)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient7697"
+ xlink:href="#linearGradient5025"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.38217566,0,0,0.38217566,54.577048,-14.612278)"
+ gradientUnits="userSpaceOnUse"
+ y2="68.443649"
+ x2="148.13901"
+ y1="101.13522"
+ x1="135.41849"
+ id="linearGradient4807"
+ xlink:href="#linearGradient4801"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="162.81865"
+ x2="209.91048"
+ y1="162.81865"
+ x1="183.766"
+ id="linearGradient4883"
+ xlink:href="#linearGradient4877"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="159.38168"
+ x2="189.22797"
+ y1="166.25562"
+ x1="189.22797"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4906"
+ xlink:href="#linearGradient4853"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="159.38168"
+ x2="209.82245"
+ y1="166.25562"
+ x1="209.82245"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4908"
+ xlink:href="#linearGradient4853"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="162.81865"
+ x2="189.316"
+ y1="162.81865"
+ x1="183.766"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4910"
+ xlink:href="#linearGradient4885"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="162.81865"
+ x2="209.91048"
+ y1="162.81865"
+ x1="204.36048"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4912"
+ xlink:href="#linearGradient4885"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="162.81865"
+ x2="209.91048"
+ y1="162.81865"
+ x1="183.766"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4914"
+ xlink:href="#linearGradient4877"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="172.46539"
+ x2="198.48969"
+ y1="165.45691"
+ x1="198.20827"
+ gradientTransform="translate(0,-0.70710676)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4916"
+ xlink:href="#linearGradient4843"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(1.1420227,0,0,1.1420227,-17.669896,-4.0712369)"
+ gradientUnits="userSpaceOnUse"
+ y2="28.666101"
+ x2="142.25706"
+ y1="28.666101"
+ x1="106.57493"
+ id="linearGradient4928"
+ xlink:href="#linearGradient4729"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4817-195">
+ <stop
+ id="stop5239"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#c8c8c8;stop-opacity:1;"
+ offset="0.5"
+ id="stop5241" />
+ <stop
+ id="stop5243"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ </linearGradient>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Livello 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1004.3622)">
+ <g
+ id="g8044"
+ transform="matrix(0.29626046,0,0,0.29626046,-45.835699,695.27639)">
+ <g
+ id="g3951"
+ style="fill:#ff0000;fill-opacity:1;stroke:none"
+ transform="translate(99.497853,-21.084127)" />
+ <g
+ id="g9321"
+ style="fill:none;fill-opacity:1;stroke:#000000;stroke-opacity:1"
+ transform="translate(156.56239,-59.761743)" />
+ <g
+ transform="matrix(3.3754082,0,0,3.3754082,-184.23079,1041.7933)"
+ id="g3302">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4781"
+ d="m 111.19217,11.545219 0,12.69197 -4.99659,-4.247067 z"
+ style="fill:url(#linearGradient4807);fill-opacity:1;stroke:none" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4787"
+ d="m 111.19217,11.545218 26.55736,0 0,9.854462 -26.55736,0 0,-9.854462"
+ style="fill:#dcdcdc;fill-opacity:1;stroke:none" />
+ <path
+ style="fill:#dcdcdc;fill-opacity:1;stroke:none"
+ d="m 137.74953,11.545219 0,12.69197 4.8869,-4.247068 z"
+ id="path4783"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4797"
+ d="m 111.19217,11.545218 0,12.69197"
+ style="fill:none;stroke:#7d7d7d;stroke-width:0.11465271000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ style="fill:none;stroke:#7d7d7d;stroke-width:0.11465271000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 137.74953,11.545219 0,12.69197"
+ id="path4799"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4790"
+ d="m 111.18832,11.545219 -4.99216,8.443693 4.99216,4.251705 0,-2.842432 26.56121,0 0,2.842432 4.88469,-4.251705 -4.88469,-8.443693 -26.56121,0 z"
+ style="fill:none;stroke:#232323;stroke-width:0.11465271;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ y="19.990124"
+ x="102.1288"
+ height="17.351954"
+ width="44.574402"
+ id="rect3362"
+ style="fill:#000e50;fill-opacity:1;stroke:#232323;stroke-width:0.11465271000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ style="fill:url(#linearGradient4928);fill-opacity:1;stroke:#232323;stroke-width:0.10444804;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4918"
+ width="40.607056"
+ height="15.807543"
+ x="104.11246"
+ y="20.762327" />
+ <g
+ transform="matrix(0.38217566,0,0,0.38217566,48.898483,-33.625515)"
+ id="g4837">
+ <g
+ id="g4897">
+ <g
+ transform="matrix(1,0,0,0.61040929,0.76074039,64.807635)"
+ id="g4833">
+ <rect
+ style="fill:url(#linearGradient4906);fill-opacity:1;stroke:#232323;stroke-width:0.17606442;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4827"
+ width="5.3739347"
+ height="6.8739367"
+ x="183.85403"
+ y="159.38168" />
+ <rect
+ y="159.38168"
+ x="204.44852"
+ height="6.8739367"
+ width="5.3739347"
+ id="rect4829"
+ style="fill:url(#linearGradient4908);fill-opacity:1;stroke:#232323;stroke-width:0.17606442;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ </g>
+ <g
+ id="g4863"
+ transform="matrix(1,0,0,0.61040929,0.76074039,64.807635)"
+ style="fill:url(#linearGradient4914);fill-opacity:1">
+ <rect
+ y="159.38168"
+ x="183.85403"
+ height="6.8739367"
+ width="5.3739347"
+ id="rect4865"
+ style="opacity:0.25;fill:url(#linearGradient4910);fill-opacity:1;stroke:#232323;stroke-width:0.17606442;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <rect
+ style="opacity:0.25;fill:url(#linearGradient4912);fill-opacity:1;stroke:#232323;stroke-width:0.17606442;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4867"
+ width="5.3739347"
+ height="6.8739367"
+ x="204.44852"
+ y="159.38168" />
+ </g>
+ <rect
+ y="164.7498"
+ x="181.72037"
+ height="7.0084896"
+ width="31.757225"
+ id="rect4831"
+ style="fill:url(#linearGradient4916);fill-opacity:1;stroke:#232323;stroke-width:0.27418944;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/ingredient.svg b/files/opencs/scalable/referenceable-record/ingredient.svg
new file mode 100644
index 0000000000..962a14ed14
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/ingredient.svg
@@ -0,0 +1,1107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="ingredient.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="14.481547"
+ inkscape:cx="20.717405"
+ inkscape:cy="29.166887"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5966"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true"
+ inkscape:snap-midpoints="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3988">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3990" />
+ <stop
+ style="stop-color:#e1e1e1;stop-opacity:0;"
+ offset="1"
+ id="stop3992" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient5606"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient5608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient5610"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient5612"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5614"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5616"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5618"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20" />
+ <linearGradient
+ id="linearGradient4676">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop4678" />
+ <stop
+ style="stop-color:#603500;stop-opacity:1;"
+ offset="1"
+ id="stop4680" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4691">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4693" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:0;"
+ offset="1"
+ id="stop4695" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4713">
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0"
+ id="stop4715" />
+ <stop
+ id="stop4721"
+ offset="0.5"
+ style="stop-color:#707070;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4717" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4723">
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="0"
+ id="stop4725" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:1;"
+ offset="1"
+ id="stop4727" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4653">
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="0"
+ id="stop4655" />
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="1"
+ id="stop4657" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.04078846,-0.04078846,0.04078846,0.04078846,-116.27838,962.59342)"
+ inkscape:collect="always"
+ xlink:href="#linearGradient8557"
+ id="linearGradient8563"
+ x1="-174.14496"
+ y1="743.24573"
+ x2="281.64856"
+ y2="316.86032"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient8557">
+ <stop
+ style="stop-color:#719632;stop-opacity:1;"
+ offset="0"
+ id="stop8559" />
+ <stop
+ style="stop-color:#9cc653;stop-opacity:1;"
+ offset="1"
+ id="stop8561" />
+ </linearGradient>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <path
+ style="fill:#9fc85a;stroke:#719632;stroke-width:1.02278747;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m -79.91051,987.16116 c -0.09782,0.0592 -0.442415,0.28361 -0.765804,0.49863 -0.597095,0.39708 -0.951207,0.61612 -1.511999,0.93532 -0.171579,0.0976 -0.437897,0.25346 -0.591891,0.34623 -0.540279,0.32553 -0.709885,0.40092 -1.567231,0.69656 -0.475402,0.16396 -1.007076,0.37165 -1.181438,0.4616 -0.343463,0.17715 -0.350513,0.17968 -0.969111,0.34498 -0.670478,0.17915 -1.265594,0.23979 -2.850538,0.29037 -0.974153,0.0311 -2.153437,-0.0503 -2.570461,-0.17747 -0.60203,-0.18351 -2.368793,-0.17325 -3.054715,0.0177 -0.624064,0.1738 -1.633926,0.73789 -2.983394,1.6665 -0.31899,0.21943 -0.75746,0.48862 -0.9745,0.59808 -0.80306,0.40508 -0.97465,0.43032 -1.224718,0.18025 -0.0926,-0.0926 -0.16869,-0.20277 -0.16914,-0.24494 -4.9e-4,-0.0422 -0.0592,-0.14676 -0.13052,-0.23249 -0.12527,-0.15058 -0.12398,-0.16131 0.0372,-0.31527 0.0919,-0.0876 0.35213,-0.26785 0.578508,-0.40041 0.45,-0.26345 1.34767,-0.89662 2.17657,-1.53519 1.018881,-0.78493 1.163732,-1.30666 0.689327,-2.48272 -0.177781,-0.44071 -0.42162,-1.329 -0.494643,-1.80204 -0.03002,-0.19434 -0.111454,-0.57176 -0.180994,-0.83872 -0.0695,-0.26696 -0.192,-0.82361 -0.27215,-1.23701 -0.0802,-0.41339 -0.21475,-1.01813 -0.29912,-1.34384 -0.0843,-0.32572 -0.18374,-0.74753 -0.22086,-0.93736 -0.16813,-0.86011 -0.22271,-3.09693 -0.0994,-4.07577 0.0319,-0.25367 0.0729,-0.58135 0.091,-0.72819 0.0331,-0.26809 0.44664,-2.35746 0.66486,-3.35881 0.0642,-0.2947 0.14813,-0.56736 0.18646,-0.6059 0.06121,-0.0615 0.244263,-0.43706 0.381944,-0.78354 0.08887,-0.2235 0.486616,-1.39049 0.508091,-1.49056 0.0354,-0.16522 0.234597,-0.64559 0.291576,-0.70325 0.02905,-0.0293 0.134986,-0.21008 0.235219,-0.40149 0.100211,-0.19142 0.312527,-0.52975 0.47169,-0.75196 0.159155,-0.22223 0.427495,-0.61465 0.59626,-0.87213 0.716427,-1.09309 0.885206,-1.32074 1.085063,-1.46349 0.111447,-0.0796 0.848033,-0.76587 1.636846,-1.52498 0.889081,-0.85542 1.490015,-1.40191 1.58097,-1.43737 0.08924,-0.0348 0.676299,-0.56373 1.498848,-1.35036 1.533071,-1.46618 2.247951,-2.0302 2.586852,-2.04076 0.102064,-0.003 0.20709,0.0157 0.233345,0.0419 0.116877,0.11687 0.532414,-0.0442 1.146452,-0.4443 0.299695,-0.19532 1.72369,-1.32612 1.932344,-1.53457 0.185873,-0.18563 0.964521,-0.66961 1.836304,-1.14136 0.262007,-0.14179 0.593793,-0.32394 0.737309,-0.40477 0.143491,-0.0808 0.458261,-0.2433 0.699485,-0.361 0.386536,-0.18861 0.464245,-0.20524 0.654726,-0.13989 0.188119,0.0645 0.258511,0.0504 0.543518,-0.10909 0.180022,-0.10083 0.438927,-0.24277 0.575205,-0.31556 0.136286,-0.0728 0.391802,-0.23944 0.567802,-0.37034 0.176002,-0.13088 0.458706,-0.31954 0.628213,-0.41924 0.16953,-0.0997 0.402593,-0.24208 0.517931,-0.31645 0.235135,-0.15156 0.739877,-0.44056 0.947907,-0.54265 0.07517,-0.037 0.266209,-0.13964 0.424329,-0.22829 0.158102,-0.0887 0.525125,-0.29065 0.815522,-0.44892 0.290451,-0.15822 0.604771,-0.33828 0.698536,-0.40008 0.09369,-0.0619 0.214384,-0.12472 0.267934,-0.13995 0.05368,-0.0151 0.176897,-0.0766 0.274013,-0.13655 0.157914,-0.0975 0.200229,-0.0939 0.401187,0.0341 0.282312,0.17978 0.505669,0.22635 0.73327,0.15284 0.09789,-0.0316 0.309533,-0.091 0.470428,-0.13202 0.160842,-0.0411 0.499525,-0.16124 0.75251,-0.26719 0.253024,-0.10592 0.53718,-0.20722 0.631467,-0.22509 0.09434,-0.0178 0.405215,-0.11077 0.690915,-0.20644 0.55535,-0.18594 1.763485,-0.43757 2.43058,-0.50619 0.223759,-0.023 0.703126,-0.0734 1.065239,-0.11194 0.362141,-0.0385 0.858514,-0.0745 1.103033,-0.0798 0.244532,-0.005 0.658257,-0.0531 0.919415,-0.10597 0.923499,-0.18719 1.462062,-0.24529 2.650695,-0.28608 0.456485,-0.0157 1.088028,0.15196 1.349486,0.35817 0.270178,0.2131 0.270504,0.28637 0.004,0.90586 -0.216294,0.50286 -0.516867,0.89505 -1.63156,2.12889 -0.362067,0.40067 -0.694374,0.80256 -0.73849,0.89305 -0.04411,0.0905 -0.177818,0.32387 -0.29706,0.51871 -0.119204,0.19486 -0.285558,0.48738 -0.369628,0.65008 -0.08407,0.1627 -0.231263,0.43766 -0.327089,0.61105 -0.09578,0.17342 -0.26029,0.48614 -0.365479,0.69501 -0.105205,0.20887 -0.315683,0.58575 -0.467742,0.83749 -0.152052,0.25176 -0.344889,0.63464 -0.428502,0.85087 -0.128989,0.33364 -0.138259,0.42525 -0.06127,0.60496 0.07638,0.17826 0.06992,0.24693 -0.04071,0.43383 -0.346887,0.58587 -0.407217,0.69187 -0.453685,0.79714 -0.02747,0.0623 -0.146891,0.32829 -0.265304,0.59125 -0.237224,0.52674 -0.437134,0.98385 -0.50883,1.1635 -0.02556,0.0642 -0.114603,0.26881 -0.197793,0.45482 -0.08319,0.18601 -0.157528,0.36865 -0.165203,0.40584 -0.0077,0.0371 -0.05647,0.13523 -0.108425,0.21788 -0.05196,0.0826 -0.170762,0.31258 -0.264009,0.51097 -0.262943,0.55949 -0.699453,1.39801 -0.758976,1.45812 -0.02966,0.0297 -0.07591,0.12372 -0.102997,0.20879 -0.02717,0.085 -0.124668,0.31789 -0.21677,0.51743 -0.272421,0.59022 -0.334978,0.94368 -0.20153,1.13877 0.114233,0.167 0.110835,0.34419 -0.01397,0.72594 -0.03322,0.10138 -0.137982,0.43713 -0.232921,0.746 -0.355434,1.15613 -0.478242,1.49664 -0.69193,1.91853 -0.120639,0.2383 -0.251102,0.51719 -0.289848,0.61983 -0.03873,0.10265 -0.09615,0.21226 -0.127534,0.24364 -0.0314,0.0314 -0.0583,0.0907 -0.0601,0.13151 -0.0016,0.041 -0.09112,0.29841 -0.198809,0.5721 -0.442599,1.12514 -0.478521,1.23878 -0.460615,1.45644 0.01343,0.16306 -0.110728,0.4292 -0.454068,0.97353 -0.342455,0.5429 -0.825402,1.23618 -1.037326,1.48913 -0.327669,0.39103 -0.706834,0.9769 -0.819716,1.26641 -0.06027,0.15447 -0.06895,0.31588 -0.02497,0.46253 0.07201,0.24009 0.06292,0.2595 -0.363672,0.77688 -0.121665,0.14754 -0.341731,0.42305 -0.489057,0.61223 -0.147255,0.18926 -0.545731,0.62351 -0.88546,0.96508 -0.761009,0.76525 -1.098653,1.25077 -1.06677,1.53338 0.01992,0.17685 0.0077,0.19764 -0.16921,0.28673 -0.230609,0.1162 -1.568724,1.31043 -2.82026,2.51704 l -0.916757,0.88394 0.0062,0.19792 c 0.0056,0.17959 -0.02293,0.22609 -0.307966,0.50179 -0.172775,0.16712 -0.394241,0.35224 -0.491991,0.41152 z"
+ id="path8041"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path8047"
+ d="m -79.91051,987.16116 c -0.09782,0.0592 -0.442415,0.28361 -0.765804,0.49863 -0.597095,0.39708 -0.951207,0.61612 -1.511999,0.93532 -0.171579,0.0976 -0.437897,0.25346 -0.591891,0.34623 -0.540279,0.32553 -0.709885,0.40092 -1.567231,0.69656 -0.475402,0.16396 -1.007076,0.37165 -1.181438,0.4616 -0.343463,0.17715 -0.350513,0.17968 -0.969111,0.34498 -0.670478,0.17915 -1.265594,0.23979 -2.850538,0.29037 -0.974153,0.0311 -2.153437,-0.0503 -2.570461,-0.17747 -0.60203,-0.18351 -2.368793,-0.17325 -3.054715,0.0177 -0.624064,0.1738 -1.633926,0.73789 -2.983394,1.6665 -0.31899,0.21943 -0.75746,0.48862 -0.9745,0.59808 -0.80306,0.40508 -0.97465,0.43032 -1.224718,0.18025 -0.0926,-0.0926 -0.16869,-0.20277 -0.16914,-0.24494 -4.9e-4,-0.0422 -0.0592,-0.14676 -0.13052,-0.23249 -0.12527,-0.15058 -0.12398,-0.16131 0.0372,-0.31527 0.0919,-0.0876 0.35213,-0.26785 0.578508,-0.40041 0.45,-0.26345 1.34767,-0.89662 2.17657,-1.53519 1.018881,-0.78493 1.163732,-1.30666 0.689327,-2.48272 -0.177781,-0.44071 -0.42162,-1.329 -0.494643,-1.80204 -0.03002,-0.19434 -0.111454,-0.57176 -0.180994,-0.83872 -0.0695,-0.26696 -0.192,-0.82361 -0.27215,-1.23701 -0.0802,-0.41339 -0.21475,-1.01813 -0.29912,-1.34384 -0.0843,-0.32572 -0.18374,-0.74753 -0.22086,-0.93736 -0.16813,-0.86011 -0.22271,-3.09693 -0.0994,-4.07577 0.0319,-0.25367 0.0729,-0.58135 0.091,-0.72819 0.0331,-0.26809 0.44664,-2.35746 0.66486,-3.35881 0.0642,-0.2947 0.14813,-0.56736 0.18646,-0.6059 0.06121,-0.0615 0.244263,-0.43706 0.381944,-0.78354 0.08887,-0.2235 0.486616,-1.39049 0.508091,-1.49056 0.0354,-0.16522 0.234597,-0.64559 0.291576,-0.70325 0.02905,-0.0293 0.134986,-0.21008 0.235219,-0.40149 0.100211,-0.19142 0.312527,-0.52975 0.47169,-0.75196 0.159155,-0.22223 0.427495,-0.61465 0.59626,-0.87213 0.716427,-1.09309 0.885206,-1.32074 1.085063,-1.46349 0.111447,-0.0796 0.848033,-0.76587 1.636846,-1.52498 0.889081,-0.85542 1.490015,-1.40191 1.58097,-1.43737 0.08924,-0.0348 0.676299,-0.56373 1.498848,-1.35036 1.533071,-1.46618 2.247951,-2.0302 2.586852,-2.04076 0.102064,-0.003 0.20709,0.0157 0.233345,0.0419 0.116877,0.11687 0.532414,-0.0442 1.146452,-0.4443 0.299695,-0.19532 1.72369,-1.32612 1.932344,-1.53457 0.185873,-0.18563 0.964521,-0.66961 1.836304,-1.14136 0.262007,-0.14179 0.593793,-0.32394 0.737309,-0.40477 0.143491,-0.0808 0.458261,-0.2433 0.699485,-0.361 0.386536,-0.18861 0.464245,-0.20524 0.654726,-0.13989 0.188119,0.0645 0.258511,0.0504 0.543518,-0.10909 0.180022,-0.10083 0.438927,-0.24277 0.575205,-0.31556 0.136286,-0.0728 0.391802,-0.23944 0.567802,-0.37034 0.176002,-0.13088 0.458706,-0.31954 0.628213,-0.41924 0.16953,-0.0997 0.402593,-0.24208 0.517931,-0.31645 0.235135,-0.15156 0.739877,-0.44056 0.947907,-0.54265 0.07517,-0.037 0.266209,-0.13964 0.424329,-0.22829 0.158102,-0.0887 0.525125,-0.29065 0.815522,-0.44892 0.290451,-0.15822 0.604771,-0.33828 0.698536,-0.40008 0.09369,-0.0619 0.214384,-0.12472 0.267934,-0.13995 0.05368,-0.0151 0.176897,-0.0766 0.274013,-0.13655 0.157914,-0.0975 0.200229,-0.0939 0.401187,0.0341 0.282312,0.17978 0.505669,0.22635 0.73327,0.15284 0.09789,-0.0316 0.309533,-0.091 0.470428,-0.13202 0.160842,-0.0411 0.499525,-0.16124 0.75251,-0.26719 0.253024,-0.10592 0.53718,-0.20722 0.631467,-0.22509 0.09434,-0.0178 0.405215,-0.11077 0.690915,-0.20644 0.55535,-0.18594 1.763485,-0.43757 2.43058,-0.50619 0.223759,-0.023 0.703126,-0.0734 1.065239,-0.11194 0.362141,-0.0385 0.858514,-0.0745 1.103033,-0.0798 0.244532,-0.005 0.658257,-0.0531 0.919415,-0.10597 0.923499,-0.18719 1.462062,-0.24529 2.650695,-0.28608 0.456485,-0.0157 1.088028,0.15196 1.349486,0.35817 0.270178,0.2131 0.270504,0.28637 0.004,0.90586 -0.216294,0.50286 -0.516867,0.89505 -1.63156,2.12889 -0.362067,0.40067 -0.694374,0.80256 -0.73849,0.89305 -0.04411,0.0905 -0.177818,0.32387 -0.29706,0.51871 -0.119204,0.19486 -0.285558,0.48738 -0.369628,0.65008 -0.08407,0.1627 -0.231263,0.43766 -0.327089,0.61105 -0.09578,0.17342 -0.26029,0.48614 -0.365479,0.69501 -0.105205,0.20887 -0.315683,0.58575 -0.467742,0.83749 -0.152052,0.25176 -0.344889,0.63464 -0.428502,0.85087 -0.128989,0.33364 -0.138259,0.42525 -0.06127,0.60496 0.07638,0.17826 0.06992,0.24693 -0.04071,0.43383 -0.346887,0.58587 -0.407217,0.69187 -0.453685,0.79714 -0.02747,0.0623 -0.146891,0.32829 -0.265304,0.59125 -0.237224,0.52674 -0.437134,0.98385 -0.50883,1.1635 -0.02556,0.0642 -0.114603,0.26881 -0.197793,0.45482 -0.08319,0.18601 -0.157528,0.36865 -0.165203,0.40584 -0.0077,0.0371 -0.05647,0.13523 -0.108425,0.21788 -0.05196,0.0826 -0.170762,0.31258 -0.264009,0.51097 -0.262943,0.55949 -0.699453,1.39801 -0.758976,1.45812 -0.02966,0.0297 -0.07591,0.12372 -0.102997,0.20879 -0.02717,0.085 -0.124668,0.31789 -0.21677,0.51743 -0.272421,0.59022 -0.334978,0.94368 -0.20153,1.13877 0.114233,0.167 0.110835,0.34419 -0.01397,0.72594 -0.03322,0.10138 -0.137982,0.43713 -0.232921,0.746 -0.355434,1.15613 -0.478242,1.49664 -0.69193,1.91853 -0.120639,0.2383 -0.251102,0.51719 -0.289848,0.61983 -0.03873,0.10265 -0.09615,0.21226 -0.127534,0.24364 -0.0314,0.0314 -0.0583,0.0907 -0.0601,0.13151 -0.0016,0.041 -0.09112,0.29841 -0.198809,0.5721 -0.442599,1.12514 -0.478521,1.23878 -0.460615,1.45644 0.01343,0.16306 -0.110728,0.4292 -0.454068,0.97353 -0.342455,0.5429 -0.825402,1.23618 -1.037326,1.48913 -0.327669,0.39103 -0.706834,0.9769 -0.819716,1.26641 -0.06027,0.15447 -0.06895,0.31588 -0.02497,0.46253 0.07201,0.24009 0.06292,0.2595 -0.363672,0.77688 -0.121665,0.14754 -0.341731,0.42305 -0.489057,0.61223 -0.147255,0.18926 -0.545731,0.62351 -0.88546,0.96508 -0.761009,0.76525 -1.098653,1.25077 -1.06677,1.53338 0.01992,0.17685 0.0077,0.19764 -0.16921,0.28673 -0.230609,0.1162 -1.568724,1.31043 -2.82026,2.51704 l -0.916757,0.88394 0.0062,0.19792 c 0.0056,0.17959 -0.02293,0.22609 -0.307966,0.50179 -0.172775,0.16712 -0.394241,0.35224 -0.491991,0.41152 z"
+ style="opacity:0.5;fill:url(#linearGradient8563);fill-opacity:1" />
+ <path
+ style="fill:none;stroke:#719632;stroke-width:0.51139373;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m -99.905798,992.79541 c 3.019481,-2.1678 9.399393,-7.45593 11.680377,-10.64931 0.706269,-0.98878 3.645673,-3.4286 5.103685,-5.49587 2.268158,-3.21597 4.923436,-6.13369 7.351116,-9.2274 2.634248,-3.35688 5.281264,-6.70844 8.402611,-9.64024 1.388389,-1.30411 2.508186,-2.87095 4.130216,-3.92755 0.738764,-0.48122 1.406642,-1.4369 2.145824,-1.91712"
+ id="path8567"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csssssc" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/light.svg b/files/opencs/scalable/referenceable-record/light.svg
new file mode 100644
index 0000000000..3bd5307f73
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/light.svg
@@ -0,0 +1,1508 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="light.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.2407734"
+ inkscape:cx="-5.4001825"
+ inkscape:cy="28.976292"
+ inkscape:document-units="px"
+ inkscape:current-layer="g4512"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="true"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient4607">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4609" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:0;"
+ offset="1"
+ id="stop4611" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4583">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop4585" />
+ <stop
+ id="stop4591"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop4587" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3969">
+ <stop
+ id="stop3977"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1"
+ id="stop3973" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4694">
+ <stop
+ style="stop-color:#ffe55f;stop-opacity:1;"
+ offset="0"
+ id="stop4696" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4698" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4640">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4642" />
+ <stop
+ id="stop4652"
+ offset="0.32644245"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop4650"
+ offset="0.57444489"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop4648"
+ offset="0.8503347"
+ style="stop-color:#000000;stop-opacity:0.78431374;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4644" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4632">
+ <stop
+ id="stop4634"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0.78456932"
+ id="stop4636" />
+ <stop
+ id="stop4638"
+ offset="1"
+ style="stop-color:#fcd63f;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4613">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4615" />
+ <stop
+ id="stop4621"
+ offset="0.5"
+ style="stop-color:#683900;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4617" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4533">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop4535" />
+ <stop
+ style="stop-color:#d07200;stop-opacity:0;"
+ offset="1"
+ id="stop4537" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4504">
+ <stop
+ id="stop4506"
+ offset="0"
+ style="stop-color:#fcd63f;stop-opacity:1;" />
+ <stop
+ style="stop-color:#f9cd21;stop-opacity:1;"
+ offset="0.94645542"
+ id="stop4508" />
+ <stop
+ id="stop4510"
+ offset="1"
+ style="stop-color:#f6c504;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4485">
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:1;"
+ offset="0"
+ id="stop4487" />
+ <stop
+ id="stop4502"
+ offset="0.5"
+ style="stop-color:#f9cd21;stop-opacity:1;" />
+ <stop
+ style="stop-color:#f6c504;stop-opacity:0;"
+ offset="1"
+ id="stop4489" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4440">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4442" />
+ <stop
+ style="stop-color:#693200;stop-opacity:1;"
+ offset="1"
+ id="stop4444" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4430">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4432" />
+ <stop
+ id="stop4438"
+ offset="0.70282459"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:0;"
+ offset="1"
+ id="stop4434" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4416">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4418" />
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:0;"
+ offset="1"
+ id="stop4420" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4393">
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:1;"
+ offset="0"
+ id="stop4395" />
+ <stop
+ id="stop4401"
+ offset="0.5"
+ style="stop-color:#fdea9d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:1;"
+ offset="1"
+ id="stop4397" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4383">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4385" />
+ <stop
+ id="stop4412"
+ offset="0.25137287"
+ style="stop-color:#c49f0e;stop-opacity:1;" />
+ <stop
+ id="stop4391"
+ offset="0.73561841"
+ style="stop-color:#e0b81a;stop-opacity:1;" />
+ <stop
+ style="stop-color:#fcd63f;stop-opacity:1;"
+ offset="1"
+ id="stop4387" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4330">
+ <stop
+ style="stop-color:#ff1212;stop-opacity:1;"
+ offset="0"
+ id="stop4332" />
+ <stop
+ style="stop-color:#ff7474;stop-opacity:1;"
+ offset="1"
+ id="stop4334" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4320">
+ <stop
+ style="stop-color:#ffef12;stop-opacity:1;"
+ offset="0"
+ id="stop4322" />
+ <stop
+ style="stop-color:#fff67d;stop-opacity:1;"
+ offset="1"
+ id="stop4324" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4275">
+ <stop
+ style="stop-color:#ffd50f;stop-opacity:1;"
+ offset="0"
+ id="stop4277" />
+ <stop
+ id="stop4283"
+ offset="0.5"
+ style="stop-color:#ffec90;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffd50f;stop-opacity:1;"
+ offset="1"
+ id="stop4279" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4248">
+ <stop
+ style="stop-color:#634400;stop-opacity:1;"
+ offset="0"
+ id="stop4250" />
+ <stop
+ style="stop-color:#be8300;stop-opacity:1;"
+ offset="1"
+ id="stop4252" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4248"
+ id="radialGradient4254"
+ cx="23.632812"
+ cy="36.867188"
+ fx="23.632812"
+ fy="36.867188"
+ r="16.360938"
+ gradientTransform="matrix(1,0,0,0.45086429,0,20.245089)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4275"
+ id="linearGradient4281"
+ x1="22.310937"
+ y1="26.515625"
+ x2="32.376563"
+ y2="26.515625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.85278916,0,0,0.96345001,-99.173541,951.12182)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient4521"
+ gradientUnits="userSpaceOnUse"
+ x1="-82.659904"
+ y1="977.68809"
+ x2="-69.36367"
+ y2="977.68809"
+ gradientTransform="translate(-4.2769717e-6,0)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4430"
+ id="radialGradient4523"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4440"
+ id="linearGradient4525"
+ gradientUnits="userSpaceOnUse"
+ x1="-75.537537"
+ y1="962.04156"
+ x2="-75.502426"
+ y2="961.15546" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4533"
+ id="radialGradient4539"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4430"
+ id="radialGradient4543"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient4555"
+ x1="20.000004"
+ y1="6.5"
+ x2="32.999996"
+ y2="6.5"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient4558"
+ gradientUnits="userSpaceOnUse"
+ x1="20.000004"
+ y1="6.5"
+ x2="32.999996"
+ y2="6.5"
+ gradientTransform="matrix(0.72205533,0,0,0.94251521,4.865527,32.418018)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4613"
+ id="radialGradient4619"
+ cx="-56.342495"
+ cy="991.77972"
+ fx="-56.342495"
+ fy="991.77972"
+ r="4.6288085"
+ gradientTransform="matrix(1,0,0,0.74947699,0.34665732,249.02427)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4640"
+ id="radialGradient4646"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4640"
+ id="radialGradient4666"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient4668"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.72205533,0,0,0.94251521,4.865527,32.418018)"
+ x1="20.000004"
+ y1="6.5"
+ x2="32.999996"
+ y2="6.5" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4430"
+ id="radialGradient4670"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4440"
+ id="linearGradient4672"
+ gradientUnits="userSpaceOnUse"
+ x1="-75.537537"
+ y1="962.04156"
+ x2="-75.502426"
+ y2="961.15546" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4613"
+ id="radialGradient4674"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.74947699,0.34665732,249.02427)"
+ spreadMethod="reflect"
+ cx="-56.342495"
+ cy="991.77972"
+ fx="-56.342495"
+ fy="991.77972"
+ r="4.6288085" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4613"
+ id="radialGradient4679"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0738716,0,0,0.75946433,13.126593,242.99931)"
+ spreadMethod="reflect"
+ cx="-56.342495"
+ cy="991.77972"
+ fx="-56.342495"
+ fy="991.77972"
+ r="4.6288085" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4440"
+ id="linearGradient4685"
+ gradientUnits="userSpaceOnUse"
+ x1="-75.537537"
+ y1="962.04156"
+ x2="-75.502426"
+ y2="961.15546"
+ gradientTransform="matrix(1.0738716,0,0,1.0133258,12.754328,-9.3433849)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient4690"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0983425,0,0,1.036417,-97.978666,990.49144)"
+ x1="20.000004"
+ y1="6.5"
+ x2="32.999996"
+ y2="6.5" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4694"
+ id="radialGradient4700"
+ cx="21.191406"
+ cy="3.078125"
+ fx="21.191406"
+ fy="3.078125"
+ r="4.6875"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.99999874,-7.3841679e-7,-2.0236369e-6)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4430"
+ id="radialGradient4710"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4640"
+ id="radialGradient4712"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3969"
+ id="linearGradient3975"
+ x1="-77.077217"
+ y1="963.77942"
+ x2="-77.077217"
+ y2="999.39368"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.70596808,0,0,0.92151616,77.661899,-876.82953)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3969"
+ id="linearGradient3982"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.70596808,0,0,0.92151616,77.661899,-876.82953)"
+ x1="-77.077217"
+ y1="962.29565"
+ x2="-77.077217"
+ y2="999.39368" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3969"
+ id="linearGradient3984"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(8.4057949,0.52897962)"
+ x1="-77.077217"
+ y1="951.2547"
+ x2="-77.077217"
+ y2="999.39368" />
+ <filter
+ inkscape:collect="always"
+ id="filter4575">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.048210478"
+ id="feGaussianBlur4577" />
+ </filter>
+ <filter
+ inkscape:collect="always"
+ id="filter4579">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.23830411"
+ id="feGaussianBlur4581" />
+ </filter>
+ <filter
+ inkscape:collect="always"
+ id="filter4583">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.14307263"
+ id="feGaussianBlur4585" />
+ </filter>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4393"
+ id="linearGradient6708"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2476493,0,0,1.1773058,-95.452038,991.94029)"
+ x1="20.000004"
+ y1="6.5"
+ x2="32.999996"
+ y2="6.5" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4694"
+ id="radialGradient6710"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.99999874,-7.3841679e-7,-2.0236363e-6)"
+ cx="21.191406"
+ cy="3.078125"
+ fx="21.191406"
+ fy="3.078125"
+ r="4.6875" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4430"
+ id="radialGradient6712"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.38461558,0,3.9999987)"
+ cx="26.5"
+ cy="6.5"
+ fx="26.5"
+ fy="6.5"
+ r="6.4999967" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4440"
+ id="linearGradient6716"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2198519,0,0,1.1510756,30.333812,-143.81039)"
+ x1="-75.537537"
+ y1="962.04156"
+ x2="-75.502426"
+ y2="961.15546" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ id="g4512"
+ transform="matrix(0.72205531,0,0,0.94251518,-23.542789,51.608167)">
+ <g
+ id="g6692"
+ transform="matrix(-0.90444509,0,0,0.90444509,-132.43957,95.466136)">
+ <path
+ id="path4545"
+ d="m -70.488913,963.58641 0,35.99004 c 0,1.62545 3.620721,2.96655 8.099595,2.96655 4.478872,0 8.099596,-1.3411 8.099596,-2.96655 l 0,-35.99004 -16.199191,0 z"
+ style="fill:url(#linearGradient6708);fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.1800501,0,0,1.5759605,-108.58772,950.54929)"
+ d="m 25.878906,3.078125 c 0,2.5888348 -2.098665,4.6875 -4.6875,4.6875 -2.588835,0 -4.6875,-2.0986652 -4.6875,-4.6875 0,-2.58883476 2.098665,-4.6875 4.6875,-4.6875 2.588835,0 4.6875,2.09866524 4.6875,4.6875 z"
+ sodipodi:ry="4.6875"
+ sodipodi:rx="4.6875"
+ sodipodi:cy="3.078125"
+ sodipodi:cx="21.191406"
+ id="path4676"
+ style="fill:url(#radialGradient6710);fill-opacity:1;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#fcd63f;fill-opacity:1;stroke:none"
+ id="path4379"
+ sodipodi:cx="26.5"
+ sodipodi:cy="6.5"
+ sodipodi:rx="6.4999967"
+ sodipodi:ry="2.5"
+ d="M 32.999997,6.5 C 32.999997,7.8807119 30.089849,9 26.5,9 22.910151,9 20.000003,7.8807119 20.000003,6.5 20.000003,5.1192881 22.910151,4 26.5,4 c 3.589849,0 6.499997,1.1192881 6.499997,2.5 z"
+ transform="matrix(1.2476493,0,0,1.1773057,-95.452015,955.91965)" />
+ <path
+ transform="matrix(1.2476493,0,0,1.1773057,-95.452015,955.91965)"
+ d="M 32.999997,6.5 C 32.999997,7.8807119 30.089849,9 26.5,9 22.910151,9 20.000003,7.8807119 20.000003,6.5 20.000003,5.1192881 22.910151,4 26.5,4 c 3.589849,0 6.499997,1.1192881 6.499997,2.5 z"
+ sodipodi:ry="2.5"
+ sodipodi:rx="6.4999967"
+ sodipodi:cy="6.5"
+ sodipodi:cx="26.5"
+ id="path4428"
+ style="opacity:0.25;fill:url(#radialGradient6712);fill-opacity:1;stroke:none"
+ sodipodi:type="arc" />
+ <rect
+ style="fill:url(#linearGradient6716);fill-opacity:1;stroke:none"
+ id="rect4403"
+ width="1.1570125"
+ height="2.2736316"
+ x="-62.967812"
+ y="961.29846" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4405"
+ d="m -50.291451,955.99365 c -0.306142,0.58022 -0.173213,1.37959 -0.741985,1.81489 -0.555003,0.71326 -0.773186,1.6155 -1.259884,2.37061 -0.545654,1.01279 -0.9642,2.21153 -0.612129,3.35954 0.288553,1.36917 0.604843,2.81123 1.512357,3.91947 0.601372,0.6775 1.7644,0.89162 2.455823,0.22684 0.753966,-0.66641 1.219278,-1.61104 1.490732,-2.56706 0.268644,-1.09118 0.258571,-2.22309 0.405108,-3.33193 0.004,-0.92336 -0.539155,-1.718 -0.841509,-2.56039 -0.468277,-1.04235 -0.941776,-2.14938 -1.812123,-2.9223 -0.181111,-0.13328 -0.382557,-0.23957 -0.59639,-0.30967 z"
+ style="fill:#ffcb80;fill-opacity:1;stroke:none;filter:url(#filter4579)"
+ transform="matrix(1.1359383,0,0,1.1359383,-5.573807,-136.87442)" />
+ <path
+ transform="matrix(1.5482576,0,0,1.5482576,57.266542,-530.34608)"
+ style="fill:#fddf6b;fill-opacity:1;stroke:none;filter:url(#filter4583)"
+ d="m -77.452275,957.06048 c -0.183801,0.34835 -0.103994,0.82827 -0.445472,1.08962 -0.333212,0.42823 -0.464204,0.96991 -0.756408,1.42326 -0.327599,0.60806 -0.578885,1.32776 -0.367509,2.017 0.173241,0.82202 0.363135,1.6878 0.907987,2.35317 0.361051,0.40675 1.059308,0.5353 1.474424,0.13618 0.452665,-0.40009 0.732028,-0.96723 0.895003,-1.5412 0.161288,-0.65512 0.155241,-1.3347 0.243219,-2.00042 0.0024,-0.55437 -0.323698,-1.03145 -0.505224,-1.53721 -0.281144,-0.6258 -0.565423,-1.29044 -1.08796,-1.75448 -0.108736,-0.08 -0.229679,-0.14384 -0.35806,-0.18592 z"
+ id="path4063"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4573"
+ d="m -50.089487,965.64147 c -0.06194,0.11739 -0.03504,0.2791 -0.150109,0.36717 -0.11228,0.14429 -0.15642,0.32682 -0.254882,0.47959 -0.110389,0.20489 -0.195064,0.4474 -0.123837,0.67965 0.05838,0.27699 0.122363,0.56873 0.305959,0.79293 0.121661,0.13707 0.356949,0.18038 0.496828,0.0459 0.152532,-0.13481 0.246667,-0.32592 0.301584,-0.51933 0.05435,-0.22075 0.05231,-0.44974 0.08196,-0.67406 8.27e-4,-0.18681 -0.109074,-0.34757 -0.170242,-0.51799 -0.09474,-0.21087 -0.190528,-0.43483 -0.366604,-0.5912 -0.03664,-0.0269 -0.07739,-0.0485 -0.120653,-0.0626 z"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter4575)"
+ transform="matrix(1.1359383,0,0,1.1359383,-5.573807,-136.87442)" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/miscellaneous.svg b/files/opencs/scalable/referenceable-record/miscellaneous.svg
new file mode 100644
index 0000000000..96522048cb
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/miscellaneous.svg
@@ -0,0 +1,965 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="miscelanius.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8.0000002"
+ inkscape:cx="3.9119117"
+ inkscape:cy="-1.8033034"
+ inkscape:document-units="px"
+ inkscape:current-layer="g4862"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-1.6900304,-354.84909)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-1.6900304,-354.84909)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-1.6900304,-354.84909)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4904"
+ id="radialGradient4910"
+ cx="270.45764"
+ cy="499.72882"
+ fx="270.45764"
+ fy="499.72882"
+ r="96.228813"
+ gradientTransform="matrix(1,0,0,0.44359312,0,278.05255)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4904">
+ <stop
+ style="stop-color:#321b00;stop-opacity:1;"
+ offset="0"
+ id="stop4906" />
+ <stop
+ style="stop-color:#653700;stop-opacity:1;"
+ offset="1"
+ id="stop4908" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4787"
+ id="linearGradient4793"
+ x1="270.45764"
+ y1="458.54239"
+ x2="270.45764"
+ y2="540.91528"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4787">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4789" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4791" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4805"
+ id="radialGradient4813"
+ cx="279.13174"
+ cy="537.41302"
+ fx="279.13174"
+ fy="537.41302"
+ r="94.728813"
+ gradientTransform="matrix(1.0793238,-0.1626202,0.09156796,0.60774413,-71.351608,234.89611)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4805">
+ <stop
+ style="stop-color:#cf0000;stop-opacity:1;"
+ offset="0"
+ id="stop4807" />
+ <stop
+ style="stop-color:#800000;stop-opacity:1;"
+ offset="1"
+ id="stop4809" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4850"
+ id="linearGradient4856"
+ x1="9"
+ y1="314.39062"
+ x2="136.21875"
+ y2="314.39062"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4850">
+ <stop
+ style="stop-color:#232620;stop-opacity:1;"
+ offset="0"
+ id="stop4852" />
+ <stop
+ id="stop4858"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ <stop
+ style="stop-color:#232620;stop-opacity:1;"
+ offset="1"
+ id="stop4854" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4795"
+ id="linearGradient4801"
+ x1="179.76239"
+ y1="205.93243"
+ x2="211.7963"
+ y2="205.93243"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4795">
+ <stop
+ style="stop-color:#800000;stop-opacity:1;"
+ offset="0"
+ id="stop4797" />
+ <stop
+ id="stop4803"
+ offset="0.5"
+ style="stop-color:#d07200;stop-opacity:1;" />
+ <stop
+ style="stop-color:#800000;stop-opacity:1;"
+ offset="1"
+ id="stop4799" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4805"
+ id="radialGradient4872"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0793238,-0.1626202,0.09156796,0.60774413,-71.351608,234.89611)"
+ cx="279.13174"
+ cy="537.41302"
+ fx="279.13174"
+ fy="537.41302"
+ r="94.728813" />
+ <linearGradient
+ id="linearGradient4965">
+ <stop
+ style="stop-color:#cf0000;stop-opacity:1;"
+ offset="0"
+ id="stop4967" />
+ <stop
+ style="stop-color:#800000;stop-opacity:1;"
+ offset="1"
+ id="stop4969" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4850"
+ id="linearGradient4874"
+ gradientUnits="userSpaceOnUse"
+ x1="9"
+ y1="314.39062"
+ x2="136.21875"
+ y2="314.39062" />
+ <linearGradient
+ id="linearGradient4972">
+ <stop
+ style="stop-color:#232620;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ <stop
+ id="stop4976"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ <stop
+ style="stop-color:#232620;stop-opacity:1;"
+ offset="1"
+ id="stop4978" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4882"
+ id="radialGradient4888"
+ cx="195.79498"
+ cy="55.193817"
+ fx="195.79498"
+ fy="55.193817"
+ r="30.780195"
+ gradientTransform="matrix(0.68778076,0,0,0.83721193,61.130961,8.984896)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4882">
+ <stop
+ style="stop-color:#824700;stop-opacity:1;"
+ offset="0"
+ id="stop4884" />
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="1"
+ id="stop4886" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4892"
+ id="linearGradient4902"
+ x1="165.0148"
+ y1="55.193817"
+ x2="226.5752"
+ y2="55.193817"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.68778076,0,0,0.68778076,61.130961,17.232572)" />
+ <linearGradient
+ id="linearGradient4892">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4894" />
+ <stop
+ id="stop4900"
+ offset="0.5"
+ style="stop-color:#595d56;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4896" />
+ </linearGradient>
+ <linearGradient
+ y2="55.193817"
+ x2="226.5752"
+ y1="55.193817"
+ x1="165.0148"
+ gradientTransform="matrix(0.68778076,0,0,0.68778076,61.130961,17.232572)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5008"
+ xlink:href="#linearGradient4892"
+ inkscape:collect="always" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g3891"
+ transform="matrix(0.02092262,0,0,0.02092262,47.215663,982.03701)">
+ <path
+ style="fill:#fa0000;fill-opacity:1;stroke:none"
+ d="m -684.61499,2674.2647 0,170.9884 -318.88181,0 520.49211,515.9507 520.492204,-515.9507 -318.881804,0 0,-170.9884 -403.2207,0 z"
+ id="path5146"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(5.0625238,0,0,5.0625238,-2302.2429,918.07499)"
+ id="g4912">
+ <g
+ transform="matrix(-1,0,0,1,391.56935,0.01189)"
+ id="g4860">
+ <g
+ id="g4862">
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient4872);fill-opacity:1;stroke:none"
+ id="path4864"
+ sodipodi:cx="270.45764"
+ sodipodi:cy="499.72882"
+ sodipodi:rx="94.728813"
+ sodipodi:ry="41.18644"
+ d="m 365.18645,499.72882 c 0,22.74664 -42.41153,41.18644 -94.72881,41.18644 -52.31728,0 -94.72881,-18.4398 -94.72881,-41.18644 0,-22.74664 42.41153,-41.18644 94.72881,-41.18644 52.31728,0 94.72881,18.4398 94.72881,41.18644 z"
+ transform="matrix(-0.67149758,0,0,0.67149758,254.22789,-33.552002)" />
+ <path
+ style="fill:none;stroke:#d07200;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="M 130.40221,313.59159 76.506851,145.58186 16.670251,315.18688"
+ id="path4866"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#d07200;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 76.506851,145.58186 -3.8906,128.77624"
+ id="path4868"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4870"
+ d="M 9.375,299.125 C 9.1474242,300.07637 9,301.02245 9,302 c 0,15.27432 28.494074,27.65625 63.625,27.65625 35.13093,0 63.59375,-12.38193 63.59375,-27.65625 0,-0.97755 -0.11617,-1.92363 -0.34375,-2.875 -3.35909,13.89991 -30.39038,24.71875 -63.25,24.71875 -32.85962,0 -59.890909,-10.81884 -63.25,-24.71875 z"
+ style="opacity:0.5;fill:url(#linearGradient4874);fill-opacity:1;stroke:none" />
+ </g>
+ </g>
+ <path
+ transform="translate(-74.678308,-139.57606)"
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient4910);fill-opacity:1;stroke:#000023;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3344"
+ sodipodi:cx="270.45764"
+ sodipodi:cy="499.72882"
+ sodipodi:rx="94.728813"
+ sodipodi:ry="41.18644"
+ d="m 365.18645,499.72882 c 0,22.74664 -42.41153,41.18644 -94.72881,41.18644 -52.31728,0 -94.72881,-18.4398 -94.72881,-41.18644 0,-22.74664 42.41153,-41.18644 94.72881,-41.18644 52.31728,0 94.72881,18.4398 94.72881,41.18644 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect3350"
+ d="m 76.5,126.5 0,19.09375 c 0,-4.64658 6.52034,-8.4375 14.5625,-8.4375 8.04215,0 14.5625,3.79092 14.5625,8.4375 l 180.3125,0 c 0,-4.64658 6.52035,-8.4375 14.5625,-8.4375 8.04215,0 14.5625,3.79092 14.5625,8.4375 l 0,-19.09375 -238.5625,0 z"
+ style="fill:#d07200;fill-opacity:1;stroke:none" />
+ <path
+ d="m 365.18645,499.72882 c 0,22.74664 -42.41153,41.18644 -94.72881,41.18644 -52.31728,0 -94.72881,-18.4398 -94.72881,-41.18644 0,-22.74664 42.41153,-41.18644 94.72881,-41.18644 52.31728,0 94.72881,18.4398 94.72881,41.18644 z"
+ sodipodi:ry="41.18644"
+ sodipodi:rx="94.728813"
+ sodipodi:cy="499.72882"
+ sodipodi:cx="270.45764"
+ id="path4785"
+ style="opacity:0.5;fill:url(#linearGradient4793);fill-opacity:1;stroke:none"
+ sodipodi:type="arc"
+ transform="translate(-74.678303,-139.57606)" />
+ <g
+ id="g3358">
+ <g
+ id="g4832">
+ <path
+ transform="matrix(-0.67149758,0,0,0.67149758,254.22789,-33.552002)"
+ d="m 365.18645,499.72882 c 0,22.74664 -42.41153,41.18644 -94.72881,41.18644 -52.31728,0 -94.72881,-18.4398 -94.72881,-41.18644 0,-22.74664 42.41153,-41.18644 94.72881,-41.18644 52.31728,0 94.72881,18.4398 94.72881,41.18644 z"
+ sodipodi:ry="41.18644"
+ sodipodi:rx="94.728813"
+ sodipodi:cy="499.72882"
+ sodipodi:cx="270.45764"
+ id="path3388"
+ style="fill:url(#radialGradient4813);fill-opacity:1;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3390"
+ d="M 130.40221,313.59159 76.506851,145.58186 16.670251,315.18688"
+ style="fill:none;stroke:#d07200;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3392"
+ d="m 76.506851,145.58186 -3.8906,128.77624"
+ style="fill:none;stroke:#d07200;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:0.5;fill:url(#linearGradient4856);fill-opacity:1;stroke:none"
+ d="M 9.375,299.125 C 9.1474242,300.07637 9,301.02245 9,302 c 0,15.27432 28.494074,27.65625 63.625,27.65625 35.13093,0 63.59375,-12.38193 63.59375,-27.65625 0,-0.97755 -0.11617,-1.92363 -0.34375,-2.875 -3.35909,13.89991 -30.39038,24.71875 -63.25,24.71875 -32.85962,0 -59.890909,-10.81884 -63.25,-24.71875 z"
+ id="path4821" />
+ </g>
+ </g>
+ <rect
+ y="51.71209"
+ x="179.76239"
+ height="308.44067"
+ width="32.033897"
+ id="rect3340"
+ style="fill:url(#linearGradient4801);fill-opacity:1;stroke:none" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4876"
+ d="m 195.7894,29.424271 c -8.89509,0 -16.09832,7.214351 -16.09832,16.109438 0,0.172769 0.005,0.339601 0.0111,0.511058 -3.16547,3.699588 -5.07725,8.503386 -5.07725,13.754126 0,11.690687 9.47378,21.16447 21.16447,21.16447 11.69069,0 21.17558,-9.473783 21.17558,-21.16447 0,-5.25074 -1.91178,-10.054538 -5.07725,-13.754126 0.005,-0.171457 0.0111,-0.338289 0.0111,-0.511058 0,-8.895087 -7.21435,-16.109438 -16.10943,-16.109438 z"
+ style="fill:url(#radialGradient4888);fill-opacity:1;stroke:none" />
+ <path
+ style="opacity:0.35;fill:url(#linearGradient5008);fill-opacity:1;stroke:none"
+ d="m 195.7894,29.424271 c -8.89509,0 -16.09832,7.214351 -16.09832,16.109438 0,0.172769 0.005,0.339601 0.0111,0.511058 -3.16547,3.699588 -5.07725,8.503386 -5.07725,13.754126 0,11.690687 9.47378,21.16447 21.16447,21.16447 11.69069,0 21.17558,-9.473783 21.17558,-21.16447 0,-5.25074 -1.91178,-10.054538 -5.07725,-13.754126 0.005,-0.171457 0.0111,-0.338289 0.0111,-0.511058 0,-8.895087 -7.21435,-16.109438 -16.10943,-16.109438 z"
+ id="path4890"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/potion.svg b/files/opencs/scalable/referenceable-record/potion.svg
new file mode 100644
index 0000000000..552c14f19f
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/potion.svg
@@ -0,0 +1,1161 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="potion.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.12"
+ inkscape:cx="-13.774317"
+ inkscape:cy="16.062941"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3910"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="false"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ id="stop3914"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4535">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4537" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4539" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#caf8f0;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ id="stop3935"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#caf8f0;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ id="stop3861"
+ offset="0"
+ style="stop-color:#8cf0eb;stop-opacity:0.78431374;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.58823532;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient5606"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient5608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient5610"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5614"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91252132,0,0,0.96973096,-5.5197559,1004.0875)"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="radialGradient3918"
+ cx="0.20415781"
+ cy="26.466549"
+ fx="0.20415781"
+ fy="26.466549"
+ r="15.52295"
+ gradientTransform="matrix(1.3421662,-1.1488417,0.96457332,1.1859542,-25.598784,-3.3438458)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3933"
+ x1="8.8399678"
+ y1="4.5"
+ x2="22.066282"
+ y2="4.5"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2031694,0,0,1.0551213,-87.506267,947.79452)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3944"
+ x1="-173.13348"
+ y1="-22.158251"
+ x2="-132.63493"
+ y2="-22.158251"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4535"
+ id="radialGradient4543"
+ cx="15.546875"
+ cy="24"
+ fx="15.546875"
+ fy="24"
+ r="15.52295"
+ gradientTransform="matrix(1.1964735,-0.0427985,0.07341799,1.5784406,-98.785396,935.74813)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3906"
+ id="linearGradient3912"
+ x1="-85.051697"
+ y1="957.60967"
+ x2="-71.991244"
+ y2="957.60967"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3922"
+ gradientUnits="userSpaceOnUse"
+ x1="-173.13348"
+ y1="-22.158251"
+ x2="-132.63493"
+ y2="-22.158251" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3145"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(7.9101566,0.87890131)"
+ x1="8.8399678"
+ y1="4.5"
+ x2="22.066282"
+ y2="4.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3148"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2031694,0,0,1.0551213,-97.009972,946.86576)"
+ x1="8.8399678"
+ y1="4.5"
+ x2="22.066282"
+ y2="4.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3927"
+ id="linearGradient3150"
+ gradientUnits="userSpaceOnUse"
+ x1="-173.13348"
+ y1="-22.158251"
+ x2="-132.63493"
+ y2="-22.158251" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ id="g5597"
+ transform="matrix(0.85483505,0,0,1,-20.035433,-2.2155805e-6)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3067"
+ d="m -96.780295,977.49393 c 0,11.04998 8.226372,20.01828 18.362212,20.01828 10.135841,0 18.362216,-8.9683 18.362216,-20.01828 l -36.724428,0 z"
+ style="fill:url(#radialGradient5606);fill-opacity:1;stroke:none" />
+ <path
+ style="opacity:0.5;fill:url(#linearGradient5608);fill-opacity:1;stroke:none"
+ d="m -96.574432,977.49391 c 0,11.05 8.226376,20.0183 18.362216,20.0183 10.135841,0 18.362216,-8.9683 18.362216,-20.0183 l -36.724432,0 z"
+ id="path3935"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient5610);fill-opacity:1;stroke:none"
+ id="path4311"
+ sodipodi:cx="-152.88422"
+ sodipodi:cy="-22.158251"
+ sodipodi:rx="19.749271"
+ sodipodi:ry="4.4194174"
+ d="m -133.13494,-22.158251 c 0,2.440777 -8.84205,4.419418 -19.74928,4.419418 -10.90722,0 -19.74927,-1.978641 -19.74927,-4.419418 0,-2.440777 8.84205,-4.419417 19.74927,-4.419417 10.90723,0 19.74928,1.97864 19.74928,4.419417 z"
+ transform="matrix(0.92927807,0,0,1.0003876,63.663518,999.66077)" />
+ <g
+ id="g3910"
+ transform="matrix(0.99443476,0,0,0.96935533,-0.53979874,30.567681)">
+ <path
+ id="path4318"
+ transform="matrix(1.1964735,0,0,1.0227875,-97.023365,948.41842)"
+ d="M 10.0625,0.21875 C 9.5192621,0.27693373 9.09375,0.85344116 9.09375,1.5625 l 0,2.59375 c 0,0.706794 0.427593,1.2818603 0.96875,1.34375 l 0,4.46875 c -5.7540013,2.820234 -9.84375,9.91868 -9.84375,18.25 0,10.803613 6.8410711,19.5625 15.3125,19.5625 8.471429,0 15.34375,-8.758887 15.34375,-19.5625 0,-8.33132 -4.089757,-15.429766 -9.84375,-18.25 l 0,-4.46875 C 21.572399,5.4384036 22,4.863044 22,4.15625 L 22,1.5625 C 22,0.84034508 21.5578,0.2585184 21,0.21875 21.037342,0.29186513 21.0625,0.36147208 21.0625,0.4375 21.0625,1.2979135 18.584434,2 15.53125,2 12.478066,2 10,1.2979135 10,0.4375 c 0,-0.0743372 0.02677,-0.14718964 0.0625,-0.21875 z"
+ style="fill:url(#radialGradient3918);fill-opacity:1;stroke:#7d7d7d;stroke-width:0.38965022999999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient3944);fill-opacity:1;stroke:#7d7d7d;stroke-opacity:1;opacity:0.75"
+ id="path3127"
+ sodipodi:cx="-152.88422"
+ sodipodi:cy="-22.158251"
+ sodipodi:rx="19.749271"
+ sodipodi:ry="4.4194174"
+ d="m -133.13494,-22.158251 c 0,2.440777 -8.84205,4.419418 -19.74928,4.419418 -10.90722,0 -19.74927,-1.978641 -19.74927,-4.419418 0,-2.440777 8.84205,-4.419417 19.74927,-4.419417 10.90723,0 19.74928,1.97864 19.74928,4.419417 z"
+ transform="matrix(0.33492065,0,0,0.3605492,-27.223425,956.85737)" />
+ <path
+ style="opacity:0.75;fill:url(#linearGradient3148);fill-opacity:1;stroke:#7d7d7d;stroke-width:0.43104154;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m -84.982974,948.64215 c -0.64997,0.0595 -1.165571,0.65963 -1.165571,1.38485 l 0,2.6378 c 0,0.7229 0.518091,1.32155 1.165571,1.38485 l 0,0.46162 c 0.544843,-0.74781 3.244284,-1.3189 6.504634,-1.3189 3.36081,0 6.119682,0.60297 6.542234,1.38484 l 0.0752,0 0,-0.52756 c 0.64747,-0.063 1.16557,-0.66195 1.16557,-1.38485 l 0,-2.6378 c 0,-0.73861 -0.535775,-1.34418 -1.203169,-1.38485 0.04468,0.0748 0.0752,0.15305 0.0752,0.23081 0,0.88002 -2.964378,1.58268 -6.617432,1.58268 -3.653054,0 -6.617431,-0.70266 -6.617431,-1.58268 0,-0.076 0.03245,-0.15762 0.0752,-0.23081 z"
+ id="path3920"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:0.75;fill:url(#radialGradient4543);fill-opacity:1;stroke:#7d7d7d;stroke-width:0.4310416;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m -84.983856,948.64215 c -0.64997,0.0595 -1.15908,0.64916 -1.15908,1.37438 l 0,2.65285 c 0,0.7229 0.5116,1.31107 1.15908,1.37437 l 0,4.57058 c -6.88451,2.8845 -11.77778,10.1447 -11.77778,18.66587 0,11.04981 8.18516,20.00825 18.320999,20.00825 10.13584,0 18.35839,-8.95844 18.35839,-20.00825 0,-8.52117 -4.893286,-15.78137 -11.777786,-18.66587 l 0,-4.57058 c 0.647471,-0.063 1.159084,-0.65147 1.159084,-1.37437 l 0,-2.65285 c 0,-0.73862 -0.529081,-1.3337 -1.196474,-1.37438 0.04468,0.0748 0.07478,0.14598 0.07478,0.22374 0,0.88002 -2.96494,1.59811 -6.617994,1.59811 -3.653054,0 -6.617999,-0.71809 -6.617999,-1.59811 0,-0.076 0.032,-0.15054 0.0748,-0.22374 z"
+ id="path4533" />
+ <path
+ transform="matrix(0.33492065,0,0,0.3605492,-27.236371,962.76818)"
+ d="m -133.13494,-22.158251 a 19.749271,4.4194174 0 1 1 -39.49855,0 19.749271,4.4194174 0 1 1 39.49855,0 z"
+ sodipodi:ry="4.4194174"
+ sodipodi:rx="19.749271"
+ sodipodi:cy="-22.158251"
+ sodipodi:cx="-152.88422"
+ id="path3916"
+ style="opacity:0.5;fill:url(#linearGradient3150);fill-opacity:1;stroke:#7d7d7d;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <path
+ style="fill:#fa0000;fill-opacity:1;stroke:none"
+ d="m -69.333164,982.81211 0,3.65905 -6.823881,0 11.138221,11.04106 11.138216,-11.04106 -6.823877,0 0,-3.65905 -8.628679,0 z"
+ id="path5146"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/random-item.svg b/files/opencs/scalable/referenceable-record/random-item.svg
new file mode 100644
index 0000000000..d051ce049f
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/random-item.svg
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="38.4pt"
+ height="38.4pt"
+ id="svg14590"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="random-item.svg">
+ <metadata
+ id="metadata14614">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ id="namedview14612"
+ showgrid="false"
+ inkscape:zoom="1.7383042"
+ inkscape:cx="-122.73952"
+ inkscape:cy="-49.526534"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g12681" />
+ <defs
+ id="defs14592" />
+ <g
+ id="layer1">
+ <path
+ id="path5146"
+ transform="translate(26.2199, 33.6274)"
+ fill="#fa0000"
+ d="M6.67184 0L6.67184 3.5776L0 3.5776L10.8901 14.3726L21.7801 3.5776L15.1083 3.5776L15.1083 0Z" />
+ <g
+ id="g7049"
+ transform="translate(-0.222381, -0.133277)"
+ fill="none">
+ <g
+ id="g9629"
+ transform=""
+ fill="none"
+ opacity="0.50000000000">
+ <g
+ id="g12681"
+ transform=""
+ fill="none">
+ <path
+ id="path9661"
+ transform="matrix(1.81491 0 0 1.53886 1.13432 15.871)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 5.9062L3.67769 20.1534L15.7441 12.5448L10.8867 0Z" />
+ <path
+ id="path9663"
+ transform="matrix(1.81491 0 0 1.53886 1.13432 0.864529)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 5.3072L0 15.6579L10.8867 9.7517L10.8867 0Z" />
+ <path
+ id="path9665"
+ transform="matrix(1.81491 0 0 1.53886 20.8927 0.864529)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 0L4.85734 11.2726L4.85734 22.2965L0 9.7517Z" />
+ <path
+ id="path9667"
+ transform="matrix(1.81491 0 0 1.53886 1.13432 0.864529)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 5.3072L3.67769 18.1097L15.7441 11.2726L10.8867 0Z"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ id="path9669"
+ transform="matrix(1.81491 0 0 1.53886 7.809 18.2115)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 6.8371L0 18.6325L12.0664 11.0239L12.0664 0Z"
+ style="fill:#dddddd;fill-opacity:1;stroke:none" />
+ <path
+ id="path9671"
+ transform="matrix(1.81491 0 0 1.53886 1.13432 9.03156)"
+ fill="#000000"
+ fill-rule="evenodd"
+ d="M0 0L3.67769 12.8025L3.67769 24.5979L0 10.3507Z"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ id="path12649"
+ transform="matrix(1.81491 0 0 1.53886 21.3443 21.6545)"
+ fill="#000000"
+ d="M3.6302 0.99212C3.6302 1.94782 2.83287 3.19372 1.83729 3.78182C0.828177 4.37792 0 4.07312 0 3.09372C0 2.11422 0.828177 0.85052 1.83729 0.27832C2.83287 -0.28638 3.6302 0.0363202 3.6302 0.99212C3.6302 0.99212 3.6302 0.99212 3.6302 0.99212" />
+ <path
+ id="path9679"
+ transform="matrix(1.81491 0 0 1.53886 9.60735 36.4088)"
+ fill="#000000"
+ d="M3.9453 0.976363C3.9453 1.97226 3.07947 3.32506 1.9978 4.00656C0.900785 4.69776 0 4.43716 0 3.41556C0 2.39396 0.900785 1.01956 1.9978 0.354263C3.07947 -0.301737 3.9453 -0.0195374 3.9453 0.976363C3.9453 0.976363 3.9453 0.976363 3.9453 0.976363" />
+ <path
+ id="path10162"
+ transform="matrix(1.81491 0 0 1.53886 10.5039 10.8555)"
+ fill="#000000"
+ d="M4.18901 1.19624C4.6807 2.34504 4.18823 3.85194 3.02179 4.51064C1.81286 5.19344 0.473844 4.64124 0.0966028 3.34104C-0.255451 2.12774 0.388369 0.756036 1.48163 0.221536C2.54082 -0.296264 3.72827 0.119836 4.18901 1.19624C4.18901 1.19624 4.18901 1.19624 4.18901 1.19624"
+ style="fill-opacity:1;fill:#000000" />
+ <path
+ id="path12645"
+ transform="matrix(1.81491 0 0 1.53886 1.67251 13.5882)"
+ fill="#000000"
+ d="M0 0.626776C0 1.52458 0.229467 3.08518 0.519084 4.13628C0.816204 5.21458 1.06278 5.34708 1.06278 4.40678C1.06278 3.46638 0.816204 1.84498 0.519084 0.809776C0.229467 -0.199324 0 -0.271024 0 0.626776C0 0.626776 0 0.626776 0 0.626776" />
+ <path
+ id="path12643"
+ transform="matrix(1.81491 0 0 1.53886 3.26728 22.7458)"
+ fill="#000000"
+ d="M0 0.616613C0 1.54961 0.247908 3.23571 0.561083 4.41011C0.882701 5.61621 1.14986 5.82431 1.14986 4.84531C1.14986 3.86621 0.882701 2.10951 0.561083 0.950013C0.247908 -0.178987 0 -0.316387 0 0.616613C0 0.616613 0 0.616613 0 0.616613" />
+ <path
+ id="path10667"
+ transform="matrix(1.81491 0 0 1.53886 4.99217 32.6497)"
+ fill="#000000"
+ d="M0 0.606353C0 1.57745 0.268664 3.40465 0.608394 4.71965C0.957671 6.07175 1.2481 6.36825 1.2481 5.34715C1.2481 4.32605 0.957671 2.41625 0.608394 1.11485C0.268664 -0.150847 0 -0.364747 0 0.606353C0 0.606353 0 0.606353 0 0.606353" />
+ <path
+ id="path12632"
+ transform="matrix(1.54043 0 0 1.30612 1.11904 0.85371)"
+ d="M12.8437 0L0 6.25L0 18.4688L4.34375 35.25L18.5625 26.2812L18.5625 13.2812Z"
+ style="fill:none;stroke-width:0.70499734;stroke-miterlimit:4;stroke-dasharray:none"
+ stroke-linejoin="round"
+ stroke-linecap="square"
+ stroke-width="0.50000000000"
+ stroke="#000000"
+ fill="#000000" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/repair.svg b/files/opencs/scalable/referenceable-record/repair.svg
new file mode 100644
index 0000000000..b1a23956e3
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/repair.svg
@@ -0,0 +1,1280 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="repair.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.8331624"
+ inkscape:cx="28.844254"
+ inkscape:cy="11.17998"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5025"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true"
+ inkscape:snap-midpoints="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient5648">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="0"
+ id="stop5650" />
+ <stop
+ id="stop5660"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:0;"
+ offset="1"
+ id="stop5652" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5625">
+ <stop
+ style="stop-color:#232620;stop-opacity:1;"
+ offset="0"
+ id="stop5627" />
+ <stop
+ style="stop-color:#232620;stop-opacity:0;"
+ offset="1"
+ id="stop5629" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5617">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.502"
+ offset="0"
+ id="stop5619" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:0;"
+ offset="1"
+ id="stop5621" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5601">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.502"
+ offset="0"
+ id="stop5603" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:0;"
+ offset="1"
+ id="stop5605" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5573">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="0"
+ id="stop5575" />
+ <stop
+ id="stop5581"
+ offset="0.5"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop5577" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3988">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3990" />
+ <stop
+ style="stop-color:#e1e1e1;stop-opacity:0;"
+ offset="1"
+ id="stop3992" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient5606"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient5608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient5610"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient5612"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5614"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5616"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5618"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4676"
+ id="linearGradient4742"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="176.0379"
+ y1="100.83343"
+ x2="174.53673"
+ y2="95.230942" />
+ <linearGradient
+ id="linearGradient4676">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop4678" />
+ <stop
+ style="stop-color:#603500;stop-opacity:1;"
+ offset="1"
+ id="stop4680" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4691"
+ id="linearGradient4744"
+ gradientUnits="userSpaceOnUse"
+ x1="108.45694"
+ y1="276.11148"
+ x2="317.44235"
+ y2="267.09256" />
+ <linearGradient
+ id="linearGradient4691">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4693" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:0;"
+ offset="1"
+ id="stop4695" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4713"
+ id="linearGradient4746"
+ gradientUnits="userSpaceOnUse"
+ x1="144.94083"
+ y1="42.470928"
+ x2="176.0379"
+ y2="42.470928" />
+ <linearGradient
+ id="linearGradient4713">
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0"
+ id="stop4715" />
+ <stop
+ id="stop4721"
+ offset="0.5"
+ style="stop-color:#707070;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4717" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4723"
+ id="radialGradient4748"
+ gradientUnits="userSpaceOnUse"
+ cx="574.21875"
+ cy="-86.71875"
+ fx="574.21875"
+ fy="-86.71875"
+ r="77.493752" />
+ <linearGradient
+ id="linearGradient4723">
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="0"
+ id="stop4725" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:1;"
+ offset="1"
+ id="stop4727" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4653"
+ id="radialGradient4750"
+ gradientUnits="userSpaceOnUse"
+ cx="574.21875"
+ cy="-86.71875"
+ fx="574.21875"
+ fy="-86.71875"
+ r="77.34375" />
+ <linearGradient
+ id="linearGradient4653">
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="0"
+ id="stop4655" />
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="1"
+ id="stop4657" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3988"
+ id="linearGradient3994"
+ x1="158.1739"
+ y1="472.02515"
+ x2="164.40285"
+ y2="-57.18573"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5573"
+ id="linearGradient5579"
+ x1="26.598907"
+ y1="17.959133"
+ x2="26.598907"
+ y2="23.967577"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5601"
+ id="linearGradient5607"
+ x1="19.28125"
+ y1="14.203125"
+ x2="16.28125"
+ y2="14.203125"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5617"
+ id="linearGradient5623"
+ x1="16.28125"
+ y1="12.451531"
+ x2="4.6925631"
+ y2="12.451531"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5625"
+ id="radialGradient5631"
+ cx="27.25"
+ cy="27.829834"
+ fx="27.25"
+ fy="27.829834"
+ r="12.65625"
+ gradientTransform="matrix(1.6395061,0,0,1.3429653,-17.42654,-9.5446668)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5648"
+ id="linearGradient5658"
+ x1="21.75"
+ y1="28.171875"
+ x2="32.75"
+ y2="28.171875"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <path
+ style="fill:#fa0000;fill-opacity:1;stroke:none"
+ d="m -69.333164,982.81214 0,3.65905 -6.823877,0 11.138217,11.04103 11.138216,-11.04103 -6.823877,0 0,-3.65905 -8.628679,0 z"
+ id="path5146"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g5025"
+ transform="translate(-5.0241951,57.212699)">
+ <path
+ style="fill:#b3bbad;fill-opacity:1;stroke:#232323;stroke-opacity:1;stroke-width:0.30683623999999998;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m -62.204584,922.76343 c -0.764629,-0.0254 -1.435388,-0.0755 -1.490577,-0.1112 -0.213728,-0.13845 -2.594071,-0.39014 -4.472877,-0.47298 -3.232556,-0.1425 -7.048705,0.0235 -8.650162,0.37642 -0.646264,0.14239 -5.315588,0.17498 -5.784921,0.0404 -0.331528,-0.0951 -0.411165,-0.19993 -0.41285,-0.54357 l 1.188955,-4.04337 c 1.387956,-1.60942 1.303411,-1.54945 2.756089,-1.95517 2.127905,-0.59433 2.975048,-1.10546 3.218162,-1.94175 0.209608,-0.72102 0.131416,-1.72413 -0.187524,-2.40569 -0.404882,-0.8652 -1.070458,-1.48506 -1.838434,-1.71214 -0.515163,-0.15235 -1.910354,-0.31446 -3.421152,-0.39751 -1.608499,-0.0885 -2.414828,-0.17538 -3.473317,-0.37451 -2.516588,-0.4734 -5.957075,-1.92345 -8.368026,-3.52686 -1.456904,-0.96891 -3.393709,-2.55988 -3.820406,-3.13824 -0.249479,-0.33814 -0.272532,-0.77339 -0.04732,-0.8939 0.107386,-0.0575 0.877671,-0.0456 2.473806,0.0383 3.29192,0.17297 7.528212,0.26118 10.60038,0.22072 l 2.63981,-0.0347 0.113525,-0.27173 c 0.127272,-0.30459 0.195916,-0.32237 1.538768,-0.39874 0.933077,-0.0531 1.156198,-0.1581 1.189526,-0.56002 0.01777,-0.21398 0.07881,-0.33444 0.203263,-0.40105 l 28.931317,0.0575 c 0.133807,0.0827 0.09208,1.35581 -0.04957,1.51233 -0.069,0.0762 -0.162395,0.37354 -0.207547,0.66066 -0.116236,0.73916 -0.14091,0.79541 -0.409874,0.93449 -0.132336,0.0684 -0.838135,0.21545 -1.568441,0.32669 -5.154543,0.78521 -6.454756,1.11266 -8.304771,2.09157 -2.876054,1.52183 -4.697572,3.49365 -5.135498,5.55928 -0.16186,0.76349 -0.115178,2.22469 0.08923,2.79297 0.282701,0.78596 1.144742,1.30738 3.113294,1.88311 0.676842,0.19795 1.289563,0.39349 1.3616,0.43454 0.260822,0.14863 2.734034,2.6172 2.909382,2.90395 l 0.179898,0.29417 0.206549,2.67694 c -0.384494,0.47459 -0.942705,0.51634 -5.070269,0.37917 z"
+ id="path5039"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccsscccssssssssccscssscccscssssscscccc" />
+ <path
+ style="fill:url(#linearGradient5658);fill-opacity:1;stroke:none;stroke-width:0.30000000999999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0.5"
+ d="M 27.25 25.75 C 24.46221 25.75 22.150905 27.823906 21.75 30.5 C 23.602479 30.27161 26.528846 30.166804 29.125 30.28125 C 30.394562 30.337227 31.872651 30.46925 32.75 30.59375 C 32.39115 27.870236 30.0715 25.75 27.25 25.75 z "
+ transform="matrix(1.0227875,0,0,1.0227875,-97.950211,891.20572)"
+ id="path5633" />
+ <path
+ style="fill:url(#radialGradient5631);fill-opacity:1;opacity:0.5"
+ d="M 17.21875 24.75 C 16.846705 24.974307 16.507371 25.340532 15.75 26.21875 L 14.59375 30.15625 C 14.595397 30.492234 14.675858 30.594519 15 30.6875 C 15.458876 30.819082 20.024385 30.795468 20.65625 30.65625 C 20.935853 30.594633 21.347287 30.54965 21.75 30.5 C 22.150905 27.823906 24.46221 25.75 27.25 25.75 C 30.0715 25.75 32.39115 27.870236 32.75 30.59375 C 33.142099 30.649391 33.435456 30.708189 33.5 30.75 C 33.553959 30.784905 34.189907 30.818916 34.9375 30.84375 C 38.973103 30.977864 39.530322 30.932766 39.90625 30.46875 L 39.71875 27.875 L 39.53125 27.59375 C 39.359809 27.313389 36.942511 24.895319 36.6875 24.75 L 17.21875 24.75 z "
+ id="path5556"
+ transform="matrix(1.0227875,0,0,1.0227875,-97.950211,891.20572)" />
+ <path
+ style="fill:url(#linearGradient5579);fill-opacity:1;opacity:0.5"
+ d="M 18.3125 18.15625 C 18.901129 18.224644 19.373157 18.300522 19.625 18.375 C 20.375866 18.597021 21.010389 19.185326 21.40625 20.03125 C 21.718084 20.697625 21.798688 21.701294 21.59375 22.40625 C 21.456486 22.878427 21.090019 23.23209 20.4375 23.5625 L 33.40625 23.5625 C 32.822119 23.242035 32.465508 22.894141 32.3125 22.46875 C 32.112646 21.913131 32.060496 20.49648 32.21875 19.75 C 32.33282 19.211951 32.543737 18.674694 32.84375 18.15625 L 18.3125 18.15625 z "
+ transform="matrix(1.0227875,0,0,1.0227875,-97.950211,891.20572)"
+ id="path5569" />
+ <path
+ style="fill:url(#linearGradient5607);fill-opacity:1"
+ d="m 19.28125,8.84375 c -0.002,6.908e-4 -0.02994,-7.028e-4 -0.03125,0 -0.12168,0.065126 -0.170126,0.1970374 -0.1875,0.40625 -0.03259,0.3929653 -0.243962,0.4793331 -1.15625,0.53125 -1.312934,0.074668 -1.375564,0.1084462 -1.5,0.40625 l -0.125,0.25 0,7.53125 c 1.283228,0.07054 2.377377,0.213472 3,0.34375 z"
+ transform="matrix(1.0227875,0,0,1.0227875,-97.950211,891.20572)"
+ id="path5585"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccc" />
+ <path
+ style="fill:url(#linearGradient5623);fill-opacity:1"
+ d="M 16.4375 10.125 C 16.427172 10.146118 16.41662 10.162683 16.40625 10.1875 L 16.28125 10.4375 L 13.71875 10.46875 C 10.715029 10.508309 6.5623269 10.419116 3.34375 10.25 C 1.7831765 10.167969 1.0112435 10.162531 0.90625 10.21875 C 0.68605566 10.336575 0.72482932 10.763144 0.96875 11.09375 C 1.3859403 11.659224 3.2630555 13.208927 4.6875 14.15625 C 7.0447357 15.723936 10.414481 17.162147 12.875 17.625 C 13.909906 17.819693 14.708588 17.882222 16.28125 17.96875 C 16.337583 17.971847 16.381537 17.965433 16.4375 17.96875 L 16.4375 10.125 z "
+ transform="matrix(1.0227875,0,0,1.0227875,-97.950211,891.20572)"
+ id="path5611" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/static.svg b/files/opencs/scalable/referenceable-record/static.svg
new file mode 100644
index 0000000000..a6f29370f0
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/static.svg
@@ -0,0 +1,1141 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="static.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.9999998"
+ inkscape:cx="27.262203"
+ inkscape:cy="12.103218"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3891"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true" />
+ <defs
+ id="defs4">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient8421">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop8423" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop8425" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4919">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop4921" />
+ <stop
+ id="stop5678"
+ offset="0.5"
+ style="stop-color:#ccd2c8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop4923" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3948"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-183.1468,-158.28118)" />
+ <linearGradient
+ id="linearGradient3942">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3944" />
+ <stop
+ style="stop-color:#343a30;stop-opacity:0;"
+ offset="1"
+ id="stop3946" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3846">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3848" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3850" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4012"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3892">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3894" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3896" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4014"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3901" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3903" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4018"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3888"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3920">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3922" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3924" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3876"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3886"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3934">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3936" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3938" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3941">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3943" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3945" />
+ </linearGradient>
+ <radialGradient
+ r="149.18433"
+ fy="260.15875"
+ fx="72.191498"
+ cy="260.15875"
+ cx="72.191498"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3964"
+ xlink:href="#linearGradient3846"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4919"
+ id="linearGradient4925"
+ x1="-1620.4071"
+ y1="2378.4158"
+ x2="-562.93918"
+ y2="2378.4158"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8421"
+ id="linearGradient8427"
+ x1="56.284344"
+ y1="40.597916"
+ x2="-14.0625"
+ y2="-3.4139358e-15"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g3891"
+ transform="matrix(0.02092262,0,0,0.02092262,47.215663,982.03701)">
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect3139"
+ width="672.52985"
+ height="323.39728"
+ x="-1920.4152"
+ y="1067.036" />
+ <rect
+ y="1067.036"
+ x="-1247.8854"
+ height="323.39728"
+ width="672.52985"
+ id="rect3141"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ y="1067.036"
+ x="-2592.9451"
+ height="323.39728"
+ width="672.52985"
+ id="rect3143"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7794"
+ width="672.52985"
+ height="323.39728"
+ x="-575.35547"
+ y="1067.036" />
+ <rect
+ y="1390.4332"
+ x="-2256.6799"
+ height="323.39728"
+ width="672.52985"
+ id="rect7796"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7798"
+ width="672.52985"
+ height="323.39728"
+ x="-1584.1501"
+ y="1390.4332" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7800"
+ width="672.52985"
+ height="323.39728"
+ x="-2929.21"
+ y="1390.4332" />
+ <rect
+ y="1390.4332"
+ x="-911.62036"
+ height="323.39728"
+ width="672.52985"
+ id="rect7802"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7812"
+ width="672.52985"
+ height="323.39728"
+ x="-239.09058"
+ y="1390.4332" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7814"
+ width="672.52985"
+ height="323.39728"
+ x="-1920.4152"
+ y="1713.8306" />
+ <rect
+ y="1713.8306"
+ x="-1247.8853"
+ height="323.39728"
+ width="672.52985"
+ id="rect7816"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ y="1713.8306"
+ x="-2592.9451"
+ height="323.39728"
+ width="672.52985"
+ id="rect7818"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7820"
+ width="672.52985"
+ height="323.39728"
+ x="-575.35541"
+ y="1713.8306" />
+ <rect
+ y="1713.8306"
+ x="97.174339"
+ height="323.39728"
+ width="672.52985"
+ id="rect7822"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ y="2037.228"
+ x="-2256.6802"
+ height="323.39728"
+ width="672.52985"
+ id="rect7824"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7826"
+ width="672.52985"
+ height="323.39728"
+ x="-1584.1504"
+ y="2037.228" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7828"
+ width="672.52985"
+ height="323.39728"
+ x="-2929.2102"
+ y="2037.228" />
+ <rect
+ y="2037.228"
+ x="-911.62061"
+ height="323.39728"
+ width="672.52985"
+ id="rect7830"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7832"
+ width="672.52985"
+ height="323.39728"
+ x="-239.09085"
+ y="2037.228" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7834"
+ width="672.52985"
+ height="323.39728"
+ x="-1920.4154"
+ y="2360.6252" />
+ <rect
+ y="2360.6252"
+ x="-1247.8855"
+ height="323.39728"
+ width="672.52985"
+ id="rect7836"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ y="2360.6252"
+ x="-2592.9453"
+ height="323.39728"
+ width="672.52985"
+ id="rect7838"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7840"
+ width="672.52985"
+ height="323.39728"
+ x="-575.35571"
+ y="2360.6252" />
+ <rect
+ y="2360.6252"
+ x="97.174065"
+ height="323.39728"
+ width="672.52985"
+ id="rect7842"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ y="2684.0227"
+ x="-2256.6802"
+ height="323.39728"
+ width="672.52985"
+ id="rect7844"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7846"
+ width="672.52985"
+ height="323.39728"
+ x="-1584.1504"
+ y="2684.0227" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7848"
+ width="672.52985"
+ height="323.39728"
+ x="-2929.2102"
+ y="2684.0227" />
+ <rect
+ y="2684.0227"
+ x="-911.62061"
+ height="323.39728"
+ width="672.52985"
+ id="rect7850"
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round" />
+ <rect
+ style="fill:#780000;fill-opacity:1;stroke:#afafaf;stroke-opacity:1;stroke-width:47.79516140999999152;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round"
+ id="rect7852"
+ width="672.52985"
+ height="323.39728"
+ x="-239.09085"
+ y="2684.0227" />
+ <path
+ style="fill:url(#linearGradient8427);fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0.5"
+ d="M -7.03125 0 L -7.03125 6.78125 L -14.0625 6.78125 L -14.0625 13.53125 L -7.03125 13.53125 L -7.03125 20.3125 L -14.0625 20.3125 L -14.0625 27.0625 L -7.03125 27.0625 L -7.03125 33.84375 L -14.0625 33.84375 L -14.0625 40.59375 L 0 40.59375 L 14.0625 40.59375 L 28.15625 40.59375 L 42.21875 40.59375 L 56.28125 40.59375 L 56.28125 33.84375 L 63.3125 33.84375 L 63.3125 27.0625 L 56.28125 27.0625 L 56.28125 20.3125 L 63.3125 20.3125 L 63.3125 13.53125 L 56.28125 13.53125 L 56.28125 6.78125 L 49.25 6.78125 L 49.25 0 L 35.1875 0 L 21.09375 0 L 7.03125 0 L -7.03125 0 z "
+ transform="matrix(47.795161,0,0,47.795161,-2256.6802,1067.0361)"
+ id="rect8362" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/referenceable-record/weapon.svg b/files/opencs/scalable/referenceable-record/weapon.svg
new file mode 100644
index 0000000000..2e832fff76
--- /dev/null
+++ b/files/opencs/scalable/referenceable-record/weapon.svg
@@ -0,0 +1,1206 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="weapon.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.12"
+ inkscape:cx="13.777504"
+ inkscape:cy="9.2319835"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5966"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true"
+ inkscape:snap-midpoints="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3988">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3990" />
+ <stop
+ style="stop-color:#e1e1e1;stop-opacity:0;"
+ offset="1"
+ id="stop3992" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient5606"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient5608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient5610"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient5612"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5614"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5616"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5618"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4676"
+ id="linearGradient4742"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="176.0379"
+ y1="100.83343"
+ x2="174.53673"
+ y2="95.230942" />
+ <linearGradient
+ id="linearGradient4676">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop4678" />
+ <stop
+ style="stop-color:#603500;stop-opacity:1;"
+ offset="1"
+ id="stop4680" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4691"
+ id="linearGradient4744"
+ gradientUnits="userSpaceOnUse"
+ x1="108.45694"
+ y1="276.11148"
+ x2="317.44235"
+ y2="267.09256" />
+ <linearGradient
+ id="linearGradient4691">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4693" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:0;"
+ offset="1"
+ id="stop4695" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4713"
+ id="linearGradient4746"
+ gradientUnits="userSpaceOnUse"
+ x1="144.94083"
+ y1="42.470928"
+ x2="176.0379"
+ y2="42.470928" />
+ <linearGradient
+ id="linearGradient4713">
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0"
+ id="stop4715" />
+ <stop
+ id="stop4721"
+ offset="0.5"
+ style="stop-color:#707070;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4717" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4723"
+ id="radialGradient4748"
+ gradientUnits="userSpaceOnUse"
+ cx="574.21875"
+ cy="-86.71875"
+ fx="574.21875"
+ fy="-86.71875"
+ r="77.493752" />
+ <linearGradient
+ id="linearGradient4723">
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="0"
+ id="stop4725" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:1;"
+ offset="1"
+ id="stop4727" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4653"
+ id="radialGradient4750"
+ gradientUnits="userSpaceOnUse"
+ cx="574.21875"
+ cy="-86.71875"
+ fx="574.21875"
+ fy="-86.71875"
+ r="77.34375" />
+ <linearGradient
+ id="linearGradient4653">
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="0"
+ id="stop4655" />
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="1"
+ id="stop4657" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3988"
+ id="linearGradient3994"
+ x1="158.1739"
+ y1="472.02515"
+ x2="164.40285"
+ y2="-57.18573"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ transform="matrix(0.09303225,0.09127375,-0.09126835,0.09303428,-75.110856,939.23963)"
+ id="g3253">
+ <path
+ style="fill:#cccccc;stroke:#232323;stroke-width:0.87579966;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 160.5,468.83343 -25.52148,-368 51.04296,0 z"
+ id="path4130"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:url(#linearGradient4742);fill-opacity:1;stroke:#232323;stroke-width:0.1104;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4139"
+ width="31.075806"
+ height="116.725"
+ x="144.9621"
+ y="-15.89157" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4689"
+ d="m 160.5,468.83343 -25.52148,-368 51.04296,0 z"
+ style="fill:url(#linearGradient4744);fill-opacity:1;stroke:none" />
+ <rect
+ y="-15.89157"
+ x="144.9621"
+ height="116.725"
+ width="31.075806"
+ id="rect4711"
+ style="opacity:0.5;fill:url(#linearGradient4746);fill-opacity:1;stroke:#000000;stroke-width:0.87579966;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4662"
+ transform="translate(0.40658791,0)">
+ <path
+ transform="matrix(0.27880611,0,0,0.27880611,-0.00228389,-3.6689184)"
+ d="m 651.5625,-86.71875 c 0,42.715774 -34.62798,77.34375 -77.34375,77.34375 -42.71577,0 -77.34375,-34.627976 -77.34375,-77.34375 0,-42.71577 34.62798,-77.34375 77.34375,-77.34375 42.71577,0 77.34375,34.62798 77.34375,77.34375 z"
+ sodipodi:ry="77.34375"
+ sodipodi:rx="77.34375"
+ sodipodi:cy="-86.71875"
+ sodipodi:cx="574.21875"
+ id="path4141"
+ style="fill:url(#radialGradient4748);fill-opacity:1;stroke:#232323;stroke-width:0.47118747;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient4750);fill-opacity:1;stroke:#b9b9b9;stroke-width:3.41451097;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path4143"
+ sodipodi:cx="574.21875"
+ sodipodi:cy="-86.71875"
+ sodipodi:rx="77.34375"
+ sodipodi:ry="77.34375"
+ d="m 651.5625,-86.71875 c 0,42.715774 -34.62798,77.34375 -77.34375,77.34375 -42.71577,0 -77.34375,-34.627976 -77.34375,-77.34375 0,-42.71577 34.62798,-77.34375 77.34375,-77.34375 42.71577,0 77.34375,34.62798 77.34375,77.34375 z"
+ transform="matrix(0.16330661,0,0,0.16330661,66.319693,-13.68489)" />
+ </g>
+ <path
+ style="fill:none;stroke:#7d7d7d;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 500,1274.0039 0,-947.4414"
+ id="path4687"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.368,0,0,0.368,-23.5,0)" />
+ <path
+ style="fill:#e1e1e1;fill-opacity:1;stroke:#232323;stroke-width:0.87579966;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 160.5,89.086592 c -57.03711,0 -103.278035,18.375308 -103.278035,41.055008 0,3.3623 1.01851,6.63587 2.93551,9.7635 11.01375,-17.96913 51.761245,-31.30301 100.342525,-31.30301 48.58127,0 89.32877,13.33388 100.34253,31.30301 1.91699,-3.12763 2.93551,-6.4012 2.93551,-9.7635 0,-22.6797 -46.24092,-41.055008 -103.27804,-41.055008 z"
+ id="path4132"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:#fa0000;fill-opacity:1;stroke:none"
+ d="m -69.333164,982.81214 0,3.65905 -6.823877,0 11.138217,11.04103 11.138216,-11.04103 -6.823877,0 0,-3.65905 -8.628679,0 z"
+ id="path5146"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g3195"
+ transform="matrix(0.09303225,0.09127375,-0.09126835,0.09303428,-75.110856,939.23963)">
+ <g
+ transform="translate(0.40658791,0)"
+ id="g3205" />
+ <path
+ id="path3213"
+ style="fill:url(#linearGradient3994);fill-opacity:1;stroke:none;stroke-width:0.87579965999999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0.50000000000000000"
+ d="m 160.5,89.086592 c -57.03711,0 -103.278035,18.375308 -103.278035,41.055008 0,3.3623 1.01851,6.63587 2.93551,9.7635 11.01375,-17.96913 51.761245,-31.30301 100.342525,-31.30301 48.58127,0 89.32877,13.33388 100.34253,31.30301 1.91699,-3.12763 2.93551,-6.4012 2.93551,-9.7635 0,-22.6797 -46.24092,-41.055008 -103.27804,-41.055008 z m 0,379.746848 0,-348.65844 m 12.63074,-148.021635 c 0,6.975768 -5.65497,12.630746 -12.63074,12.630746 -6.97577,0 -12.63075,-5.654978 -12.63075,-12.630746 0,-6.975768 5.65498,-12.630746 12.63075,-12.630746 6.97577,0 12.63074,5.654978 12.63074,12.630746 z m 8.93317,-1e-6 c 0,11.909419 -9.65449,21.5639103 -21.56391,21.5639103 -11.90942,0 -21.56391,-9.6544913 -21.56391,-21.5639103 0,-11.909417 9.65449,-21.56391 21.56391,-21.56391 11.90942,0 21.56391,9.654493 21.56391,21.56391 z m -37.10181,11.955066 31.0758,0 0,116.725 -31.0758,0 z m 15.5379,484.725 -25.52148,-368 51.04296,0 z m -15.5379,-484.725 31.0758,0 0,116.725 -31.0758,0 z m 15.5379,484.725 -25.52148,-368 51.04296,0 z" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/status/.directory b/files/opencs/scalable/status/.directory
new file mode 100644
index 0000000000..e2d80ed58c
--- /dev/null
+++ b/files/opencs/scalable/status/.directory
@@ -0,0 +1,5 @@
+[Dolphin]
+PreviewsShown=true
+Timestamp=2013,3,21,10,19,49
+Version=3
+ViewMode=1
diff --git a/files/opencs/scalable/status/added.svg b/files/opencs/scalable/status/added.svg
new file mode 100644
index 0000000000..d83649f505
--- /dev/null
+++ b/files/opencs/scalable/status/added.svg
@@ -0,0 +1,932 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="added.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="-3.0608173"
+ inkscape:cy="3.614868"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3948"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-183.1468,-158.28118)" />
+ <linearGradient
+ id="linearGradient3942">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3944" />
+ <stop
+ style="stop-color:#343a30;stop-opacity:0;"
+ offset="1"
+ id="stop3946" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3846">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3848" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3850" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4012"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3892">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3894" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3896" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4014"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3901" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3903" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4018"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3888"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3920">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3922" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3924" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3876"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3886"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3934">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3936" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3938" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3941">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3943" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3945" />
+ </linearGradient>
+ <radialGradient
+ r="149.18433"
+ fy="260.15875"
+ fx="72.191498"
+ cy="260.15875"
+ cx="72.191498"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3964"
+ xlink:href="#linearGradient3846"
+ inkscape:collect="always" />
+ <filter
+ inkscape:collect="always"
+ id="filter4622">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="57.354192"
+ id="feGaussianBlur4624" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3133-9"
+ id="radialGradient6149"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.59728002,0,0,0.40433765,4.9162097,-18.482133)"
+ cx="64"
+ cy="35.686314"
+ fx="64"
+ fy="35.686314"
+ r="40" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3133-9">
+ <stop
+ style="stop-color:#646661;stop-opacity:1"
+ offset="0"
+ id="stop3135-5" />
+ <stop
+ style="stop-color:#111111;stop-opacity:1"
+ offset="1"
+ id="stop3137-0" />
+ </linearGradient>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4626"
+ d="m 17.873381,1008.757 0,13.4786 -13.478558,0 0,12.2533 13.478558,0 0,13.4785 12.278765,0 0,-13.4785 13.453031,0 0,-12.2533 -13.453031,0 0,-13.4786 -12.278765,0 z"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:round" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:234.03692627;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter4622)"
+ d="m -1468.0601,1067.0361 0,788.6201 -788.6201,0 0,716.9274 788.6201,0 0,788.6202 718.42104,0 0,-788.6202 787.126554,0 0,-716.9274 -787.126554,0 0,-788.6201 -718.42104,0 z"
+ id="path4608"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.01709132,0,0,0.01709132,42.964466,990.51997)" />
+ <path
+ style="fill:#91cfff;fill-opacity:1;stroke:none"
+ d="m 37.487494,1067.0361 0,788.6201 -788.620104,0 0,716.9274 788.620104,0 0,788.6202 718.421036,0 0,-788.6202 787.12657,0 0,-716.9274 -787.12657,0 0,-788.6201 -718.421036,0 z"
+ id="path4628"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.01709132,0,0,0.01709132,17.232671,990.51997)" />
+ </g>
+</svg>
diff --git a/files/opencs/scalable/status/base.svg b/files/opencs/scalable/status/base.svg
new file mode 100644
index 0000000000..90c9bc319a
--- /dev/null
+++ b/files/opencs/scalable/status/base.svg
@@ -0,0 +1,942 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="base.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="7.8146534"
+ inkscape:cy="27.892232"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3948"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-183.1468,-158.28118)" />
+ <linearGradient
+ id="linearGradient3942">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3944" />
+ <stop
+ style="stop-color:#343a30;stop-opacity:0;"
+ offset="1"
+ id="stop3946" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3846">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3848" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3850" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4012"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3892">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3894" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3896" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4014"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3901" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3903" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4018"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3888"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3920">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3922" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3924" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3876"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3886"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3934">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3936" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3938" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3941">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3943" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3945" />
+ </linearGradient>
+ <radialGradient
+ r="149.18433"
+ fy="260.15875"
+ fx="72.191498"
+ cy="260.15875"
+ cx="72.191498"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3964"
+ xlink:href="#linearGradient3846"
+ inkscape:collect="always" />
+ <filter
+ inkscape:collect="always"
+ id="filter4622">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="57.354192"
+ id="feGaussianBlur4624" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3133-9"
+ id="radialGradient6149"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.59728002,0,0,0.40433765,4.9162097,-18.482133)"
+ cx="64"
+ cy="35.686314"
+ fx="64"
+ fy="35.686314"
+ r="40" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3133-9">
+ <stop
+ style="stop-color:#646661;stop-opacity:1"
+ offset="0"
+ id="stop3135-5" />
+ <stop
+ style="stop-color:#111111;stop-opacity:1"
+ offset="1"
+ id="stop3137-0" />
+ </linearGradient>
+ <filter
+ inkscape:collect="always"
+ id="filter3925">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="1.3078125"
+ id="feGaussianBlur3927" />
+ </filter>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g6138"
+ transform="translate(3.5529905,1004.1857)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter3925)"
+ d="m 3.15625,1004.3622 0,7.4687 0,37.4063 0,7.4375 7.46875,0 37.375,0 7.46875,0 0,-44.8438 0,-7.4687 -7.46875,0 -37.375,0 -7.46875,0 z m 7.46875,7.4687 37.375,0 0,37.4063 -37.375,0 0,-37.4063 z"
+ id="rect3911"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.76654432,0,0,0.76654432,1.5416655,238.43513)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3929"
+ d="m 3.9610709,1008.3233 0,5.7251 0,28.6736 0,5.7011 5.7251276,0 28.6495945,0 5.725129,0 0,-34.3747 0,-5.7251 -5.725129,0 -28.6495945,0 -5.7251276,0 z m 5.7251276,5.7251 28.6495945,0 0,28.6736 -28.6495945,0 0,-28.6736 z"
+ style="fill:none;stroke:#000000;stroke-width:3.06617737;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ style="fill:#b4b4b4;fill-opacity:1;stroke:none"
+ d="m 3.9610709,1008.3233 0,5.7251 0,28.6736 0,5.7011 5.7251276,0 28.6495945,0 5.725129,0 0,-34.3747 0,-5.7251 -5.725129,0 -28.6495945,0 -5.7251276,0 z m 5.7251276,5.7251 28.6495945,0 0,28.6736 -28.6495945,0 0,-28.6736 z"
+ id="path3931"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/files/opencs/scalable/status/modified.svg b/files/opencs/scalable/status/modified.svg
new file mode 100644
index 0000000000..8d2ec47105
--- /dev/null
+++ b/files/opencs/scalable/status/modified.svg
@@ -0,0 +1,1155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="modified.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.12"
+ inkscape:cx="-14.949424"
+ inkscape:cy="44.402567"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5966"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true"
+ inkscape:snap-midpoints="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3988">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3990" />
+ <stop
+ style="stop-color:#e1e1e1;stop-opacity:0;"
+ offset="1"
+ id="stop3992" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient5606"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient5608"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient5610"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient5612"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5614"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5616"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient5618"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20" />
+ <linearGradient
+ id="linearGradient4676">
+ <stop
+ style="stop-color:#d07200;stop-opacity:1;"
+ offset="0"
+ id="stop4678" />
+ <stop
+ style="stop-color:#603500;stop-opacity:1;"
+ offset="1"
+ id="stop4680" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4691">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4693" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:0;"
+ offset="1"
+ id="stop4695" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4713">
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="0"
+ id="stop4715" />
+ <stop
+ id="stop4721"
+ offset="0.5"
+ style="stop-color:#707070;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4717" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4723">
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="0"
+ id="stop4725" />
+ <stop
+ style="stop-color:#cccccc;stop-opacity:1;"
+ offset="1"
+ id="stop4727" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4653">
+ <stop
+ style="stop-color:#fa00c8;stop-opacity:1;"
+ offset="0"
+ id="stop4655" />
+ <stop
+ style="stop-color:#aa0088;stop-opacity:1;"
+ offset="1"
+ id="stop4657" />
+ </linearGradient>
+ <filter
+ inkscape:collect="always"
+ id="filter7327">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="2.2557222"
+ id="feGaussianBlur7329" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3133-9"
+ id="radialGradient6149"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.59728002,0,0,0.40433765,4.9162097,-18.482133)"
+ cx="64"
+ cy="35.686314"
+ fx="64"
+ fy="35.686314"
+ r="40" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3133-9">
+ <stop
+ style="stop-color:#646661;stop-opacity:1"
+ offset="0"
+ id="stop3135-5" />
+ <stop
+ style="stop-color:#111111;stop-opacity:1"
+ offset="1"
+ id="stop3137-0" />
+ </linearGradient>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <path
+ sodipodi:type="star"
+ style="fill:none;stroke:#000000;stroke-width:9.83509541;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter7327)"
+ id="path6813"
+ sodipodi:sides="5"
+ sodipodi:cx="0.5859375"
+ sodipodi:cy="12.257812"
+ sodipodi:r1="23.003752"
+ sodipodi:r2="48.618244"
+ sodipodi:arg1="0.31518269"
+ sodipodi:arg2="0.94350122"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 22.456519,19.388748 29.122755,51.620023 0.5623949,35.261551 -28.031389,51.56153 -21.299194,19.343968 -45.63736,-2.8133661 -12.916275,-6.3664428 0.63569457,-36.360407 14.126243,-6.3387668 46.839987,-2.7187225 z"
+ transform="matrix(0.41597462,0,0,0.41597462,-78.677638,969.79151)"
+ inkscape:transform-center-x="-0.41181632"
+ inkscape:transform-center-y="0.42083913" />
+ <path
+ inkscape:transform-center-y="0.42083913"
+ inkscape:transform-center-x="-0.41181632"
+ transform="matrix(0.41597462,0,0,0.41597462,-78.677638,969.79151)"
+ d="M 22.456519,19.388748 29.122755,51.620023 0.5623949,35.261551 -28.031389,51.56153 -21.299194,19.343968 -45.63736,-2.8133661 -12.916275,-6.3664428 0.63569457,-36.360407 14.126243,-6.3387668 46.839987,-2.7187225 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="0.94350122"
+ sodipodi:arg1="0.31518269"
+ sodipodi:r2="48.618244"
+ sodipodi:r1="23.003752"
+ sodipodi:cy="12.257812"
+ sodipodi:cx="0.5859375"
+ sodipodi:sides="5"
+ id="path7331"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:9.83509541;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="star" />
+ <path
+ sodipodi:type="star"
+ style="fill:#ffe791;fill-opacity:1;stroke:none"
+ id="path7333"
+ sodipodi:sides="5"
+ sodipodi:cx="0.5859375"
+ sodipodi:cy="12.257812"
+ sodipodi:r1="23.003752"
+ sodipodi:r2="48.618244"
+ sodipodi:arg1="0.31518269"
+ sodipodi:arg2="0.94350122"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 22.456519,19.388748 29.122755,51.620023 0.5623949,35.261551 -28.031389,51.56153 -21.299194,19.343968 -45.63736,-2.8133661 -12.916275,-6.3664428 0.63569457,-36.360407 14.126243,-6.3387668 46.839987,-2.7187225 z"
+ transform="matrix(0.41597462,0,0,0.41597462,-78.677638,969.79151)"
+ inkscape:transform-center-x="-0.41181632"
+ inkscape:transform-center-y="0.42083913" />
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/status/removed.svg b/files/opencs/scalable/status/removed.svg
new file mode 100644
index 0000000000..c879af91f2
--- /dev/null
+++ b/files/opencs/scalable/status/removed.svg
@@ -0,0 +1,935 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="removed.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="-52.969004"
+ inkscape:cy="-36.7439"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="1001"
+ inkscape:window-x="0"
+ inkscape:window-y="23"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="true"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true" />
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3902"
+ inkscape:collect="always">
+ <stop
+ id="stop3904"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ id="stop3906"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3886"
+ inkscape:collect="always">
+ <stop
+ id="stop3888"
+ offset="0"
+ style="stop-color:#fff226;stop-opacity:1;" />
+ <stop
+ id="stop3890"
+ offset="1"
+ style="stop-color:#fff226;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9248">
+ <stop
+ id="stop9250"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop9252"
+ offset="1"
+ style="stop-color:#b4b4b4;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient8521">
+ <stop
+ id="stop8523"
+ offset="0"
+ style="stop-color:#00105d;stop-opacity:1;" />
+ <stop
+ id="stop8525"
+ offset="1"
+ style="stop-color:#00105d;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7487">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop7489" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop7491" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6034">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop6036" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop6038" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <linearGradient
+ y2="350.56357"
+ x2="518.24652"
+ y1="536.11566"
+ x1="553.62225"
+ gradientTransform="translate(-26.263966,56.568543)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4078-954"
+ xlink:href="#linearGradient3955-87-471"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3955-87-471">
+ <stop
+ style="stop-color:#436123;stop-opacity:1"
+ offset="0"
+ id="stop4122" />
+ <stop
+ id="stop4124"
+ offset="0.04243463"
+ style="stop-color:#74984d;stop-opacity:1;" />
+ <stop
+ style="stop-color:#74984d;stop-opacity:1"
+ offset="1"
+ id="stop4126" />
+ </linearGradient>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="2"
+ height="1"
+ patternTransform="matrix(0,4.4721359,-4.4721359,0,-50.004131,-3.0322266e-6)"
+ id="Strips1_1"
+ inkscape:stockid="Stripes 1:1">
+ <rect
+ style="fill:black;stroke:none"
+ x="0"
+ y="-0.5"
+ width="1"
+ height="2"
+ id="rect3917" />
+ </pattern>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6034"
+ id="linearGradient6042"
+ gradientUnits="userSpaceOnUse"
+ x1="-2256.6802"
+ y1="1067.036"
+ x2="37.487514"
+ y2="2532.4438"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ y2="2444.7776"
+ x2="-2151.6707"
+ y1="2903.8035"
+ x1="-2148.2864"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient8509"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="3353.4497"
+ x2="-1962.6486"
+ y1="2540.8635"
+ x1="-1962.6486"
+ id="linearGradient8527"
+ xlink:href="#linearGradient7487"
+ inkscape:collect="always" />
+ <linearGradient
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ y2="2914.2673"
+ x2="-115.04873"
+ y1="2899.3862"
+ x1="-115.04873"
+ id="linearGradient9254"
+ xlink:href="#linearGradient9248"
+ inkscape:collect="always"
+ gradientTransform="translate(-5.8907179,205.15612)" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.60717495,0,6.6355278)"
+ r="3.1054714"
+ fy="16.891813"
+ fx="29.111721"
+ cy="16.891813"
+ cx="29.111721"
+ id="radialGradient3892"
+ xlink:href="#linearGradient3886"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.1494315,0.36121083,-0.30519899,1.8161266,2.9871553,-34.404392)"
+ r="15.46875"
+ fy="32.971859"
+ fx="13.599908"
+ cy="32.971859"
+ cx="13.599908"
+ id="radialGradient3908"
+ xlink:href="#linearGradient3902"
+ inkscape:collect="always" />
+ <inkscape:path-effect
+ fuse_tolerance="0"
+ vertical_pattern="false"
+ prop_units="false"
+ tang_offset="0"
+ normal_offset="0"
+ spacing="0"
+ scale_y_rel="false"
+ prop_scale="1"
+ copytype="single_stretched"
+ pattern="m 1273.479,-26681.071 0,10 10,-5 z"
+ is_visible="true"
+ id="path-effect4754"
+ effect="skeletal" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect4760"
+ is_visible="true"
+ pattern="m 1315.479,-26621.071 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <inkscape:path-effect
+ effect="skeletal"
+ id="path-effect2991"
+ is_visible="true"
+ pattern="m 595.66194,-26736.611 0,10 10,-5 z"
+ copytype="single_stretched"
+ prop_scale="1"
+ scale_y_rel="false"
+ spacing="0"
+ normal_offset="0"
+ tang_offset="0"
+ prop_units="false"
+ vertical_pattern="false"
+ fuse_tolerance="0" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3942"
+ id="linearGradient3948"
+ x1="74.006111"
+ y1="153.75172"
+ x2="344.5"
+ y2="330.63455"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-183.1468,-158.28118)" />
+ <linearGradient
+ id="linearGradient3942">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3944" />
+ <stop
+ style="stop-color:#343a30;stop-opacity:0;"
+ offset="1"
+ id="stop3946" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4010"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3846">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3848" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3850" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4012"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3892">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3894" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3896" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4014"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3901" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3903" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient4016"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857" />
+ <linearGradient
+ id="linearGradient3906">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3908" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3910" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient4018"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3888"
+ cx="237.35278"
+ cy="147.22479"
+ fx="237.35278"
+ fy="147.22479"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00345228)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3920">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3922" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3924" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3876"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,205.85223,33.676924)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3927">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3929" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3931" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3880"
+ id="radialGradient3886"
+ cx="83.637177"
+ cy="260.86032"
+ fx="83.637177"
+ fy="260.86032"
+ r="107.16857"
+ gradientTransform="matrix(1,0,0,1.0000234,0,-0.00611206)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3934">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3936" />
+ <stop
+ style="stop-color:#d6dbd3;stop-opacity:1;"
+ offset="1"
+ id="stop3938" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3846"
+ id="radialGradient3878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ cx="72.191498"
+ cy="260.15875"
+ fx="72.191498"
+ fy="260.15875"
+ r="149.18433" />
+ <linearGradient
+ id="linearGradient3941">
+ <stop
+ style="stop-color:#222620;stop-opacity:1;"
+ offset="0"
+ id="stop3943" />
+ <stop
+ style="stop-color:#9da795;stop-opacity:1;"
+ offset="1"
+ id="stop3945" />
+ </linearGradient>
+ <radialGradient
+ r="149.18433"
+ fy="260.15875"
+ fx="72.191498"
+ cy="260.15875"
+ cx="72.191498"
+ gradientTransform="matrix(0.43641683,0,0,0.43645606,52.136611,147.31246)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3964"
+ xlink:href="#linearGradient3846"
+ inkscape:collect="always" />
+ <filter
+ inkscape:collect="always"
+ id="filter4622">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="57.354192"
+ id="feGaussianBlur4624" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3133-9"
+ id="radialGradient6149"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.59728002,0,0,0.40433765,4.9162097,-18.482133)"
+ cx="64"
+ cy="35.686314"
+ fx="64"
+ fy="35.686314"
+ r="40" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3133-9">
+ <stop
+ style="stop-color:#646661;stop-opacity:1"
+ offset="0"
+ id="stop3135-5" />
+ <stop
+ style="stop-color:#111111;stop-opacity:1"
+ offset="1"
+ id="stop3137-0" />
+ </linearGradient>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g6138"
+ transform="translate(3.5529905,1004.1857)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:234.03692627;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter4622)"
+ d="m -1468.0601,1067.0361 0,788.6201 -788.6201,0 0,716.9274 788.6201,0 0,788.6202 718.42104,0 0,-788.6202 787.126554,0 0,-716.9274 -787.126554,0 0,-788.6201 -718.42104,0 z"
+ id="path4608"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.01208539,-0.01208539,0.01208539,0.01208539,10.642377,988.20282)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4626"
+ d="m 5.7958312,1018.8404 9.5308098,9.5308 -9.5307801,9.5308 8.6643921,8.6644 9.530779,-9.5308 9.530739,9.5307 8.682398,-8.6824 -9.530739,-9.5307 9.51273,-9.5127 -8.664392,-8.6644 -9.512729,9.5127 -9.53081,-9.5308 -8.6823978,8.6824 z"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ style="fill:#ff9191;fill-opacity:1;stroke:none"
+ d="m 37.487494,1067.0361 0,788.6201 -788.620104,0 0,716.9274 788.620104,0 0,788.6202 718.421036,0 0,-788.6202 787.12657,0 0,-716.9274 -787.12657,0 0,-788.6201 -718.421036,0 z"
+ id="path4628"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.01208539,-0.01208539,0.01208539,0.01208539,-7.5527533,1006.3979)" />
+ </g>
+</svg>
diff --git a/files/opencs/scalable/top-level/gmst.svg b/files/opencs/scalable/top-level/gmst.svg
new file mode 100644
index 0000000000..3b59a44fe9
--- /dev/null
+++ b/files/opencs/scalable/top-level/gmst.svg
@@ -0,0 +1,1047 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="gmst.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.1200001"
+ inkscape:cx="-0.29452"
+ inkscape:cy="17.486894"
+ inkscape:document-units="px"
+ inkscape:current-layer="text4529"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="997"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="false"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="false"
+ inkscape:snap-global="true"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3913">
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="0"
+ id="stop3915" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop3917" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3985">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3987" />
+ <stop
+ id="stop3993"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.5"
+ offset="1"
+ id="stop3989" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3954">
+ <stop
+ style="stop-color:#b89f9f;stop-opacity:1;"
+ offset="0"
+ id="stop3956" />
+ <stop
+ style="stop-color:#443232;stop-opacity:1;"
+ offset="1"
+ id="stop3958" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3944">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3946" />
+ <stop
+ style="stop-color:#b49797;stop-opacity:1;"
+ offset="1"
+ id="stop3948" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3918">
+ <stop
+ style="stop-color:#737373;stop-opacity:1;"
+ offset="0"
+ id="stop3920" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3922" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#493535;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3913"
+ id="linearGradient3919"
+ x1="-1.2203984e-08"
+ y1="1.4275813e-06"
+ x2="48"
+ y2="48"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3913"
+ id="linearGradient4552"
+ gradientUnits="userSpaceOnUse"
+ x1="-1.2203984e-08"
+ y1="1.4275813e-06"
+ x2="48"
+ y2="48" />
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ id="g5597"
+ transform="matrix(0.85483505,0,0,1,-20.035433,-2.2155805e-6)">
+ <g
+ id="g4544"
+ transform="matrix(0.97959175,0,0,0.97959175,-1.3940485,19.856516)">
+ <rect
+ ry="3.7288833"
+ transform="matrix(1.1964735,0,0,1.0227875,-97.023365,948.41842)"
+ y="0"
+ x="0"
+ height="48"
+ width="48"
+ id="rect3135"
+ style="fill:url(#linearGradient4552);fill-opacity:1;stroke:#232323;stroke-opacity:1" />
+ <g
+ id="text4529"
+ style="font-size:40.12634659px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#232323;fill-opacity:1;stroke:none;font-family:Inconsolata;-inkscape-font-specification:Inconsolata"
+ transform="matrix(1.3895604,0,0,0.58291816,-6.8307996,97.296531)">
+ <g
+ id="g4574"
+ transform="matrix(0.95,0,0,0.95,-2.212112,74.758303)">
+ <path
+ d="m -45.907434,1469.279 -1.966191,2.7686 c -0.160522,-0.1476 -0.334403,-0.4982 -0.521643,-1.052 -0.401279,-1.2184 -1.029924,-2.1966 -1.885938,-2.9348 -0.829291,-0.7754 -1.805698,-1.163 -2.929224,-1.163 -0.775786,0 -1.578312,0.2214 -2.40758,0.6645 -0.802535,0.443 -1.538184,1.1998 -2.206949,2.2704 -0.668779,1.0705 -1.217171,2.4918 -1.645181,4.2637 -0.401267,1.7352 -0.601899,3.9317 -0.601895,6.5897 -4e-6,2.3995 0.173877,4.5222 0.521643,6.368 0.347756,1.8459 0.829272,3.3963 1.444548,4.6513 0.615264,1.2184 1.337538,2.1598 2.166823,2.8242 0.856019,0.6275 1.778924,0.9413 2.768718,0.9413 1.738795,0 3.317097,-0.7013 4.734909,-2.1042 l 0,-8.1954 -4.534277,0 0,-3.3777 7.142489,0 0,13.5112 c -2.380846,2.3995 -4.82855,3.5993 -7.343121,3.5993 -1.49806,0 -2.848979,-0.406 -4.052761,-1.2181 -1.177046,-0.8491 -2.193579,-2.0304 -3.049603,-3.544 -0.829281,-1.5135 -1.471301,-3.341 -1.926064,-5.4821 -0.428016,-2.1411 -0.642023,-4.5222 -0.642022,-7.1432 -10e-7,-3.6179 0.321009,-6.645 0.963033,-9.0814 0.668769,-2.4366 1.524797,-4.2823 2.568086,-5.5375 1.043279,-1.292 2.099938,-2.1781 3.169981,-2.6579 1.096778,-0.48 2.08656,-0.72 2.96935,-0.72 1.524788,0 2.942584,0.5354 4.253393,1.606 1.310777,1.0336 2.313935,2.418 3.009476,4.1531"
+ id="path4536"
+ inkscape:connector-curvature="0"
+ style="fill:#232323;fill-opacity:1" />
+ <path
+ d="m -42.576948,1463.8522 2.206949,0 6.179458,16.8892 6.299836,-16.9446 2.126697,0 0,34.5536 -2.648339,0 0,-26.0259 -5.417057,13.899 -1.083412,0 -5.055919,-13.7328 0,25.8597 -2.608213,0 0,-34.4982"
+ id="path4538"
+ inkscape:connector-curvature="0"
+ style="fill:#232323;fill-opacity:1" />
+ <path
+ d="m -46.83034,1510.6992 -1.76556,3.3223 c -0.13377,-0.073 -0.214022,-0.2212 -0.240758,-0.4429 -1.5e-5,-0.2583 -0.02677,-0.443 -0.08025,-0.5537 -0.508282,-1.0335 -1.203805,-1.8642 -2.08657,-2.4918 -0.856042,-0.6277 -1.926076,-0.9414 -3.210108,-0.9415 -1.23055,10e-5 -2.260459,0.4985 -3.089729,1.4951 -0.802533,0.9968 -1.203796,2.1782 -1.20379,3.544 -6e-6,0.8122 0.09362,1.5136 0.280884,2.1043 0.214001,0.5907 0.521636,1.1629 0.922906,1.7166 0.428007,0.5537 1.043277,1.1261 1.845812,1.7167 0.802517,0.5536 1.845801,1.1813 3.129855,1.8827 1.230528,0.7013 2.247061,1.4028 3.049602,2.1042 0.802512,0.6644 1.444532,1.3843 1.926065,2.1596 0.481499,0.7752 0.815885,1.6059 1.003159,2.4918 0.213989,0.849 0.320992,1.8089 0.32101,2.8794 -1.8e-5,1.1814 -0.160523,2.3812 -0.481516,3.5994 -0.321028,1.1813 -0.829294,2.2519 -1.524801,3.2117 -0.668788,0.9229 -1.524816,1.6796 -2.568086,2.2704 -1.043298,0.5906 -2.300589,0.886 -3.771877,0.886 -3.236865,0 -5.845075,-1.4768 -7.824637,-4.43 l 1.64518,-3.987 c 0.133751,0.1104 0.200628,0.2954 0.200632,0.5537 -4e-6,0.2585 0.04012,0.4615 0.120379,0.6091 0.615266,1.0707 1.444543,1.9751 2.487833,2.7133 1.043278,0.7016 2.300569,1.0522 3.771877,1.0522 0.749014,0 1.431161,-0.1662 2.046444,-0.4984 0.642008,-0.3691 1.190401,-0.8305 1.64518,-1.3843 0.454751,-0.5906 0.815887,-1.2737 1.083411,-2.0487 0.267494,-0.8123 0.401248,-1.6613 0.401264,-2.5474 -1.6e-5,-0.7013 -0.09364,-1.329 -0.280885,-1.8828 -0.16052,-0.5906 -0.441404,-1.1257 -0.842653,-1.6057 -0.401278,-0.5169 -0.976421,-1.0153 -1.725433,-1.4951 -0.722286,-0.5168 -1.685318,-1.1075 -2.889097,-1.772 -1.310802,-0.7014 -2.407588,-1.4028 -3.29036,-2.1042 -0.856035,-0.7384 -1.551557,-1.4766 -2.08657,-2.215 -0.535022,-0.7382 -0.92291,-1.532 -1.163664,-2.3812 -0.214011,-0.849 -0.321014,-1.7719 -0.321011,-2.7687 -3e-6,-1.2551 0.187253,-2.4364 0.561769,-3.544 0.401259,-1.1075 0.936277,-2.0672 1.605053,-2.8794 0.695518,-0.8491 1.51142,-1.495 2.447708,-1.9381 0.936273,-0.4799 1.93943,-0.7199 3.009476,-0.7199 1.364284,0 2.648325,0.3878 3.852129,1.163 1.203775,0.7383 2.233683,1.7903 3.089729,3.1563"
+ id="path4540"
+ inkscape:connector-curvature="0"
+ style="fill:#232323;fill-opacity:1" />
+ <path
+ d="m -42.978211,1506.5461 17.334582,0 0,3.4885 -7.383248,0 0,31.065 -2.848971,0 0,-31.065 -7.102363,0 0,-3.4885"
+ id="path4542"
+ inkscape:connector-curvature="0"
+ style="fill:#232323;fill-opacity:1" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/scalable/top-level/topic-regular.svg b/files/opencs/scalable/top-level/topic-regular.svg
new file mode 100644
index 0000000000..c972dfa18c
--- /dev/null
+++ b/files/opencs/scalable/top-level/topic-regular.svg
@@ -0,0 +1,1045 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="topic-regular.svg">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.6203868"
+ inkscape:cx="26.14041"
+ inkscape:cy="-2.627844"
+ inkscape:document-units="px"
+ inkscape:current-layer="g5462"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1280"
+ inkscape:window-height="997"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:snap-page="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:object-nodes="false"
+ inkscape:snap-global="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-grids="false"
+ inkscape:snap-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3225" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3985">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3987" />
+ <stop
+ id="stop3993"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:0;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.5"
+ offset="1"
+ id="stop3989" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3954">
+ <stop
+ style="stop-color:#b89f9f;stop-opacity:1;"
+ offset="0"
+ id="stop3956" />
+ <stop
+ style="stop-color:#443232;stop-opacity:1;"
+ offset="1"
+ id="stop3958" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3944">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3946" />
+ <stop
+ style="stop-color:#b49797;stop-opacity:1;"
+ offset="1"
+ id="stop3948" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3918">
+ <stop
+ style="stop-color:#737373;stop-opacity:1;"
+ offset="0"
+ id="stop3920" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3922" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4480">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop4482" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4484" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3882">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3884" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3886" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3939">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.784"
+ offset="0"
+ id="stop3941" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3943" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#b4b4b4;stop-opacity:0.72549021;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3867">
+ <stop
+ style="stop-color:#5a0048;stop-opacity:1;"
+ offset="0"
+ id="stop3869" />
+ <stop
+ style="stop-color:#960078;stop-opacity:1;"
+ offset="1"
+ id="stop3871" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3853">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="0"
+ id="stop3855" />
+ <stop
+ id="stop3861"
+ offset="0.33690071"
+ style="stop-color:#caf8db;stop-opacity:0.58823532;" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0.39215687;"
+ offset="1"
+ id="stop3857" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3845">
+ <stop
+ style="stop-color:#493535;stop-opacity:1;"
+ offset="0"
+ id="stop3847" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop3849" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3837">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.78431374;"
+ offset="0"
+ id="stop3839" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop3841" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5958">
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:1;"
+ offset="0"
+ id="stop5960" />
+ <stop
+ style="stop-color:#caf8f6;stop-opacity:0;"
+ offset="1"
+ id="stop5962" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5922">
+ <stop
+ style="stop-color:#c84900;stop-opacity:1;"
+ offset="0"
+ id="stop5924" />
+ <stop
+ style="stop-color:#c84900;stop-opacity:0.19607843;"
+ offset="1"
+ id="stop5926" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6815">
+ <stop
+ id="stop6817"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825" />
+ <stop
+ id="stop6819"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4022">
+ <stop
+ id="stop4024"
+ offset="0"
+ style="stop-color:#204a87;stop-opacity:1;" />
+ <stop
+ id="stop4026"
+ offset="1"
+ style="stop-color:#729fcf;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3723">
+ <stop
+ id="stop3725"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3727"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3715">
+ <stop
+ id="stop3717"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3707">
+ <stop
+ id="stop3709"
+ offset="0"
+ style="stop-color:#a6c4d9;stop-opacity:0.78431374;" />
+ <stop
+ id="stop3711"
+ offset="1"
+ style="stop-color:#75a3c3;stop-opacity:1;" />
+ </linearGradient>
+ <inkscape:perspective
+ id="perspective10"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective2920" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ id="linearGradient3713"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ id="linearGradient3721"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ id="linearGradient3729"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="translate(0.2341196,6.0908086)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3735"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3737"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="translate(0.2341189,5.74082)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3739"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3742"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3744"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3747"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="983.41931"
+ x2="63.765881"
+ y1="936.95227"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965567,-0.75665045)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3753"
+ xlink:href="#linearGradient3715"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1029.8864"
+ x2="63.765881"
+ y1="949.3266"
+ x1="63.765881"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3755"
+ xlink:href="#linearGradient3707"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="1030.7611"
+ x2="37.375645"
+ y1="948.8266"
+ x1="37.375645"
+ gradientTransform="matrix(1.0066488,0,0,1.0066488,0.21965498,-1.1089658)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3757"
+ xlink:href="#linearGradient3723"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.91716429,0,0,0.91716429,2.2512556,85.18512)"
+ gradientUnits="userSpaceOnUse"
+ y2="1008.9376"
+ x2="47.902649"
+ y1="1048.3364"
+ x1="14.991861"
+ id="linearGradient4028"
+ xlink:href="#linearGradient4022"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3818"
+ id="linearGradient5433"
+ x1="-14.939182"
+ y1="166.73387"
+ x2="-15.495684"
+ y2="164.65698"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.71906912,0,0,0.71906912,35.096859,924.87424)" />
+ <linearGradient
+ id="linearGradient3818">
+ <stop
+ style="stop-color:#1e1e1e;stop-opacity:1;"
+ offset="0"
+ id="stop3820" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3822" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="123.88583"
+ fx="-171.01654"
+ cy="123.88583"
+ cx="-171.01654"
+ gradientTransform="matrix(0.23290796,0.98292589,-1.5211573,0.36044393,244.44953,1091.8596)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,227.36718,1219.3395)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641"
+ xlink:href="#linearGradient5299"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,240.54684,45.0811)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient6812"
+ xlink:href="#linearGradient3715-2"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823"
+ xlink:href="#linearGradient6815"
+ inkscape:collect="always"
+ gradientTransform="translate(8.4245144,951.32919)" />
+ <linearGradient
+ y2="965.56183"
+ x2="63.765881"
+ y1="941.44623"
+ x1="63.765881"
+ gradientTransform="matrix(0.37749331,0,0,0.37749331,-0.0711918,657.55701)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2843"
+ xlink:href="#linearGradient3715-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-6">
+ <stop
+ id="stop3717-6"
+ offset="0"
+ style="stop-color:#99bbd4;stop-opacity:1;" />
+ <stop
+ id="stop3719-4"
+ offset="1"
+ style="stop-color:#5d93ba;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3184"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.37749331,0,0,0.37749331,4.1838142,536.26868)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3715-6"
+ id="linearGradient3321"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.28231332,0,0,0.28231332,64.982195,664.15172)"
+ x1="63.765881"
+ y1="941.44623"
+ x2="63.765881"
+ y2="965.56183" />
+ <linearGradient
+ gradientTransform="translate(-82.281067,1085.8147)"
+ gradientUnits="userSpaceOnUse"
+ y2="37.613949"
+ x2="26.643335"
+ y1="39.630547"
+ x1="27.183681"
+ id="linearGradient6823-6"
+ xlink:href="#linearGradient6815-6"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient6815-6">
+ <stop
+ id="stop6817-4"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#3c3c3c;stop-opacity:0.58823532;"
+ offset="0.40229002"
+ id="stop6825-9" />
+ <stop
+ id="stop6819-5"
+ offset="1"
+ style="stop-color:#3c3c3c;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="29.000444"
+ fy="119.59179"
+ fx="-172.875"
+ cy="119.59179"
+ cx="-172.875"
+ gradientTransform="matrix(1.0541003,0.65674043,-0.42405323,0.68062603,158.26577,1130.8958)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5639-0"
+ xlink:href="#linearGradient3715-2-4"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3715-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3717-4-8" />
+ <stop
+ style="stop-color:#b3bbad;stop-opacity:1;"
+ offset="1"
+ id="stop3719-9-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,218.94267,1272.3725)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient5641-1"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient5299-7">
+ <stop
+ style="stop-color:#000e50;stop-opacity:1;"
+ offset="0"
+ id="stop5301-2" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop5303-7" />
+ </linearGradient>
+ <radialGradient
+ r="28.421875"
+ fy="116.19179"
+ fx="-182.4375"
+ cy="116.19179"
+ cx="-182.4375"
+ gradientTransform="matrix(0.30667246,1.5835715,-1.396495,0.27044345,136.6616,1353.825)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3063"
+ xlink:href="#linearGradient5299-7"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5928"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5958"
+ id="radialGradient5964"
+ cx="-103.70541"
+ cy="-39.275696"
+ fx="-103.70541"
+ fy="-39.275696"
+ r="20.15"
+ gradientTransform="matrix(0.61309318,1.698523,-1.2180273,0.43965501,-77.239822,152.43381)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5922"
+ id="linearGradient5973"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect"
+ x1="-72.843758"
+ y1="-45.656265"
+ x2="-72.843758"
+ y2="-45.944225"
+ gradientTransform="matrix(0.91762814,0,0,1.0003875,-5.0078269,1004.2961)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3843"
+ cx="-97.089668"
+ cy="-33.913769"
+ fx="-97.089668"
+ fy="-33.913769"
+ r="20"
+ gradientTransform="matrix(-0.03337852,1.2984223,-1.7840914,0.11666088,-155.09309,1098.8832)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3867"
+ id="radialGradient3851"
+ cx="34.5625"
+ cy="54.418995"
+ fx="34.5625"
+ fy="54.418995"
+ r="13.49375"
+ gradientTransform="matrix(1.3458814,-1.3743665,1.2334026,1.233403,-192.05563,977.88376)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3853"
+ id="linearGradient3859"
+ x1="-140.89291"
+ y1="1017.7004"
+ x2="-104.17097"
+ y2="1017.9076"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(44.104424,-40.169184)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3837"
+ id="radialGradient3865"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.00108712,0.18221846,-0.82985011,4.6460161e-7,-113.08325,972.23283)"
+ cx="-116.29909"
+ cy="-37.504356"
+ fx="-116.29909"
+ fy="-37.504356"
+ r="20" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3875"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3885"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3889"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3893"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3897"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3901"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3905"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3909"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3913"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3917"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3921"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3939"
+ id="linearGradient3945"
+ x1="-79.476425"
+ y1="982.83502"
+ x2="-79.476425"
+ y2="996.9176"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.20586672,1.984673e-5)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3947"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3949"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3951"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3953"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3955"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3957"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3959"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3961"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3963"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879"
+ id="radialGradient3965"
+ gradientUnits="userSpaceOnUse"
+ cx="30.273438"
+ cy="53.175781"
+ fx="30.273438"
+ fy="53.175781"
+ r="3.4179688" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3882"
+ id="radialGradient3888"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="19.749271"
+ gradientTransform="matrix(0.13602194,8.9478727e-7,-2.3305974e-7,0.03057111,-132.08862,-21.48071)"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4480"
+ id="linearGradient4486"
+ x1="-152.88422"
+ y1="-17.455395"
+ x2="-152.88422"
+ y2="-41.022785"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3944"
+ id="radialGradient3942"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.92976036,0,0,2.6869958,-56.971163,888.30913)"
+ cx="-15.5"
+ cy="30.53125"
+ fx="-15.5"
+ fy="30.53125"
+ r="15.34375" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3944"
+ id="radialGradient3952"
+ cx="-152.88422"
+ cy="-22.158251"
+ fx="-152.88422"
+ fy="-22.158251"
+ r="20.249271"
+ gradientTransform="matrix(1,0,0,0.24294293,0,-16.77506)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3954"
+ id="linearGradient3960"
+ x1="-126.17325"
+ y1="953.44806"
+ x2="-137.5746"
+ y2="991.67816"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.98345881,0,0,1.0246049,42.42393,-31.855387)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3845"
+ id="radialGradient3962"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.99999984,-2.6136065e-7,1.3555931e-7,0.44755236,-2.2357346,-2.665094)"
+ cx="-150.6485"
+ cy="-33.680485"
+ fx="-150.6485"
+ fy="-33.680485"
+ r="19.749271" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3985"
+ id="linearGradient3991"
+ x1="-102.68832"
+ y1="973.31067"
+ x2="-78.542328"
+ y2="980.34857"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ inkscape:collect="always"
+ id="filter5454">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="1.203242"
+ id="feGaussianBlur5456" />
+ </filter>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1004.3622)"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Livello 1">
+ <g
+ id="g5966"
+ transform="matrix(0.97772023,0,0,0.97772023,100.68016,77.074325)">
+ <g
+ id="g5597"
+ transform="matrix(0.85483505,0,0,1,-20.035433,-2.2155805e-6)">
+ <g
+ id="g5462"
+ transform="matrix(0.91879529,0,0,1.0126101,-3.441935,-11.987314)">
+ <path
+ style="fill:#ffffff;fill-opacity:1;stroke:#232323;stroke-width:1.44010305;stroke-opacity:1"
+ d="m -70.365049,949.66885 c -16.528231,0 -29.910361,6.59004 -29.910361,14.70886 0,8.11883 13.38213,14.70887 29.910361,14.70887 2.950499,0 5.812493,-0.20895 8.505123,-0.59972 15.169731,15.37061 18.841494,16.72896 18.841494,16.72896 l -6.266933,-20.39039 c 5.486348,-2.66477 8.871373,-6.35834 8.871373,-10.44772 0,-8.11882 -13.422827,-14.70886 -29.951057,-14.70886 z"
+ id="path3136"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/files/opencs/static.png b/files/opencs/static.png
new file mode 100755
index 0000000000..b53be12d9a
--- /dev/null
+++ b/files/opencs/static.png
Binary files differ
diff --git a/files/opencs/weapon.png b/files/opencs/weapon.png
new file mode 100755
index 0000000000..3d4b534661
--- /dev/null
+++ b/files/opencs/weapon.png
Binary files differ
diff --git a/files/openmw.bmp b/files/openmw.bmp
new file mode 100644
index 0000000000..be3fd94ce7
--- /dev/null
+++ b/files/openmw.bmp
Binary files differ
diff --git a/files/openmw.cfg b/files/openmw.cfg
new file mode 100644
index 0000000000..15fde1bcb9
--- /dev/null
+++ b/files/openmw.cfg
@@ -0,0 +1,4 @@
+data="?global?data"
+data="?mw?Data Files"
+data-local="?user?data"
+resources=${MORROWIND_RESOURCE_FILES}
diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local
new file mode 100644
index 0000000000..dd116e1080
--- /dev/null
+++ b/files/openmw.cfg.local
@@ -0,0 +1,2 @@
+data=./data
+resources=./resources
diff --git a/files/openmw.desktop b/files/openmw.desktop
new file mode 100644
index 0000000000..118cd3bbe6
--- /dev/null
+++ b/files/openmw.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Name=OpenMW Launcher
+GenericName=Role Playing Game
+Comment=An engine replacement for The Elder Scrolls III: Morrowind
+TryExec=omwlauncher
+Exec=omwlauncher
+Icon=openmw
+Categories=Game;RolePlaying;
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
new file mode 100644
index 0000000000..f191430df1
--- /dev/null
+++ b/files/settings-default.cfg
@@ -0,0 +1,171 @@
+# WARNING: Editing this file might have no effect, as these
+# settings are overwritten by your user settings file.
+
+[Video]
+resolution x = 800
+resolution y = 600
+
+fullscreen = false
+screen = 0
+
+# Render system
+# blank means default
+# Valid values:
+# OpenGL Rendering Subsystem
+# Direct3D9 Rendering Subsystem
+render system =
+
+# Valid values:
+# none
+# MSAA 2
+# MSAA 4
+# MSAA 8
+# MSAA 16
+antialiasing = none
+
+vsync = false
+
+# opengl render to texture mode, valid options:
+# PBuffer, FBO, Copy
+opengl rtt mode = FBO
+
+[GUI]
+# 1 is fully opaque
+menu transparency = 0.84
+
+# 0 - instantly, 1 - max. delay
+tooltip delay = 0
+
+subtitles = false
+
+[General]
+# Camera field of view
+field of view = 55
+
+# Texture filtering mode. valid values:
+# none
+# anisotropic
+# bilinear
+# trilinear
+texture filtering = anisotropic
+
+# Has no effect when texture filtering is not 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
+
+shader mode =
+
+[Shadows]
+# Shadows are only supported when object shaders are on!
+enabled = false
+
+# Split the shadow maps, allows for a larger shadow distance
+# Warning: enabling this will cause some terrain textures to disappear due to
+# hitting the texture unit limit of the terrain material
+split = false
+
+# Increasing shadow distance will lower the shadow quality.
+# Uses "shadow distance" or "split shadow distance" depending on "split" setting.
+shadow distance = 1300
+# This one shouldn't be too low, otherwise you'll see artifacts. Use at least 2x max viewing distance.
+split shadow distance = 14000
+
+# Size of the shadow textures, higher means higher quality
+texture size = 1024
+
+# Turn on/off various shadow casters
+actor shadows = true
+misc shadows = true
+statics shadows = true
+terrain shadows = true
+
+# Fraction of the total shadow distance after which the shadow starts to fade out
+fade start = 0.8
+
+debug = false
+
+[HUD]
+# FPS counter
+# 0: not visible
+# 1: basic FPS display
+# 2: advanced FPS display (batches, triangles)
+fps = 0
+
+crosshair = true
+
+[Objects]
+shaders = true
+
+# Max. number of lights that affect objects. Setting to 1 will only reflect sunlight
+# Note: has no effect when shaders are turned off
+num lights = 8
+
+# Use static geometry for static objects. Improves rendering speed.
+use static geometry = true
+
+[Viewing distance]
+# Limit the rendering distance of small objects
+limit small object distance = false
+
+# Size below which an object is considered as small
+small object size = 250
+
+# Rendering distance for small objects
+small object distance = 3500
+
+# Max viewing distance at clear weather conditions
+max viewing distance = 5600
+
+# Distance at which fog starts (proportional to viewing distance)
+fog start factor = 0.5
+
+# Distance at which fog ends (proportional to viewing distance)
+fog end factor = 1.0
+
+[Terrain]
+distant land = false
+
+shader = true
+
+[Water]
+shader = true
+
+refraction = true
+
+rtt size = 512
+reflect terrain = true
+reflect statics = false
+reflect small statics = false
+reflect actors = false
+reflect misc = false
+
+[Sound]
+# Device name. Blank means default
+device =
+
+# Volumes. master volume affects all other volumes.
+master volume = 1.0
+sfx volume = 1.0
+music volume = 0.4
+footsteps volume = 0.6
+voice volume = 1.0
+
+
+[Input]
+
+invert y axis = false
+
+camera sensitivity = 1.0
+
+ui sensitivity = 1.0
+
+camera y multiplier = 1.0
+
+ui y multiplier = 1.0
+
+[Game]
+# Always use the most powerful attack when striking with a weapon (chop, slash or thrust)
+best attack = false
diff --git a/files/transparency-overrides.cfg b/files/transparency-overrides.cfg
new file mode 100644
index 0000000000..65f9b477af
--- /dev/null
+++ b/files/transparency-overrides.cfg
@@ -0,0 +1,623 @@
+# Bethesda has used wrong transparency settings for many textures
+# (who would have guessed)
+# This is very unfortunate because objects with real transparency:
+# - cannot cast shadows
+# - cannot receive advanced framebuffer effects like depth of field or ambient occlusion
+# - cannot cover lens flare effects (the lens flare will just shine through)
+
+# This file lists textures that should be using alpha rejection instead of transparency
+# basically these are textures that are not translucent (i.e. at one spot on the texture, either transparent or opaque)
+
+# Note: all the texture names here have to be lowercase
+
+# fauna
+[textures\tx_wickwheat_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_wickwheat_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_red_lichen_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_stone_flower_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ivy_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ivy_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_saltrice_04.dds]
+ alphaRejectValue = 128
+
+[textures\tx_black_lichen_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_04.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_06.dds]
+ alphaRejectValue = 128
+
+[textures\tx_leaves_07.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ai_heather_01.dds]
+ alphaRejectValue = 96
+
+[textures\tx_goldkanet_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_goldkanet_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_plant_tails00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_vine_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_comberry_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_willow_flower_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_cork_bulb_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_green_lichen_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_roobrush_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bittergreen_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_chokeweed_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_branches_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_branches_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_guarskin_hut_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_hackle-lo_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_fern_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_fern_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_leaves_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_marshmerrow_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_moss_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_moss_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_lilypad_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_lilypad_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bc_lilypad_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fire_fern_01.dds]
+ alphaRejectValue = 128
+
+# banners and flags
+[textures\tx_flag_imp_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_arena_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_comfort_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_child_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_count_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_faith_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_walk_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_imp_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_redoran_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_avs_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_serving_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_speak_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_stdeyln_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_stolms_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_thin_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_vivec_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_vivec_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_04.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_05.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_06.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_banner_07.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_a_banner.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_e_banner.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_u_banner.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_z_banner.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_6th.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_6th_tall.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_gnisis_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_gnisis_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_bhm_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_04.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_05.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_06.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_07.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_08.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_08.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_09.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_10.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_11.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_12.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_tapestry_13.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_lutestrings_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_imp_altar_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_akatosh_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_apprentice_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_arkay_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_dibella_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_golem_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_julianos_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_kynareth_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_lady_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_lord_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_lover_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_mara_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_ritual_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_shadow_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_steed_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_stendarr_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_thief_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_tower_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_warrior_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_wizard_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_c_t_zenithar_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_dagoth_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_tavern_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_goods_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_danger_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_welcome_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_clothing_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_alchemy_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_hlaalu_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_redoran_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_temple_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_temple_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_book_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_ald_velothi.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_gnaar_mok.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_hla_oad.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_khull.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_pawn_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_sadrith_mora.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_tel_aruhn.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_tel_branora.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_tel_fyr.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_tel_mora.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_telvani_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_tel_vos.dds]
+ alphaRejectValue = 128
+
+[textures\tx_de_banner_vos.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bannerd_w_a_shop_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_banner_temple_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_mural1_00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_mural1_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_mural4_00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_mural4_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_mural5_00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_telvanni_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_v_b_hlaalu_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_tapestry.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_tapestry_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_tapestry_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_tapestry_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_fabric_tapestry_04.dds]
+ alphaRejectValue = 128
+
+# characters
+[textures\tx_netchgod00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_argonian_f_hair02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_argonian_f_hair03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_argonian_m_hair01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_argonian_m_hair04.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_argonian_m_hair05.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_khajiit_f_hair01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_khajiit_f_hair02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_khajiit_m_hair01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_corprus_stalker12.dds]
+ alphaRejectValue = 128
+
+[textures\tx_a_clavicus02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_dark elf_m_hair11.dds]
+ alphaRejectValue = 128
+
+[textures\tx_b_n_dark elf_f_hair10.dds]
+ alphaRejectValue = 128
+
+# misc items
+[textures\tx_sail.dds]
+ alphaRejectValue = 128
+
+[textures\tx_longboatsail01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_longboatsail01a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_longboatsail01b.dds]
+ alphaRejectValue = 128
+
+[textures\tx_longboatsail02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_quill.dds]
+ alphaRejectValue = 128
+
+[textures\tx_note_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_note_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_parchment_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_parchment_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_scroll_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_scroll_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_scroll_03.dds]
+ alphaRejectValue = 128
+
+[textures\tx_alpha_small_edge.dds]
+ alphaRejectValue = 128
+
+[textures\tx_alpha_shadow_circular.dds]
+ alphaRejectValue = 128
+
+# building materials
+[textures\tx_shack_thatch_strip.dds]
+ alphaRejectValue = 128
+
+[textures\tx_rug00.dds]
+ alphaRejectValue = 128
+
+[textures\tx_rug_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_rug_edge_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_awning_thatch_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_awning_woven_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bridgeropes.dds]
+ alphaRejectValue = 128
+
+[textures\tx_rope_woven_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_rope_woven_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_ashl_tent_06.dds]
+ alphaRejectValue = 128
+
+[textures\tx_guar_tarp.dds]
+ alphaRejectValue = 128
+
+[textures\tx_velothi_glyph00.dds]
+ alphaRejectValue = 128
+
+
+
+# Bloodmoon
+
+[textures\tx_bm_holly_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_holly_snow_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_pine_04a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_pine_03a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_pine_02a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_pine_01a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_shrub_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_shrub_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_snow_pine_01a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_snow_pine_02a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_snow_pine_03a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_snow_pine_04a.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_deadpine_01.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_shrub_snow_02.dds]
+ alphaRejectValue = 128
+
+[textures\tx_bm_s_deadpine_01.dds]
+ alphaRejectValue = 128
diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui
new file mode 100644
index 0000000000..041a9576d0
--- /dev/null
+++ b/files/ui/datafilespage.ui
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DataFilesPage</class>
+ <widget class="QWidget" name="DataFilesPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>518</width>
+ <height>304</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="filterLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="filterLabel">
+ <property name="text">
+ <string>Filter:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="LineEdit" name="filterLineEdit"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
+ <widget class="QTableView" name="mastersTable"/>
+ <widget class="QTableView" name="pluginsTable"/>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="profileLabel">
+ <property name="text">
+ <string>Current Profile:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="ProfilesComboBox" name="profilesComboBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="newProfileButton">
+ <property name="toolTip">
+ <string>New Profile</string>
+ </property>
+ <property name="text">
+ <string>&amp;New Profile</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="deleteProfileButton">
+ <property name="toolTip">
+ <string>Delete Profile</string>
+ </property>
+ <property name="text">
+ <string>Delete Profile</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+D</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ <action name="newProfileAction">
+ <property name="icon">
+ <iconset theme="document-new">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>New Profile</string>
+ </property>
+ <property name="toolTip">
+ <string>New Profile</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ <action name="deleteProfileAction">
+ <property name="icon">
+ <iconset theme="edit-delete">
+ <normaloff/>
+ </iconset>
+ </property>
+ <property name="text">
+ <string>Delete Profile</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete Profile</string>
+ </property>
+ </action>
+ <action name="checkAction">
+ <property name="text">
+ <string>Check Selection</string>
+ </property>
+ </action>
+ <action name="uncheckAction">
+ <property name="text">
+ <string>Uncheck Selection</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>LineEdit</class>
+ <extends>QLineEdit</extends>
+ <header location="global">components/fileorderlist/utils/lineedit.hpp</header>
+ </customwidget>
+ <customwidget>
+ <class>ProfilesComboBox</class>
+ <extends>QComboBox</extends>
+ <header location="global">components/fileorderlist/utils/profilescombobox.hpp</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui
new file mode 100644
index 0000000000..7e9fe00d9e
--- /dev/null
+++ b/files/ui/graphicspage.ui
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GraphicsPage</class>
+ <widget class="QWidget" name="GraphicsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>332</width>
+ <height>297</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="rendererGroup">
+ <property name="title">
+ <string>Render System</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="rendererLabel">
+ <property name="text">
+ <string>Rendering Subsystem:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="rendererComboBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="displayGroup">
+ <property name="title">
+ <string>Display</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="vSyncCheckBox">
+ <property name="text">
+ <string>Vertical Sync</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="fullScreenCheckBox">
+ <property name="text">
+ <string>Full Screen</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="antiAliasingLabel">
+ <property name="text">
+ <string>Anti-aliasing:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="screenLabel">
+ <property name="text">
+ <string>Screen:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="resolutionLabel">
+ <property name="text">
+ <string>Resolution:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="antiAliasingComboBox"/>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="screenComboBox"/>
+ </item>
+ <item row="4" column="1">
+ <layout class="QGridLayout" name="resolutionLayout">
+ <item row="1" column="2">
+ <layout class="QHBoxLayout" name="customResolutionLayout" stretch="1,0,1">
+ <item>
+ <widget class="QSpinBox" name="customWidthSpinBox">
+ <property name="minimum">
+ <number>800</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="multiplyLabel">
+ <property name="text">
+ <string> x </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="customHeightSpinBox">
+ <property name="minimum">
+ <number>600</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="1">
+ <widget class="QRadioButton" name="customRadioButton">
+ <property name="text">
+ <string>Custom:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QRadioButton" name="standardRadioButton">
+ <property name="text">
+ <string>Standard:</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QComboBox" name="resolutionComboBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>61</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui
new file mode 100644
index 0000000000..a1dfb172b2
--- /dev/null
+++ b/files/ui/mainwindow.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="minimumSize">
+ <size>
+ <width>575</width>
+ <height>535</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>OpenMW Launcher</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../launcher/launcher.qrc">
+ <normaloff>:/images/openmw.png</normaloff>:/images/openmw.png</iconset>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QListWidget" name="iconWidget">
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#iconWidget {
+ background-image: url(&quot;:/images/openmw-header.png&quot;);
+ background-color: palette(base);
+ background-repeat: no-repeat;
+ background-attachment: scroll;
+ background-position: right;
+}
+</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QStackedWidget" name="pagesWidget"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources>
+ <include location="../launcher/launcher.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/files/ui/playpage.ui b/files/ui/playpage.ui
new file mode 100644
index 0000000000..bf883b96ef
--- /dev/null
+++ b/files/ui/playpage.ui
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PlayPage</class>
+ <widget class="QWidget" name="PlayPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>274</width>
+ <height>317</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="Scroll" native="true">
+ <property name="styleSheet">
+ <string notr="true">#Scroll {
+ background-image: url(&quot;:/images/playpage-background.png&quot;);
+ background-repeat: no-repeat;
+ background-position: top;
+}
+</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>30</number>
+ </property>
+ <property name="topMargin">
+ <number>100</number>
+ </property>
+ <property name="rightMargin">
+ <number>30</number>
+ </property>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="profilesComboBox">
+ <property name="styleSheet">
+ <string notr="true">#profilesComboBox {
+ padding: 1px 18px 1px 3px;
+
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, stop:0.2 rgba(0, 0, 0, 25), stop:1 white);
+ border-width: 1px;
+ border-color: rgba(0, 0, 0, 125);
+ border-style: solid;
+ border-radius: 2px;
+
+ font-size: 12pt;
+ font-family: &quot;EB Garamond&quot;, &quot;EB Garamond 08&quot;;
+ color: black;
+}
+
+/*QComboBox gets the &quot;on&quot; state when the popup is open */
+#profilesComboBox:!editable:on {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 rgba(0, 0, 0, 75),
+ stop:0.1 rgba(0, 0, 0, 15),
+ stop:0.2 rgba(255, 255, 255, 55));
+
+ border: 1px solid rgba(0, 0, 0, 55);
+}
+
+#profilesComboBox:on { /* shift the text when the popup opens */
+ padding-top: 3px;
+ padding-left: 4px;
+}
+
+#profilesComboBox::drop-down {
+ subcontrol-origin: padding;
+ subcontrol-position: top right;
+
+ border-width: 1px;
+ border-left-width: 1px;
+ border-left-color: darkgray;
+ border-left-style: solid; /* just a single line */
+ border-top-right-radius: 3px; /* same radius as the QComboBox */
+ border-bottom-right-radius: 3px;
+}
+
+#profilesComboBox::down-arrow {
+ image: url(&quot;:/images/down.png&quot;);
+}
+
+#profilesComboBox::down-arrow:on { /* shift the arrow when popup is open */
+ top: 1px;
+ left: 1px;
+}
+
+#profilesComboBox QAbstractItemView {
+ border: 0px;
+}</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="profileLabel">
+ <property name="styleSheet">
+ <string notr="true">#profileLabel {
+ font-size: 18pt;
+ font-family: &quot;EB Garamond&quot;, &quot;EB Garamond 08&quot;;
+ color: black;
+}
+</string>
+ </property>
+ <property name="text">
+ <string>Current Profile:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="playButton">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>85</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>200</width>
+ <height>85</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#playButton {
+ height: 50px;
+ margin-bottom: 30px;
+
+ background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1,
+ stop:0 rgba(255, 255, 255, 200),
+ stop:0.1 rgba(255, 255, 255, 15),
+ stop:0.49 rgba(255, 255, 255, 75),
+ stop:0.5 rgba(0, 0, 0, 0),
+ stop:0.9 rgba(0, 0, 0, 55),
+ stop:1 rgba(0, 0, 0, 100));
+
+ font-size: 26pt;
+ font-family: &quot;EB Garamond&quot;, &quot;EB Garamond 08&quot;;
+ color: black;
+
+ border-right: 1px solid rgba(0, 0, 0, 155);
+ border-left: 1px solid rgba(0, 0, 0, 55);
+ border-top: 1px solid rgba(0, 0, 0, 55);
+ border-bottom: 1px solid rgba(0, 0, 0, 155);
+
+ border-radius: 5px;
+}
+
+#playButton:hover {
+ border-bottom: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0));
+ border-top: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0));
+ border-right: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0));
+ border-left: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(164, 192, 228, 255), stop:1 rgba(255, 255, 255, 0));
+ border-width: 2px;
+ border-style: solid;
+}
+
+#playButton:pressed {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 rgba(0, 0, 0, 75),
+ stop:0.1 rgba(0, 0, 0, 15),
+ stop:0.2 rgba(255, 255, 255, 55)
+ stop:0.95 rgba(255, 255, 255, 55),
+ stop:1 rgba(255, 255, 255, 155));
+
+ border: 1px solid rgba(0, 0, 0, 55);
+}</string>
+ </property>
+ <property name="text">
+ <string>Play</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/files/water/circle.png b/files/water/circle.png
new file mode 100644
index 0000000000..9a1cf268c0
--- /dev/null
+++ b/files/water/circle.png
Binary files differ
diff --git a/files/water/water_nm.png b/files/water/water_nm.png
new file mode 100644
index 0000000000..361431a0ef
--- /dev/null
+++ b/files/water/water_nm.png
Binary files differ
diff --git a/libs/openengine/README b/libs/openengine/README
new file mode 100644
index 0000000000..621fe8d60c
--- /dev/null
+++ b/libs/openengine/README
@@ -0,0 +1,12 @@
+OpenEngine README
+=================
+
+OpenEngine is a bunch of stand-alone game engine modules collected from the OpenMW project (see http://github.com/korslund/openmw or http://openmw.com ) and from certain other projects.
+
+It is currently a very early work in progress, and development will follow OpenMW closely for a while forward.
+
+OpenEngine will depend heavily on Mangle ( http://github.com/korslund/mangle/ ) and will thus aim to be backend agnostic. When finished it should work with a variety for free and commercial middleware libraries as backends for graphics, sound, physics, input and so on.
+
+All questions can be directed to Nicolay Korslund at korslund@gmail.com
+
+- Nicolay
diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp
new file mode 100644
index 0000000000..b0fa07fd60
--- /dev/null
+++ b/libs/openengine/bullet/BtOgre.cpp
@@ -0,0 +1,1094 @@
+/*
+ * =============================================================================================
+ *
+ * Filename: BtOgre.cpp
+ *
+ * Description: BtOgre implementation.
+ *
+ * Version: 1.0
+ * Created: 27/12/2008 01:47:56 PM
+ *
+ * Author: Nikhilesh (nikki)
+ *
+ * =============================================================================================
+ */
+
+#include "BtOgrePG.h"
+#include "BtOgreGP.h"
+#include "BtOgreExtras.h"
+
+#include <OgreEntity.h>
+#include <OgreSubMesh.h>
+#include <OgreSubEntity.h>
+
+using namespace Ogre;
+
+namespace BtOgre {
+
+/*
+ * =============================================================================================
+ * BtOgre::VertexIndexToShape
+ * =============================================================================================
+ */
+
+ void VertexIndexToShape::addStaticVertexData(const VertexData *vertex_data)
+ {
+ if (!vertex_data)
+ return;
+
+ const VertexData *data = vertex_data;
+
+ const unsigned int prev_size = mVertexCount;
+ mVertexCount += (unsigned int)data->vertexCount;
+
+ Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount];
+ if (mVertexBuffer)
+ {
+ memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size);
+ delete[] mVertexBuffer;
+ }
+ mVertexBuffer = tmp_vert;
+
+ // Get the positional buffer element
+ {
+ const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);
+ Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource());
+ const unsigned int vSize = (unsigned int)vbuf->getVertexSize();
+
+ unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
+ float* pReal;
+ Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size];
+ const unsigned int vertexCount = (unsigned int)data->vertexCount;
+ for(unsigned int j = 0; j < vertexCount; ++j)
+ {
+ posElem->baseVertexPointerToElement(vertex, &pReal);
+ vertex += vSize;
+
+ curVertices->x = (*pReal++);
+ curVertices->y = (*pReal++);
+ curVertices->z = (*pReal++);
+
+ *curVertices = mTransform * (*curVertices);
+
+ curVertices++;
+ }
+ vbuf->unlock();
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void VertexIndexToShape::addAnimatedVertexData(const Ogre::VertexData *vertex_data,
+ const Ogre::VertexData *blend_data,
+ const Ogre::Mesh::IndexMap *indexMap)
+ {
+ // Get the bone index element
+ assert(vertex_data);
+
+ const VertexData *data = blend_data;
+ const unsigned int prev_size = mVertexCount;
+ mVertexCount += (unsigned int)data->vertexCount;
+ Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount];
+ if (mVertexBuffer)
+ {
+ memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size);
+ delete[] mVertexBuffer;
+ }
+ mVertexBuffer = tmp_vert;
+
+ // Get the positional buffer element
+ {
+ const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);
+ assert (posElem);
+ Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource());
+ const unsigned int vSize = (unsigned int)vbuf->getVertexSize();
+
+ unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
+ float* pReal;
+ Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size];
+ const unsigned int vertexCount = (unsigned int)data->vertexCount;
+ for(unsigned int j = 0; j < vertexCount; ++j)
+ {
+ posElem->baseVertexPointerToElement(vertex, &pReal);
+ vertex += vSize;
+
+ curVertices->x = (*pReal++);
+ curVertices->y = (*pReal++);
+ curVertices->z = (*pReal++);
+
+ *curVertices = mTransform * (*curVertices);
+
+ curVertices++;
+ }
+ vbuf->unlock();
+ }
+
+ {
+ const Ogre::VertexElement* bneElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_BLEND_INDICES);
+ assert (bneElem);
+
+ Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(bneElem->getSource());
+ const unsigned int vSize = (unsigned int)vbuf->getVertexSize();
+ unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
+
+ unsigned char* pBone;
+
+ if (!mBoneIndex)
+ mBoneIndex = new BoneIndex();
+ BoneIndex::iterator i;
+
+ Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size];
+
+ const unsigned int vertexCount = (unsigned int)vertex_data->vertexCount;
+ for(unsigned int j = 0; j < vertexCount; ++j)
+ {
+ bneElem->baseVertexPointerToElement(vertex, &pBone);
+ vertex += vSize;
+
+ const unsigned char currBone = (indexMap) ? (*indexMap)[*pBone] : *pBone;
+ i = mBoneIndex->find (currBone);
+ Vector3Array* l = 0;
+ if (i == mBoneIndex->end())
+ {
+ l = new Vector3Array;
+ mBoneIndex->insert(BoneKeyIndex(currBone, l));
+ }
+ else
+ {
+ l = i->second;
+ }
+
+ l->push_back(*curVertices);
+
+ curVertices++;
+ }
+ vbuf->unlock();
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void VertexIndexToShape::addIndexData(IndexData *data, const unsigned int offset)
+ {
+ const unsigned int prev_size = mIndexCount;
+ mIndexCount += (unsigned int)data->indexCount;
+
+ unsigned int* tmp_ind = new unsigned int[mIndexCount];
+ if (mIndexBuffer)
+ {
+ memcpy (tmp_ind, mIndexBuffer, sizeof(unsigned int) * prev_size);
+ delete[] mIndexBuffer;
+ }
+ mIndexBuffer = tmp_ind;
+
+ const unsigned int numTris = (unsigned int) data->indexCount / 3;
+ HardwareIndexBufferSharedPtr ibuf = data->indexBuffer;
+ const bool use32bitindexes = (ibuf->getType() == HardwareIndexBuffer::IT_32BIT);
+ unsigned int index_offset = prev_size;
+
+ if (use32bitindexes)
+ {
+ const unsigned int* pInt = static_cast<unsigned int*>(ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
+ for(unsigned int k = 0; k < numTris; ++k)
+ {
+ mIndexBuffer[index_offset ++] = offset + *pInt++;
+ mIndexBuffer[index_offset ++] = offset + *pInt++;
+ mIndexBuffer[index_offset ++] = offset + *pInt++;
+ }
+ ibuf->unlock();
+ }
+ else
+ {
+ const unsigned short* pShort = static_cast<unsigned short*>(ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
+ for(unsigned int k = 0; k < numTris; ++k)
+ {
+ mIndexBuffer[index_offset ++] = offset + static_cast<unsigned int> (*pShort++);
+ mIndexBuffer[index_offset ++] = offset + static_cast<unsigned int> (*pShort++);
+ mIndexBuffer[index_offset ++] = offset + static_cast<unsigned int> (*pShort++);
+ }
+ ibuf->unlock();
+ }
+
+ }
+
+ //------------------------------------------------------------------------------------------------
+ Real VertexIndexToShape::getRadius()
+ {
+ if (mBoundRadius == (-1))
+ {
+ getSize();
+ mBoundRadius = (std::max(mBounds.x,std::max(mBounds.y,mBounds.z)) * 0.5);
+ }
+ return mBoundRadius;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ Vector3 VertexIndexToShape::getSize()
+ {
+ const unsigned int vCount = getVertexCount();
+ if (mBounds == Ogre::Vector3(-1,-1,-1) && vCount > 0)
+ {
+
+ const Ogre::Vector3 * const v = getVertices();
+
+ Ogre::Vector3 vmin(v[0]);
+ Ogre::Vector3 vmax(v[0]);
+
+ for(unsigned int j = 1; j < vCount; j++)
+ {
+ vmin.x = std::min(vmin.x, v[j].x);
+ vmin.y = std::min(vmin.y, v[j].y);
+ vmin.z = std::min(vmin.z, v[j].z);
+
+ vmax.x = std::max(vmax.x, v[j].x);
+ vmax.y = std::max(vmax.y, v[j].y);
+ vmax.z = std::max(vmax.z, v[j].z);
+ }
+
+ mBounds.x = vmax.x - vmin.x;
+ mBounds.y = vmax.y - vmin.y;
+ mBounds.z = vmax.z - vmin.z;
+ }
+
+ return mBounds;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ const Ogre::Vector3* VertexIndexToShape::getVertices()
+ {
+ return mVertexBuffer;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ unsigned int VertexIndexToShape::getVertexCount()
+ {
+ return mVertexCount;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ const unsigned int* VertexIndexToShape::getIndices()
+ {
+ return mIndexBuffer;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ unsigned int VertexIndexToShape::getIndexCount()
+ {
+ return mIndexCount;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btSphereShape* VertexIndexToShape::createSphere()
+ {
+ const Ogre::Real rad = getRadius();
+ assert((rad > 0.0) &&
+ ("Sphere radius must be greater than zero"));
+ btSphereShape* shape = new btSphereShape(rad);
+
+ shape->setLocalScaling(Convert::toBullet(mScale));
+
+ return shape;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btBoxShape* VertexIndexToShape::createBox()
+ {
+ const Ogre::Vector3 sz = getSize();
+
+ assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.z > 0.0) &&
+ ("Size of box must be greater than zero on all axes"));
+
+ btBoxShape* shape = new btBoxShape(Convert::toBullet(sz * 0.5));
+
+ shape->setLocalScaling(Convert::toBullet(mScale));
+
+ return shape;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btCylinderShape* VertexIndexToShape::createCylinder()
+ {
+ const Ogre::Vector3 sz = getSize();
+
+ assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.z > 0.0) &&
+ ("Size of Cylinder must be greater than zero on all axes"));
+
+ btCylinderShape* shape = new btCylinderShapeX(Convert::toBullet(sz * 0.5));
+
+ shape->setLocalScaling(Convert::toBullet(mScale));
+
+ return shape;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btConvexHullShape* VertexIndexToShape::createConvex()
+ {
+ assert(mVertexCount && (mIndexCount >= 6) &&
+ ("Mesh must have some vertices and at least 6 indices (2 triangles)"));
+
+ return new btConvexHullShape((btScalar*) &mVertexBuffer[0].x, mVertexCount, sizeof(Vector3));
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btBvhTriangleMeshShape* VertexIndexToShape::createTrimesh()
+ {
+ assert(mVertexCount && (mIndexCount >= 6) &&
+ ("Mesh must have some vertices and at least 6 indices (2 triangles)"));
+
+ unsigned int numFaces = mIndexCount / 3;
+
+ btTriangleMesh *trimesh = new btTriangleMesh();
+ unsigned int *indices = mIndexBuffer;
+ Vector3 *vertices = mVertexBuffer;
+
+ btVector3 vertexPos[3];
+ for (unsigned int n = 0; n < numFaces; ++n)
+ {
+ {
+ const Vector3 &vec = vertices[*indices];
+ vertexPos[0][0] = vec.x;
+ vertexPos[0][1] = vec.y;
+ vertexPos[0][2] = vec.z;
+ }
+ {
+ const Vector3 &vec = vertices[*(indices + 1)];
+ vertexPos[1][0] = vec.x;
+ vertexPos[1][1] = vec.y;
+ vertexPos[1][2] = vec.z;
+ }
+ {
+ const Vector3 &vec = vertices[*(indices + 2)];
+ vertexPos[2][0] = vec.x;
+ vertexPos[2][1] = vec.y;
+ vertexPos[2][2] = vec.z;
+ }
+
+ indices += 3;
+
+ trimesh->addTriangle(vertexPos[0], vertexPos[1], vertexPos[2]);
+ }
+
+ const bool useQuantizedAABB = true;
+ btBvhTriangleMeshShape *shape = new btBvhTriangleMeshShape(trimesh, useQuantizedAABB);
+
+ shape->setLocalScaling(Convert::toBullet(mScale));
+
+ return shape;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ VertexIndexToShape::~VertexIndexToShape()
+ {
+ delete[] mVertexBuffer;
+ delete[] mIndexBuffer;
+
+ if (mBoneIndex)
+ {
+ for(BoneIndex::iterator i = mBoneIndex->begin();
+ i != mBoneIndex->end();
+ ++i)
+ {
+ delete i->second;
+ }
+ delete mBoneIndex;
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ VertexIndexToShape::VertexIndexToShape(const Matrix4 &transform) :
+ mVertexBuffer (0),
+ mIndexBuffer (0),
+ mVertexCount (0),
+ mIndexCount (0),
+ mTransform (transform),
+ mBoundRadius (-1),
+ mBounds (Vector3(-1,-1,-1)),
+ mBoneIndex (0),
+ mScale(1)
+ {
+ }
+
+/*
+ * =============================================================================================
+ * BtOgre::StaticMeshToShapeConverter
+ * =============================================================================================
+ */
+
+ StaticMeshToShapeConverter::StaticMeshToShapeConverter() :
+ VertexIndexToShape(),
+ mEntity (0),
+ mNode (0)
+ {
+ }
+
+ //------------------------------------------------------------------------------------------------
+ StaticMeshToShapeConverter::~StaticMeshToShapeConverter()
+ {
+ }
+
+ //------------------------------------------------------------------------------------------------
+ StaticMeshToShapeConverter::StaticMeshToShapeConverter(Entity *entity, const Matrix4 &transform) :
+ VertexIndexToShape(transform),
+ mEntity (0),
+ mNode (0)
+ {
+ addEntity(entity, transform);
+ }
+
+ //------------------------------------------------------------------------------------------------
+ StaticMeshToShapeConverter::StaticMeshToShapeConverter(Renderable *rend, const Matrix4 &transform) :
+ VertexIndexToShape(transform),
+ mEntity (0),
+ mNode (0)
+ {
+ RenderOperation op;
+ rend->getRenderOperation(op);
+ VertexIndexToShape::addStaticVertexData(op.vertexData);
+ if(op.useIndexes)
+ VertexIndexToShape::addIndexData(op.indexData);
+
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void StaticMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform)
+ {
+ // Each entity added need to reset size and radius
+ // next time getRadius and getSize are asked, they're computed.
+ mBounds = Ogre::Vector3(-1,-1,-1);
+ mBoundRadius = -1;
+
+ mEntity = entity;
+ mNode = (SceneNode*)(mEntity->getParentNode());
+ mTransform = transform;
+ mScale = mNode->getScale();
+
+ if (mEntity->getMesh()->sharedVertexData)
+ {
+ VertexIndexToShape::addStaticVertexData (mEntity->getMesh()->sharedVertexData);
+ }
+
+ for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i)
+ {
+ SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh();
+
+ if (!sub_mesh->useSharedVertices)
+ {
+ VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount);
+ VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData);
+ }
+ else
+ {
+ VertexIndexToShape::addIndexData (sub_mesh->indexData);
+ }
+
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void StaticMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform)
+ {
+ // Each entity added need to reset size and radius
+ // next time getRadius and getSize are asked, they're computed.
+ mBounds = Ogre::Vector3(-1,-1,-1);
+ mBoundRadius = -1;
+
+ //_entity = entity;
+ //_node = (SceneNode*)(_entity->getParentNode());
+ mTransform = transform;
+
+ if (mesh->hasSkeleton ())
+ Ogre::LogManager::getSingleton().logMessage("MeshToShapeConverter::addMesh : Mesh " + mesh->getName () + " as skeleton but added to trimesh non animated");
+
+ if (mesh->sharedVertexData)
+ {
+ VertexIndexToShape::addStaticVertexData (mesh->sharedVertexData);
+ }
+
+ for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i)
+ {
+ SubMesh *sub_mesh = mesh->getSubMesh(i);
+
+ if (!sub_mesh->useSharedVertices)
+ {
+ VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount);
+ VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData);
+ }
+ else
+ {
+ VertexIndexToShape::addIndexData (sub_mesh->indexData);
+ }
+
+ }
+ }
+
+/*
+ * =============================================================================================
+ * BtOgre::AnimatedMeshToShapeConverter
+ * =============================================================================================
+ */
+
+ AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter(Entity *entity,const Matrix4 &transform) :
+ VertexIndexToShape(transform),
+ mEntity (0),
+ mNode (0),
+ mTransformedVerticesTemp(0),
+ mTransformedVerticesTempSize(0)
+ {
+ addEntity(entity, transform);
+ }
+
+ //------------------------------------------------------------------------------------------------
+ AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter() :
+ VertexIndexToShape(),
+ mEntity (0),
+ mNode (0),
+ mTransformedVerticesTemp(0),
+ mTransformedVerticesTempSize(0)
+ {
+ }
+
+ //------------------------------------------------------------------------------------------------
+ AnimatedMeshToShapeConverter::~AnimatedMeshToShapeConverter()
+ {
+ delete[] mTransformedVerticesTemp;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void AnimatedMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform)
+ {
+ // Each entity added need to reset size and radius
+ // next time getRadius and getSize are asked, they're computed.
+ mBounds = Ogre::Vector3(-1,-1,-1);
+ mBoundRadius = -1;
+
+ mEntity = entity;
+ mNode = (SceneNode*)(mEntity->getParentNode());
+ mTransform = transform;
+
+ assert (entity->getMesh()->hasSkeleton ());
+
+ mEntity->addSoftwareAnimationRequest(false);
+ mEntity->_updateAnimation();
+
+ if (mEntity->getMesh()->sharedVertexData)
+ {
+ VertexIndexToShape::addAnimatedVertexData (mEntity->getMesh()->sharedVertexData,
+ mEntity->_getSkelAnimVertexData(),
+ &mEntity->getMesh()->sharedBlendIndexToBoneIndexMap);
+ }
+
+ for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i)
+ {
+ SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh();
+
+ if (!sub_mesh->useSharedVertices)
+ {
+ VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount);
+
+ VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData,
+ mEntity->getSubEntity(i)->_getSkelAnimVertexData(),
+ &sub_mesh->blendIndexToBoneIndexMap);
+ }
+ else
+ {
+ VertexIndexToShape::addIndexData (sub_mesh->indexData);
+ }
+
+ }
+
+ mEntity->removeSoftwareAnimationRequest(false);
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void AnimatedMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform)
+ {
+ // Each entity added need to reset size and radius
+ // next time getRadius and getSize are asked, they're computed.
+ mBounds = Ogre::Vector3(-1,-1,-1);
+ mBoundRadius = -1;
+
+ //_entity = entity;
+ //_node = (SceneNode*)(_entity->getParentNode());
+ mTransform = transform;
+
+ assert (mesh->hasSkeleton ());
+
+ if (mesh->sharedVertexData)
+ {
+ VertexIndexToShape::addAnimatedVertexData (mesh->sharedVertexData,
+ 0,
+ &mesh->sharedBlendIndexToBoneIndexMap);
+ }
+
+ for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i)
+ {
+ SubMesh *sub_mesh = mesh->getSubMesh(i);
+
+ if (!sub_mesh->useSharedVertices)
+ {
+ VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount);
+
+ VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData,
+ 0,
+ &sub_mesh->blendIndexToBoneIndexMap);
+ }
+ else
+ {
+ VertexIndexToShape::addIndexData (sub_mesh->indexData);
+ }
+
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ bool AnimatedMeshToShapeConverter::getBoneVertices(unsigned char bone,
+ unsigned int &vertex_count,
+ Ogre::Vector3* &vertices,
+ const Vector3 &bonePosition)
+ {
+ BoneIndex::iterator i = mBoneIndex->find(bone);
+
+ if (i == mBoneIndex->end())
+ return false;
+
+ if (i->second->empty())
+ return false;
+
+ vertex_count = (unsigned int) i->second->size() + 1;
+ if (vertex_count > mTransformedVerticesTempSize)
+ {
+ if (mTransformedVerticesTemp)
+ delete[] mTransformedVerticesTemp;
+
+ mTransformedVerticesTemp = new Ogre::Vector3[vertex_count];
+
+ }
+
+ vertices = mTransformedVerticesTemp;
+ vertices[0] = bonePosition;
+ //mEntity->_getParentNodeFullTransform() *
+ //mEntity->getSkeleton()->getBone(bone)->_getDerivedPosition();
+
+ //mEntity->getSkeleton()->getBone(bone)->_getDerivedOrientation()
+ unsigned int currBoneVertex = 1;
+ Vector3Array::iterator j = i->second->begin();
+ while(j != i->second->end())
+ {
+ vertices[currBoneVertex] = (*j);
+ ++j;
+ ++currBoneVertex;
+ }
+ return true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btBoxShape* AnimatedMeshToShapeConverter::createAlignedBox(unsigned char bone,
+ const Vector3 &bonePosition,
+ const Quaternion &boneOrientation)
+ {
+ unsigned int vertex_count;
+ Vector3* vertices;
+
+ if (!getBoneVertices(bone, vertex_count, vertices, bonePosition))
+ return 0;
+
+ Vector3 min_vec(vertices[0]);
+ Vector3 max_vec(vertices[0]);
+
+ for(unsigned int j = 1; j < vertex_count ;j++)
+ {
+ min_vec.x = std::min(min_vec.x,vertices[j].x);
+ min_vec.y = std::min(min_vec.y,vertices[j].y);
+ min_vec.z = std::min(min_vec.z,vertices[j].z);
+
+ max_vec.x = std::max(max_vec.x,vertices[j].x);
+ max_vec.y = std::max(max_vec.y,vertices[j].y);
+ max_vec.z = std::max(max_vec.z,vertices[j].z);
+ }
+ const Ogre::Vector3 maxMinusMin(max_vec - min_vec);
+ btBoxShape* box = new btBoxShape(Convert::toBullet(maxMinusMin));
+
+ /*const Ogre::Vector3 pos
+ (min_vec.x + (maxMinusMin.x * 0.5),
+ min_vec.y + (maxMinusMin.y * 0.5),
+ min_vec.z + (maxMinusMin.z * 0.5));*/
+
+ //box->setPosition(pos);
+
+ return box;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ bool AnimatedMeshToShapeConverter::getOrientedBox(unsigned char bone,
+ const Vector3 &bonePosition,
+ const Quaternion &boneOrientation,
+ Vector3 &box_afExtent,
+ Vector3 *box_akAxis,
+ Vector3 &box_kCenter)
+ {
+ unsigned int vertex_count;
+ Vector3* vertices;
+
+ if (!getBoneVertices(bone, vertex_count, vertices, bonePosition))
+ return false;
+
+ box_kCenter = Vector3::ZERO;
+
+ {
+ for(unsigned int c = 0 ;c < vertex_count;c++)
+ {
+ box_kCenter += vertices[c];
+ }
+ const Ogre::Real invVertexCount = 1.0 / vertex_count;
+ box_kCenter *= invVertexCount;
+ }
+ Quaternion orient = boneOrientation;
+ orient.ToAxes(box_akAxis);
+
+ // Let C be the box center and let U0, U1, and U2 be the box axes. Each
+ // input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. The
+ // following code computes min(y0), max(y0), min(y1), max(y1), min(y2),
+ // and max(y2). The box center is then adjusted to be
+ // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 +
+ // 0.5*(min(y2)+max(y2))*U2
+
+ Ogre::Vector3 kDiff (vertices[1] - box_kCenter);
+ Ogre::Real fY0Min = kDiff.dotProduct(box_akAxis[0]), fY0Max = fY0Min;
+ Ogre::Real fY1Min = kDiff.dotProduct(box_akAxis[1]), fY1Max = fY1Min;
+ Ogre::Real fY2Min = kDiff.dotProduct(box_akAxis[2]), fY2Max = fY2Min;
+
+ for (unsigned int i = 2; i < vertex_count; i++)
+ {
+ kDiff = vertices[i] - box_kCenter;
+
+ const Ogre::Real fY0 = kDiff.dotProduct(box_akAxis[0]);
+ if ( fY0 < fY0Min )
+ fY0Min = fY0;
+ else if ( fY0 > fY0Max )
+ fY0Max = fY0;
+
+ const Ogre::Real fY1 = kDiff.dotProduct(box_akAxis[1]);
+ if ( fY1 < fY1Min )
+ fY1Min = fY1;
+ else if ( fY1 > fY1Max )
+ fY1Max = fY1;
+
+ const Ogre::Real fY2 = kDiff.dotProduct(box_akAxis[2]);
+ if ( fY2 < fY2Min )
+ fY2Min = fY2;
+ else if ( fY2 > fY2Max )
+ fY2Max = fY2;
+ }
+
+ box_afExtent.x = ((Real)0.5)*(fY0Max - fY0Min);
+ box_afExtent.y = ((Real)0.5)*(fY1Max - fY1Min);
+ box_afExtent.z = ((Real)0.5)*(fY2Max - fY2Min);
+
+ box_kCenter += (0.5*(fY0Max+fY0Min))*box_akAxis[0] +
+ (0.5*(fY1Max+fY1Min))*box_akAxis[1] +
+ (0.5*(fY2Max+fY2Min))*box_akAxis[2];
+
+ box_afExtent *= 2.0;
+
+ return true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ btBoxShape *AnimatedMeshToShapeConverter::createOrientedBox(unsigned char bone,
+ const Vector3 &bonePosition,
+ const Quaternion &boneOrientation)
+ {
+ Ogre::Vector3 box_akAxis[3];
+ Ogre::Vector3 box_afExtent;
+ Ogre::Vector3 box_afCenter;
+
+ if (!getOrientedBox(bone, bonePosition, boneOrientation,
+ box_afExtent,
+ box_akAxis,
+ box_afCenter))
+ return 0;
+
+ btBoxShape *geom = new btBoxShape(Convert::toBullet(box_afExtent));
+ //geom->setOrientation(Quaternion(box_akAxis[0],box_akAxis[1],box_akAxis[2]));
+ //geom->setPosition(box_afCenter);
+ return geom;
+ }
+
+/*
+ * =============================================================================================
+ * BtOgre::DynamicRenderable
+ * =============================================================================================
+ */
+
+ DynamicRenderable::DynamicRenderable()
+ {
+ }
+
+ //------------------------------------------------------------------------------------------------
+ DynamicRenderable::~DynamicRenderable()
+ {
+ delete mRenderOp.vertexData;
+ delete mRenderOp.indexData;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicRenderable::initialize(RenderOperation::OperationType operationType,
+ bool useIndices)
+ {
+ // Initialize render operation
+ mRenderOp.operationType = operationType;
+ mRenderOp.useIndexes = useIndices;
+ mRenderOp.vertexData = new VertexData;
+ if (mRenderOp.useIndexes)
+ mRenderOp.indexData = new IndexData;
+
+ // Reset buffer capacities
+ mVertexBufferCapacity = 0;
+ mIndexBufferCapacity = 0;
+
+ // Create vertex declaration
+ createVertexDeclaration();
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicRenderable::prepareHardwareBuffers(size_t vertexCount,
+ size_t indexCount)
+ {
+ // Prepare vertex buffer
+ size_t newVertCapacity = mVertexBufferCapacity;
+ if ((vertexCount > mVertexBufferCapacity) ||
+ (!mVertexBufferCapacity))
+ {
+ // vertexCount exceeds current capacity!
+ // It is necessary to reallocate the buffer.
+
+ // Check if this is the first call
+ if (!newVertCapacity)
+ newVertCapacity = 1;
+
+ // Make capacity the next power of two
+ while (newVertCapacity < vertexCount)
+ newVertCapacity <<= 1;
+ }
+ else if (vertexCount < mVertexBufferCapacity>>1) {
+ // Make capacity the previous power of two
+ while (vertexCount < newVertCapacity>>1)
+ newVertCapacity >>= 1;
+ }
+ if (newVertCapacity != mVertexBufferCapacity)
+ {
+ mVertexBufferCapacity = newVertCapacity;
+ // Create new vertex buffer
+ HardwareVertexBufferSharedPtr vbuf =
+ HardwareBufferManager::getSingleton().createVertexBuffer(
+ mRenderOp.vertexData->vertexDeclaration->getVertexSize(0),
+ mVertexBufferCapacity,
+ HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_?
+
+ // Bind buffer
+ mRenderOp.vertexData->vertexBufferBinding->setBinding(0, vbuf);
+ }
+ // Update vertex count in the render operation
+ mRenderOp.vertexData->vertexCount = vertexCount;
+
+ if (mRenderOp.useIndexes)
+ {
+ OgreAssert(indexCount <= std::numeric_limits<unsigned short>::max(), "indexCount exceeds 16 bit");
+
+ size_t newIndexCapacity = mIndexBufferCapacity;
+ // Prepare index buffer
+ if ((indexCount > newIndexCapacity) ||
+ (!newIndexCapacity))
+ {
+ // indexCount exceeds current capacity!
+ // It is necessary to reallocate the buffer.
+
+ // Check if this is the first call
+ if (!newIndexCapacity)
+ newIndexCapacity = 1;
+
+ // Make capacity the next power of two
+ while (newIndexCapacity < indexCount)
+ newIndexCapacity <<= 1;
+
+ }
+ else if (indexCount < newIndexCapacity>>1)
+ {
+ // Make capacity the previous power of two
+ while (indexCount < newIndexCapacity>>1)
+ newIndexCapacity >>= 1;
+ }
+
+ if (newIndexCapacity != mIndexBufferCapacity)
+ {
+ mIndexBufferCapacity = newIndexCapacity;
+ // Create new index buffer
+ mRenderOp.indexData->indexBuffer =
+ HardwareBufferManager::getSingleton().createIndexBuffer(
+ HardwareIndexBuffer::IT_16BIT,
+ mIndexBufferCapacity,
+ HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_?
+ }
+
+ // Update index count in the render operation
+ mRenderOp.indexData->indexCount = indexCount;
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------
+ Real DynamicRenderable::getBoundingRadius(void) const
+ {
+ return Math::Sqrt(std::max(mBox.getMaximum().squaredLength(), mBox.getMinimum().squaredLength()));
+ }
+
+ //------------------------------------------------------------------------------------------------
+ Real DynamicRenderable::getSquaredViewDepth(const Camera* cam) const
+ {
+ Vector3 vMin, vMax, vMid, vDist;
+ vMin = mBox.getMinimum();
+ vMax = mBox.getMaximum();
+ vMid = ((vMax - vMin) * 0.5) + vMin;
+ vDist = cam->getDerivedPosition() - vMid;
+
+ return vDist.squaredLength();
+ }
+
+/*
+ * =============================================================================================
+ * BtOgre::DynamicLines
+ * =============================================================================================
+ */
+
+ enum {
+ POSITION_BINDING,
+ TEXCOORD_BINDING
+ };
+
+ //------------------------------------------------------------------------------------------------
+ DynamicLines::DynamicLines(OperationType opType)
+ {
+ initialize(opType,false);
+ setMaterial("BaseWhiteNoLighting");
+ mDirty = true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ DynamicLines::~DynamicLines()
+ {
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::setOperationType(OperationType opType)
+ {
+ mRenderOp.operationType = opType;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ RenderOperation::OperationType DynamicLines::getOperationType() const
+ {
+ return mRenderOp.operationType;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::addPoint(const Vector3 &p)
+ {
+ mPoints.push_back(p);
+ mDirty = true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::addPoint(Real x, Real y, Real z)
+ {
+ mPoints.push_back(Vector3(x,y,z));
+ mDirty = true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ const Vector3& DynamicLines::getPoint(unsigned short index) const
+ {
+ assert(index < mPoints.size() && "Point index is out of bounds!!");
+ return mPoints[index];
+ }
+
+ //------------------------------------------------------------------------------------------------
+ unsigned short DynamicLines::getNumPoints(void) const
+ {
+ return (unsigned short)mPoints.size();
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::setPoint(unsigned short index, const Vector3 &value)
+ {
+ assert(index < mPoints.size() && "Point index is out of bounds!!");
+
+ mPoints[index] = value;
+ mDirty = true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::clear()
+ {
+ mPoints.clear();
+ mDirty = true;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::update()
+ {
+ if (mDirty) fillHardwareBuffers();
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::createVertexDeclaration()
+ {
+ VertexDeclaration *decl = mRenderOp.vertexData->vertexDeclaration;
+ decl->addElement(POSITION_BINDING, 0, VET_FLOAT3, VES_POSITION);
+ }
+
+ //------------------------------------------------------------------------------------------------
+ void DynamicLines::fillHardwareBuffers()
+ {
+ int size = mPoints.size();
+
+ prepareHardwareBuffers(size,0);
+
+ if (!size) {
+ mBox.setExtents(Vector3::ZERO,Vector3::ZERO);
+ mDirty=false;
+ return;
+ }
+
+ Vector3 vaabMin = mPoints[0];
+ Vector3 vaabMax = mPoints[0];
+
+ HardwareVertexBufferSharedPtr vbuf =
+ mRenderOp.vertexData->vertexBufferBinding->getBuffer(0);
+
+ Real *prPos = static_cast<Real*>(vbuf->lock(HardwareBuffer::HBL_DISCARD));
+ {
+ for(int i = 0; i < size; i++)
+ {
+ *prPos++ = mPoints[i].x;
+ *prPos++ = mPoints[i].y;
+ *prPos++ = mPoints[i].z;
+
+ if(mPoints[i].x < vaabMin.x)
+ vaabMin.x = mPoints[i].x;
+ if(mPoints[i].y < vaabMin.y)
+ vaabMin.y = mPoints[i].y;
+ if(mPoints[i].z < vaabMin.z)
+ vaabMin.z = mPoints[i].z;
+
+ if(mPoints[i].x > vaabMax.x)
+ vaabMax.x = mPoints[i].x;
+ if(mPoints[i].y > vaabMax.y)
+ vaabMax.y = mPoints[i].y;
+ if(mPoints[i].z > vaabMax.z)
+ vaabMax.z = mPoints[i].z;
+ }
+ }
+ vbuf->unlock();
+
+ mBox.setExtents(vaabMin, vaabMax);
+
+ mDirty = false;
+ }
+}
diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h
new file mode 100644
index 0000000000..b20a3ff984
--- /dev/null
+++ b/libs/openengine/bullet/BtOgreExtras.h
@@ -0,0 +1,282 @@
+/*
+ * =====================================================================================
+ *
+ * Filename: BtOgreExtras.h
+ *
+ * Description: Contains the Ogre Mesh to Bullet Shape converters.
+ *
+ * Version: 1.0
+ * Created: 27/12/2008 01:45:56 PM
+ *
+ * Author: Nikhilesh (nikki)
+ *
+ * =====================================================================================
+ */
+
+#ifndef _BtOgreShapes_H_
+#define _BtOgreShapes_H_
+
+#include "btBulletDynamicsCommon.h"
+#include "OgreSimpleRenderable.h"
+#include "OgreCamera.h"
+#include "OgreHardwareBufferManager.h"
+#include "OgreMaterialManager.h"
+#include "OgreTechnique.h"
+#include "OgrePass.h"
+
+#include "OgreLogManager.h"
+
+namespace BtOgre
+{
+
+typedef std::vector<Ogre::Vector3> Vector3Array;
+
+//Converts from and to Bullet and Ogre stuff. Pretty self-explanatory.
+class Convert
+{
+public:
+ Convert() {};
+ ~Convert() {};
+
+ static btQuaternion toBullet(const Ogre::Quaternion &q)
+ {
+ return btQuaternion(q.x, q.y, q.z, q.w);
+ }
+ static btVector3 toBullet(const Ogre::Vector3 &v)
+ {
+ return btVector3(v.x, v.y, v.z);
+ }
+
+ static Ogre::Quaternion toOgre(const btQuaternion &q)
+ {
+ return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z());
+ }
+ static Ogre::Vector3 toOgre(const btVector3 &v)
+ {
+ return Ogre::Vector3(v.x(), v.y(), v.z());
+ }
+};
+
+//From here on its debug-drawing stuff. ------------------------------------------------------------------
+
+class DynamicRenderable : public Ogre::SimpleRenderable
+{
+public:
+ /// Constructor
+ DynamicRenderable();
+ /// Virtual destructor
+ virtual ~DynamicRenderable();
+
+ /** Initializes the dynamic renderable.
+ @remarks
+ This function should only be called once. It initializes the
+ render operation, and calls the abstract function
+ createVertexDeclaration().
+ @param operationType The type of render operation to perform.
+ @param useIndices Specifies whether to use indices to determine the
+ vertices to use as input. */
+ void initialize(Ogre::RenderOperation::OperationType operationType,
+ bool useIndices);
+
+ /// Implementation of Ogre::SimpleRenderable
+ virtual Ogre::Real getBoundingRadius(void) const;
+ /// Implementation of Ogre::SimpleRenderable
+ virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
+
+protected:
+ /// Maximum capacity of the currently allocated vertex buffer.
+ size_t mVertexBufferCapacity;
+ /// Maximum capacity of the currently allocated index buffer.
+ size_t mIndexBufferCapacity;
+
+ /** Creates the vertex declaration.
+ @remarks
+ Override and set mRenderOp.vertexData->vertexDeclaration here.
+ mRenderOp.vertexData will be created for you before this method
+ is called. */
+ virtual void createVertexDeclaration() = 0;
+
+ /** Prepares the hardware buffers for the requested vertex and index counts.
+ @remarks
+ This function must be called before locking the buffers in
+ fillHardwareBuffers(). It guarantees that the hardware buffers
+ are large enough to hold at least the requested number of
+ vertices and indices (if using indices). The buffers are
+ possibly reallocated to achieve this.
+ @par
+ The vertex and index count in the render operation are set to
+ the values of vertexCount and indexCount respectively.
+ @param vertexCount The number of vertices the buffer must hold.
+
+ @param indexCount The number of indices the buffer must hold. This
+ parameter is ignored if not using indices. */
+ void prepareHardwareBuffers(size_t vertexCount, size_t indexCount);
+
+ /** Fills the hardware vertex and index buffers with data.
+ @remarks
+ This function must call prepareHardwareBuffers() before locking
+ the buffers to ensure the they are large enough for the data to
+ be written. Afterwards the vertex and index buffers (if using
+ indices) can be locked, and data can be written to them. */
+ virtual void fillHardwareBuffers() = 0;
+};
+
+class DynamicLines : public DynamicRenderable
+{
+ typedef Ogre::Vector3 Vector3;
+ typedef Ogre::Quaternion Quaternion;
+ typedef Ogre::Camera Camera;
+ typedef Ogre::Real Real;
+ typedef Ogre::RenderOperation::OperationType OperationType;
+
+public:
+ /// Constructor - see setOperationType() for description of argument.
+ DynamicLines(OperationType opType=Ogre::RenderOperation::OT_LINE_STRIP);
+ virtual ~DynamicLines();
+
+ /// Add a point to the point list
+ void addPoint(const Ogre::Vector3 &p);
+ /// Add a point to the point list
+ void addPoint(Real x, Real y, Real z);
+
+ /// Change the location of an existing point in the point list
+ void setPoint(unsigned short index, const Vector3 &value);
+
+ /// Return the location of an existing point in the point list
+ const Vector3& getPoint(unsigned short index) const;
+
+ /// Return the total number of points in the point list
+ unsigned short getNumPoints(void) const;
+
+ /// Remove all points from the point list
+ void clear();
+
+ /// Call this to update the hardware buffer after making changes.
+ void update();
+
+ /** Set the type of operation to draw with.
+ * @param opType Can be one of
+ * - RenderOperation::OT_LINE_STRIP
+ * - RenderOperation::OT_LINE_LIST
+ * - RenderOperation::OT_POINT_LIST
+ * - RenderOperation::OT_TRIANGLE_LIST
+ * - RenderOperation::OT_TRIANGLE_STRIP
+ * - RenderOperation::OT_TRIANGLE_FAN
+ * The default is OT_LINE_STRIP.
+ */
+ void setOperationType(OperationType opType);
+ OperationType getOperationType() const;
+
+protected:
+ /// Implementation DynamicRenderable, creates a simple vertex-only decl
+ virtual void createVertexDeclaration();
+ /// Implementation DynamicRenderable, pushes point list out to hardware memory
+ virtual void fillHardwareBuffers();
+
+private:
+ std::vector<Vector3> mPoints;
+ bool mDirty;
+};
+
+class DebugDrawer : public btIDebugDraw
+{
+protected:
+ Ogre::SceneNode *mNode;
+ btDynamicsWorld *mWorld;
+ DynamicLines *mLineDrawer;
+ bool mDebugOn;
+
+public:
+
+ DebugDrawer(Ogre::SceneNode *node, btDynamicsWorld *world)
+ : mNode(node),
+ mWorld(world),
+ mDebugOn(true)
+ {
+ mLineDrawer = new DynamicLines(Ogre::RenderOperation::OT_LINE_LIST);
+ mNode->attachObject(mLineDrawer);
+
+ if (!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists("BtOgre"))
+ Ogre::ResourceGroupManager::getSingleton().createResourceGroup("BtOgre");
+ if (!Ogre::MaterialManager::getSingleton().resourceExists("BtOgre/DebugLines"))
+ {
+ Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("BtOgre/DebugLines", "BtOgre");
+ mat->setReceiveShadows(false);
+ mat->setSelfIllumination(1,1,1);
+ }
+
+ mLineDrawer->setMaterial("BtOgre/DebugLines");
+
+ //mLineDrawer->setVisibilityFlags (1024);
+ }
+
+ ~DebugDrawer()
+ {
+ Ogre::MaterialManager::getSingleton().remove("BtOgre/DebugLines");
+ Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup("BtOgre");
+ delete mLineDrawer;
+ }
+
+ void step()
+ {
+ if (mDebugOn)
+ {
+ mWorld->debugDrawWorld();
+ mLineDrawer->update();
+ mNode->needUpdate();
+ mLineDrawer->clear();
+ }
+ else
+ {
+ mLineDrawer->clear();
+ mLineDrawer->update();
+ mNode->needUpdate();
+ }
+ }
+
+ void drawLine(const btVector3& from,const btVector3& to,const btVector3& color)
+ {
+ mLineDrawer->addPoint(Convert::toOgre(from));
+ mLineDrawer->addPoint(Convert::toOgre(to));
+ }
+
+ void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color)
+ {
+ mLineDrawer->addPoint(Convert::toOgre(PointOnB));
+ mLineDrawer->addPoint(Convert::toOgre(PointOnB) + (Convert::toOgre(normalOnB) * distance * 20));
+ }
+
+ void reportErrorWarning(const char* warningString)
+ {
+ Ogre::LogManager::getSingleton().logMessage(warningString);
+ }
+
+ void draw3dText(const btVector3& location,const char* textString)
+ {
+ }
+
+ //0 for off, anything else for on.
+ void setDebugMode(int isOn)
+ {
+ mDebugOn = (isOn == 0) ? false : true;
+
+ if (!mDebugOn)
+ mLineDrawer->clear();
+ }
+
+ //0 for off, anything else for on.
+ int getDebugMode() const
+ {
+ return mDebugOn;
+ }
+
+};
+
+}
+
+#endif
+
+
+
+
+
diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h
new file mode 100644
index 0000000000..4ce2f181ee
--- /dev/null
+++ b/libs/openengine/bullet/BtOgreGP.h
@@ -0,0 +1,145 @@
+/*
+ * =====================================================================================
+ *
+ * Filename: BtOgreGP.h
+ *
+ * Description: The part of BtOgre that handles information transfer from Ogre to
+ * Bullet (like mesh data for making trimeshes).
+ *
+ * Version: 1.0
+ * Created: 27/12/2008 03:29:56 AM
+ *
+ * Author: Nikhilesh (nikki)
+ *
+ * =====================================================================================
+ */
+
+#ifndef _BtOgrePG_H_
+#define _BtOgrePG_H_
+
+#include "btBulletDynamicsCommon.h"
+#include "BtOgreExtras.h"
+
+#include <OgreMatrix4.h>
+#include <OgreMesh.h>
+#include <OgreVector3.h>
+
+namespace BtOgre {
+
+typedef std::map<unsigned char, Vector3Array*> BoneIndex;
+typedef std::pair<unsigned short, Vector3Array*> BoneKeyIndex;
+
+class VertexIndexToShape
+{
+public:
+ VertexIndexToShape(const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+ ~VertexIndexToShape();
+
+ Ogre::Real getRadius();
+ Ogre::Vector3 getSize();
+
+
+ btSphereShape* createSphere();
+ btBoxShape* createBox();
+ btBvhTriangleMeshShape* createTrimesh();
+ btCylinderShape* createCylinder();
+ btConvexHullShape* createConvex();
+
+ const Ogre::Vector3* getVertices();
+ unsigned int getVertexCount();
+ const unsigned int* getIndices();
+ unsigned int getIndexCount();
+
+protected:
+
+ void addStaticVertexData(const Ogre::VertexData *vertex_data);
+
+ void addAnimatedVertexData(const Ogre::VertexData *vertex_data,
+ const Ogre::VertexData *blended_data,
+ const Ogre::Mesh::IndexMap *indexMap);
+
+ void addIndexData(Ogre::IndexData *data, const unsigned int offset = 0);
+
+
+protected:
+ Ogre::Vector3* mVertexBuffer;
+ unsigned int* mIndexBuffer;
+ unsigned int mVertexCount;
+ unsigned int mIndexCount;
+
+ Ogre::Matrix4 mTransform;
+
+ Ogre::Real mBoundRadius;
+ Ogre::Vector3 mBounds;
+
+ BoneIndex *mBoneIndex;
+
+ Ogre::Vector3 mScale;
+};
+
+//For static (non-animated) meshes.
+class StaticMeshToShapeConverter : public VertexIndexToShape
+{
+public:
+
+ StaticMeshToShapeConverter(Ogre::Renderable *rend, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+ StaticMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+ StaticMeshToShapeConverter();
+
+ ~StaticMeshToShapeConverter();
+
+ void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+
+ void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+
+
+protected:
+
+ Ogre::Entity* mEntity;
+ Ogre::SceneNode* mNode;
+};
+
+//For animated meshes.
+class AnimatedMeshToShapeConverter : public VertexIndexToShape
+{
+public:
+
+ AnimatedMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+ AnimatedMeshToShapeConverter();
+ ~AnimatedMeshToShapeConverter();
+
+ void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY);
+ void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform);
+
+ btBoxShape* createAlignedBox(unsigned char bone,
+ const Ogre::Vector3 &bonePosition,
+ const Ogre::Quaternion &boneOrientation);
+
+ btBoxShape* createOrientedBox(unsigned char bone,
+ const Ogre::Vector3 &bonePosition,
+ const Ogre::Quaternion &boneOrientation);
+
+protected:
+
+ bool getBoneVertices(unsigned char bone,
+ unsigned int &vertex_count,
+ Ogre::Vector3* &vertices,
+ const Ogre::Vector3 &bonePosition);
+
+ bool getOrientedBox(unsigned char bone,
+ const Ogre::Vector3 &bonePosition,
+ const Ogre::Quaternion &boneOrientation,
+ Ogre::Vector3 &extents,
+ Ogre::Vector3 *axis,
+ Ogre::Vector3 &center);
+
+ Ogre::Entity* mEntity;
+ Ogre::SceneNode* mNode;
+
+ Ogre::Vector3 *mTransformedVerticesTemp;
+ size_t mTransformedVerticesTempSize;
+};
+
+}
+
+#endif
diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h
new file mode 100644
index 0000000000..9ff069a8f9
--- /dev/null
+++ b/libs/openengine/bullet/BtOgrePG.h
@@ -0,0 +1,81 @@
+/*
+ * =====================================================================================
+ *
+ * Filename: BtOgrePG.h
+ *
+ * Description: The part of BtOgre that handles information transfer from Bullet to
+ * Ogre (like updating graphics object positions).
+ *
+ * Version: 1.0
+ * Created: 27/12/2008 03:40:56 AM
+ *
+ * Author: Nikhilesh (nikki)
+ *
+ * =====================================================================================
+ */
+
+#ifndef _BtOgreGP_H_
+#define _BtOgreGP_H_
+
+#include "btBulletDynamicsCommon.h"
+#include "OgreSceneNode.h"
+#include "BtOgreExtras.h"
+
+namespace BtOgre {
+
+//A MotionState is Bullet's way of informing you about updates to an object.
+//Pass this MotionState to a btRigidBody to have your SceneNode updated automaticaly.
+class RigidBodyState : public btMotionState
+{
+ protected:
+ btTransform mTransform;
+ btTransform mCenterOfMassOffset;
+
+ Ogre::SceneNode *mNode;
+
+ public:
+ RigidBodyState(Ogre::SceneNode *node, const btTransform &transform, const btTransform &offset = btTransform::getIdentity())
+ : mTransform(transform),
+ mCenterOfMassOffset(offset),
+ mNode(node)
+ {
+ }
+
+ RigidBodyState(Ogre::SceneNode *node)
+ : mTransform(((node != NULL) ? BtOgre::Convert::toBullet(node->getOrientation()) : btQuaternion(0,0,0,1)),
+ ((node != NULL) ? BtOgre::Convert::toBullet(node->getPosition()) : btVector3(0,0,0))),
+ mCenterOfMassOffset(btTransform::getIdentity()),
+ mNode(node)
+ {
+ }
+
+ virtual void getWorldTransform(btTransform &ret) const
+ {
+ ret = mCenterOfMassOffset.inverse() * mTransform;
+ }
+
+ virtual void setWorldTransform(const btTransform &in)
+ {
+ if (mNode == NULL)
+ return;
+
+ mTransform = in;
+ btTransform transform = in * mCenterOfMassOffset;
+
+ btQuaternion rot = transform.getRotation();
+ btVector3 pos = transform.getOrigin();
+ mNode->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
+ mNode->setPosition(pos.x(), pos.y(), pos.z());
+ }
+
+ void setNode(Ogre::SceneNode *node)
+ {
+ mNode = node;
+ }
+};
+
+//Softbody-Ogre connection goes here!
+
+}
+
+#endif
diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp
new file mode 100644
index 0000000000..5528924a9a
--- /dev/null
+++ b/libs/openengine/bullet/BulletShapeLoader.cpp
@@ -0,0 +1,153 @@
+#include "BulletShapeLoader.h"
+
+namespace OEngine {
+namespace Physic
+{
+
+BulletShape::BulletShape(Ogre::ResourceManager* creator, const Ogre::String &name,
+ Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual,
+ Ogre::ManualResourceLoader *loader) :
+Ogre::Resource(creator, name, handle, group, isManual, loader)
+{
+ /* If you were storing a pointer to an object, then you would set that pointer to NULL here.
+ */
+
+ /* For consistency with StringInterface, but we don't add any parameters here
+ That's because the Resource implementation of StringInterface is to
+ list all the options that need to be set before loading, of which
+ we have none as such. Full details can be set through scripts.
+ */
+ mCollisionShape = NULL;
+ mRaycastingShape = NULL;
+ mHasCollisionNode = false;
+ mCollide = true;
+ createParamDictionary("BulletShape");
+}
+
+BulletShape::~BulletShape()
+{
+ deleteShape(mCollisionShape);
+ deleteShape(mRaycastingShape);
+}
+
+// farm out to BulletShapeLoader
+void BulletShape::loadImpl()
+{
+ mLoader->loadResource(this);
+}
+
+void BulletShape::deleteShape(btCollisionShape* shape)
+{
+ if(shape!=NULL)
+ {
+ if(shape->isCompound())
+ {
+ btCompoundShape* ms = static_cast<btCompoundShape*>(mCollisionShape);
+ int a = ms->getNumChildShapes();
+ for(int i=0; i <a;i++)
+ {
+ deleteShape(ms->getChildShape(i));
+ }
+ }
+ delete shape;
+ }
+ shape = NULL;
+}
+
+void BulletShape::unloadImpl()
+{
+ deleteShape(mCollisionShape);
+ deleteShape(mRaycastingShape);
+}
+
+//TODO:change this?
+size_t BulletShape::calculateSize() const
+{
+ return 1;
+}
+
+
+
+//=============================================================================================================
+BulletShapeManager *BulletShapeManager::sThis = 0;
+
+BulletShapeManager *BulletShapeManager::getSingletonPtr()
+{
+ return sThis;
+}
+
+BulletShapeManager &BulletShapeManager::getSingleton()
+{
+ assert(sThis);
+ return(*sThis);
+}
+
+BulletShapeManager::BulletShapeManager()
+{
+ assert(!sThis);
+ sThis = this;
+
+ mResourceType = "BulletShape";
+
+ // low, because it will likely reference other resources
+ mLoadOrder = 30.0f;
+
+ // this is how we register the ResourceManager with OGRE
+ Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
+}
+
+BulletShapeManager::~BulletShapeManager()
+{
+ // and this is how we unregister it
+ Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
+
+ sThis = 0;
+}
+
+#if (OGRE_VERSION >= ((1 << 16) | (9 << 8) | 0))
+BulletShapePtr BulletShapeManager::getByName(const Ogre::String& name, const Ogre::String& groupName)
+{
+ return getResourceByName(name, groupName).staticCast<BulletShape>();
+}
+
+BulletShapePtr BulletShapeManager::create (const Ogre::String& name, const Ogre::String& group,
+ bool isManual, Ogre::ManualResourceLoader* loader,
+ const Ogre::NameValuePairList* createParams)
+{
+ return createResource(name,group,isManual,loader,createParams).staticCast<BulletShape>();
+}
+#endif
+
+BulletShapePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group)
+{
+ BulletShapePtr textf = getByName(name);
+
+ if (textf.isNull())
+ textf = create(name, group);
+
+ textf->load();
+ return textf;
+}
+
+Ogre::Resource *BulletShapeManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
+ const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
+ const Ogre::NameValuePairList *createParams)
+{
+ BulletShape* res = new BulletShape(this, name, handle, group, isManual, loader);
+ //if(isManual)
+ //{
+ //loader->loadResource(res);
+ //}
+ return res;
+}
+
+
+//====================================================================
+void BulletShapeLoader::loadResource(Ogre::Resource *resource)
+{}
+
+void BulletShapeLoader::load(const std::string &name,const std::string &group)
+{}
+
+}
+}
diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h
new file mode 100644
index 0000000000..98cda859db
--- /dev/null
+++ b/libs/openengine/bullet/BulletShapeLoader.h
@@ -0,0 +1,176 @@
+#ifndef _BULLET_SHAPE_LOADER_H_
+#define _BULLET_SHAPE_LOADER_H_
+
+#include <OgreResource.h>
+#include <OgreResourceManager.h>
+#include <btBulletCollisionCommon.h>
+#include <OgreVector3.h>
+
+namespace OEngine {
+namespace Physic
+{
+
+/**
+*Define a new resource which describe a Shape usable by bullet.See BulletShapeManager for how to get/use them.
+*/
+class BulletShape : public Ogre::Resource
+{
+ Ogre::String mString;
+
+protected:
+ void loadImpl();
+ void unloadImpl();
+ size_t calculateSize() const;
+
+ void deleteShape(btCollisionShape* shape);
+
+public:
+
+ BulletShape(Ogre::ResourceManager *creator, const Ogre::String &name,
+ Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false,
+ Ogre::ManualResourceLoader *loader = 0);
+
+ virtual ~BulletShape();
+
+ btCollisionShape* mCollisionShape;
+ btCollisionShape* mRaycastingShape;
+
+ // Whether or not a NiRootCollisionNode was present in the .nif. If there is none, the collision behaviour
+ // depends on object type, so we need to expose this variable.
+ bool mHasCollisionNode;
+
+ Ogre::Vector3 mBoxTranslation;
+ Ogre::Quaternion mBoxRotation;
+ //this flag indicate if the shape is used for collision or if it's for raycasting only.
+ bool mCollide;
+};
+
+/**
+*
+*/
+
+#if (OGRE_VERSION < ((1 << 16) | (9 << 8) | 0))
+class BulletShapePtr : public Ogre::SharedPtr<BulletShape>
+{
+public:
+ BulletShapePtr() : Ogre::SharedPtr<BulletShape>() {}
+ explicit BulletShapePtr(BulletShape *rep) : Ogre::SharedPtr<BulletShape>(rep) {}
+ BulletShapePtr(const BulletShapePtr &r) : Ogre::SharedPtr<BulletShape>(r) {}
+ BulletShapePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr<BulletShape>()
+ {
+ if( r.isNull() )
+ return;
+ // lock & copy other mutex pointer
+ OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
+ OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
+ pRep = static_cast<BulletShape*>(r.getPointer());
+ pUseCount = r.useCountPointer();
+ useFreeMethod = r.freeMethod();
+ if (pUseCount)
+ {
+ ++(*pUseCount);
+ }
+ }
+
+ /// Operator used to convert a ResourcePtr to a BulletShapePtr
+ BulletShapePtr& operator=(const Ogre::ResourcePtr& r)
+ {
+ if(pRep == static_cast<BulletShape*>(r.getPointer()))
+ return *this;
+ release();
+ if( r.isNull() )
+ return *this; // resource ptr is null, so the call to release above has done all we need to do.
+ // lock & copy other mutex pointer
+ OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
+ OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
+ pRep = static_cast<BulletShape*>(r.getPointer());
+ pUseCount = r.useCountPointer();
+ useFreeMethod = r.freeMethod();
+ if (pUseCount)
+ {
+ ++(*pUseCount);
+ }
+ return *this;
+ }
+};
+#else
+typedef Ogre::SharedPtr<BulletShape> BulletShapePtr;
+#endif
+
+/**
+*Hold any BulletShape that was created by the ManualBulletShapeLoader.
+*
+*To get a bulletShape, you must load it first.
+*First, create a manualBulletShapeLoader. Then call ManualBulletShapeManager->load(). This create an "empty" resource.
+*Then use BulletShapeManager->load(). This will fill the resource with the required info.
+*To get the resource,use BulletShapeManager::getByName.
+*When you use the resource no more, just use BulletShapeManager->unload(). It won't completly delete the resource, but it will
+*"empty" it.This allow a better management of memory: when you are leaving a cell, just unload every useless shape.
+*
+*Alternatively, you can call BulletShape->load() in order to actually load the resource.
+*When you are finished with it, just call BulletShape->unload().
+*
+*IMO: prefere the first methode, i am not completly sure about the 2nd.
+*
+*Important Note: i have no idea of what happen if you try to load two time the same resource without unloading.
+*It won't crash, but it might lead to memory leaks(I don't know how Ogre handle this). So don't do it!
+*/
+class BulletShapeManager : public Ogre::ResourceManager
+{
+protected:
+
+ // must implement this from ResourceManager's interface
+ Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
+ const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
+ const Ogre::NameValuePairList *createParams);
+
+ static BulletShapeManager *sThis;
+
+private:
+ /** \brief Explicit private copy constructor. This is a forbidden operation.*/
+ BulletShapeManager(const BulletShapeManager &);
+
+ /** \brief Private operator= . This is a forbidden operation. */
+ BulletShapeManager& operator=(const BulletShapeManager &);
+
+
+public:
+
+ BulletShapeManager();
+ virtual ~BulletShapeManager();
+
+
+#if (OGRE_VERSION >= ((1 << 16) | (9 << 8) | 0))
+ /// Get a resource by name
+ /// @see ResourceManager::getByName
+ BulletShapePtr getByName(const Ogre::String& name, const Ogre::String& groupName = Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);
+
+ /// Create a new shape
+ /// @see ResourceManager::createResource
+ BulletShapePtr create (const Ogre::String& name, const Ogre::String& group,
+ bool isManual = false, Ogre::ManualResourceLoader* loader = 0,
+ const Ogre::NameValuePairList* createParams = 0);
+#endif
+
+ virtual BulletShapePtr load(const Ogre::String &name, const Ogre::String &group);
+
+ static BulletShapeManager &getSingleton();
+ static BulletShapeManager *getSingletonPtr();
+};
+
+class BulletShapeLoader : public Ogre::ManualResourceLoader
+{
+public:
+
+ BulletShapeLoader(){};
+ virtual ~BulletShapeLoader() {}
+
+ virtual void loadResource(Ogre::Resource *resource);
+
+ virtual void load(const std::string &name,const std::string &group);
+};
+
+}
+}
+
+#endif
diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp
new file mode 100644
index 0000000000..c20415884a
--- /dev/null
+++ b/libs/openengine/bullet/CMotionState.cpp
@@ -0,0 +1,46 @@
+#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
new file mode 100644
index 0000000000..3508ab4ef1
--- /dev/null
+++ b/libs/openengine/bullet/CMotionState.h
@@ -0,0 +1,52 @@
+#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/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp
new file mode 100644
index 0000000000..fc4f3278f4
--- /dev/null
+++ b/libs/openengine/bullet/btKinematicCharacterController.cpp
@@ -0,0 +1,643 @@
+/*
+Bullet Continuous Collision Detection and Physics Library
+Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it freely,
+subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "LinearMath/btIDebugDraw.h"
+#include "BulletCollision/CollisionDispatch/btGhostObject.h"
+#include "BulletCollision/CollisionShapes/btMultiSphereShape.h"
+#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
+#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"
+#include "BulletCollision/CollisionDispatch/btCollisionWorld.h"
+#include "LinearMath/btDefaultMotionState.h"
+#include "btKinematicCharacterController.h"
+
+///@todo Interact with dynamic objects,
+///Ride kinematicly animated platforms properly
+///Support ducking
+class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
+{
+public:
+ btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
+ {
+ m_me[0] = me;
+ count = 1;
+ }
+
+ btKinematicClosestNotMeRayResultCallback (btCollisionObject* me[], int count_) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
+ {
+ count = count_;
+
+ for(int i = 0; i < count; i++)
+ m_me[i] = me[i];
+ }
+
+ virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
+ {
+ for(int i = 0; i < count; i++)
+ if (rayResult.m_collisionObject == m_me[i])
+ return 1.0;
+
+ return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace);
+ }
+protected:
+ btCollisionObject* m_me[10];
+ int count;
+};
+
+class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
+{
+public:
+ btKinematicClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot )
+ : btCollisionWorld::ClosestConvexResultCallback( btVector3( 0.0, 0.0, 0.0 ), btVector3( 0.0, 0.0, 0.0 ) ),
+ m_me( me ), m_up( up ), m_minSlopeDot( minSlopeDot )
+ {
+ }
+
+ virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
+ {
+ if( convexResult.m_hitCollisionObject == m_me )
+ return btScalar( 1 );
+
+ btVector3 hitNormalWorld;
+ if( normalInWorldSpace )
+ {
+ hitNormalWorld = convexResult.m_hitNormalLocal;
+ }
+ else
+ {
+ ///need to transform normal into worldspace
+ hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
+ }
+
+ // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box...
+
+ btScalar dotUp = m_up.dot(hitNormalWorld);
+ if( dotUp < m_minSlopeDot )
+ return btScalar( 1 );
+
+ return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace);
+ }
+
+protected:
+ btCollisionObject* m_me;
+ const btVector3 m_up;
+ btScalar m_minSlopeDot;
+};
+
+
+
+btKinematicCharacterController::btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject_,
+ btPairCachingGhostObject* internalGhostObject_,
+ btScalar stepHeight,
+ btScalar constantScale,
+ btScalar gravity,
+ btScalar fallVelocity,
+ btScalar jumpVelocity,
+ btScalar recoveringFactor )
+{
+ m_upAxis = btKinematicCharacterController::Y_AXIS;
+
+ m_walkDirection.setValue( btScalar( 0 ), btScalar( 0 ), btScalar( 0 ) );
+
+ m_useGhostObjectSweepTest = true;
+
+ externalGhostObject = externalGhostObject_;
+ internalGhostObject = internalGhostObject_;
+
+ m_recoveringFactor = recoveringFactor;
+
+ m_stepHeight = stepHeight;
+
+ m_useWalkDirection = true; // use walk direction by default, legacy behavior
+ m_velocityTimeInterval = btScalar( 0 );
+ m_verticalVelocity = btScalar( 0 );
+ m_verticalOffset = btScalar( 0 );
+
+ m_gravity = constantScale * gravity;
+ m_fallSpeed = constantScale * fallVelocity; // Terminal velocity of a sky diver in m/s.
+
+ m_jumpSpeed = constantScale * jumpVelocity; // ?
+ m_wasJumping = false;
+
+ setMaxSlope( btRadians( 45.0 ) );
+
+ mCollision = true;
+}
+
+
+btKinematicCharacterController::~btKinematicCharacterController ()
+{
+}
+
+void btKinematicCharacterController::setVerticalVelocity(float z)
+{
+ m_verticalVelocity = z;
+}
+
+bool btKinematicCharacterController::recoverFromPenetration( btCollisionWorld* collisionWorld )
+{
+ bool penetration = false;
+
+ if(!mCollision) return penetration;
+
+ collisionWorld->getDispatcher()->dispatchAllCollisionPairs( internalGhostObject->getOverlappingPairCache(),
+ collisionWorld->getDispatchInfo(),
+ collisionWorld->getDispatcher() );
+
+ btVector3 currentPosition = internalGhostObject->getWorldTransform().getOrigin();
+
+ btScalar maxPen = btScalar( 0 );
+
+ for( int i = 0; i < internalGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ )
+ {
+ m_manifoldArray.resize(0);
+
+ btBroadphasePair* collisionPair = &internalGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i];
+
+ if( collisionPair->m_algorithm )
+ collisionPair->m_algorithm->getAllContactManifolds( m_manifoldArray );
+
+
+ for( int j = 0; j < m_manifoldArray.size(); j++ )
+ {
+ btPersistentManifold* manifold = m_manifoldArray[j];
+
+ btScalar directionSign = manifold->getBody0() == internalGhostObject ? btScalar( -1.0 ) : btScalar( 1.0 );
+
+ for( int p = 0; p < manifold->getNumContacts(); p++ )
+ {
+ const btManifoldPoint&pt = manifold->getContactPoint( p );
+ if( (manifold->getBody1() == externalGhostObject && manifold->getBody0() == internalGhostObject)
+ ||(manifold->getBody0() == externalGhostObject && manifold->getBody1() == internalGhostObject) )
+ {
+ }
+ else
+ {
+ btScalar dist = pt.getDistance();
+
+ if( dist < 0.0 )
+ {
+ if( dist < maxPen )
+ maxPen = dist;
+
+ // NOTE : btScalar affects the stairs but the parkinson...
+ // 0.0 , the capsule can break the walls...
+ currentPosition += pt.m_normalWorldOnB * directionSign * dist * m_recoveringFactor;
+
+ penetration = true;
+ }
+ }
+ }
+
+ // ???
+ //manifold->clearManifold();
+ }
+ }
+
+ btTransform transform = internalGhostObject->getWorldTransform();
+
+ transform.setOrigin( currentPosition );
+
+ internalGhostObject->setWorldTransform( transform );
+ externalGhostObject->setWorldTransform( transform );
+
+ return penetration;
+}
+
+
+btVector3 btKinematicCharacterController::stepUp( btCollisionWorld* world, const btVector3& currentPosition, btScalar& currentStepOffset )
+{
+ btVector3 targetPosition = currentPosition + getUpAxisDirections()[ m_upAxis ] * ( m_stepHeight + ( m_verticalOffset > btScalar( 0.0 ) ? m_verticalOffset : 0.0 ) );
+
+ //if the no collisions mode is on, no need to go any further
+ if(!mCollision)
+ {
+ currentStepOffset = m_stepHeight;
+ return targetPosition;
+ }
+
+ // Retrieve the collision shape
+ //
+ btCollisionShape* collisionShape = externalGhostObject->getCollisionShape();
+ btAssert( collisionShape->isConvex() );
+
+ btConvexShape* convexShape = ( btConvexShape* )collisionShape;
+
+ // FIXME: Handle penetration properly
+ //
+ btTransform start;
+ start.setIdentity();
+ start.setOrigin( currentPosition + getUpAxisDirections()[ m_upAxis ] * ( convexShape->getMargin() ) );
+
+ btTransform end;
+ end.setIdentity();
+ end.setOrigin( targetPosition );
+
+ btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, -getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine );
+ callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup;
+ callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask;
+
+ // Sweep test
+ //
+ if( m_useGhostObjectSweepTest )
+ externalGhostObject->convexSweepTest( convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration );
+
+ else
+ world->convexSweepTest( convexShape, start, end, callback );
+
+ if( callback.hasHit() )
+ {
+ // Only modify the position if the hit was a slope and not a wall or ceiling.
+ //
+ if( callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > btScalar( 0.0 ) )
+ {
+ // We moved up only a fraction of the step height
+ //
+ currentStepOffset = m_stepHeight * callback.m_closestHitFraction;
+
+ return currentPosition.lerp( targetPosition, callback.m_closestHitFraction );
+ }
+
+ m_verticalVelocity = btScalar( 0.0 );
+ m_verticalOffset = btScalar( 0.0 );
+
+ return currentPosition;
+ }
+ else
+ {
+ currentStepOffset = m_stepHeight;
+ return targetPosition;
+ }
+}
+
+
+///Reflect the vector d around the vector r
+inline btVector3 reflect( const btVector3& d, const btVector3& r )
+{
+ return d - ( btScalar( 2.0 ) * d.dot( r ) ) * r;
+}
+
+
+///Project a vector u on another vector v
+inline btVector3 project( const btVector3& u, const btVector3& v )
+{
+ return v * u.dot( v );
+}
+
+
+///Helper for computing the character sliding
+inline btVector3 slide( const btVector3& direction, const btVector3& planeNormal )
+{
+ return direction - project( direction, planeNormal );
+}
+
+
+
+btVector3 slideOnCollision( const btVector3& fromPosition, const btVector3& toPosition, const btVector3& hitNormal )
+{
+ btVector3 moveDirection = toPosition - fromPosition;
+ btScalar moveLength = moveDirection.length();
+
+ if( moveLength <= btScalar( SIMD_EPSILON ) )
+ return toPosition;
+
+ moveDirection.normalize();
+
+ btVector3 reflectDir = reflect( moveDirection, hitNormal );
+ reflectDir.normalize();
+
+ return fromPosition + slide( reflectDir, hitNormal ) * moveLength;
+}
+
+
+btVector3 btKinematicCharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove )
+{
+ // We go to !
+ //
+ btVector3 targetPosition = currentPosition + walkMove;
+
+ //if the no collisions mode is on, no need to go any further
+ if(!mCollision) return targetPosition;
+
+ // Retrieve the collision shape
+ //
+ btCollisionShape* collisionShape = externalGhostObject->getCollisionShape();
+ btAssert( collisionShape->isConvex() );
+
+ btConvexShape* convexShape = ( btConvexShape* )collisionShape;
+
+ btTransform start;
+ start.setIdentity();
+
+ btTransform end;
+ end.setIdentity();
+
+ btScalar fraction = btScalar( 1.0 );
+
+ // This optimization scheme suffers in the corners.
+ // It basically jumps from a wall to another, then fails to find a new
+ // position (after 4 iterations here) and finally don't move at all.
+ //
+ // The stepping algorithm adds some problems with stairs. It seems
+ // the treads create some fake corner using capsules for collisions.
+ //
+ for( int i = 0; i < 4 && fraction > btScalar( 0.01 ); i++ )
+ {
+ start.setOrigin( currentPosition );
+ end.setOrigin( targetPosition );
+
+ btVector3 sweepDirNegative = currentPosition - targetPosition;
+
+ btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, sweepDirNegative, btScalar( 0.0 ) );
+ callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup;
+ callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask;
+
+ if( m_useGhostObjectSweepTest )
+ externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration );
+
+ else
+ collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration );
+
+ if( callback.hasHit() )
+ {
+ // Try another target position
+ //
+ targetPosition = slideOnCollision( currentPosition, targetPosition, callback.m_hitNormalWorld );
+ fraction = callback.m_closestHitFraction;
+ }
+ else
+
+ // Move to the valid target position
+ //
+ return targetPosition;
+ }
+
+ // Don't move if you can't find a valid target position...
+ // It prevents some flickering.
+ //
+ return currentPosition;
+}
+
+
+///Handle the gravity
+btScalar btKinematicCharacterController::addFallOffset( bool wasOnGround, btScalar currentStepOffset, btScalar dt )
+{
+ btScalar downVelocity = ( m_verticalVelocity < 0.0 ? -m_verticalVelocity : btScalar( 0.0 ) ) * dt;
+
+ if( downVelocity > btScalar( 0.0 ) && downVelocity < m_stepHeight && ( wasOnGround || !m_wasJumping ) )
+ downVelocity = m_stepHeight;
+
+ return currentStepOffset + downVelocity;
+}
+
+
+btVector3 btKinematicCharacterController::stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset )
+{
+ btVector3 stepDrop = getUpAxisDirections()[ m_upAxis ] * currentStepOffset;
+
+ // Be sure we are falling from the last m_currentPosition
+ // It prevents some flickering
+ //
+ btVector3 targetPosition = currentPosition - stepDrop;
+
+ //if the no collisions mode is on, no need to go any further
+ if(!mCollision) return targetPosition;
+
+ btTransform start;
+ start.setIdentity();
+ start.setOrigin( currentPosition );
+
+ btTransform end;
+ end.setIdentity();
+ end.setOrigin( targetPosition );
+
+ btKinematicClosestNotMeConvexResultCallback callback( internalGhostObject, getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine );
+ callback.m_collisionFilterGroup = internalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup;
+ callback.m_collisionFilterMask = internalGhostObject->getBroadphaseHandle()->m_collisionFilterMask;
+
+ // Retrieve the collision shape
+ //
+ btCollisionShape* collisionShape = internalGhostObject->getCollisionShape();
+ btAssert( collisionShape->isConvex() );
+ btConvexShape* convexShape = ( btConvexShape* )collisionShape;
+
+ if( m_useGhostObjectSweepTest )
+ externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration );
+
+ else
+ collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration );
+
+ if( callback.hasHit() )
+ {
+ m_verticalVelocity = btScalar( 0.0 );
+ m_verticalOffset = btScalar( 0.0 );
+ m_wasJumping = false;
+
+ // We dropped a fraction of the height -> hit floor
+ //
+ return currentPosition.lerp( targetPosition, callback.m_closestHitFraction );
+ }
+ else
+
+ // We dropped the full height
+ //
+ return targetPosition;
+}
+
+
+
+void btKinematicCharacterController::setWalkDirection( const btVector3& walkDirection )
+{
+ m_useWalkDirection = true;
+ m_walkDirection = walkDirection;
+}
+
+
+void btKinematicCharacterController::setVelocityForTimeInterval( const btVector3& velocity, btScalar timeInterval )
+{
+ m_useWalkDirection = false;
+ m_walkDirection = velocity;
+ m_velocityTimeInterval = timeInterval;
+}
+
+
+void btKinematicCharacterController::reset()
+{
+}
+
+
+void btKinematicCharacterController::warp( const btVector3& origin )
+{
+ btTransform transform;
+ transform.setIdentity();
+ transform.setOrigin( -origin );
+
+ externalGhostObject->setWorldTransform( transform );
+ internalGhostObject->setWorldTransform( transform );
+}
+
+
+void btKinematicCharacterController::preStep( btCollisionWorld* collisionWorld )
+{
+ BT_PROFILE( "preStep" );
+
+ for( int i = 0; i < 4 && recoverFromPenetration ( collisionWorld ); i++ );
+}
+
+
+void btKinematicCharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt )
+{
+ BT_PROFILE( "playerStep" );
+
+ if( !m_useWalkDirection && m_velocityTimeInterval <= btScalar( 0.0 ) )
+ return;
+
+ bool wasOnGround = onGround();
+
+ // Handle the gravity
+ //
+ m_verticalVelocity -= m_gravity * dt;
+
+ if( m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed )
+ m_verticalVelocity = m_jumpSpeed;
+
+ if( m_verticalVelocity < 0.0 && btFabs( m_verticalVelocity ) > btFabs( m_fallSpeed ) )
+ m_verticalVelocity = -btFabs( m_fallSpeed );
+
+ m_verticalOffset = m_verticalVelocity * dt;
+
+ // This forced stepping up can cause problems when the character
+ // walks (jump in fact...) under too low ceilings.
+ //
+ btVector3 currentPosition = externalGhostObject->getWorldTransform().getOrigin();
+ btScalar currentStepOffset;
+
+ currentPosition = stepUp( collisionWorld, currentPosition, currentStepOffset );
+
+ // Move in the air and slide against the walls ignoring the stair steps.
+ //
+ if( m_useWalkDirection )
+ currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, m_walkDirection );
+
+ else
+ {
+ btScalar dtMoving = ( dt < m_velocityTimeInterval ) ? dt : m_velocityTimeInterval;
+ m_velocityTimeInterval -= dt;
+
+ // How far will we move while we are moving ?
+ //
+ btVector3 moveDirection = m_walkDirection * dtMoving;
+
+ currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, moveDirection );
+ }
+
+ // Finally find the ground.
+ //
+ currentStepOffset = addFallOffset( wasOnGround, currentStepOffset, dt );
+
+ currentPosition = stepDown( collisionWorld, currentPosition, currentStepOffset );
+
+ // Apply the new position to the collision objects.
+ //
+ btTransform tranform;
+ tranform = externalGhostObject->getWorldTransform();
+ tranform.setOrigin( currentPosition );
+
+ externalGhostObject->setWorldTransform( tranform );
+ internalGhostObject->setWorldTransform( tranform );
+}
+
+
+void btKinematicCharacterController::setFallSpeed( btScalar fallSpeed )
+{
+ m_fallSpeed = fallSpeed;
+}
+
+
+void btKinematicCharacterController::setJumpSpeed( btScalar jumpSpeed )
+{
+ m_jumpSpeed = jumpSpeed;
+}
+
+
+void btKinematicCharacterController::setMaxJumpHeight( btScalar maxJumpHeight )
+{
+ m_maxJumpHeight = maxJumpHeight;
+}
+
+
+bool btKinematicCharacterController::canJump() const
+{
+ return onGround();
+}
+
+
+void btKinematicCharacterController::jump()
+{
+ if( !canJump() )
+ return;
+
+ m_verticalVelocity = m_jumpSpeed;
+ m_wasJumping = true;
+}
+
+
+void btKinematicCharacterController::setGravity( btScalar gravity )
+{
+ m_gravity = gravity;
+}
+
+
+btScalar btKinematicCharacterController::getGravity() const
+{
+ return m_gravity;
+}
+
+
+void btKinematicCharacterController::setMaxSlope( btScalar slopeRadians )
+{
+ m_maxSlopeRadians = slopeRadians;
+ m_maxSlopeCosine = btCos( slopeRadians );
+}
+
+
+btScalar btKinematicCharacterController::getMaxSlope() const
+{
+ return m_maxSlopeRadians;
+}
+
+
+bool btKinematicCharacterController::onGround() const
+{
+ return btFabs( m_verticalVelocity ) < btScalar( SIMD_EPSILON ) &&
+ btFabs( m_verticalOffset ) < btScalar( SIMD_EPSILON );
+}
+
+
+btVector3* btKinematicCharacterController::getUpAxisDirections()
+{
+ static btVector3 sUpAxisDirection[] =
+ {
+ btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 0.0 ) ),
+ btVector3( btScalar( 0.0 ), btScalar( 1.0 ), btScalar( 0.0 ) ),
+ btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 1.0 ) )
+ };
+
+ return sUpAxisDirection;
+}
+
+
+void btKinematicCharacterController::debugDraw( btIDebugDraw* debugDrawer )
+{
+}
diff --git a/libs/openengine/bullet/btKinematicCharacterController.h b/libs/openengine/bullet/btKinematicCharacterController.h
new file mode 100644
index 0000000000..d24cd97222
--- /dev/null
+++ b/libs/openengine/bullet/btKinematicCharacterController.h
@@ -0,0 +1,168 @@
+/*
+Bullet Continuous Collision Detection and Physics Library
+Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it freely,
+subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef KINEMATIC_CHARACTER_CONTROLLER_H
+#define KINEMATIC_CHARACTER_CONTROLLER_H
+
+#include "LinearMath/btVector3.h"
+#include "LinearMath/btQuickprof.h"
+
+#include "BulletDynamics/Character/btCharacterControllerInterface.h"
+
+#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"
+
+
+class btCollisionShape;
+class btRigidBody;
+class btCollisionWorld;
+class btCollisionDispatcher;
+class btPairCachingGhostObject;
+
+///btKinematicCharacterController is an object that supports a sliding motion in a world.
+///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations.
+///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user.
+class btKinematicCharacterController : public btCharacterControllerInterface
+{
+public:
+ enum UpAxis
+ {
+ X_AXIS = 0,
+ Y_AXIS = 1,
+ Z_AXIS = 2
+ };
+
+private:
+ btPairCachingGhostObject* externalGhostObject; // use this for querying collisions for sliding and move
+ btPairCachingGhostObject* internalGhostObject; // and this for recoreving from penetrations
+
+ btScalar m_verticalVelocity;
+ btScalar m_verticalOffset;
+ btScalar m_fallSpeed;
+ btScalar m_jumpSpeed;
+ btScalar m_maxJumpHeight;
+ btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
+ btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization)
+ btScalar m_gravity;
+ btScalar m_recoveringFactor;
+
+ btScalar m_stepHeight;
+
+ ///this is the desired walk direction, set by the user
+ btVector3 m_walkDirection;
+
+ ///keep track of the contact manifolds
+ btManifoldArray m_manifoldArray;
+
+ ///Gravity attributes
+ bool m_wasJumping;
+
+ bool m_useGhostObjectSweepTest;
+ bool m_useWalkDirection;
+ btScalar m_velocityTimeInterval;
+
+ UpAxis m_upAxis;
+
+ static btVector3* getUpAxisDirections();
+
+ bool recoverFromPenetration ( btCollisionWorld* collisionWorld );
+
+ btVector3 stepUp( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar& currentStepOffset );
+ btVector3 stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove );
+ btScalar addFallOffset( bool wasJumping, btScalar currentStepOffset, btScalar dt );
+ btVector3 stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset );
+
+public:
+ /// externalGhostObject is used for querying the collisions for sliding along the wall,
+ /// and internalGhostObject is used for querying the collisions for recovering from large penetrations.
+ /// These parameters can point on the same object.
+ /// Using a smaller internalGhostObject can help for removing some flickering but create some
+ /// stopping artefacts when sliding along stairs or small walls.
+ /// Don't forget to scale gravity and fallSpeed if you scale the world.
+ btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject,
+ btPairCachingGhostObject* internalGhostObject,
+ btScalar stepHeight,
+ btScalar constantScale = btScalar( 1.0 ),
+ btScalar gravity = btScalar( 9.8 ),
+ btScalar fallVelocity = btScalar( 55.0 ),
+ btScalar jumpVelocity = btScalar( 9.8 ),
+ btScalar recoveringFactor = btScalar( 0.2 ) );
+
+ ~btKinematicCharacterController ();
+
+ void setVerticalVelocity(float z);
+
+ ///btActionInterface interface
+ virtual void updateAction( btCollisionWorld* collisionWorld, btScalar deltaTime )
+ {
+ preStep( collisionWorld );
+ playerStep( collisionWorld, deltaTime );
+ }
+
+ ///btActionInterface interface
+ void debugDraw( btIDebugDraw* debugDrawer );
+
+ void setUpAxis( UpAxis axis )
+ {
+ m_upAxis = axis;
+ }
+
+ /// This should probably be called setPositionIncrementPerSimulatorStep.
+ /// This is neither a direction nor a velocity, but the amount to
+ /// increment the position each simulation iteration, regardless
+ /// of dt.
+ /// This call will reset any velocity set by setVelocityForTimeInterval().
+ virtual void setWalkDirection(const btVector3& walkDirection);
+
+ /// Caller provides a velocity with which the character should move for
+ /// the given time period. After the time period, velocity is reset
+ /// to zero.
+ /// This call will reset any walk direction set by setWalkDirection().
+ /// Negative time intervals will result in no motion.
+ virtual void setVelocityForTimeInterval(const btVector3& velocity,
+ btScalar timeInterval);
+
+ void reset();
+ void warp( const btVector3& origin );
+
+ void preStep( btCollisionWorld* collisionWorld );
+ void playerStep( btCollisionWorld* collisionWorld, btScalar dt );
+
+ void setFallSpeed( btScalar fallSpeed );
+ void setJumpSpeed( btScalar jumpSpeed );
+ void setMaxJumpHeight( btScalar maxJumpHeight );
+ bool canJump() const;
+
+ void jump();
+
+ void setGravity( btScalar gravity );
+ btScalar getGravity() const;
+
+ /// The max slope determines the maximum angle that the controller can walk up.
+ /// The slope angle is measured in radians.
+ void setMaxSlope( btScalar slopeRadians );
+ btScalar getMaxSlope() const;
+
+ void setUseGhostSweepTest( bool useGhostObjectSweepTest )
+ {
+ m_useGhostObjectSweepTest = useGhostObjectSweepTest;
+ }
+
+ bool onGround() const;
+
+ //if set to false, there will be no collision.
+ bool mCollision;
+};
+
+#endif // KINEMATIC_CHARACTER_CONTROLLER_H
diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp
new file mode 100644
index 0000000000..e33edda183
--- /dev/null
+++ b/libs/openengine/bullet/physic.cpp
@@ -0,0 +1,799 @@
+#include "physic.hpp"
+#include <btBulletDynamicsCommon.h>
+#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"
+#include "BtOgreGP.h"
+#include "BtOgreExtras.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+namespace OEngine {
+namespace Physic
+{
+
+ PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale)
+ : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0)
+ , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0)
+ , mForce(0.0f)
+ {
+ mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation);
+ mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true);
+ Ogre::Quaternion inverse = mBoxRotation.Inverse();
+ mBoxRotationInverse = btQuaternion(inverse.x, inverse.y, inverse.z,inverse.w);
+ mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map
+ }
+
+ PhysicActor::~PhysicActor()
+ {
+ if(mBody)
+ {
+ mEngine->dynamicsWorld->removeRigidBody(mBody);
+ delete mBody;
+ }
+ if(mRaycastingBody)
+ {
+ mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody);
+ delete mRaycastingBody;
+ }
+ }
+
+ void PhysicActor::enableCollisions(bool collision)
+ {
+ assert(mBody);
+ if(collision && !mCollisionMode) enableCollisionBody();
+ if(!collision && mCollisionMode) disableCollisionBody();
+ mCollisionMode = collision;
+ }
+
+
+ void PhysicActor::setPosition(const Ogre::Vector3 &pos)
+ {
+ assert(mBody);
+ if(pos != getPosition())
+ {
+ mEngine->adjustRigidBody(mBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation);
+ mEngine->adjustRigidBody(mRaycastingBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation);
+ }
+ }
+
+ void PhysicActor::setRotation(const Ogre::Quaternion &quat)
+ {
+ assert(mBody);
+ if(!quat.equals(getRotation(), Ogre::Radian(0))){
+ mEngine->adjustRigidBody(mBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation);
+ mEngine->adjustRigidBody(mRaycastingBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation);
+
+ }
+ }
+
+
+ Ogre::Vector3 PhysicActor::getPosition()
+ {
+ assert(mBody);
+ btVector3 vec = mBody->getWorldTransform().getOrigin();
+ Ogre::Quaternion rotation = Ogre::Quaternion(mBody->getWorldTransform().getRotation().getW(), mBody->getWorldTransform().getRotation().getX(),
+ mBody->getWorldTransform().getRotation().getY(), mBody->getWorldTransform().getRotation().getZ());
+ Ogre::Vector3 transrot = rotation * mBoxScaledTranslation;
+ Ogre::Vector3 visualPosition = Ogre::Vector3(vec.getX(), vec.getY(), vec.getZ()) - transrot;
+ return visualPosition;
+ }
+
+ Ogre::Quaternion PhysicActor::getRotation()
+ {
+ assert(mBody);
+ btQuaternion quat = mBody->getWorldTransform().getRotation() * mBoxRotationInverse;
+ return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ());
+ }
+
+ void PhysicActor::setScale(float scale){
+ //We only need to change the scaled box translation, box rotations remain the same.
+ assert(mBody);
+ mBoxScaledTranslation = mBoxScaledTranslation / mBody->getCollisionShape()->getLocalScaling().getX();
+ mBoxScaledTranslation *= scale;
+ Ogre::Vector3 pos = getPosition();
+ Ogre::Quaternion rot = getRotation();
+ if(mBody){
+ mEngine->dynamicsWorld->removeRigidBody(mBody);
+ mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody);
+ delete mBody;
+ delete mRaycastingBody;
+ }
+ //Create the newly scaled rigid body
+ mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot);
+ mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot, 0, 0, true);
+ mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map
+ }
+
+ Ogre::Vector3 PhysicActor::getHalfExtents() const
+ {
+ if(mBody)
+ {
+ btBoxShape *box = static_cast<btBoxShape*>(mBody->getCollisionShape());
+ if(box != NULL)
+ {
+ btVector3 size = box->getHalfExtentsWithMargin();
+ return Ogre::Vector3(size.getX(), size.getY(), size.getZ());
+ }
+ }
+ return Ogre::Vector3(0.0f);
+ }
+
+ void PhysicActor::setInertialForce(const Ogre::Vector3 &force)
+ {
+ mForce = force;
+ }
+
+ void PhysicActor::setOnGround(bool grounded)
+ {
+ mOnGround = grounded;
+ }
+
+ void PhysicActor::disableCollisionBody()
+ {
+ mEngine->dynamicsWorld->removeRigidBody(mBody);
+ }
+
+ void PhysicActor::enableCollisionBody()
+ {
+ mEngine->dynamicsWorld->addRigidBody(mBody,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+ RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name)
+ : btRigidBody(CI)
+ , mName(name)
+ , mPlaceable(false)
+ {
+ }
+
+ RigidBody::~RigidBody()
+ {
+ delete getMotionState();
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) :
+ mDebugActive(0)
+ , mSceneMgr(NULL)
+ {
+ // Set up the collision configuration and dispatcher
+ collisionConfiguration = new btDefaultCollisionConfiguration();
+ dispatcher = new btCollisionDispatcher(collisionConfiguration);
+
+ // The actual physics solver
+ solver = new btSequentialImpulseConstraintSolver;
+
+ //btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache();
+ pairCache = new btSortedOverlappingPairCache();
+
+ //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() );
+
+ broadphase = new btDbvtBroadphase();
+
+ // The world.
+ dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);
+ dynamicsWorld->setGravity(btVector3(0,0,-10));
+
+ if(BulletShapeManager::getSingletonPtr() == NULL)
+ {
+ new BulletShapeManager();
+ }
+ //TODO:singleton?
+ mShapeLoader = shapeLoader;
+
+ isDebugCreated = false;
+ mDebugDrawer = NULL;
+ }
+
+ void PhysicEngine::createDebugRendering()
+ {
+ if(!isDebugCreated)
+ {
+ Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld);
+ dynamicsWorld->setDebugDrawer(mDebugDrawer);
+ isDebugCreated = true;
+ dynamicsWorld->debugDrawWorld();
+ }
+ }
+
+ void PhysicEngine::setDebugRenderingMode(int mode)
+ {
+ if(!isDebugCreated)
+ {
+ createDebugRendering();
+ }
+ mDebugDrawer->setDebugMode(mode);
+ mDebugActive = mode;
+ }
+
+ bool PhysicEngine::toggleDebugRendering()
+ {
+ setDebugRenderingMode(!mDebugActive);
+ return mDebugActive;
+ }
+
+ void PhysicEngine::setSceneManager(Ogre::SceneManager* sceneMgr)
+ {
+ mSceneMgr = sceneMgr;
+ }
+
+ PhysicEngine::~PhysicEngine()
+ {
+ HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin();
+ for (; hf_it != mHeightFieldMap.end(); ++hf_it)
+ {
+ dynamicsWorld->removeRigidBody(hf_it->second.mBody);
+ delete hf_it->second.mShape;
+ delete hf_it->second.mBody;
+ }
+
+ RigidBodyContainer::iterator rb_it = mCollisionObjectMap.begin();
+ for (; rb_it != mCollisionObjectMap.end(); ++rb_it)
+ {
+ if (rb_it->second != NULL)
+ {
+ dynamicsWorld->removeRigidBody(rb_it->second);
+
+ delete rb_it->second;
+ rb_it->second = NULL;
+ }
+ }
+ rb_it = mRaycastingObjectMap.begin();
+ for (; rb_it != mRaycastingObjectMap.end(); ++rb_it)
+ {
+ if (rb_it->second != NULL)
+ {
+ dynamicsWorld->removeRigidBody(rb_it->second);
+
+ delete rb_it->second;
+ rb_it->second = NULL;
+ }
+ }
+
+ PhysicActorContainer::iterator pa_it = mActorMap.begin();
+ for (; pa_it != mActorMap.end(); ++pa_it)
+ {
+ if (pa_it->second != NULL)
+ {
+ delete pa_it->second;
+ pa_it->second = NULL;
+ }
+ }
+
+ delete mDebugDrawer;
+
+ delete dynamicsWorld;
+ delete solver;
+ delete collisionConfiguration;
+ delete dispatcher;
+ delete broadphase;
+ delete pairCache;
+ delete mShapeLoader;
+
+ delete BulletShapeManager::getSingletonPtr();
+ }
+
+ void PhysicEngine::addHeightField(float* heights,
+ int x, int y, float yoffset,
+ float triSize, float sqrtVerts)
+ {
+ const std::string name = "HeightField_"
+ + boost::lexical_cast<std::string>(x) + "_"
+ + boost::lexical_cast<std::string>(y);
+
+ // find the minimum and maximum heights (needed for bullet)
+ float minh = heights[0];
+ float maxh = heights[0];
+ for (int i=0; i<sqrtVerts*sqrtVerts; ++i)
+ {
+ float h = heights[i];
+
+ if (h>maxh) maxh = h;
+ if (h<minh) minh = h;
+ }
+
+ btHeightfieldTerrainShape* hfShape = new btHeightfieldTerrainShape(
+ sqrtVerts, sqrtVerts, heights, 1,
+ minh, maxh, 2,
+ PHY_FLOAT,true);
+
+ hfShape->setUseDiamondSubdivision(true);
+
+ btVector3 scl(triSize, triSize, 1);
+ hfShape->setLocalScaling(scl);
+
+ CMotionState* newMotionState = new CMotionState(this,name);
+
+ btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,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));
+
+ HeightField hf;
+ hf.mBody = body;
+ hf.mShape = hfShape;
+
+ mHeightFieldMap [name] = hf;
+
+ dynamicsWorld->addRigidBody(body,CollisionType_HeightMap|CollisionType_Raycasting,
+ CollisionType_World|CollisionType_Actor|CollisionType_Raycasting);
+ }
+
+ void PhysicEngine::removeHeightField(int x, int y)
+ {
+ const std::string name = "HeightField_"
+ + boost::lexical_cast<std::string>(x) + "_"
+ + boost::lexical_cast<std::string>(y);
+
+ HeightField hf = mHeightFieldMap [name];
+
+ dynamicsWorld->removeRigidBody(hf.mBody);
+ delete hf.mShape;
+ delete hf.mBody;
+
+ mHeightFieldMap.erase(name);
+ }
+
+ void PhysicEngine::adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
+ const Ogre::Vector3 &scaledBoxTranslation, const Ogre::Quaternion &boxRotation)
+ {
+ btTransform tr;
+ Ogre::Quaternion boxrot = rotation * boxRotation;
+ Ogre::Vector3 transrot = boxrot * scaledBoxTranslation;
+ Ogre::Vector3 newPosition = transrot + position;
+
+ tr.setOrigin(btVector3(newPosition.x, newPosition.y, newPosition.z));
+ tr.setRotation(btQuaternion(boxrot.x,boxrot.y,boxrot.z,boxrot.w));
+ body->setWorldTransform(tr);
+ }
+ void PhysicEngine::boxAdjustExternal(const std::string &mesh, RigidBody* body,
+ float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation)
+ {
+ std::string sid = (boost::format("%07.3f") % scale).str();
+ std::string outputstring = mesh + sid;
+ //std::cout << "The string" << outputstring << "\n";
+
+ //get the shape from the .nif
+ mShapeLoader->load(outputstring,"General");
+ BulletShapeManager::getSingletonPtr()->load(outputstring,"General");
+ BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring,"General");
+
+ adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation);
+ }
+
+ RigidBody* PhysicEngine::createAndAdjustRigidBody(const std::string &mesh, const std::string &name,
+ float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
+ Ogre::Vector3* scaledBoxTranslation, Ogre::Quaternion* boxRotation, bool raycasting, bool placeable)
+ {
+ std::string sid = (boost::format("%07.3f") % scale).str();
+ std::string outputstring = mesh + sid;
+
+ //get the shape from the .nif
+ mShapeLoader->load(outputstring,"General");
+ BulletShapeManager::getSingletonPtr()->load(outputstring,"General");
+ BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring,"General");
+
+ if (placeable && !raycasting && shape->mCollisionShape && !shape->mHasCollisionNode)
+ return NULL;
+
+ if (!shape->mCollisionShape && !raycasting)
+ return NULL;
+ if (!shape->mRaycastingShape && raycasting)
+ return NULL;
+
+ if (!raycasting)
+ shape->mCollisionShape->setLocalScaling( btVector3(scale,scale,scale));
+ 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);
+ RigidBody* body = new RigidBody(CI,name);
+ body->mPlaceable = placeable;
+
+ if(scaledBoxTranslation != 0)
+ *scaledBoxTranslation = shape->mBoxTranslation * scale;
+ if(boxRotation != 0)
+ *boxRotation = shape->mBoxRotation;
+
+ adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation);
+
+ return body;
+
+ }
+
+ void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody,bool actor)
+ {
+ if(!body && !raycastingBody)
+ return; // nothing to do
+
+ const std::string& name = (body ? body->mName : raycastingBody->mName);
+
+ if (body){
+ if(actor) dynamicsWorld->addRigidBody(body,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap);
+ else dynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap);
+ }
+
+ if (raycastingBody)
+ dynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_World);
+
+ if(addToMap){
+ removeRigidBody(name);
+ deleteRigidBody(name);
+
+ if (body)
+ mCollisionObjectMap[name] = body;
+ if (raycastingBody)
+ mRaycastingObjectMap[name] = raycastingBody;
+ }
+ }
+
+ void PhysicEngine::removeRigidBody(const std::string &name)
+ {
+ RigidBodyContainer::iterator it = mCollisionObjectMap.find(name);
+ if (it != mCollisionObjectMap.end() )
+ {
+ RigidBody* body = it->second;
+ if(body != NULL)
+ {
+ dynamicsWorld->removeRigidBody(body);
+ }
+ }
+ it = mRaycastingObjectMap.find(name);
+ if (it != mRaycastingObjectMap.end() )
+ {
+ RigidBody* body = it->second;
+ if(body != NULL)
+ {
+ dynamicsWorld->removeRigidBody(body);
+ }
+ }
+ }
+
+ void PhysicEngine::deleteRigidBody(const std::string &name)
+ {
+ RigidBodyContainer::iterator it = mCollisionObjectMap.find(name);
+ if (it != mCollisionObjectMap.end() )
+ {
+ RigidBody* body = it->second;
+
+ if(body != NULL)
+ {
+ delete body;
+ }
+ mCollisionObjectMap.erase(it);
+ }
+ it = mRaycastingObjectMap.find(name);
+ if (it != mRaycastingObjectMap.end() )
+ {
+ RigidBody* body = it->second;
+
+ if(body != NULL)
+ {
+ delete body;
+ }
+ mRaycastingObjectMap.erase(it);
+ }
+ }
+
+ RigidBody* PhysicEngine::getRigidBody(const std::string &name, bool raycasting)
+ {
+ RigidBodyContainer* map = raycasting ? &mRaycastingObjectMap : &mCollisionObjectMap;
+ RigidBodyContainer::iterator it = map->find(name);
+ if (it != map->end() )
+ {
+ RigidBody* body = (*map)[name];
+ return body;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback
+ {
+ public:
+ std::vector<std::string> mResult;
+
+ // added in bullet 2.81
+ // this is just a quick hack, as there does not seem to be a BULLET_VERSION macro?
+#if defined(BT_COLLISION_OBJECT_WRAPPER_H)
+ virtual btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,int partId0,int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,int partId1,int index1)
+ {
+ const RigidBody* body = dynamic_cast<const RigidBody*>(colObj0Wrap->m_collisionObject);
+ if (body && !(colObj0Wrap->m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup
+ & CollisionType_Raycasting))
+ mResult.push_back(body->mName);
+
+ return 0.f;
+ }
+#else
+ virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0,
+ const btCollisionObject* col1, int partId1, int index1)
+ {
+ const RigidBody* body = dynamic_cast<const RigidBody*>(col0);
+ if (body && !(col0->getBroadphaseHandle()->m_collisionFilterGroup
+ & CollisionType_Raycasting))
+ mResult.push_back(body->mName);
+
+ return 0.f;
+ }
+#endif
+ };
+
+ class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback
+ {
+ const std::string &mFilter;
+ // Store the real origin, since the shape's origin is its center
+ btVector3 mOrigin;
+
+ public:
+ const RigidBody *mObject;
+ btVector3 mContactPoint;
+ btScalar mLeastDistSqr;
+
+ DeepestNotMeContactTestResultCallback(const std::string &filter, const btVector3 &origin)
+ : mFilter(filter), mOrigin(origin), mObject(0), mContactPoint(0,0,0),
+ mLeastDistSqr(std::numeric_limits<float>::max())
+ { }
+
+#if defined(BT_COLLISION_OBJECT_WRAPPER_H)
+ virtual btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
+ const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
+ {
+ const RigidBody* body = dynamic_cast<const RigidBody*>(col1Wrap->m_collisionObject);
+ if(body && body->mName != mFilter)
+ {
+ btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());
+ if(!mObject || distsqr < mLeastDistSqr)
+ {
+ mObject = body;
+ mLeastDistSqr = distsqr;
+ mContactPoint = cp.getPositionWorldOnA();
+ }
+ }
+
+ return 0.f;
+ }
+#else
+ virtual btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObject* col0, int partId0, int index0,
+ const btCollisionObject* col1, int partId1, int index1)
+ {
+ const RigidBody* body = dynamic_cast<const RigidBody*>(col1);
+ if(body && body->mName != mFilter)
+ {
+ btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());
+ if(!mObject || distsqr < mLeastDistSqr)
+ {
+ mObject = body;
+ mLeastDistSqr = distsqr;
+ mContactPoint = cp.getPositionWorldOnA();
+ }
+ }
+
+ return 0.f;
+ }
+#endif
+ };
+
+
+ std::vector<std::string> PhysicEngine::getCollisions(const std::string& name)
+ {
+ RigidBody* body = getRigidBody(name);
+ ContactTestResultCallback callback;
+ dynamicsWorld->contactTest(body, callback);
+ return callback.mResult;
+ }
+
+
+ std::pair<const RigidBody*,btVector3> PhysicEngine::getFilteredContact(const std::string &filter,
+ const btVector3 &origin,
+ btCollisionObject *object)
+ {
+ DeepestNotMeContactTestResultCallback callback(filter, origin);
+ dynamicsWorld->contactTest(object, callback);
+ return std::make_pair(callback.mObject, callback.mContactPoint);
+ }
+
+
+ void PhysicEngine::stepSimulation(double deltaT)
+ {
+ // This seems to be needed for character controller objects
+ dynamicsWorld->stepSimulation(deltaT,10, 1/60.0);
+ if(isDebugCreated)
+ {
+ mDebugDrawer->step();
+ }
+ }
+
+ void PhysicEngine::addCharacter(const std::string &name, const std::string &mesh,
+ const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation)
+ {
+ // Remove character with given name, so we don't make memory
+ // leak when character would be added twice
+ removeCharacter(name);
+
+ PhysicActor* newActor = new PhysicActor(name, mesh, this, position, rotation, scale);
+
+
+ //dynamicsWorld->addAction( newActor->mCharacter );
+ mActorMap[name] = newActor;
+ }
+
+ void PhysicEngine::removeCharacter(const std::string &name)
+ {
+ PhysicActorContainer::iterator it = mActorMap.find(name);
+ if (it != mActorMap.end() )
+ {
+ PhysicActor* act = it->second;
+ if(act != NULL)
+ {
+
+ delete act;
+ }
+ mActorMap.erase(it);
+ }
+ }
+
+ PhysicActor* PhysicEngine::getCharacter(const std::string &name)
+ {
+ PhysicActorContainer::iterator it = mActorMap.find(name);
+ if (it != mActorMap.end() )
+ {
+ PhysicActor* act = mActorMap[name];
+ return act;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ void PhysicEngine::emptyEventLists(void)
+ {
+ }
+
+ std::pair<std::string,float> PhysicEngine::rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly,bool ignoreHeightMap)
+ {
+ std::string name = "";
+ float d = -1;
+
+ btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
+ if(raycastingObjectOnly)
+ resultCallback1.m_collisionFilterMask = CollisionType_Raycasting;
+ else
+ resultCallback1.m_collisionFilterMask = CollisionType_World;
+
+ if(!ignoreHeightMap)
+ resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask | CollisionType_HeightMap;
+ dynamicsWorld->rayTest(from, to, resultCallback1);
+ if (resultCallback1.hasHit())
+ {
+ name = static_cast<const RigidBody&>(*resultCallback1.m_collisionObject).mName;
+ d = resultCallback1.m_closestHitFraction;;
+ }
+
+ return std::pair<std::string,float>(name,d);
+ }
+
+ // callback that ignores player in results
+ struct OurClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
+ {
+ public:
+ OurClosestConvexResultCallback(const btVector3& convexFromWorld,const btVector3& convexToWorld)
+ : btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld) {}
+
+ virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
+ {
+ if (const RigidBody* body = dynamic_cast<const RigidBody*>(convexResult.m_hitCollisionObject))
+ if (body->mName == "player")
+ return 0;
+ return btCollisionWorld::ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
+ }
+ };
+
+ std::pair<bool, float> PhysicEngine::sphereCast (float radius, btVector3& from, btVector3& to)
+ {
+ OurClosestConvexResultCallback callback(from, to);
+ callback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap;
+
+ btSphereShape shape(radius);
+ const btQuaternion btrot(0.0f, 0.0f, 0.0f);
+
+ btTransform from_ (btrot, from);
+ btTransform to_ (btrot, to);
+
+ dynamicsWorld->convexSweepTest(&shape, from_, to_, callback);
+
+ if (callback.hasHit())
+ return std::make_pair(true, callback.m_closestHitFraction);
+ else
+ return std::make_pair(false, 1);
+ }
+
+ std::vector< std::pair<float, std::string> > PhysicEngine::rayTest2(btVector3& from, btVector3& to)
+ {
+ MyRayResultCallback resultCallback1;
+ resultCallback1.m_collisionFilterMask = CollisionType_Raycasting;
+ dynamicsWorld->rayTest(from, to, resultCallback1);
+ std::vector< std::pair<float, const btCollisionObject*> > results = resultCallback1.results;
+
+ std::vector< std::pair<float, std::string> > results2;
+
+ for (std::vector< std::pair<float, const btCollisionObject*> >::iterator it=results.begin();
+ it != results.end(); ++it)
+ {
+ results2.push_back( std::make_pair( (*it).first, static_cast<const RigidBody&>(*(*it).second).mName ) );
+ }
+
+ std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp);
+
+ return results2;
+ }
+
+ void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max)
+ {
+ std::string sid = (boost::format("%07.3f") % scale).str();
+ std::string outputstring = mesh + sid;
+
+ mShapeLoader->load(outputstring, "General");
+ BulletShapeManager::getSingletonPtr()->load(outputstring, "General");
+ BulletShapePtr shape =
+ BulletShapeManager::getSingleton().getByName(outputstring, "General");
+
+ btTransform trans;
+ trans.setIdentity();
+
+ if (shape->mRaycastingShape)
+ shape->mRaycastingShape->getAabb(trans, min, max);
+ else if (shape->mCollisionShape)
+ shape->mCollisionShape->getAabb(trans, min, max);
+ else
+ {
+ min = btVector3(0,0,0);
+ max = btVector3(0,0,0);
+ }
+ }
+
+ bool PhysicEngine::isAnyActorStandingOn (const std::string& objectName)
+ {
+ for (PhysicActorContainer::iterator it = mActorMap.begin(); it != mActorMap.end(); ++it)
+ {
+ if (!it->second->getOnGround())
+ continue;
+ Ogre::Vector3 pos = it->second->getPosition();
+ btVector3 from (pos.x, pos.y, pos.z);
+ btVector3 to = from - btVector3(0,0,5);
+ std::pair<std::string, float> result = rayTest(from, to);
+ if (result.first == objectName)
+ return true;
+ }
+ return false;
+ }
+
+}
+}
diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp
new file mode 100644
index 0000000000..f28f95ccb8
--- /dev/null
+++ b/libs/openengine/bullet/physic.hpp
@@ -0,0 +1,389 @@
+#ifndef OENGINE_BULLET_PHYSIC_H
+#define OENGINE_BULLET_PHYSIC_H
+
+#include <BulletDynamics/Dynamics/btRigidBody.h>
+#include "BulletCollision/CollisionDispatch/btGhostObject.h"
+#include <string>
+#include <list>
+#include <map>
+#include "BulletShapeLoader.h"
+#include "BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h"
+
+
+
+class btRigidBody;
+class btBroadphaseInterface;
+class btDefaultCollisionConfiguration;
+class btSequentialImpulseConstraintSolver;
+class btCollisionDispatcher;
+class btDiscreteDynamicsWorld;
+class btHeightfieldTerrainShape;
+
+namespace BtOgre
+{
+ class DebugDrawer;
+}
+
+namespace Ogre
+{
+ class SceneManager;
+}
+
+namespace MWWorld
+{
+ class World;
+}
+
+
+namespace OEngine {
+namespace Physic
+{
+ class CMotionState;
+ struct PhysicEvent;
+ class PhysicEngine;
+ class RigidBody;
+
+ enum CollisionType {
+ CollisionType_Nothing = 0, //<Collide with nothing
+ CollisionType_World = 1<<0, //<Collide with world objects
+ CollisionType_Actor = 1<<1, //<Collide sith actors
+ CollisionType_HeightMap = 1<<2, //<collide with heightmap
+ CollisionType_Raycasting = 1<<3 //Still used?
+ };
+
+ /**
+ *This is just used to be able to name objects.
+ */
+ class PairCachingGhostObject : public btPairCachingGhostObject
+ {
+ public:
+ PairCachingGhostObject(std::string name)
+ :btPairCachingGhostObject(),mName(name)
+ {
+ }
+ virtual ~PairCachingGhostObject(){}
+
+ std::string mName;
+ };
+
+ /**
+ *This class is just an extension of normal btRigidBody in order to add extra info.
+ *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody,
+ *so one never should use btRigidBody directly!
+ */
+ class RigidBody: public btRigidBody
+ {
+ public:
+ RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name);
+ virtual ~RigidBody();
+ std::string mName;
+ bool mPlaceable;
+ };
+
+ /**
+ * A physic actor uses a rigid body based on box shapes.
+ * Pmove is used to move the physic actor around the dynamic world.
+ */
+ class PhysicActor
+ {
+ public:
+ PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale);
+
+ ~PhysicActor();
+
+ void setPosition(const Ogre::Vector3 &pos);
+
+ /**
+ * This adjusts the rotation of a PhysicActor
+ * If we have any problems with this (getting stuck in pmove) we should change it
+ * from setting the visual orientation to setting the orientation of the rigid body directly.
+ */
+ void setRotation(const Ogre::Quaternion &quat);
+
+ void enableCollisions(bool collision);
+
+ bool getCollisionMode() const
+ {
+ return mCollisionMode;
+ }
+
+
+ /**
+ * This returns the visual position of the PhysicActor (used to position a scenenode).
+ * Note - this is different from the position of the contained mBody.
+ */
+ Ogre::Vector3 getPosition();
+
+ /**
+ * Returns the visual orientation of the PhysicActor
+ */
+ Ogre::Quaternion getRotation();
+
+ /**
+ * Sets the scale of the PhysicActor
+ */
+ void setScale(float scale);
+
+ /**
+ * Returns the half extents for this PhysiActor
+ */
+ Ogre::Vector3 getHalfExtents() const;
+
+ /**
+ * Sets the current amount of inertial force (incl. gravity) affecting this physic actor
+ */
+ void setInertialForce(const Ogre::Vector3 &force);
+
+ /**
+ * Gets the current amount of inertial force (incl. gravity) affecting this physic actor
+ */
+ const Ogre::Vector3 &getInertialForce() const
+ {
+ return mForce;
+ }
+
+ void setOnGround(bool grounded);
+
+ bool getOnGround() const
+ {
+ return mCollisionMode && mOnGround;
+ }
+
+ btCollisionObject *getCollisionBody() const
+ {
+ return mBody;
+ }
+
+ 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;
+
+ Ogre::Vector3 mBoxScaledTranslation;
+ Ogre::Quaternion mBoxRotation;
+ btQuaternion mBoxRotationInverse;
+
+ Ogre::Vector3 mForce;
+ bool mOnGround;
+ bool mCollisionMode;
+
+ std::string mMesh;
+ std::string mName;
+ PhysicEngine *mEngine;
+ };
+
+
+ struct HeightField
+ {
+ btHeightfieldTerrainShape* mShape;
+ RigidBody* mBody;
+ };
+
+ /**
+ * The PhysicEngine class contain everything which is needed for Physic.
+ * It's needed that Ogre Resources are set up before the PhysicEngine is created.
+ * Note:deleting it WILL NOT delete the RigidBody!
+ * TODO:unload unused resources?
+ */
+ class PhysicEngine
+ {
+ public:
+ /**
+ * Note that the shapeLoader IS destroyed by the phyic Engine!!
+ */
+ PhysicEngine(BulletShapeLoader* shapeLoader);
+
+ /**
+ * It DOES destroy the shape loader!
+ */
+ ~PhysicEngine();
+
+ /**
+ * Creates a RigidBody. It does not add it to the simulation.
+ * After created, the body is set to the correct rotation, position, and scale
+ */
+ RigidBody* createAndAdjustRigidBody(const std::string &mesh, const std::string &name,
+ float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
+ Ogre::Vector3* scaledBoxTranslation = 0, Ogre::Quaternion* boxRotation = 0, bool raycasting=false, bool placeable=false);
+
+ /**
+ * Adjusts a rigid body to the right position and rotation
+ */
+
+ void adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
+ const Ogre::Vector3 &scaledBoxTranslation = Ogre::Vector3::ZERO,
+ const Ogre::Quaternion &boxRotation = Ogre::Quaternion::IDENTITY);
+ /**
+ Mainly used to (but not limited to) adjust rigid bodies based on box shapes to the right position and rotation.
+ */
+ void boxAdjustExternal(const std::string &mesh, RigidBody* body, float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation);
+ /**
+ * Add a HeightField to the simulation
+ */
+ void addHeightField(float* heights,
+ int x, int y, float yoffset,
+ float triSize, float sqrtVerts);
+
+ /**
+ * Remove a HeightField from the simulation
+ */
+ void removeHeightField(int x, int y);
+
+ /**
+ * Add a RigidBody to the simulation
+ */
+ void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL,bool actor = false);
+
+ /**
+ * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap.
+ */
+ void removeRigidBody(const std::string &name);
+
+ /**
+ * Delete a RigidBody, and remove it from RigidBodyMap.
+ */
+ void deleteRigidBody(const std::string &name);
+
+ /**
+ * Return a pointer to a given rigid body.
+ */
+ RigidBody* getRigidBody(const std::string &name, bool raycasting=false);
+
+ /**
+ * Create and add a character to the scene, and add it to the ActorMap.
+ */
+ void addCharacter(const std::string &name, const std::string &mesh,
+ const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation);
+
+ /**
+ * Remove a character from the scene. TODO:delete it! for now, a small memory leak^^ done?
+ */
+ void removeCharacter(const std::string &name);
+
+ /**
+ * Return a pointer to a character
+ * TODO:check if the actor exist...
+ */
+ PhysicActor* getCharacter(const std::string &name);
+
+ /**
+ * This step the simulation of a given time.
+ */
+ void stepSimulation(double deltaT);
+
+ /**
+ * Empty events lists
+ */
+ void emptyEventLists(void);
+
+ /**
+ * Create a debug rendering. It is called by setDebgRenderingMode if it's not created yet.
+ * Important Note: this will crash if the Render is not yet initialise!
+ */
+ void createDebugRendering();
+
+ /**
+ * Set the debug rendering mode. 0 to turn it off.
+ * Important Note: this will crash if the Render is not yet initialise!
+ */
+ void setDebugRenderingMode(int mode);
+
+ bool toggleDebugRendering();
+
+ void getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max);
+
+ void setSceneManager(Ogre::SceneManager* sceneMgr);
+
+ bool isAnyActorStandingOn (const std::string& objectName);
+
+ /**
+ * Return the closest object hit by a ray. If there are no objects, it will return ("",-1).
+ */
+ std::pair<std::string,float> rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly = true,bool ignoreHeightMap = false);
+
+ /**
+ * Return all objects hit by a ray.
+ */
+ std::vector< std::pair<float, std::string> > rayTest2(btVector3& from, btVector3& to);
+
+ std::pair<bool, float> sphereCast (float radius, btVector3& from, btVector3& to);
+ ///< @return (hit, relative distance)
+
+ std::vector<std::string> getCollisions(const std::string& name);
+
+ // Get the nearest object that's inside the given object, filtering out objects of the
+ // provided name
+ std::pair<const RigidBody*,btVector3> getFilteredContact(const std::string &filter,
+ 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;
+ btDefaultCollisionConfiguration* collisionConfiguration;
+ btSequentialImpulseConstraintSolver* solver;
+ btCollisionDispatcher* dispatcher;
+ btDiscreteDynamicsWorld* dynamicsWorld;
+
+ //the NIF file loader.
+ BulletShapeLoader* mShapeLoader;
+
+ typedef std::map<std::string, HeightField> HeightFieldContainer;
+ HeightFieldContainer mHeightFieldMap;
+
+ typedef std::map<std::string,RigidBody*> RigidBodyContainer;
+ RigidBodyContainer mCollisionObjectMap;
+
+ RigidBodyContainer mRaycastingObjectMap;
+
+ typedef std::map<std::string, PhysicActor*> PhysicActorContainer;
+ PhysicActorContainer mActorMap;
+
+ Ogre::SceneManager* mSceneMgr;
+
+ //debug rendering
+ BtOgre::DebugDrawer* mDebugDrawer;
+ bool isDebugCreated;
+ bool mDebugActive;
+ };
+
+
+ struct MyRayResultCallback : public btCollisionWorld::RayResultCallback
+ {
+ virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace)
+ {
+ results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) );
+ return rayResult.m_hitFraction;
+ }
+
+ static bool cmp( const std::pair<float, std::string>& i, const std::pair<float, std::string>& j )
+ {
+ if( i.first > j.first ) return false;
+ if( j.first > i.first ) return true;
+ return false;
+ }
+
+ std::vector < std::pair<float, const btCollisionObject*> > results;
+ };
+
+}}
+
+#endif
diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp
new file mode 100644
index 0000000000..afda52448e
--- /dev/null
+++ b/libs/openengine/bullet/trace.cpp
@@ -0,0 +1,130 @@
+
+#include "trace.h"
+
+#include <map>
+
+#include <btBulletDynamicsCommon.h>
+#include <btBulletCollisionCommon.h>
+
+#include "physic.hpp"
+
+
+namespace OEngine
+{
+namespace Physic
+{
+
+class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
+{
+public:
+ ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot)
+ : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
+ mMe(me), mUp(up), mMinSlopeDot(minSlopeDot)
+ {
+ }
+
+ virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
+ {
+ if(convexResult.m_hitCollisionObject == mMe)
+ return btScalar( 1 );
+
+ btVector3 hitNormalWorld;
+ if(normalInWorldSpace)
+ hitNormalWorld = convexResult.m_hitNormalLocal;
+ else
+ {
+ ///need to transform normal into worldspace
+ hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
+ }
+
+ // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box...
+
+ btScalar dotUp = mUp.dot(hitNormalWorld);
+ if(dotUp < mMinSlopeDot)
+ return btScalar(1);
+
+ return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
+ }
+
+protected:
+ btCollisionObject *mMe;
+ const btVector3 mUp;
+ const btScalar mMinSlopeDot;
+};
+
+
+void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass)
+{
+ const btVector3 btstart(start.x, start.y, start.z);
+ const btVector3 btend(end.x, end.y, end.z);
+
+ const btTransform &trans = actor->getWorldTransform();
+ btTransform from(trans);
+ btTransform to(trans);
+ from.setOrigin(btstart);
+ to.setOrigin(btend);
+
+ ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0));
+ newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap |
+ CollisionType_Actor;
+
+ btCollisionShape *shape = actor->getCollisionShape();
+ assert(shape->isConvex());
+ enginePass->dynamicsWorld->convexSweepTest(static_cast<btConvexShape*>(shape),
+ from, to, newTraceCallback);
+
+ // Copy the hit data over to our trace results struct:
+ if(newTraceCallback.hasHit())
+ {
+ const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
+ mFraction = newTraceCallback.m_closestHitFraction;
+ mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
+ mEndPos = (end-start)*mFraction + start;
+ }
+ else
+ {
+ mEndPos = end;
+ mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f);
+ mFraction = 1.0f;
+ }
+}
+
+void ActorTracer::findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass)
+{
+ const btVector3 btstart(start.x, start.y, start.z+1.0f);
+ const btVector3 btend(end.x, end.y, end.z+1.0f);
+
+ const btTransform &trans = actor->getWorldTransform();
+ btTransform from(trans.getBasis(), btstart);
+ btTransform to(trans.getBasis(), btend);
+
+ ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0));
+ newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap |
+ CollisionType_Actor;
+
+ const btBoxShape *shape = dynamic_cast<btBoxShape*>(actor->getCollisionShape());
+ assert(shape);
+
+ btVector3 halfExtents = shape->getHalfExtentsWithMargin();
+ halfExtents[2] = 1.0f;
+ btBoxShape box(halfExtents);
+
+ enginePass->dynamicsWorld->convexSweepTest(&box, from, to, newTraceCallback);
+ if(newTraceCallback.hasHit())
+ {
+ const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
+ mFraction = newTraceCallback.m_closestHitFraction;
+ mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
+ mEndPos = (end-start)*mFraction + start;
+ mEndPos[2] -= 1.0f;
+ }
+ else
+ {
+ mEndPos = end;
+ mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f);
+ mFraction = 1.0f;
+ }
+}
+
+}
+}
diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h
new file mode 100644
index 0000000000..92795c87fa
--- /dev/null
+++ b/libs/openengine/bullet/trace.h
@@ -0,0 +1,31 @@
+#ifndef OENGINE_BULLET_TRACE_H
+#define OENGINE_BULLET_TRACE_H
+
+#include <OgreVector3.h>
+
+
+class btCollisionObject;
+
+
+namespace OEngine
+{
+namespace Physic
+{
+ class PhysicEngine;
+
+ struct ActorTracer
+ {
+ Ogre::Vector3 mEndPos;
+ Ogre::Vector3 mPlaneNormal;
+
+ float mFraction;
+
+ void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end,
+ const PhysicEngine *enginePass);
+ void findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end,
+ const PhysicEngine *enginePass);
+ };
+}
+}
+
+#endif
diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp
new file mode 100644
index 0000000000..9040dfb90e
--- /dev/null
+++ b/libs/openengine/gui/layout.hpp
@@ -0,0 +1,162 @@
+#ifndef OENGINE_MYGUI_LAYOUT_H
+#define OENGINE_MYGUI_LAYOUT_H
+
+#include <MyGUI.h>
+
+namespace OEngine {
+namespace GUI
+{
+ /** The Layout class is an utility class used to load MyGUI layouts
+ from xml files, and to manipulate member widgets.
+ */
+ class Layout
+ {
+ public:
+ Layout(const std::string & _layout, MyGUI::Widget* _parent = nullptr)
+ : mMainWidget(nullptr)
+ { initialise(_layout, _parent); }
+ virtual ~Layout() { shutdown(); }
+
+ template <typename T>
+ void getWidget(T * & _widget, const std::string & _name, bool _throw = true)
+ {
+ _widget = nullptr;
+ for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin();
+ iter!=mListWindowRoot.end(); ++iter)
+ {
+ MyGUI::Widget* find = (*iter)->findWidget(mPrefix + _name);
+ if (nullptr != find)
+ {
+ T * cast = find->castType<T>(false);
+ if (nullptr != cast)
+ _widget = cast;
+ else if (_throw)
+ {
+ MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName()
+ << "' source name = '" << find->getName()
+ << "' source type = '" << find->getTypeName() << "' in layout '" << mLayoutName << "'");
+ }
+ return;
+ }
+ }
+ MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' in layout '" << mLayoutName << "' not found.");
+ }
+
+ void initialise(const std::string & _layout,
+ MyGUI::Widget* _parent = nullptr)
+ {
+ const std::string MAIN_WINDOW = "_Main";
+ mLayoutName = _layout;
+
+ if (mLayoutName.empty())
+ mMainWidget = _parent;
+ else
+ {
+ mPrefix = MyGUI::utility::toString(this, "_");
+ mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent);
+
+ const std::string main_name = mPrefix + MAIN_WINDOW;
+ for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); iter!=mListWindowRoot.end(); ++iter)
+ {
+ if ((*iter)->getName() == main_name)
+ {
+ mMainWidget = (*iter);
+ break;
+ }
+ }
+ MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found.");
+ }
+ }
+
+ void shutdown()
+ {
+ MyGUI::Gui::getInstance().destroyWidget(mMainWidget);
+ mListWindowRoot.clear();
+ }
+
+ void setCoord(int x, int y, int w, int h)
+ {
+ mMainWidget->setCoord(x,y,w,h);
+ }
+
+ void adjustWindowCaption()
+ {
+ // adjust the size of the window caption so that all text is visible
+ // NOTE: this assumes that mMainWidget is of type Window.
+ MyGUI::TextBox* box = static_cast<MyGUI::Window*>(mMainWidget)->getCaptionWidget();
+ box->setSize(box->getTextSize().width + 24, box->getSize().height);
+
+ // in order to trigger alignment updates, we need to update the parent
+ // mygui doesn't provide a proper way of doing this, so we are just changing size
+ box->getParent()->setCoord(MyGUI::IntCoord(
+ box->getParent()->getCoord().left,
+ box->getParent()->getCoord().top,
+ box->getParent()->getCoord().width,
+ box->getParent()->getCoord().height+1
+ ));
+ box->getParent()->setCoord(MyGUI::IntCoord(
+ box->getParent()->getCoord().left,
+ box->getParent()->getCoord().top,
+ box->getParent()->getCoord().width,
+ box->getParent()->getCoord().height-1
+ ));
+ }
+
+ virtual void setVisible(bool b)
+ {
+ mMainWidget->setVisible(b);
+ }
+
+ void setText(const std::string& name, const std::string& caption)
+ {
+ MyGUI::Widget* pt;
+ getWidget(pt, name);
+ static_cast<MyGUI::TextBox*>(pt)->setCaption(caption);
+ }
+
+ void setTitle(const std::string& title)
+ {
+ // NOTE: this assume that mMainWidget is of type Window.
+ static_cast<MyGUI::Window*>(mMainWidget)->setCaptionWithReplacing(title);
+ adjustWindowCaption();
+ }
+
+ void setState(const std::string& widget, const std::string& state)
+ {
+ MyGUI::Widget* pt;
+ getWidget(pt, widget);
+ pt->_setWidgetState(state);
+ }
+
+ void setTextColor(const std::string& name, float r, float g, float b)
+ {
+ MyGUI::Widget* pt;
+ getWidget(pt, name);
+ MyGUI::TextBox *st = dynamic_cast<MyGUI::TextBox*>(pt);
+ if(st != NULL)
+ st->setTextColour(MyGUI::Colour(b,g,r));
+ }
+
+ void setImage(const std::string& name, const std::string& imgName)
+ {
+ MyGUI::ImageBox* pt;
+ getWidget(pt, name);
+ pt->setImageTexture(imgName);
+ }
+
+ void adjustButtonSize(MyGUI::Button* button)
+ {
+ // adjust size of button to fit its text
+ MyGUI::IntSize size = button->getTextSize();
+ button->setSize(size.width + 24, button->getSize().height);
+ }
+
+ protected:
+
+ MyGUI::Widget* mMainWidget;
+ std::string mPrefix;
+ std::string mLayoutName;
+ MyGUI::VectorWidgetPtr mListWindowRoot;
+ };
+}}
+#endif
diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp
new file mode 100644
index 0000000000..91937d24bc
--- /dev/null
+++ b/libs/openengine/gui/manager.cpp
@@ -0,0 +1,652 @@
+#include "manager.hpp"
+
+#include <MyGUI_Gui.h>
+#include <MyGUI_OgrePlatform.h>
+#include <MyGUI_Timer.h>
+
+#include <cassert>
+
+#include <extern/shiny/Main/Factory.hpp>
+#include <extern/shiny/Platforms/Ogre/OgreMaterial.hpp>
+
+using namespace OEngine::GUI;
+
+namespace MyGUI
+{
+
+/*
+ * As of MyGUI 3.2.0, MyGUI::OgreDataManager::isDataExist is unnecessarily complex
+ * this override fixes the resulting performance issue.
+ */
+class FixedOgreDataManager : public MyGUI::OgreDataManager
+{
+public:
+ bool isDataExist(const std::string& _name)
+ {
+ return Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup (_name);
+ }
+};
+
+
+/*
+ * As of MyGUI 3.2.0, rendering with shaders is not supported.
+ * We definitely need this though to run in GL3 core / DX11 at all.
+ * To make matters worse, subclassing OgreRenderManager doesn't seem to be possible here. :/
+ */
+class ShaderBasedRenderManager : public RenderManager,
+ public IRenderTarget,
+ public Ogre::WindowEventListener,
+ public Ogre::RenderQueueListener,
+ public Ogre::RenderSystem::Listener
+{
+ // флаг Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð²Ñех и вÑÑ
+ bool mUpdate;
+
+ IntSize mViewSize;
+
+ Ogre::SceneManager* mSceneManager;
+
+ VertexColourType mVertexFormat;
+
+ // окно, на которое мы подпиÑываемÑÑ Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð¾Ð²
+ Ogre::RenderWindow* mWindow;
+
+ // вьюпорт, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼ работает ÑиÑтема
+ unsigned short mActiveViewport;
+
+ Ogre::RenderSystem* mRenderSystem;
+ Ogre::TextureUnitState::UVWAddressingMode mTextureAddressMode;
+ Ogre::LayerBlendModeEx mColorBlendMode, mAlphaBlendMode;
+
+ RenderTargetInfo mInfo;
+
+ typedef std::map<std::string, ITexture*> MapTexture;
+ MapTexture mTextures;
+
+ bool mIsInitialise;
+ bool mManualRender;
+ size_t mCountBatch;
+
+ // ADDED
+ Ogre::GpuProgram* mVertexProgramNoTexture;
+ Ogre::GpuProgram* mVertexProgramOneTexture;
+ Ogre::GpuProgram* mFragmentProgramNoTexture;
+ Ogre::GpuProgram* mFragmentProgramOneTexture;
+
+public:
+ ShaderBasedRenderManager& getInstance()
+ {
+ return *getInstancePtr();
+ }
+ ShaderBasedRenderManager* getInstancePtr()
+ {
+ return static_cast<ShaderBasedRenderManager*>(RenderManager::getInstancePtr());
+ }
+
+ ShaderBasedRenderManager() :
+ mUpdate(false),
+ mSceneManager(nullptr),
+ mWindow(nullptr),
+ mActiveViewport(0),
+ mRenderSystem(nullptr),
+ mIsInitialise(false),
+ mManualRender(false),
+ mCountBatch(0),
+ mVertexProgramNoTexture(NULL),
+ mFragmentProgramNoTexture(NULL),
+ mVertexProgramOneTexture(NULL),
+ mFragmentProgramOneTexture(NULL)
+ {
+ }
+
+ void initialise(Ogre::RenderWindow* _window, Ogre::SceneManager* _scene)
+ {
+ MYGUI_PLATFORM_ASSERT(!mIsInitialise, getClassTypeName() << " initialised twice");
+ MYGUI_PLATFORM_LOG(Info, "* Initialise: " << getClassTypeName());
+
+ mColorBlendMode.blendType = Ogre::LBT_COLOUR;
+ mColorBlendMode.source1 = Ogre::LBS_TEXTURE;
+ mColorBlendMode.source2 = Ogre::LBS_DIFFUSE;
+ mColorBlendMode.operation = Ogre::LBX_MODULATE;
+
+ mAlphaBlendMode.blendType = Ogre::LBT_ALPHA;
+ mAlphaBlendMode.source1 = Ogre::LBS_TEXTURE;
+ mAlphaBlendMode.source2 = Ogre::LBS_DIFFUSE;
+ mAlphaBlendMode.operation = Ogre::LBX_MODULATE;
+
+ mTextureAddressMode.u = Ogre::TextureUnitState::TAM_CLAMP;
+ mTextureAddressMode.v = Ogre::TextureUnitState::TAM_CLAMP;
+ mTextureAddressMode.w = Ogre::TextureUnitState::TAM_CLAMP;
+
+ mSceneManager = nullptr;
+ mWindow = nullptr;
+ mUpdate = false;
+ mRenderSystem = nullptr;
+ mActiveViewport = 0;
+
+ Ogre::Root* root = Ogre::Root::getSingletonPtr();
+ if (root != nullptr)
+ setRenderSystem(root->getRenderSystem());
+ setRenderWindow(_window);
+ setSceneManager(_scene);
+
+ // ADDED
+ sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture");
+ sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default");
+ mVertexProgramNoTexture = static_cast<sh::OgreMaterial*>(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0)
+ ->getVertexProgram()->_getBindingDelegate();
+
+ mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture");
+ sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default");
+ mVertexProgramOneTexture = static_cast<sh::OgreMaterial*>(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0)
+ ->getVertexProgram()->_getBindingDelegate();
+
+ mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture");
+ sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default");
+ mFragmentProgramNoTexture = static_cast<sh::OgreMaterial*>(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0)
+ ->getFragmentProgram()->_getBindingDelegate();
+
+ mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture");
+ sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default");
+ mFragmentProgramOneTexture = static_cast<sh::OgreMaterial*>(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0)
+ ->getFragmentProgram()->_getBindingDelegate();
+
+
+
+ MYGUI_PLATFORM_LOG(Info, getClassTypeName() << " successfully initialized");
+ mIsInitialise = true;
+ }
+
+ void shutdown()
+ {
+ MYGUI_PLATFORM_ASSERT(mIsInitialise, getClassTypeName() << " is not initialised");
+ MYGUI_PLATFORM_LOG(Info, "* Shutdown: " << getClassTypeName());
+
+ destroyAllResources();
+
+ setSceneManager(nullptr);
+ setRenderWindow(nullptr);
+ setRenderSystem(nullptr);
+
+ MYGUI_PLATFORM_LOG(Info, getClassTypeName() << " successfully shutdown");
+ mIsInitialise = false;
+ }
+
+ void setRenderSystem(Ogre::RenderSystem* _render)
+ {
+ // отпиÑываемÑÑ
+ if (mRenderSystem != nullptr)
+ {
+ mRenderSystem->removeListener(this);
+ mRenderSystem = nullptr;
+ }
+
+ mRenderSystem = _render;
+
+ // подпиÑываемÑÑ Ð½Ð° рендер евент
+ if (mRenderSystem != nullptr)
+ {
+ mRenderSystem->addListener(this);
+
+ // формат цвета в вершинах
+ Ogre::VertexElementType vertex_type = mRenderSystem->getColourVertexElementType();
+ if (vertex_type == Ogre::VET_COLOUR_ARGB)
+ mVertexFormat = VertexColourType::ColourARGB;
+ else if (vertex_type == Ogre::VET_COLOUR_ABGR)
+ mVertexFormat = VertexColourType::ColourABGR;
+
+ updateRenderInfo();
+ }
+ }
+
+ Ogre::RenderSystem* getRenderSystem()
+ {
+ return mRenderSystem;
+ }
+
+ void setRenderWindow(Ogre::RenderWindow* _window)
+ {
+ // отпиÑываемÑÑ
+ if (mWindow != nullptr)
+ {
+ Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this);
+ mWindow = nullptr;
+ }
+
+ mWindow = _window;
+
+ if (mWindow != nullptr)
+ {
+ Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);
+ windowResized(mWindow);
+ }
+ }
+
+ void setSceneManager(Ogre::SceneManager* _scene)
+ {
+ if (nullptr != mSceneManager)
+ {
+ mSceneManager->removeRenderQueueListener(this);
+ mSceneManager = nullptr;
+ }
+
+ mSceneManager = _scene;
+
+ if (nullptr != mSceneManager)
+ {
+ mSceneManager->addRenderQueueListener(this);
+ }
+ }
+
+ void setActiveViewport(unsigned short _num)
+ {
+ mActiveViewport = _num;
+
+ if (mWindow != nullptr)
+ {
+ Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this);
+ Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);
+
+ // раÑÑылка обновлений
+ windowResized(mWindow);
+ }
+ }
+
+ void renderQueueStarted(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& skipThisInvocation)
+ {
+ Gui* gui = Gui::getInstancePtr();
+ if (gui == nullptr)
+ return;
+
+ if (Ogre::RENDER_QUEUE_OVERLAY != queueGroupId)
+ return;
+
+ Ogre::Viewport* viewport = mSceneManager->getCurrentViewport();
+ if (nullptr == viewport
+ || !viewport->getOverlaysEnabled())
+ return;
+
+ if (mWindow->getNumViewports() <= mActiveViewport
+ || viewport != mWindow->getViewport(mActiveViewport))
+ return;
+
+ mCountBatch = 0;
+
+ static Timer timer;
+ static unsigned long last_time = timer.getMilliseconds();
+ unsigned long now_time = timer.getMilliseconds();
+ unsigned long time = now_time - last_time;
+
+ onFrameEvent((float)((double)(time) / (double)1000));
+
+ last_time = now_time;
+
+ //begin();
+ setManualRender(true);
+ onRenderToTarget(this, mUpdate);
+ //end();
+
+ // ÑбраÑываем флаг
+ mUpdate = false;
+ }
+
+ void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation)
+ {
+ }
+
+ void eventOccurred(const Ogre::String& eventName, const Ogre::NameValuePairList* parameters)
+ {
+ if (eventName == "DeviceLost")
+ {
+ }
+ else if (eventName == "DeviceRestored")
+ {
+ // обновить вÑех
+ mUpdate = true;
+ }
+ }
+
+ IVertexBuffer* createVertexBuffer()
+ {
+ return new OgreVertexBuffer();
+ }
+
+ void destroyVertexBuffer(IVertexBuffer* _buffer)
+ {
+ delete _buffer;
+ }
+
+ // Ð´Ð»Ñ Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ð¹ об изменении окна рендера
+ void windowResized(Ogre::RenderWindow* _window)
+ {
+ if (_window->getNumViewports() > mActiveViewport)
+ {
+ Ogre::Viewport* port = _window->getViewport(mActiveViewport);
+#if OGRE_VERSION >= MYGUI_DEFINE_VERSION(1, 7, 0) && OGRE_NO_VIEWPORT_ORIENTATIONMODE == 0
+ Ogre::OrientationMode orient = port->getOrientationMode();
+ if (orient == Ogre::OR_DEGREE_90 || orient == Ogre::OR_DEGREE_270)
+ mViewSize.set(port->getActualHeight(), port->getActualWidth());
+ else
+ mViewSize.set(port->getActualWidth(), port->getActualHeight());
+#else
+ mViewSize.set(port->getActualWidth(), port->getActualHeight());
+#endif
+
+ // обновить вÑех
+ mUpdate = true;
+
+ updateRenderInfo();
+
+ onResizeView(mViewSize);
+ }
+ }
+
+ void updateRenderInfo()
+ {
+ if (mRenderSystem != nullptr)
+ {
+ mInfo.maximumDepth = mRenderSystem->getMaximumDepthInputValue();
+ mInfo.hOffset = mRenderSystem->getHorizontalTexelOffset() / float(mViewSize.width);
+ mInfo.vOffset = mRenderSystem->getVerticalTexelOffset() / float(mViewSize.height);
+ mInfo.aspectCoef = float(mViewSize.height) / float(mViewSize.width);
+ mInfo.pixScaleX = 1.0f / float(mViewSize.width);
+ mInfo.pixScaleY = 1.0f / float(mViewSize.height);
+ }
+ }
+
+ void doRender(IVertexBuffer* _buffer, ITexture* _texture, size_t _count)
+ {
+ if (getManualRender())
+ {
+ begin();
+ setManualRender(false);
+ }
+
+ // ADDED
+
+ if (_texture)
+ {
+ Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mVertexProgramOneTexture);
+ Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mFragmentProgramOneTexture);
+ }
+ else
+ {
+ Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mVertexProgramNoTexture);
+ Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mFragmentProgramNoTexture);
+ }
+
+ if (_texture)
+ {
+ OgreTexture* texture = static_cast<OgreTexture*>(_texture);
+ Ogre::TexturePtr texture_ptr = texture->getOgreTexture();
+ if (!texture_ptr.isNull())
+ {
+ mRenderSystem->_setTexture(0, true, texture_ptr);
+ mRenderSystem->_setTextureUnitFiltering(0, Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_NONE);
+ }
+ }
+
+ OgreVertexBuffer* buffer = static_cast<OgreVertexBuffer*>(_buffer);
+ Ogre::RenderOperation* operation = buffer->getRenderOperation();
+ operation->vertexData->vertexCount = _count;
+
+ mRenderSystem->_render(*operation);
+
+ ++ mCountBatch;
+ }
+
+ void begin()
+ {
+ // set-up matrices
+ mRenderSystem->_setWorldMatrix(Ogre::Matrix4::IDENTITY);
+ mRenderSystem->_setViewMatrix(Ogre::Matrix4::IDENTITY);
+
+#if OGRE_VERSION >= MYGUI_DEFINE_VERSION(1, 7, 0) && OGRE_NO_VIEWPORT_ORIENTATIONMODE == 0
+ Ogre::OrientationMode orient = mWindow->getViewport(mActiveViewport)->getOrientationMode();
+ mRenderSystem->_setProjectionMatrix(Ogre::Matrix4::IDENTITY * Ogre::Quaternion(Ogre::Degree(orient * 90.f), Ogre::Vector3::UNIT_Z));
+#else
+ mRenderSystem->_setProjectionMatrix(Ogre::Matrix4::IDENTITY);
+#endif
+
+ // initialise render settings
+ mRenderSystem->setLightingEnabled(false);
+ mRenderSystem->_setDepthBufferParams(false, false);
+ mRenderSystem->_setDepthBias(0, 0);
+ mRenderSystem->_setCullingMode(Ogre::CULL_NONE);
+ mRenderSystem->_setFog(Ogre::FOG_NONE);
+ mRenderSystem->_setColourBufferWriteEnabled(true, true, true, true);
+ mRenderSystem->unbindGpuProgram(Ogre::GPT_FRAGMENT_PROGRAM);
+ mRenderSystem->unbindGpuProgram(Ogre::GPT_VERTEX_PROGRAM);
+ mRenderSystem->setShadingType(Ogre::SO_GOURAUD);
+
+ // initialise texture settings
+ mRenderSystem->_setTextureCoordCalculation(0, Ogre::TEXCALC_NONE);
+ mRenderSystem->_setTextureCoordSet(0, 0);
+ mRenderSystem->_setTextureUnitFiltering(0, Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_NONE);
+ mRenderSystem->_setTextureAddressingMode(0, mTextureAddressMode);
+ mRenderSystem->_setTextureMatrix(0, Ogre::Matrix4::IDENTITY);
+#if OGRE_VERSION < MYGUI_DEFINE_VERSION(1, 6, 0)
+ mRenderSystem->_setAlphaRejectSettings(Ogre::CMPF_ALWAYS_PASS, 0);
+#else
+ mRenderSystem->_setAlphaRejectSettings(Ogre::CMPF_ALWAYS_PASS, 0, false);
+#endif
+ mRenderSystem->_setTextureBlendMode(0, mColorBlendMode);
+ mRenderSystem->_setTextureBlendMode(0, mAlphaBlendMode);
+ mRenderSystem->_disableTextureUnitsFrom(1);
+
+ // enable alpha blending
+ mRenderSystem->_setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA);
+
+ // always use wireframe
+ // TODO: add option to enable wireframe mode in platform
+ mRenderSystem->_setPolygonMode(Ogre::PM_SOLID);
+ }
+
+ void end()
+ {
+ }
+
+ ITexture* createTexture(const std::string& _name)
+ {
+ MapTexture::const_iterator item = mTextures.find(_name);
+ MYGUI_PLATFORM_ASSERT(item == mTextures.end(), "Texture '" << _name << "' already exist");
+
+ OgreTexture* texture = new OgreTexture(_name, OgreDataManager::getInstance().getGroup());
+ mTextures[_name] = texture;
+ return texture;
+ }
+
+ void destroyTexture(ITexture* _texture)
+ {
+ if (_texture == nullptr) return;
+
+ MapTexture::iterator item = mTextures.find(_texture->getName());
+ MYGUI_PLATFORM_ASSERT(item != mTextures.end(), "Texture '" << _texture->getName() << "' not found");
+
+ mTextures.erase(item);
+ delete _texture;
+ }
+
+ ITexture* getTexture(const std::string& _name)
+ {
+ MapTexture::const_iterator item = mTextures.find(_name);
+ if (item == mTextures.end())
+ {
+ Ogre::TexturePtr texture = (Ogre::TexturePtr)Ogre::TextureManager::getSingleton().getByName(_name);
+ if (!texture.isNull())
+ {
+ ITexture* result = createTexture(_name);
+ static_cast<OgreTexture*>(result)->setOgreTexture(texture);
+ return result;
+ }
+ return nullptr;
+ }
+ return item->second;
+ }
+
+ bool isFormatSupported(PixelFormat _format, TextureUsage _usage)
+ {
+ return Ogre::TextureManager::getSingleton().isFormatSupported(
+ Ogre::TEX_TYPE_2D,
+ OgreTexture::convertFormat(_format),
+ OgreTexture::convertUsage(_usage));
+ }
+
+ void destroyAllResources()
+ {
+ for (MapTexture::const_iterator item = mTextures.begin(); item != mTextures.end(); ++item)
+ {
+ delete item->second;
+ }
+ mTextures.clear();
+ }
+
+#if MYGUI_DEBUG_MODE == 1
+ bool checkTexture(ITexture* _texture)
+ {
+ for (MapTexture::const_iterator item = mTextures.begin(); item != mTextures.end(); ++item)
+ {
+ if (item->second == _texture)
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ const IntSize& getViewSize() const
+ {
+ return mViewSize;
+ }
+
+ VertexColourType getVertexFormat()
+ {
+ return mVertexFormat;
+ }
+
+ const RenderTargetInfo& getInfo()
+ {
+ return mInfo;
+ }
+
+ size_t getActiveViewport()
+ {
+ return mActiveViewport;
+ }
+
+ Ogre::RenderWindow* getRenderWindow()
+ {
+ return mWindow;
+ }
+
+ bool getManualRender()
+ {
+ return mManualRender;
+ }
+
+ void setManualRender(bool _value)
+ {
+ mManualRender = _value;
+ }
+
+ size_t getBatchCount() const
+ {
+ return mCountBatch;
+ }
+};
+
+}
+
+
+void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir)
+{
+ assert(wnd);
+ assert(mgr);
+
+ mSceneMgr = mgr;
+ mShaderRenderManager = NULL;
+ mRenderManager = NULL;
+
+ using namespace MyGUI;
+
+ // Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log is
+ // still enabled.) In order to do this we have to initialize the log
+ // manager before the main gui system itself, otherwise the main
+ // object will get the chance to spit out a few messages before we
+ // can able to disable it.
+
+ std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME);
+ if(!logDir.empty())
+ theLogFile.insert(0, logDir);
+
+ // Set up OGRE platform (bypassing OgrePlatform). We might make this more generic later.
+ mLogManager = new LogManager();
+ if (!Ogre::Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_FIXED_FUNCTION))
+ mShaderRenderManager = new MyGUI::ShaderBasedRenderManager();
+ else
+ mRenderManager = new MyGUI::OgreRenderManager();
+ mDataManager = new MyGUI::FixedOgreDataManager();
+
+ LogManager::getInstance().setSTDOutputEnabled(logging);
+
+ if (!theLogFile.empty())
+ LogManager::getInstance().createDefaultSource(theLogFile);
+
+ if (mShaderRenderManager)
+ mShaderRenderManager->initialise(wnd, mgr);
+ else
+ mRenderManager->initialise(wnd, mgr);
+ mDataManager->initialise("General");
+
+ // Create GUI
+ mGui = new Gui();
+ mGui->initialise("");
+}
+
+void MyGUIManager::updateWindow (Ogre::RenderWindow *wnd)
+{
+ if (mShaderRenderManager)
+ {
+ mShaderRenderManager->setRenderWindow (wnd);
+ mShaderRenderManager->setActiveViewport(0);
+ }
+ else
+ {
+ mRenderManager->setRenderWindow (wnd);
+ mRenderManager->setActiveViewport(0);
+ }
+}
+
+void MyGUIManager::windowResized()
+{
+ mRenderManager->setActiveViewport(0);
+}
+
+void MyGUIManager::shutdown()
+{
+ mGui->shutdown ();
+ delete mGui;
+ if(mRenderManager)
+ {
+ mRenderManager->shutdown();
+ delete mRenderManager;
+ mRenderManager = NULL;
+ }
+ if(mShaderRenderManager)
+ {
+ mShaderRenderManager->shutdown();
+ delete mShaderRenderManager;
+ mShaderRenderManager = NULL;
+ }
+ if(mDataManager)
+ {
+ mDataManager->shutdown();
+ delete mDataManager;
+ mDataManager = NULL;
+ }
+ if (mLogManager)
+ {
+ delete mLogManager;
+ mLogManager = NULL;
+ }
+ mGui = NULL;
+}
diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp
new file mode 100644
index 0000000000..cca70dfcfe
--- /dev/null
+++ b/libs/openengine/gui/manager.hpp
@@ -0,0 +1,55 @@
+#ifndef OENGINE_MYGUI_MANAGER_H
+#define OENGINE_MYGUI_MANAGER_H
+
+#include <string>
+
+namespace MyGUI
+{
+ class Gui;
+ class LogManager;
+ class OgreDataManager;
+ class OgreRenderManager;
+ class ShaderBasedRenderManager;
+}
+
+namespace Ogre
+{
+ class RenderWindow;
+ class SceneManager;
+}
+
+namespace OEngine {
+namespace GUI
+{
+ class MyGUIManager
+ {
+ MyGUI::Gui *mGui;
+ MyGUI::LogManager* mLogManager;
+ MyGUI::OgreDataManager* mDataManager;
+ MyGUI::OgreRenderManager* mRenderManager;
+ MyGUI::ShaderBasedRenderManager* mShaderRenderManager;
+ Ogre::SceneManager* mSceneMgr;
+
+
+ public:
+ MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string(""))
+ {
+ setup(wnd,mgr,logging, logDir);
+ }
+ ~MyGUIManager()
+ {
+ shutdown();
+ }
+
+ void updateWindow (Ogre::RenderWindow* wnd);
+
+ void windowResized();
+
+ void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string(""));
+ void shutdown();
+
+ MyGUI::Gui *getGui() { return mGui; }
+ };
+}
+}
+#endif
diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp
new file mode 100644
index 0000000000..bda9cb8de7
--- /dev/null
+++ b/libs/openengine/misc/list.hpp
@@ -0,0 +1,178 @@
+#ifndef MISC_LIST_H
+#define MISC_LIST_H
+
+#include <cassert>
+
+namespace Misc{
+
+/*
+ A simple and completely allocation-less doubly linked list. The
+ class only manages pointers to and between elements. It leaving all
+ memory management to the user.
+*/
+template <typename Elem>
+struct List
+{
+ List() : head(0), tail(0), totalNum(0) {}
+
+ // Empty the list.
+ void reset()
+ {
+ head = 0;
+ tail = 0;
+ totalNum = 0;
+ }
+
+ // Insert an element at the end of the list. The element cannot be
+ // part of any other list when this is called.
+ void insert(Elem *p)
+ {
+ if(tail)
+ {
+ // There are existing elements. Insert the node at the end of
+ // the list.
+ assert(head && totalNum > 0);
+ tail->next = p;
+ }
+ else
+ {
+ // This is the first element
+ assert(head == 0 && totalNum == 0);
+ head = p;
+ }
+
+ // These have to be done in either case
+ p->prev = tail;
+ p->next = 0;
+ tail = p;
+
+ totalNum++;
+ }
+
+ // Remove element from the list. The element MUST be part of the
+ // list when this is called.
+ void remove(Elem *p)
+ {
+ assert(totalNum > 0);
+
+ if(p->next)
+ {
+ // There's an element following us. Set it up correctly.
+ p->next->prev = p->prev;
+ assert(tail && tail != p);
+ }
+ else
+ {
+ // We're the tail
+ assert(tail == p);
+ tail = p->prev;
+ }
+
+ // Now do exactly the same for the previous element
+ if(p->prev)
+ {
+ p->prev->next = p->next;
+ assert(head && head != p);
+ }
+ else
+ {
+ assert(head == p);
+ head = p->next;
+ }
+
+ totalNum--;
+ }
+
+ // Pop the first element off the list
+ Elem *pop()
+ {
+ Elem *res = getHead();
+ if(res) remove(res);
+ return res;
+ }
+
+ // Swap the contents of this list with another of the same type
+ void swap(List &other)
+ {
+ Elem *tmp;
+
+ tmp = head;
+ head = other.head;
+ other.head = tmp;
+
+ tmp = tail;
+ tail = other.tail;
+ other.tail = tmp;
+
+ unsigned int tmp2 = totalNum;
+ totalNum = other.totalNum;
+ other.totalNum = tmp2;
+ }
+
+ /* Absorb the contents of another list. All the elements from the
+ list are moved to the end of this list, and the other list is
+ cleared.
+ */
+ void absorb(List &other)
+ {
+ assert(&other != this);
+ if(other.totalNum)
+ {
+ absorb(other.head, other.tail, other.totalNum);
+ other.reset();
+ }
+ assert(other.totalNum == 0);
+ }
+
+ /* Absorb a range of elements, endpoints included. The elements are
+ assumed NOT to belong to any list, but they ARE assumed to be
+ connected with a chain between them.
+
+ The connection MUST run all the way from 'first' to 'last'
+ through the ->next pointers, and vice versa through ->prev
+ pointers.
+
+ The parameter 'num' must give the exact number of elements in the
+ chain.
+
+ Passing first == last, num == 1 is allowed and is equivalent to
+ calling insert().
+ */
+ void absorb(Elem* first, Elem *last, int num)
+ {
+ assert(first && last && num>=1);
+ if(tail)
+ {
+ // There are existing elements. Insert the first node at the
+ // end of the list.
+ assert(head && totalNum > 0);
+ tail->next = first;
+ }
+ else
+ {
+ // This is the first element
+ assert(head == 0 && totalNum == 0);
+ head = first;
+ }
+
+ // These have to be done in either case
+ first->prev = tail;
+ last->next = 0;
+ tail = last;
+
+ totalNum += num;
+ }
+
+ Elem* getHead() const { return head; }
+ Elem* getTail() const { return tail; }
+ unsigned int getNum() const { return totalNum; }
+
+private:
+
+ Elem *head;
+ Elem *tail;
+ unsigned int totalNum;
+};
+
+}
+#endif
diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp
new file mode 100644
index 0000000000..923b0b7e38
--- /dev/null
+++ b/libs/openengine/ogre/fader.cpp
@@ -0,0 +1,135 @@
+#include "fader.hpp"
+
+#include <OgreMaterial.h>
+#include <OgreTechnique.h>
+#include <OgreMaterialManager.h>
+#include <OgreResourceGroupManager.h>
+#include <OgreRectangle2D.h>
+#include <OgreSceneManager.h>
+
+
+using namespace Ogre;
+using namespace OEngine::Render;
+
+Fader::Fader(Ogre::SceneManager* sceneMgr)
+ : mSceneMgr(sceneMgr)
+ , mMode(FadingMode_In)
+ , mRemainingTime(0.f)
+ , mTargetTime(0.f)
+ , mTargetAlpha(0.f)
+ , mCurrentAlpha(0.f)
+ , mStartAlpha(0.f)
+ , mFactor(1.f)
+{
+ // Create the fading material
+ MaterialPtr material = MaterialManager::getSingleton().create("FadeInOutMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME );
+ Pass* pass = material->getTechnique(0)->getPass(0);
+ pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
+ pass->setDepthWriteEnabled (false);
+ mFadeTextureUnit = pass->createTextureUnitState("black.png");
+ mFadeTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(0.f, 0.f, 0.f)); // always black colour
+
+ mRectangle = new Ogre::Rectangle2D(true);
+ mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
+ mRectangle->setMaterial("FadeInOutMaterial");
+ mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1);
+ // Use infinite AAB to always stay visible
+ Ogre::AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+ mRectangle->setBoundingBox(aabInf);
+ // Attach background to the scene
+ Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
+ node->attachObject(mRectangle);
+ mRectangle->setVisible(false);
+ mRectangle->setVisibilityFlags (2048);
+}
+
+Fader::~Fader()
+{
+ delete mRectangle;
+}
+
+void Fader::update(float dt)
+{
+ if (mRemainingTime > 0)
+ {
+ if (mMode == FadingMode_In)
+ {
+ mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha);
+ if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha;
+ }
+ else if (mMode == FadingMode_Out)
+ {
+ mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha);
+ if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha;
+ }
+
+ mRemainingTime -= dt;
+ }
+
+ if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f)
+ mRectangle->setVisible(false);
+ else
+ applyAlpha();
+}
+
+void Fader::applyAlpha()
+{
+ mRectangle->setVisible(true);
+ mFadeTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, 1.f-((1.f-mCurrentAlpha) * mFactor));
+}
+
+void Fader::fadeIn(float time)
+{
+ if (time<0.f) return;
+ if (time==0.f)
+ {
+ mCurrentAlpha = 0.f;
+ applyAlpha();
+ return;
+ }
+
+ mStartAlpha = mCurrentAlpha;
+ mTargetAlpha = 0.f;
+ mMode = FadingMode_In;
+ mTargetTime = time;
+ mRemainingTime = time;
+}
+
+void Fader::fadeOut(const float time)
+{
+ if (time<0.f) return;
+ if (time==0.f)
+ {
+ mCurrentAlpha = 1.f;
+ applyAlpha();
+ return;
+ }
+
+ mStartAlpha = mCurrentAlpha;
+ mTargetAlpha = 1.f;
+ mMode = FadingMode_Out;
+ mTargetTime = time;
+ mRemainingTime = time;
+}
+
+void Fader::fadeTo(const int percent, const float time)
+{
+ if (time<0.f) return;
+ if (time==0.f)
+ {
+ mCurrentAlpha = percent/100.f;
+ applyAlpha();
+ return;
+ }
+
+ mStartAlpha = mCurrentAlpha;
+ mTargetAlpha = percent/100.f;
+
+ if (mTargetAlpha == mStartAlpha) return;
+ else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out;
+ else mMode = FadingMode_In;
+
+ mTargetTime = time;
+ mRemainingTime = time;
+}
diff --git a/libs/openengine/ogre/fader.hpp b/libs/openengine/ogre/fader.hpp
new file mode 100644
index 0000000000..53124e2f65
--- /dev/null
+++ b/libs/openengine/ogre/fader.hpp
@@ -0,0 +1,59 @@
+#ifndef OENGINE_OGRE_FADE_H
+#define OENGINE_OGRE_FADE_H
+
+/*
+ A class that handles fading in the screen from black or fading it out to black.
+
+ To achieve this, it uses a full-screen Rectangle2d
+ */
+
+namespace Ogre
+{
+ class TextureUnitState;
+ class Rectangle2D;
+ class SceneManager;
+}
+
+namespace OEngine {
+namespace Render
+{
+ class Fader
+ {
+ public:
+ Fader(Ogre::SceneManager* sceneMgr);
+ ~Fader();
+
+ void update(float dt);
+
+ void fadeIn(const float time);
+ void fadeOut(const float time);
+ void fadeTo(const int percent, const float time);
+
+ void setFactor (float factor) { mFactor = factor; }
+
+ private:
+ enum FadingMode
+ {
+ FadingMode_In,
+ FadingMode_Out
+ };
+
+ void applyAlpha();
+
+ Ogre::TextureUnitState* mFadeTextureUnit;
+ Ogre::Rectangle2D* mRectangle;
+
+ FadingMode mMode;
+
+ float mRemainingTime;
+ float mTargetTime;
+ float mTargetAlpha;
+ float mCurrentAlpha;
+ float mStartAlpha;
+
+ float mFactor;
+
+ Ogre::SceneManager* mSceneMgr;
+ };
+ }}
+#endif
diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp
new file mode 100644
index 0000000000..9c32924f1f
--- /dev/null
+++ b/libs/openengine/ogre/imagerotate.cpp
@@ -0,0 +1,88 @@
+#include "imagerotate.hpp"
+
+#include <OgreRoot.h>
+#include <OgreSceneManager.h>
+#include <OgreImage.h>
+#include <OgreTexture.h>
+#include <OgreRenderTarget.h>
+#include <OgreCamera.h>
+#include <OgreTextureUnitState.h>
+#include <OgreHardwarePixelBuffer.h>
+
+using namespace Ogre;
+using namespace OEngine::Render;
+
+void ImageRotate::rotate(const std::string& sourceImage, const std::string& destImage, const float angle)
+{
+ Root* root = Ogre::Root::getSingletonPtr();
+
+ std::string destImageRot = std::string(destImage) + std::string("_rot");
+
+ SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC);
+ Camera* camera = sceneMgr->createCamera("ImageRotateCamera");
+
+ MaterialPtr material = MaterialManager::getSingleton().create("ImageRotateMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+ material->getTechnique(0)->getPass(0)->setLightingEnabled(false);
+ material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
+ TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(sourceImage);
+ Degree deg(angle);
+ tus->setTextureRotate(Radian(deg.valueRadians()));
+ tus->setTextureAddressingMode(TextureUnitState::TAM_BORDER);
+ tus->setTextureBorderColour(ColourValue(0, 0, 0, 0));
+
+ Rectangle2D* rect = new Rectangle2D(true);
+ rect->setCorners(-1.0, 1.0, 1.0, -1.0);
+ rect->setMaterial("ImageRotateMaterial");
+ // Render the background before everything else
+ rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND);
+
+ // Use infinite AAB to always stay visible
+ AxisAlignedBox aabInf;
+ aabInf.setInfinite();
+ rect->setBoundingBox(aabInf);
+
+ // Attach background to the scene
+ SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode();
+ node->attachObject(rect);
+
+ // retrieve image width and height
+ TexturePtr sourceTexture = TextureManager::getSingleton().getByName(sourceImage);
+ unsigned int width = sourceTexture->getWidth();
+ unsigned int height = sourceTexture->getHeight();
+
+ TexturePtr destTextureRot = TextureManager::getSingleton().createManual(
+ destImageRot,
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ width, height,
+ 0,
+ PF_A8B8G8R8,
+ TU_RENDERTARGET);
+
+ RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget();
+ rtt->setAutoUpdated(false);
+ Viewport* vp = rtt->addViewport(camera);
+ vp->setOverlaysEnabled(false);
+ vp->setShadowsEnabled(false);
+ vp->setBackgroundColour(ColourValue(0,0,0,0));
+
+ rtt->update();
+
+ //copy the rotated image to a static texture
+ TexturePtr destTexture = TextureManager::getSingleton().createManual(
+ destImage,
+ ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ TEX_TYPE_2D,
+ width, height,
+ 0,
+ PF_A8B8G8R8,
+ Ogre::TU_STATIC);
+
+ destTexture->getBuffer()->blit(destTextureRot->getBuffer());
+
+ // remove all the junk we've created
+ TextureManager::getSingleton().remove(destImageRot);
+ MaterialManager::getSingleton().remove("ImageRotateMaterial");
+ root->destroySceneManager(sceneMgr);
+ delete rect;
+}
diff --git a/libs/openengine/ogre/imagerotate.hpp b/libs/openengine/ogre/imagerotate.hpp
new file mode 100644
index 0000000000..a3f6d662f3
--- /dev/null
+++ b/libs/openengine/ogre/imagerotate.hpp
@@ -0,0 +1,27 @@
+#ifndef OENGINE_OGRE_IMAGEROTATE_HPP
+#define OENGINE_OGRE_IMAGEROTATE_HPP
+
+#include <string>
+
+namespace OEngine
+{
+namespace Render
+{
+
+ /// Rotate an image by certain degrees and save as file, uses the GPU
+ /// Make sure Ogre Root is initialised before calling
+ class ImageRotate
+ {
+ public:
+ /**
+ * @param source image (file name - has to exist in an resource group)
+ * @param name of the destination texture to save to (in memory)
+ * @param angle in degrees to turn
+ */
+ static void rotate(const std::string& sourceImage, const std::string& destImage, const float angle);
+ };
+
+}
+}
+
+#endif
diff --git a/libs/openengine/ogre/lights.cpp b/libs/openengine/ogre/lights.cpp
new file mode 100644
index 0000000000..52aca6a705
--- /dev/null
+++ b/libs/openengine/ogre/lights.cpp
@@ -0,0 +1,118 @@
+#include "lights.hpp"
+
+#include <OgreLight.h>
+#include <OgreMath.h>
+
+namespace OEngine {
+namespace Render {
+
+
+LightFunction::LightFunction(LightType type)
+ : ControllerFunction<Ogre::Real>(true)
+ , mType(type)
+ , mPhase(Ogre::Math::RangeRandom(-500.0f, +500.0f))
+ , mDirection(1.0f)
+{
+}
+
+Ogre::Real LightFunction::pulseAmplitude(Ogre::Real time)
+{
+ return std::sin(time);
+}
+
+Ogre::Real LightFunction::flickerAmplitude(Ogre::Real time)
+{
+ static const float fb = 1.17024f;
+ static const float f[3] = { 1.5708f, 4.18774f, 5.19934f };
+ static const float o[3] = { 0.804248f, 2.11115f, 3.46832f };
+ static const float m[3] = { 1.0f, 0.785f, 0.876f };
+ static const float s = 0.394f;
+
+ float v = 0.0f;
+ for(int i = 0;i < 3;++i)
+ v += std::sin(fb*time*f[i] + o[1])*m[i];
+ return v * s;
+}
+
+Ogre::Real LightFunction::flickerFrequency(Ogre::Real phase)
+{
+ static const float fa = 0.785398f;
+ static const float tdo = 0.94f;
+ static const float tdm = 2.48f;
+
+ return tdo + tdm*std::sin(fa * phase);
+}
+
+Ogre::Real LightFunction::calculate(Ogre::Real value)
+{
+ Ogre::Real brightness = 1.0f;
+ float cycle_time;
+ float time_distortion;
+
+ if(mType == LT_Pulse || mType == LT_PulseSlow)
+ {
+ cycle_time = 2.0f * Ogre::Math::PI;
+ time_distortion = 20.0f;
+ }
+ else
+ {
+ static const float fa = 0.785398f;
+ static const float phase_wavelength = 120.0f * 3.14159265359f / fa;
+
+ cycle_time = 500.0f;
+ mPhase = std::fmod(mPhase + value, phase_wavelength);
+ time_distortion = flickerFrequency(mPhase);
+ }
+
+ mDeltaCount += mDirection*value*time_distortion;
+ if(mDirection > 0 && mDeltaCount > +cycle_time)
+ {
+ mDirection = -1.0f;
+ mDeltaCount = 2.0f*cycle_time - mDeltaCount;
+ }
+ if(mDirection < 0 && mDeltaCount < -cycle_time)
+ {
+ mDirection = +1.0f;
+ mDeltaCount = -2.0f*cycle_time - mDeltaCount;
+ }
+
+ static const float fast = 4.0f/1.0f;
+ static const float slow = 1.0f/1.0f;
+
+ // These formulas are just guesswork, but they work pretty well
+ if(mType == LT_Normal)
+ {
+ // Less than 1/255 light modifier for a constant light:
+ brightness = 1.0 + flickerAmplitude(mDeltaCount*slow)/255.0f;
+ }
+ else if(mType == LT_Flicker)
+ brightness = 0.75 + flickerAmplitude(mDeltaCount*fast)*0.25;
+ else if(mType == LT_FlickerSlow)
+ brightness = 0.75 + flickerAmplitude(mDeltaCount*slow)*0.25;
+ else if(mType == LT_Pulse)
+ brightness = 1.0 + pulseAmplitude(mDeltaCount*fast)*0.25;
+ else if(mType == LT_PulseSlow)
+ brightness = 1.0 + pulseAmplitude(mDeltaCount*slow)*0.25;
+
+ return brightness;
+}
+
+
+LightValue::LightValue(Ogre::Light *light, const Ogre::ColourValue &color)
+ : mTarget(light)
+ , mColor(color)
+{
+}
+
+Ogre::Real LightValue::getValue() const
+{
+ return 0.0f;
+}
+
+void LightValue::setValue(Ogre::Real value)
+{
+ mTarget->setDiffuseColour(mColor * value);
+}
+
+}
+}
diff --git a/libs/openengine/ogre/lights.hpp b/libs/openengine/ogre/lights.hpp
new file mode 100644
index 0000000000..c63f164255
--- /dev/null
+++ b/libs/openengine/ogre/lights.hpp
@@ -0,0 +1,51 @@
+#ifndef OENGINE_OGRE_LIGHTS_H
+#define OENGINE_OGRE_LIGHTS_H
+
+#include <OgreController.h>
+#include <OgreColourValue.h>
+
+/*
+ * Controller classes to handle pulsing and flicker lights
+ */
+
+namespace OEngine {
+namespace Render {
+ enum LightType {
+ LT_Normal,
+ LT_Flicker,
+ LT_FlickerSlow,
+ LT_Pulse,
+ LT_PulseSlow
+ };
+
+ class LightFunction : public Ogre::ControllerFunction<Ogre::Real>
+ {
+ LightType mType;
+ Ogre::Real mPhase;
+ Ogre::Real mDirection;
+
+ static Ogre::Real pulseAmplitude(Ogre::Real time);
+
+ static Ogre::Real flickerAmplitude(Ogre::Real time);
+ static Ogre::Real flickerFrequency(Ogre::Real phase);
+
+ public:
+ LightFunction(LightType type);
+ virtual Ogre::Real calculate(Ogre::Real value);
+ };
+
+ class LightValue : public Ogre::ControllerValue<Ogre::Real>
+ {
+ Ogre::Light *mTarget;
+ Ogre::ColourValue mColor;
+
+ public:
+ LightValue(Ogre::Light *light, const Ogre::ColourValue &color);
+
+ virtual Ogre::Real getValue() const;
+ virtual void setValue(Ogre::Real value);
+ };
+}
+}
+
+#endif
diff --git a/libs/openengine/ogre/particles.cpp b/libs/openengine/ogre/particles.cpp
new file mode 100644
index 0000000000..707bd75e08
--- /dev/null
+++ b/libs/openengine/ogre/particles.cpp
@@ -0,0 +1,693 @@
+#include "particles.hpp"
+
+#include <OgreStringConverter.h>
+#include <OgreParticleSystem.h>
+#include <OgreParticleEmitter.h>
+#include <OgreParticleAffector.h>
+#include <OgreParticle.h>
+
+/* FIXME: "Nif" isn't really an appropriate emitter name. */
+class NifEmitter : public Ogre::ParticleEmitter
+{
+public:
+ /** Command object for the emitter width (see Ogre::ParamCommand).*/
+ class CmdWidth : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ return Ogre::StringConverter::toString(static_cast<const NifEmitter*>(target)->getWidth());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ static_cast<NifEmitter*>(target)->setWidth(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Command object for the emitter height (see Ogre::ParamCommand).*/
+ class CmdHeight : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ return Ogre::StringConverter::toString(static_cast<const NifEmitter*>(target)->getHeight());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ static_cast<NifEmitter*>(target)->setHeight(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Command object for the emitter depth (see Ogre::ParamCommand).*/
+ class CmdDepth : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ return Ogre::StringConverter::toString(static_cast<const NifEmitter*>(target)->getDepth());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ static_cast<NifEmitter*>(target)->setDepth(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Command object for the emitter vertical_direction (see Ogre::ParamCommand).*/
+ class CmdVerticalDir : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const NifEmitter *self = static_cast<const NifEmitter*>(target);
+ return Ogre::StringConverter::toString(self->getVerticalDirection().valueDegrees());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ NifEmitter *self = static_cast<NifEmitter*>(target);
+ self->setVerticalDirection(Ogre::Degree(Ogre::StringConverter::parseReal(val)));
+ }
+ };
+
+ /** Command object for the emitter vertical_angle (see Ogre::ParamCommand).*/
+ class CmdVerticalAngle : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const NifEmitter *self = static_cast<const NifEmitter*>(target);
+ return Ogre::StringConverter::toString(self->getVerticalAngle().valueDegrees());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ NifEmitter *self = static_cast<NifEmitter*>(target);
+ self->setVerticalAngle(Ogre::Degree(Ogre::StringConverter::parseReal(val)));
+ }
+ };
+
+ /** Command object for the emitter horizontal_direction (see Ogre::ParamCommand).*/
+ class CmdHorizontalDir : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const NifEmitter *self = static_cast<const NifEmitter*>(target);
+ return Ogre::StringConverter::toString(self->getHorizontalDirection().valueDegrees());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ NifEmitter *self = static_cast<NifEmitter*>(target);
+ self->setHorizontalDirection(Ogre::Degree(Ogre::StringConverter::parseReal(val)));
+ }
+ };
+
+ /** Command object for the emitter horizontal_angle (see Ogre::ParamCommand).*/
+ class CmdHorizontalAngle : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const NifEmitter *self = static_cast<const NifEmitter*>(target);
+ return Ogre::StringConverter::toString(self->getHorizontalAngle().valueDegrees());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ NifEmitter *self = static_cast<NifEmitter*>(target);
+ self->setHorizontalAngle(Ogre::Degree(Ogre::StringConverter::parseReal(val)));
+ }
+ };
+
+
+ NifEmitter(Ogre::ParticleSystem *psys)
+ : Ogre::ParticleEmitter(psys)
+ {
+ initDefaults("Nif");
+ }
+
+ /** See Ogre::ParticleEmitter. */
+ unsigned short _getEmissionCount(Ogre::Real timeElapsed)
+ {
+ // Use basic constant emission
+ return genConstantEmissionCount(timeElapsed);
+ }
+
+ /** See Ogre::ParticleEmitter. */
+ void _initParticle(Ogre::Particle *particle)
+ {
+ Ogre::Vector3 xOff, yOff, zOff;
+
+ // Call superclass
+ ParticleEmitter::_initParticle(particle);
+
+ xOff = Ogre::Math::SymmetricRandom() * mXRange;
+ yOff = Ogre::Math::SymmetricRandom() * mYRange;
+ zOff = Ogre::Math::SymmetricRandom() * mZRange;
+
+ particle->position = mPosition + xOff + yOff + zOff;
+
+ // Generate complex data by reference
+ genEmissionColour(particle->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) *
+ Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) *
+ Ogre::Vector3::UNIT_Z;
+
+ genEmissionVelocity(particle->direction);
+
+ // Generate simpler data
+ particle->timeToLive = particle->totalTimeToLive = genEmissionTTL();
+ }
+
+ /** Overloaded to update the trans. matrix */
+ void setDirection(const Ogre::Vector3 &dir)
+ {
+ ParticleEmitter::setDirection(dir);
+ genAreaAxes();
+ }
+
+ /** Sets the size of the area from which particles are emitted.
+ @param
+ size Vector describing the size of the area. The area extends
+ around the center point by half the x, y and z components of
+ this vector. The box is aligned such that it's local Z axis points
+ along it's direction (see setDirection)
+ */
+ void setSize(const Ogre::Vector3 &size)
+ {
+ mSize = size;
+ genAreaAxes();
+ }
+
+ /** Sets the size of the area from which particles are emitted.
+ @param x,y,z
+ Individual axis lengths describing the size of the area. The area
+ extends around the center point by half the x, y and z components
+ of this vector. The box is aligned such that it's local Z axis
+ points along it's direction (see setDirection)
+ */
+ void setSize(Ogre::Real x, Ogre::Real y, Ogre::Real z)
+ {
+ mSize.x = x;
+ mSize.y = y;
+ mSize.z = z;
+ genAreaAxes();
+ }
+
+ /** Sets the width (local x size) of the emitter. */
+ void setWidth(Ogre::Real width)
+ {
+ mSize.x = width;
+ genAreaAxes();
+ }
+ /** Gets the width (local x size) of the emitter. */
+ Ogre::Real getWidth(void) const
+ { return mSize.x; }
+ /** Sets the height (local y size) of the emitter. */
+ void setHeight(Ogre::Real height)
+ {
+ mSize.y = height;
+ genAreaAxes();
+ }
+ /** Gets the height (local y size) of the emitter. */
+ Ogre::Real getHeight(void) const
+ { return mSize.y; }
+ /** Sets the depth (local y size) of the emitter. */
+ void setDepth(Ogre::Real depth)
+ {
+ mSize.z = depth;
+ genAreaAxes();
+ }
+ /** Gets the depth (local y size) of the emitter. */
+ Ogre::Real getDepth(void) const
+ { return mSize.z; }
+
+ void setVerticalDirection(Ogre::Radian vdir)
+ { mVerticalDir = vdir; }
+ Ogre::Radian getVerticalDirection(void) const
+ { return mVerticalDir; }
+
+ void setVerticalAngle(Ogre::Radian vangle)
+ { mVerticalAngle = vangle; }
+ Ogre::Radian getVerticalAngle(void) const
+ { return mVerticalAngle; }
+
+ void setHorizontalDirection(Ogre::Radian hdir)
+ { mHorizontalDir = hdir; }
+ Ogre::Radian getHorizontalDirection(void) const
+ { return mHorizontalDir; }
+
+ void setHorizontalAngle(Ogre::Radian hangle)
+ { mHorizontalAngle = hangle; }
+ Ogre::Radian getHorizontalAngle(void) const
+ { return mHorizontalAngle; }
+
+
+protected:
+ /// Size of the area
+ Ogre::Vector3 mSize;
+
+ Ogre::Radian mVerticalDir;
+ Ogre::Radian mVerticalAngle;
+ Ogre::Radian mHorizontalDir;
+ Ogre::Radian mHorizontalAngle;
+
+ /// Local axes, not normalised, their magnitude reflects area size
+ Ogre::Vector3 mXRange, mYRange, mZRange;
+
+ /// Internal method for generating the area axes
+ void genAreaAxes(void)
+ {
+ Ogre::Vector3 mLeft = mUp.crossProduct(mDirection);
+ mXRange = mLeft * (mSize.x * 0.5f);
+ mYRange = mUp * (mSize.y * 0.5f);
+ mZRange = mDirection * (mSize.z * 0.5f);
+ }
+
+ /** Internal for initializing some defaults and parameters
+ @return True if custom parameters need initialising
+ */
+ bool initDefaults(const Ogre::String &t)
+ {
+ // Defaults
+ mDirection = Ogre::Vector3::UNIT_Z;
+ mUp = Ogre::Vector3::UNIT_Y;
+ setSize(100.0f, 100.0f, 100.0f);
+ mType = t;
+
+ // Set up parameters
+ if(createParamDictionary(mType + "Emitter"))
+ {
+ addBaseParameters();
+ Ogre::ParamDictionary *dict = getParamDictionary();
+
+ // Custom params
+ dict->addParameter(Ogre::ParameterDef("width",
+ "Width of the shape in world coordinates.",
+ Ogre::PT_REAL),
+ &msWidthCmd);
+ dict->addParameter(Ogre::ParameterDef("height",
+ "Height of the shape in world coordinates.",
+ Ogre::PT_REAL),
+ &msHeightCmd);
+ dict->addParameter(Ogre::ParameterDef("depth",
+ "Depth of the shape in world coordinates.",
+ Ogre::PT_REAL),
+ &msDepthCmd);
+
+ dict->addParameter(Ogre::ParameterDef("vertical_direction",
+ "Vertical direction of emitted particles (in degrees).",
+ Ogre::PT_REAL),
+ &msVerticalDirCmd);
+ dict->addParameter(Ogre::ParameterDef("vertical_angle",
+ "Vertical direction variance of emitted particles (in degrees).",
+ Ogre::PT_REAL),
+ &msVerticalAngleCmd);
+ dict->addParameter(Ogre::ParameterDef("horizontal_direction",
+ "Horizontal direction of emitted particles (in degrees).",
+ Ogre::PT_REAL),
+ &msHorizontalDirCmd);
+ dict->addParameter(Ogre::ParameterDef("horizontal_angle",
+ "Horizontal direction variance of emitted particles (in degrees).",
+ Ogre::PT_REAL),
+ &msHorizontalAngleCmd);
+
+ return true;
+ }
+ return false;
+ }
+
+ /// Command objects
+ static CmdWidth msWidthCmd;
+ static CmdHeight msHeightCmd;
+ static CmdDepth msDepthCmd;
+ static CmdVerticalDir msVerticalDirCmd;
+ static CmdVerticalAngle msVerticalAngleCmd;
+ static CmdHorizontalDir msHorizontalDirCmd;
+ static CmdHorizontalAngle msHorizontalAngleCmd;
+};
+NifEmitter::CmdWidth NifEmitter::msWidthCmd;
+NifEmitter::CmdHeight NifEmitter::msHeightCmd;
+NifEmitter::CmdDepth NifEmitter::msDepthCmd;
+NifEmitter::CmdVerticalDir NifEmitter::msVerticalDirCmd;
+NifEmitter::CmdVerticalAngle NifEmitter::msVerticalAngleCmd;
+NifEmitter::CmdHorizontalDir NifEmitter::msHorizontalDirCmd;
+NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd;
+
+Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys)
+{
+ Ogre::ParticleEmitter *emit = OGRE_NEW NifEmitter(psys);
+ mEmitters.push_back(emit);
+ return emit;
+}
+
+
+class GrowFadeAffector : public Ogre::ParticleAffector
+{
+public:
+ /** Command object for grow_time (see Ogre::ParamCommand).*/
+ class CmdGrowTime : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GrowFadeAffector *self = static_cast<const GrowFadeAffector*>(target);
+ return Ogre::StringConverter::toString(self->getGrowTime());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GrowFadeAffector *self = static_cast<GrowFadeAffector*>(target);
+ self->setGrowTime(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Command object for fade_time (see Ogre::ParamCommand).*/
+ class CmdFadeTime : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GrowFadeAffector *self = static_cast<const GrowFadeAffector*>(target);
+ return Ogre::StringConverter::toString(self->getFadeTime());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GrowFadeAffector *self = static_cast<GrowFadeAffector*>(target);
+ self->setFadeTime(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Default constructor. */
+ GrowFadeAffector(Ogre::ParticleSystem *psys) : ParticleAffector(psys)
+ {
+ mGrowTime = 0.0f;
+ mFadeTime = 0.0f;
+
+ mType = "GrowFade";
+
+ // Init parameters
+ if(createParamDictionary("GrowFadeAffector"))
+ {
+ Ogre::ParamDictionary *dict = getParamDictionary();
+
+ Ogre::String grow_title("grow_time");
+ Ogre::String fade_title("fade_time");
+ Ogre::String grow_descr("Time from begin to reach full size.");
+ Ogre::String fade_descr("Time from end to shrink.");
+
+ dict->addParameter(Ogre::ParameterDef(grow_title, grow_descr, Ogre::PT_REAL), &msGrowCmd);
+ dict->addParameter(Ogre::ParameterDef(fade_title, fade_descr, Ogre::PT_REAL), &msFadeCmd);
+ }
+ }
+
+ /** See Ogre::ParticleAffector. */
+ void _initParticle(Ogre::Particle *particle)
+ {
+ const Ogre::Real life_time = particle->totalTimeToLive;
+ Ogre::Real particle_time = particle->timeToLive;
+
+ Ogre::Real width = mParent->getDefaultWidth();
+ Ogre::Real height = mParent->getDefaultHeight();
+ if(life_time-particle_time < mGrowTime)
+ {
+ Ogre::Real scale = (life_time-particle_time) / mGrowTime;
+ width *= scale;
+ height *= scale;
+ }
+ if(particle_time < mFadeTime)
+ {
+ Ogre::Real scale = particle_time / mFadeTime;
+ width *= scale;
+ height *= scale;
+ }
+ particle->setDimensions(width, height);
+ }
+
+ /** See Ogre::ParticleAffector. */
+ void _affectParticles(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed)
+ {
+ Ogre::ParticleIterator pi = psys->_getIterator();
+ while (!pi.end())
+ {
+ Ogre::Particle *p = pi.getNext();
+ const Ogre::Real life_time = p->totalTimeToLive;
+ Ogre::Real particle_time = p->timeToLive;
+
+ Ogre::Real width = mParent->getDefaultWidth();
+ Ogre::Real height = mParent->getDefaultHeight();
+ if(life_time-particle_time < mGrowTime)
+ {
+ Ogre::Real scale = (life_time-particle_time) / mGrowTime;
+ width *= scale;
+ height *= scale;
+ }
+ if(particle_time < mFadeTime)
+ {
+ Ogre::Real scale = particle_time / mFadeTime;
+ width *= scale;
+ height *= scale;
+ }
+ p->setDimensions(width, height);
+ }
+ }
+
+ void setGrowTime(Ogre::Real time)
+ {
+ mGrowTime = time;
+ }
+ Ogre::Real getGrowTime() const
+ { return mGrowTime; }
+
+ void setFadeTime(Ogre::Real time)
+ {
+ mFadeTime = time;
+ }
+ Ogre::Real getFadeTime() const
+ { return mFadeTime; }
+
+ static CmdGrowTime msGrowCmd;
+ static CmdFadeTime msFadeCmd;
+
+protected:
+ Ogre::Real mGrowTime;
+ Ogre::Real mFadeTime;
+};
+GrowFadeAffector::CmdGrowTime GrowFadeAffector::msGrowCmd;
+GrowFadeAffector::CmdFadeTime GrowFadeAffector::msFadeCmd;
+
+Ogre::ParticleAffector *GrowFadeAffectorFactory::createAffector(Ogre::ParticleSystem *psys)
+{
+ Ogre::ParticleAffector *p = new GrowFadeAffector(psys);
+ mAffectors.push_back(p);
+ return p;
+}
+
+
+class GravityAffector : public Ogre::ParticleAffector
+{
+ enum ForceType {
+ Type_Wind,
+ Type_Point
+ };
+
+public:
+ /** Command object for force (see Ogre::ParamCommand).*/
+ class CmdForce : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GravityAffector *self = static_cast<const GravityAffector*>(target);
+ return Ogre::StringConverter::toString(self->getForce());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GravityAffector *self = static_cast<GravityAffector*>(target);
+ self->setForce(Ogre::StringConverter::parseReal(val));
+ }
+ };
+
+ /** Command object for force_type (see Ogre::ParamCommand).*/
+ class CmdForceType : public Ogre::ParamCommand
+ {
+ static ForceType getTypeFromString(const Ogre::String &type)
+ {
+ if(type == "wind")
+ return Type_Wind;
+ if(type == "point")
+ return Type_Point;
+ OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "Invalid force type string: "+type,
+ "CmdForceType::getTypeFromString");
+ }
+
+ static Ogre::String getStringFromType(ForceType type)
+ {
+ switch(type)
+ {
+ case Type_Wind: return "wind";
+ case Type_Point: return "point";
+ }
+ OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "Invalid force type enum: "+Ogre::StringConverter::toString(type),
+ "CmdForceType::getStringFromType");
+ }
+
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GravityAffector *self = static_cast<const GravityAffector*>(target);
+ return getStringFromType(self->getForceType());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GravityAffector *self = static_cast<GravityAffector*>(target);
+ self->setForceType(getTypeFromString(val));
+ }
+ };
+
+ /** Command object for direction (see Ogre::ParamCommand).*/
+ class CmdDirection : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GravityAffector *self = static_cast<const GravityAffector*>(target);
+ return Ogre::StringConverter::toString(self->getDirection());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GravityAffector *self = static_cast<GravityAffector*>(target);
+ self->setDirection(Ogre::StringConverter::parseVector3(val));
+ }
+ };
+
+ /** Command object for position (see Ogre::ParamCommand).*/
+ class CmdPosition : public Ogre::ParamCommand
+ {
+ public:
+ Ogre::String doGet(const void *target) const
+ {
+ const GravityAffector *self = static_cast<const GravityAffector*>(target);
+ return Ogre::StringConverter::toString(self->getPosition());
+ }
+ void doSet(void *target, const Ogre::String &val)
+ {
+ GravityAffector *self = static_cast<GravityAffector*>(target);
+ self->setPosition(Ogre::StringConverter::parseVector3(val));
+ }
+ };
+
+
+ /** Default constructor. */
+ GravityAffector(Ogre::ParticleSystem *psys)
+ : ParticleAffector(psys)
+ , mForce(0.0f)
+ , mForceType(Type_Wind)
+ , mPosition(0.0f)
+ , mDirection(0.0f)
+ {
+ mType = "Gravity";
+
+ // Init parameters
+ if(createParamDictionary("GravityAffector"))
+ {
+ Ogre::ParamDictionary *dict = getParamDictionary();
+
+ Ogre::String force_title("force");
+ Ogre::String force_descr("Amount of force applied to particles.");
+ Ogre::String force_type_title("force_type");
+ Ogre::String force_type_descr("Type of force applied to particles (point or wind).");
+ Ogre::String direction_title("direction");
+ Ogre::String direction_descr("Direction of wind forces.");
+ Ogre::String position_title("position");
+ Ogre::String position_descr("Position of point forces.");
+
+ dict->addParameter(Ogre::ParameterDef(force_title, force_descr, Ogre::PT_REAL), &msForceCmd);
+ dict->addParameter(Ogre::ParameterDef(force_type_title, force_type_descr, Ogre::PT_STRING), &msForceTypeCmd);
+ dict->addParameter(Ogre::ParameterDef(direction_title, direction_descr, Ogre::PT_VECTOR3), &msDirectionCmd);
+ dict->addParameter(Ogre::ParameterDef(position_title, position_descr, Ogre::PT_VECTOR3), &msPositionCmd);
+ }
+ }
+
+ /** See Ogre::ParticleAffector. */
+ void _affectParticles(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed)
+ {
+ switch(mForceType)
+ {
+ case Type_Wind:
+ applyWindForce(psys, timeElapsed);
+ break;
+ case Type_Point:
+ applyPointForce(psys, timeElapsed);
+ break;
+ }
+ }
+
+ void setForce(Ogre::Real force)
+ { mForce = force; }
+ Ogre::Real getForce() const
+ { return mForce; }
+
+ void setForceType(ForceType type)
+ { mForceType = type; }
+ ForceType getForceType() const
+ { return mForceType; }
+
+ void setDirection(const Ogre::Vector3 &dir)
+ { mDirection = dir; }
+ const Ogre::Vector3 &getDirection() const
+ { return mDirection; }
+
+ void setPosition(const Ogre::Vector3 &pos)
+ { mPosition = pos; }
+ const Ogre::Vector3 &getPosition() const
+ { return mPosition; }
+
+ static CmdForce msForceCmd;
+ static CmdForceType msForceTypeCmd;
+ static CmdDirection msDirectionCmd;
+ static CmdPosition msPositionCmd;
+
+protected:
+ void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed)
+ {
+ const Ogre::Vector3 vec = mDirection * mForce * timeElapsed;
+ Ogre::ParticleIterator pi = psys->_getIterator();
+ while (!pi.end())
+ {
+ Ogre::Particle *p = pi.getNext();
+ p->direction += vec;
+ }
+ }
+
+ void applyPointForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed)
+ {
+ const Ogre::Real force = mForce * timeElapsed;
+ Ogre::ParticleIterator pi = psys->_getIterator();
+ while (!pi.end())
+ {
+ Ogre::Particle *p = pi.getNext();
+ const Ogre::Vector3 vec = (p->position - mPosition).normalisedCopy() * force;
+ p->direction += vec;
+ }
+ }
+
+
+ float mForce;
+
+ ForceType mForceType;
+
+ Ogre::Vector3 mPosition;
+ Ogre::Vector3 mDirection;
+};
+GravityAffector::CmdForce GravityAffector::msForceCmd;
+GravityAffector::CmdForceType GravityAffector::msForceTypeCmd;
+GravityAffector::CmdDirection GravityAffector::msDirectionCmd;
+GravityAffector::CmdPosition GravityAffector::msPositionCmd;
+
+Ogre::ParticleAffector *GravityAffectorFactory::createAffector(Ogre::ParticleSystem *psys)
+{
+ Ogre::ParticleAffector *p = new GravityAffector(psys);
+ mAffectors.push_back(p);
+ return p;
+}
diff --git a/libs/openengine/ogre/particles.hpp b/libs/openengine/ogre/particles.hpp
new file mode 100644
index 0000000000..e1f3fd282c
--- /dev/null
+++ b/libs/openengine/ogre/particles.hpp
@@ -0,0 +1,41 @@
+#ifndef OENGINE_OGRE_PARTICLES_H
+#define OENGINE_OGRE_PARTICLES_H
+
+#include <OgreParticleEmitterFactory.h>
+#include <OgreParticleAffectorFactory.h>
+
+/** Factory class for NifEmitter. */
+class NifEmitterFactory : public Ogre::ParticleEmitterFactory
+{
+public:
+ /** See ParticleEmitterFactory */
+ Ogre::String getName() const
+ { return "Nif"; }
+
+ /** See ParticleEmitterFactory */
+ Ogre::ParticleEmitter* createEmitter(Ogre::ParticleSystem *psys);
+};
+
+/** Factory class for GrowFadeAffector. */
+class GrowFadeAffectorFactory : public Ogre::ParticleAffectorFactory
+{
+ /** See Ogre::ParticleAffectorFactory */
+ Ogre::String getName() const
+ { return "GrowFade"; }
+
+ /** See Ogre::ParticleAffectorFactory */
+ Ogre::ParticleAffector *createAffector(Ogre::ParticleSystem *psys);
+};
+
+/** Factory class for GravityAffector. */
+class GravityAffectorFactory : public Ogre::ParticleAffectorFactory
+{
+ /** See Ogre::ParticleAffectorFactory */
+ Ogre::String getName() const
+ { return "Gravity"; }
+
+ /** See Ogre::ParticleAffectorFactory */
+ Ogre::ParticleAffector *createAffector(Ogre::ParticleSystem *psys);
+};
+
+#endif /* OENGINE_OGRE_PARTICLES_H */
diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp
new file mode 100644
index 0000000000..9127812408
--- /dev/null
+++ b/libs/openengine/ogre/renderer.cpp
@@ -0,0 +1,319 @@
+#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 <extern/sdl4ogre/sdlwindowhelper.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 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)
+{
+ mFader->update(dt);
+}
+
+void OgreRenderer::screenshot(const std::string &file)
+{
+ mWindow->writeContentsToFile(file);
+}
+
+float OgreRenderer::getFPS()
+{
+ return mWindow->getLastFPS();
+}
+
+void OgreRenderer::configure(const std::string &logPath,
+ const std::string& renderSystem,
+ const std::string& rttMode,
+ bool _logging)
+{
+ // 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);
+
+
+ RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem);
+ if (rs == 0)
+ throw std::runtime_error ("RenderSystem with name " + renderSystem + " not found, make sure the plugins are loaded");
+ mRoot->setRenderSystem(rs);
+
+ if (rs->getName().find("OpenGL") != std::string::npos)
+ rs->setConfigOption ("RTT Preferred Mode", rttMode);
+}
+
+void OgreRenderer::createWindow(const std::string &title, const WindowSettings& settings)
+{
+ assert(mRoot);
+ mRoot->initialise(false);
+
+ NameValuePairList params;
+ params.insert(std::make_pair("title", title));
+ params.insert(std::make_pair("FSAA", settings.fsaa));
+ params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false"));
+
+ int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen),
+ pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen);
+
+ if(settings.fullscreen)
+ {
+ pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen);
+ pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen);
+ }
+
+
+ // Create an application window with the following settings:
+ mSDLWindow = SDL_CreateWindow(
+ "OpenMW", // window title
+ pos_x, // initial x position
+ pos_y, // initial y position
+ settings.window_x, // width, in pixels
+ settings.window_y, // height, in pixels
+ SDL_WINDOW_SHOWN
+ | (settings.fullscreen ? SDL_WINDOW_FULLSCREEN : 0) | SDL_WINDOW_RESIZABLE
+ );
+
+ SFO::SDLWindowHelper helper(mSDLWindow, settings.window_x, settings.window_y, title, settings.fullscreen, params);
+ if (settings.icon != "")
+ helper.setWindowIcon(settings.icon);
+ mWindow = helper.getWindow();
+
+
+ // create the semi-transparent black background texture used by the GUI.
+ // has to be created in code with TU_DYNAMIC_WRITE_ONLY param
+ // so that it can be modified at runtime.
+ Ogre::TextureManager::getSingleton().createManual(
+ "transparent.png",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D,
+ 1, 1,
+ 0,
+ Ogre::PF_A8R8G8B8,
+ Ogre::TU_WRITE_ONLY);
+
+ mScene = mRoot->createSceneManager(ST_GENERIC);
+
+ mFader = new Fader(mScene);
+
+ mCamera = mScene->createCamera("cam");
+
+ // Create one viewport, entire window
+ mView = mWindow->addViewport(mCamera);
+ // Alter the camera aspect ratio to match the viewport
+ mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight()));
+}
+
+void OgreRenderer::adjustCamera(float fov, float nearClip)
+{
+ mCamera->setNearClipDistance(nearClip);
+ mCamera->setFOVy(Degree(fov));
+}
+
+void OgreRenderer::adjustViewport()
+{
+ // Alter the camera aspect ratio to match the viewport
+ if(mCamera != NULL)
+ {
+ mView->setDimensions(0, 0, 1, 1);
+ mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight()));
+ }
+}
+
+void OgreRenderer::setFov(float fov)
+{
+ mCamera->setFOVy(Degree(fov));
+}
+
+void OgreRenderer::windowResized(int x, int y)
+{
+ mWindowListener->windowResized(x,y);
+}
diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp
new file mode 100644
index 0000000000..89edc567dd
--- /dev/null
+++ b/libs/openengine/ogre/renderer.hpp
@@ -0,0 +1,213 @@
+#ifndef OENGINE_OGRE_RENDERER_H
+#define OENGINE_OGRE_RENDERER_H
+
+/*
+ Ogre renderer class
+ */
+
+#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>
+
+#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;
+ class Viewport;
+ class ParticleEmitterFactory;
+ class ParticleAffectorFactory;
+}
+
+namespace OEngine
+{
+ namespace Render
+ {
+ struct WindowSettings
+ {
+ bool vsync;
+ bool fullscreen;
+ int window_x, window_y;
+ int screen;
+ std::string fsaa;
+ 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
+ {
+ public:
+ virtual void windowResized (int x, int y) = 0;
+ };
+
+ 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
+ Fader* mFader;
+ std::vector<Ogre::ParticleEmitterFactory*> mEmitterFactories;
+ std::vector<Ogre::ParticleAffectorFactory*> mAffectorFactories;
+ bool logging;
+
+ WindowSizeListener* mWindowListener;
+
+ public:
+ OgreRenderer()
+ : mRoot(NULL)
+ , mWindow(NULL)
+ , mSDLWindow(NULL)
+ , 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
+ , mFader(NULL)
+ , logging(false)
+ {
+ }
+
+ ~OgreRenderer() { cleanup(); }
+
+ /** Configure the renderer. This will load configuration files and
+ set up the Root and logging classes. */
+ 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
+
+ /// Create a window with the given title
+ void createWindow(const std::string &title, const WindowSettings& settings);
+
+ /// Set up the scene manager, camera and viewport
+ void adjustCamera(
+ float fov=55, // Field of view angle
+ float nearClip=5 // Near clip distance
+ );
+
+ void setFov(float fov);
+
+ /// 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
+ void screenshot(const std::string &file);
+
+ float getFPS();
+
+ void windowResized(int x, int y);
+
+ /// Get the Root
+ Ogre::Root *getRoot() { return mRoot; }
+
+ /// Get the rendering window
+ Ogre::RenderWindow *getWindow() { return mWindow; }
+
+ /// Get the SDL Window
+ SDL_Window *getSDLWindow() { return mSDLWindow; }
+
+ /// Get the scene manager
+ Ogre::SceneManager *getScene() { return mScene; }
+
+ /// Get the screen colour fader
+ Fader *getFader() { return mFader; }
+
+ /// Camera
+ Ogre::Camera *getCamera() { return mCamera; }
+
+ /// Viewport
+ Ogre::Viewport *getViewport() { return mView; }
+
+ void setWindowListener(WindowSizeListener* listener) { mWindowListener = listener; }
+
+ void adjustViewport();
+ };
+ }
+}
+#endif
diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp
new file mode 100644
index 0000000000..69375b74d2
--- /dev/null
+++ b/libs/openengine/ogre/selectionbuffer.cpp
@@ -0,0 +1,134 @@
+#include "selectionbuffer.hpp"
+
+#include <OgreHardwarePixelBuffer.h>
+#include <OgreRenderTexture.h>
+#include <OgreSubEntity.h>
+#include <OgreEntity.h>
+#include <OgreTechnique.h>
+#include <stdexcept>
+
+#include <extern/shiny/Main/Factory.hpp>
+
+namespace OEngine
+{
+namespace Render
+{
+
+ SelectionBuffer::SelectionBuffer(Ogre::Camera *camera, int sizeX, int sizeY, int visibilityFlags)
+ {
+ mTexture = Ogre::TextureManager::getSingleton().createManual("SelectionBuffer",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
+
+ mRenderTarget = mTexture->getBuffer()->getRenderTarget();
+ Ogre::Viewport* vp = mRenderTarget->addViewport(camera);
+ vp->setOverlaysEnabled(false);
+ vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0));
+ vp->setShadowsEnabled(false);
+ vp->setMaterialScheme("selectionbuffer");
+ if (visibilityFlags != 0)
+ vp->setVisibilityMask (visibilityFlags);
+ mRenderTarget->setActive(true);
+ mRenderTarget->setAutoUpdated (false);
+
+ mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3);
+ }
+
+ SelectionBuffer::~SelectionBuffer()
+ {
+ Ogre::TextureManager::getSingleton ().remove("SelectionBuffer");
+ }
+
+ void SelectionBuffer::update ()
+ {
+ Ogre::MaterialManager::getSingleton ().addListener (this);
+
+ mRenderTarget->update();
+
+ Ogre::MaterialManager::getSingleton ().removeListener (this);
+
+ mTexture->convertToImage(mBuffer);
+ }
+
+ int SelectionBuffer::getSelected(int xPos, int yPos)
+ {
+ Ogre::ColourValue clr = mBuffer.getColourAt (xPos, yPos, 0);
+ clr.a = 1;
+ if (mColourMap.find(clr) != mColourMap.end())
+ return mColourMap[clr];
+ else
+ return -1; // nothing selected
+ }
+
+ Ogre::Technique* SelectionBuffer::handleSchemeNotFound (
+ unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial,
+ unsigned short lodIndex, const Ogre::Renderable *rend)
+ {
+ if (schemeName == "selectionbuffer")
+ {
+ sh::Factory::getInstance ()._ensureMaterial ("SelectionColour", "Default");
+
+ Ogre::MaterialPtr m = Ogre::MaterialManager::getSingleton ().getByName("SelectionColour");
+
+
+ if(typeid(*rend) == typeid(Ogre::SubEntity))
+ {
+ const Ogre::SubEntity *subEntity = static_cast<const Ogre::SubEntity *>(rend);
+ int id = Ogre::any_cast<int>(subEntity->getParent ()->getUserObjectBindings().getUserAny());
+ bool found = false;
+ Ogre::ColourValue colour;
+ for (std::map<Ogre::ColourValue, int, cmp_ColourValue>::iterator it = mColourMap.begin(); it != mColourMap.end(); ++it)
+ {
+ if (it->second == id)
+ {
+ found = true;
+ colour = it->first;
+ }
+ }
+
+
+ if (!found)
+ {
+ getNextColour();
+ const_cast<Ogre::SubEntity *>(subEntity)->setCustomParameter(1, Ogre::Vector4(mCurrentColour.r, mCurrentColour.g, mCurrentColour.b, 1.0));
+ mColourMap[mCurrentColour] = id;
+ }
+ else
+ {
+ const_cast<Ogre::SubEntity *>(subEntity)->setCustomParameter(1, Ogre::Vector4(colour.r, colour.g, colour.b, 1.0));
+ }
+
+ assert(m->getTechnique(1));
+ return m->getTechnique(1);
+ }
+ else
+ {
+ m = Ogre::MaterialManager::getSingleton().getByName("NullMaterial");
+ if(m.isNull())
+ {
+ m = Ogre::MaterialManager::getSingleton().create("NullMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
+ m->getTechnique(0)->getPass(0)->setDepthCheckEnabled(true);
+ m->getTechnique(0)->getPass(0)->setDepthFunction(Ogre::CMPF_ALWAYS_FAIL);
+ }
+ return m->getTechnique(0);
+ }
+ }
+ return NULL;
+ }
+
+ void SelectionBuffer::getNextColour ()
+ {
+ Ogre::ARGB color = (float(rand()) / float(RAND_MAX)) * std::numeric_limits<Ogre::uint32>::max();
+
+ if (mCurrentColour.getAsARGB () == color)
+ {
+ getNextColour();
+ return;
+ }
+
+ mCurrentColour.setAsARGB(color);
+ mCurrentColour.a = 1;
+ }
+
+
+}
+}
diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp
new file mode 100644
index 0000000000..c487b24b04
--- /dev/null
+++ b/libs/openengine/ogre/selectionbuffer.hpp
@@ -0,0 +1,54 @@
+#ifndef OENGINE_SELECTIONBUFFER_H
+#define OENGINE_SELECTIONBUFFER_H
+
+
+#include <OgreTexture.h>
+#include <OgreRenderTarget.h>
+#include <OgreMaterialManager.h>
+
+namespace OEngine
+{
+namespace Render
+{
+
+ struct cmp_ColourValue
+ {
+ bool operator()(const Ogre::ColourValue &a, const Ogre::ColourValue &b) const
+ {
+ return a.getAsBGRA() < b.getAsBGRA();
+ }
+ };
+
+ class SelectionBuffer : public Ogre::MaterialManager::Listener
+ {
+ public:
+ SelectionBuffer(Ogre::Camera* camera, int sizeX, int sizeY, int visibilityFlags);
+ virtual ~SelectionBuffer();
+
+ int getSelected(int xPos, int yPos);
+ ///< @return ID of the selected object
+
+ void update();
+
+ virtual Ogre::Technique* handleSchemeNotFound (
+ unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial,
+ unsigned short lodIndex, const Ogre::Renderable *rend);
+
+
+ private:
+ Ogre::TexturePtr mTexture;
+ Ogre::RenderTexture* mRenderTarget;
+
+ Ogre::Image mBuffer;
+
+ std::map<Ogre::ColourValue, int, cmp_ColourValue> mColourMap;
+
+ Ogre::ColourValue mCurrentColour;
+
+ void getNextColour();
+ };
+
+}
+}
+
+#endif
diff --git a/libs/openengine/testall.sh b/libs/openengine/testall.sh
new file mode 100755
index 0000000000..097fdabd5b
--- /dev/null
+++ b/libs/openengine/testall.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+function run()
+{
+ echo "TESTING $1"
+ cd "$1/tests/"
+ ./test.sh
+ cd ../../
+}
+
+run input
diff --git a/libs/platform/stdint.h b/libs/platform/stdint.h
new file mode 100644
index 0000000000..00af741b1e
--- /dev/null
+++ b/libs/platform/stdint.h
@@ -0,0 +1,21 @@
+// Wrapper for MSVC
+#ifndef _STDINT_WRAPPER_H
+#define _STDINT_WRAPPER_H
+
+#if (_MSC_VER >= 1600)
+
+#include <cstdint>
+
+#else
+
+#include <boost/cstdint.hpp>
+
+// Pull the boost names into the global namespace for convenience
+using boost::int32_t;
+using boost::uint32_t;
+using boost::int64_t;
+using boost::uint64_t;
+
+#endif
+
+#endif
diff --git a/libs/platform/string.h b/libs/platform/string.h
new file mode 100644
index 0000000000..5368d757cc
--- /dev/null
+++ b/libs/platform/string.h
@@ -0,0 +1,34 @@
+// Wrapper for string.h on Mac and MinGW
+#ifndef _STRING_WRAPPER_H
+#define _STRING_WRAPPER_H
+
+#ifdef __APPLE__
+#include <Availability.h>
+#endif
+
+#include <string.h>
+#if (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) || defined(__MINGW32__)
+// need our own implementation of strnlen
+#ifdef __MINGW32__
+static size_t strnlen(const char *s, size_t n)
+{
+ const char *p = (const char *)memchr(s, 0, n);
+ return(p ? p-s : n);
+}
+#elif (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
+static size_t mw_strnlen(const char *s, size_t n)
+{
+ if (strnlen != NULL) {
+ return strnlen(s, n);
+ }
+ else {
+ const char *p = (const char *)memchr(s, 0, n);
+ return(p ? p-s : n);
+ }
+}
+#define strnlen mw_strnlen
+#endif
+
+#endif
+
+#endif
diff --git a/libs/platform/strings.h b/libs/platform/strings.h
new file mode 100644
index 0000000000..c0fbb1a1b2
--- /dev/null
+++ b/libs/platform/strings.h
@@ -0,0 +1,18 @@
+// Wrapper for MSVC/GCC
+#ifndef _STRINGS_WRAPPER_H
+#define _STRINGS_WRAPPER_H
+
+
+// For GCC, just use strings.h (this applies to mingw too)
+#if defined(__GNUC__)
+# include <strings.h>
+#elif defined(MSVC) || defined(_MSC_VER)
+# pragma warning(disable: 4996)
+# define strcasecmp stricmp
+# define snprintf _snprintf
+#else
+# warning "Unable to determine your compiler, you should probably take a look here."
+# include <strings.h> // Just take a guess
+#endif
+
+#endif /* _STRINGS_WRAPPER_H */
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000000..7865f8dba2
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,756 @@
+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.26.0
+License: GPL (see GPL3.txt for more information)
+Website: http://www.openmw.org
+
+Font Licenses:
+EBGaramond-Regular.ttf: OFL (see OFL.txt for more information)
+DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information)
+
+
+
+INSTALLATION
+
+Windows:
+Run the installer.
+
+Linux:
+Ubuntu (and most others)
+Download the .deb file and install it in the usual way.
+
+Arch Linux
+There's an OpenMW package available in the AUR Repository:
+http://aur.archlinux.org/packages.php?ID=21419
+
+OS X:
+Open DMG file, copy OpenMW folder anywhere, for example in /Applications
+
+BUILD FROM SOURCE
+
+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
+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
+
+CHANGELOG
+
+0.26.0
+
+Bug #274: Inconsistencies in the terrain
+Bug #557: Already-dead NPCs do not equip clothing/items.
+Bug #592: Window resizing
+Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren)
+Bug #664: Heart of lorkhan acts like a dead body (container)
+Bug #767: Wonky ramp physics & water
+Bug #780: Swimming out of water
+Bug #792: Wrong ground alignment on actors when no clipping
+Bug #796: Opening and closing door sound issue
+Bug #797: No clipping hinders opening and closing of doors
+Bug #799: sliders in enchanting window
+Bug #838: Pressing key during startup procedure freezes the game
+Bug #839: Combat/magic stances during character creation
+Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment
+Bug #844: Resting "until healed" option given even with full stats
+Bug #846: Equipped torches are invisible.
+Bug #847: Incorrect formula for autocalculated NPC initial health
+Bug #850: Shealt weapon sound plays when leaving magic-ready stance
+Bug #852: Some boots do not produce footstep sounds
+Bug #860: FPS bar misalignment
+Bug #861: Unable to print screen
+Bug #863: No sneaking and jumping at the same time
+Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start.
+Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance.
+Bug #868: Idle animations are repeated
+Bug #874: Underwater swimming close to the ground is jerky
+Bug #875: Animation problem while swimming on the surface and looking up
+Bug #876: Always a starting upper case letter in the inventory
+Bug #878: Active spell effects don't update the layout properly when ended
+Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load
+Bug #896: New game sound issue
+Feature #49: Melee Combat
+Feature #71: Lycanthropy
+Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList
+Feature #622: Multiple positions for inventory window
+Feature #627: Drowning
+Feature #786: Allow the 'Activate' key to close the countdialog window
+Feature #798: Morrowind installation via Launcher (Linux/Max OS only)
+Feature #851: First/Third person transitions with mouse wheel
+Task #689: change PhysicActor::enableCollisions
+Task #707: Reorganise Compiler
+
+0.25.0
+
+Bug #411: Launcher crash on OS X < 10.8
+Bug #604: Terrible performance drop in the Census and Excise Office.
+Bug #676: Start Scripts fail to load
+Bug #677: OpenMW does not accept script names with -
+Bug #766: Extra space in front of topic links
+Bug #793: AIWander Isn't Being Passed The Repeat Parameter
+Bug #795: Sound playing with drawn weapon and crossing cell-border
+Bug #800: can't select weapon for enchantment
+Bug #801: Player can move while over-encumbered
+Bug #802: Dead Keys not working
+Bug #808: mouse capture
+Bug #809: ini Importer does not work without an existing cfg file
+Bug #812: Launcher will run OpenMW with no ESM or ESP selected
+Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected
+Bug #817: Dead NPCs and Creatures still have collision boxes
+Bug #820: Incorrect sorting of answers (Dialogue)
+Bug #826: mwinimport dumps core when given an unknown parameter
+Bug #833: getting stuck in door
+Bug #835: Journals/books not showing up properly.
+Feature #38: SoundGen
+Feature #105: AI Package: Wander
+Feature #230: 64-bit compatibility for OS X
+Feature #263: Hardware mouse cursors
+Feature #449: Allow mouse outside of window while paused
+Feature #736: First person animations
+Feature #750: Using mouse wheel in third person mode
+Feature #822: Autorepeat for slider buttons
+
+0.24.0
+
+Bug #284: Book's text misalignment
+Bug #445: Camera able to get slightly below floor / terrain
+Bug #582: Seam issue in Red Mountain
+Bug #632: Journal Next Button shows white square
+Bug #653: IndexedStore ignores index
+Bug #694: Parser does not recognize float values starting with .
+Bug #699: Resource handling broken with Ogre 1.9 trunk
+Bug #718: components/esm/loadcell is using the mwworld subsystem
+Bug #729: Levelled item list tries to add nonexistent item
+Bug #730: Arrow buttons in the settings menu do not work.
+Bug #732: Erroneous behavior when binding keys
+Bug #733: Unclickable dialogue topic
+Bug #734: Book empty line problem
+Bug #738: OnDeath only works with implicit references
+Bug #740: Script compiler fails on scripts with special names
+Bug #742: Wait while no clipping
+Bug #743: Problem with changeweather console command
+Bug #744: No wait dialogue after starting a new game
+Bug #748: Player is not able to unselect objects with the console
+Bug #751: AddItem should only spawn a message box when called from dialogue
+Bug #752: The enter button has several functions in trade and looting that is not impelemted.
+Bug #753: Fargoth's Ring Quest Strange Behavior
+Bug #755: Launcher writes duplicate lines into settings.cfg
+Bug #759: Second quest in mages guild does not work
+Bug #763: Enchantment cast cost is wrong
+Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly
+Bug #773: AIWander Isn't Being Passed The Correct idle Values
+Bug #778: The journal can be opened at the start of a new game
+Bug #779: Divayth Fyr starts as dead
+Bug #787: "Batch count" on detailed FPS counter gets cut-off
+Bug #788: chargen scroll layout does not match vanilla
+Feature #60: Atlethics Skill
+Feature #65: Security Skill
+Feature #74: Interaction with non-load-doors
+Feature #98: Render Weapon and Shield
+Feature #102: AI Package: Escort, EscortCell
+Feature #182: Advanced Journal GUI
+Feature #288: Trading enhancements
+Feature #405: Integrate "new game" into the menu
+Feature #537: Highlight dialogue topic links
+Feature #658: Rotate, RotateWorld script instructions and local rotations
+Feature #690: Animation Layering
+Feature #722: Night Eye/Blind magic effects
+Feature #735: Move, MoveWorld script instructions.
+Feature #760: Non-removable corpses
+
+0.23.0
+
+Bug #522: Player collides with placeable items
+Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open
+Bug #561: Tooltip word wrapping delay
+Bug #578: Bribing works incorrectly
+Bug #601: PositionCell fails on negative coordinates
+Bug #606: Some NPCs hairs not rendered with Better Heads addon
+Bug #609: Bad rendering of bone boots
+Bug #613: Messagebox causing assert to fail
+Bug #631: Segfault on shutdown
+Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard
+Bug #635: Scale NPCs depending on race
+Bug #643: Dialogue Race select function is inverted
+Bug #646: Twohanded weapons don't work properly
+Bug #654: Crash when dropping objects without a collision shape
+Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell
+Bug #660: "g" in "change" cut off in Race Menu
+Bug #661: Arrille sells me the key to his upstairs room
+Bug #662: Day counter starts at 2 instead of 1
+Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur
+Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window.
+Bug #666: Looking up/down problem
+Bug #667: Active effects border visible during loading
+Bug #669: incorrect player position at new game start
+Bug #670: race selection menu: sex, face and hair left button not totally clickable
+Bug #671: new game: player is naked
+Bug #674: buying or selling items doesn't change amount of gold
+Bug #675: fatigue is not set to its maximum when starting a new game
+Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly
+Bug #680: different gold coins in Tel Mara
+Bug #682: Race menu ignores playable flag for some hairs and faces
+Bug #685: Script compiler does not accept ":" after a function name
+Bug #688: dispose corpse makes cross-hair to disappear
+Bug #691: Auto equipping ignores equipment conditions
+Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder
+Bug #696: Draugr incorrect head offset
+Bug #697: Sail transparency issue
+Bug #700: "On the rocks" mod does not load its UV coordinates correctly.
+Bug #702: Some race mods don't work
+Bug #711: Crash during character creation
+Bug #715: Growing Tauryon
+Bug #725: Auto calculate stats
+Bug #728: Failure to open container and talk dialogue
+Bug #731: Crash with Mush-Mere's "background" topic
+Feature #55/657: Item Repairing
+Feature #62/87: Enchanting
+Feature #99: Pathfinding
+Feature #104: AI Package: Travel
+Feature #129: Levelled items
+Feature #204: Texture animations
+Feature #239: Fallback-Settings
+Feature #535: Console object selection improvements
+Feature #629: Add levelup description in levelup layout dialog
+Feature #630: Optional format subrecord in (tes3) header
+Feature #641: Armor rating
+Feature #645: OnDeath script function
+Feature #683: Companion item UI
+Feature #698: Basic Particles
+Task #648: Split up components/esm/loadlocks
+Task #695: mwgui cleanup
+
+0.22.0
+
+Bug #311: Potential infinite recursion in script compiler
+Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit.
+Bug #382: Weird effect in 3rd person on water
+Bug #387: Always use detailed shape for physics raycasts
+Bug #420: Potion/ingredient effects do not stack
+Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips
+Bug #434/Bug #605: Object movement between cells not properly implemented
+Bug #502: Duplicate player collision model at origin
+Bug #509: Dialogue topic list shifts inappropriately
+Bug #513: Sliding stairs
+Bug #515: Launcher does not support non-latin strings
+Bug #525: Race selection preview camera wrong position
+Bug #526: Attributes / skills should not go below zero
+Bug #529: Class and Birthsign menus options should be preselected
+Bug #530: Lock window button graphic missing
+Bug #532: Missing map menu graphics
+Bug #545: ESX selector does not list ESM files properly
+Bug #547: Global variables of type short are read incorrectly
+Bug #550: Invisible meshes collision and tooltip
+Bug #551: Performance drop when loading multiple ESM files
+Bug #552: Don't list CG in options if it is not available
+Bug #555: Character creation windows "OK" button broken
+Bug #558: Segmentation fault when Alt-tabbing with console opened
+Bug #559: Dialog window should not be available before character creation is finished
+Bug #560: Tooltip borders should be stretched
+Bug #562: Sound should not be played when an object cannot be picked up
+Bug #565: Water animation speed + timescale
+Bug #572: Better Bodies' textures don't work
+Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod)
+Bug #574: Moving left/right should not cancel auto-run
+Bug #575: Crash entering the Chamber of Song
+Bug #576: Missing includes
+Bug #577: Left Gloves Addon causes ESMReader exception
+Bug #579: Unable to open container "Kvama Egg Sack"
+Bug #581: Mimicking vanilla Morrowind water
+Bug #583: Gender not recognized
+Bug #586: Wrong char gen behaviour
+Bug #587: "End" script statements with spaces don't work
+Bug #589: Closing message boxes by pressing the activation key
+Bug #590: Ugly Dagoth Ur rendering
+Bug #591: Race selection issues
+Bug #593: Persuasion response should be random
+Bug #595: Footless guard
+Bug #599: Waterfalls are invisible from a certain distance
+Bug #600: Waterfalls rendered incorrectly, cut off by water
+Bug #607: New beast bodies mod crashes
+Bug #608: Crash in cell "Mournhold, Royal Palace"
+Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt
+Bug #613: Messagebox causing assert to fail
+Bug #615: Meshes invisible from above water
+Bug #617: Potion effects should be hidden until discovered
+Bug #619: certain moss hanging from tree has rendering bug
+Bug #621: Batching bloodmoon's trees
+Bug #623: NiMaterialProperty alpha unhandled
+Bug #628: Launcher in latest master crashes the game
+Bug #633: Crash on startup: Better Heads
+Bug #636: Incorrect Char Gen Menu Behavior
+Feature #29: Allow ESPs and multiple ESMs
+Feature #94: Finish class selection-dialogue
+Feature #149: Texture Alphas
+Feature #237: Run Morrowind-ini importer from launcher
+Feature #286: Update Active Spell Icons
+Feature #334: Swimming animation
+Feature #335: Walking animation
+Feature #360: Proper collision shapes for NPCs and creatures
+Feature #367: Lights that behave more like original morrowind implementation
+Feature #477: Special local scripting variables
+Feature #528: Message boxes should close when enter is pressed under certain conditions.
+Feature #543: Add bsa files to the settings imported by the ini importer
+Feature #594: coordinate space and utility functions
+Feature #625: Zoom in vanity mode
+Task #464: Refactor launcher ESX selector into a re-usable component
+Task #624: Unified implementation of type-variable sub-records
+
+0.21.0
+
+Bug #253: Dialogs don't work for Russian version of Morrowind
+Bug #267: Activating creatures without dialogue can still activate the dialogue GUI
+Bug #354: True flickering lights
+Bug #386: The main menu's first entry is wrong (in french)
+Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations
+Bug #495: Activation Range
+Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned
+Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available
+Bug #500: Disposition for most NPCs is 0/100
+Bug #501: Getdisposition command wrongly returns base disposition
+Bug #506: Journal UI doesn't update anymore
+Bug #507: EnableRestMenu is not a valid command - change it to EnableRest
+Bug #508: Crash in Ald Daedroth Shrine
+Bug #517: Wrong price calculation when untrading an item
+Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin
+Bug #524: Beast races are able to wear shoes
+Bug #527: Background music fails to play
+Bug #533: The arch at Gnisis entrance is not displayed
+Bug #534: Terrain gets its correct shape only some time after the cell is loaded
+Bug #536: The same entry can be added multiple times to the journal
+Bug #539: Race selection is broken
+Bug #544: Terrain normal map corrupt when the map is rendered
+Feature #39: Video Playback
+Feature #151: ^-escape sequences in text output
+Feature #392: Add AI related script functions
+Feature #456: Determine required ini fallback values and adjust the ini importer accordingly
+Feature #460: Experimental DirArchives improvements
+Feature #540: Execute scripts of objects in containers/inventories in active cells
+Task #401: Review GMST fixing
+Task #453: Unify case smashing/folding
+Task #512: Rewrite utf8 component
+
+0.20.0
+
+Bug #366: Changing the player's race during character creation does not change the look of the player character
+Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell
+Bug #437: Stop animations when paused
+Bug #438: Time displays as "0 a.m." when it should be "12 a.m."
+Bug #439: Text in "name" field of potion/spell creation window is persistent
+Bug #440: Starting date at a new game is off by one day
+Bug #442: Console window doesn't close properly sometimes
+Bug #448: Do not break container window formatting when item names are very long
+Bug #458: Topics sometimes not automatically added to known topic list
+Bug #476: Auto-Moving allows player movement after using DisablePlayerControls
+Bug #478: After sleeping in a bed the rest dialogue window opens automtically again
+Bug #492: On creating potions the ingredients are removed twice
+Feature #63: Mercantile skill
+Feature #82: Persuasion Dialogue
+Feature #219: Missing dialogue filters/functions
+Feature #369: Add a FailedAction
+Feature #377: Select head/hair on character creation
+Feature #391: Dummy AI package classes
+Feature #435: Global Map, 2nd Layer
+Feature #450: Persuasion
+Feature #457: Add more script instructions
+Feature #474: update the global variable pcrace when the player's race is changed
+Task #158: Move dynamically generated classes from Player class to World Class
+Task #159: ESMStore rework and cleanup
+Task #163: More Component Namespace Cleanup
+Task #402: Move player data from MWWorld::Player to the player's NPC record
+Task #446: Fix no namespace in BulletShapeLoader
+
+0.19.0
+
+Bug #374: Character shakes in 3rd person mode near the origin
+Bug #404: Gamma correct rendering
+Bug #407: Shoes of St. Rilm do not work
+Bug #408: Rugs has collision even if they are not supposed to
+Bug #412: Birthsign menu sorted incorrectly
+Bug #413: Resolutions presented multiple times in launcher
+Bug #414: launcher.cfg file stored in wrong directory
+Bug #415: Wrong esm order in openmw.cfg
+Bug #418: Sound listener position updates incorrectly
+Bug #423: wrong usage of "Version" entry in openmw.desktop
+Bug #426: Do not use hardcoded splash images
+Bug #431: Don't use markers for raycast
+Bug #432: Crash after picking up items from an NPC
+Feature #21/#95: Sleeping/resting
+Feature #61: Alchemy Skill
+Feature #68: Death
+Feature #69/#86: Spell Creation
+Feature #72/#84: Travel
+Feature #76: Global Map, 1st Layer
+Feature #120: Trainer Window
+Feature #152: Skill Increase from Skill Books
+Feature #160: Record Saving
+Task #400: Review GMST access
+
+0.18.0
+
+Bug #310: Button of the "preferences menu" are too small
+Bug #361: Hand-to-hand skill is always 100
+Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to
+Bug #372: playSound3D uses original coordinates instead of current coordinates.
+Bug #373: Static OGRE build faulty
+Bug #375: Alt-tab toggle view
+Bug #376: Screenshots are disable
+Bug #378: Exception when drinking self-made potions
+Bug #380: Cloth visibility problem
+Bug #384: Weird character on doors tooltip.
+Bug #398: Some objects do not collide in MW, but do so in OpenMW
+Feature #22: Implement level-up
+Feature #36: Hide Marker
+Feature #88: Hotkey Window
+Feature #91: Level-Up Dialogue
+Feature #118: Keyboard and Mouse-Button bindings
+Feature #119: Spell Buying Window
+Feature #133: Handle resources across multiple data directories
+Feature #134: Generate a suitable default-value for --data-local
+Feature #292: Object Movement/Creation Script Instructions
+Feature #340: AIPackage data structures
+Feature #356: Ingredients use
+Feature #358: Input system rewrite
+Feature #370: Target handling in actions
+Feature #379: Door markers on the local map
+Feature #389: AI framework
+Feature #395: Using keys to open doors / containers
+Feature #396: Loading screens
+Feature #397: Inventory avatar image and race selection head preview
+Task #339: Move sounds into Action
+
+0.17.0
+
+Bug #225: Valgrind reports about 40MB of leaked memory
+Bug #241: Some physics meshes still don't match
+Bug #248: Some textures are too dark
+Bug #300: Dependency on proprietary CG toolkit
+Bug #302: Some objects don't collide although they should
+Bug #308: Freeze in Balmora, Meldor: Armorer
+Bug #313: openmw without a ~/.config/openmw folder segfault.
+Bug #317: adding non-existing spell via console locks game
+Bug #318: Wrong character normals
+Bug #341: Building with Ogre Debug libraries does not use debug version of plugins
+Bug #347: Crash when running openmw with --start="XYZ"
+Bug #353: FindMyGUI.cmake breaks path on Windows
+Bug #359: WindowManager throws exception at destruction
+Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation
+Feature #33: Allow objects to cross cell-borders
+Feature #59: Dropping Items (replaced stopgap implementation with a proper one)
+Feature #93: Main Menu
+Feature #96/329/330/331/332/333: Player Control
+Feature #180: Object rotation and scaling.
+Feature #272: Incorrect NIF material sharing
+Feature #314: Potion usage
+Feature #324: Skill Gain
+Feature #342: Drain/fortify dynamic stats/attributes magic effects
+Feature #350: Allow console only script instructions
+Feature #352: Run scripts in console on startup
+Task #107: Refactor mw*-subsystems
+Task #325: Make CreatureStats into a class
+Task #345: Use Ogre's animation system
+Task #351: Rewrite Action class to support automatic sound playing
+
+0.16.0
+
+Bug #250: OpenMW launcher erratic behaviour
+Bug #270: Crash because of underwater effect on OS X
+Bug #277: Auto-equipping in some cells not working
+Bug #294: Container GUI ignores disabled inventory menu
+Bug #297: Stats review dialog shows all skills and attribute values as 0
+Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses
+Bug #299: Crash in World::disable
+Bug #306: Non-existent ~/.config/openmw "crash" the launcher.
+Bug #307: False "Data Files" location make the launcher "crash"
+Feature #81: Spell Window
+Feature #85: Alchemy Window
+Feature #181: Support for x.y script syntax
+Feature #242: Weapon and Spell icons
+Feature #254: Ingame settings window
+Feature #293: Allow "stacking" game modes
+Feature #295: Class creation dialog tooltips
+Feature #296: Clicking on the HUD elements should show/hide the respective window
+Feature #301: Direction after using a Teleport Door
+Feature #303: Allow object selection in the console
+Feature #305: Allow the use of = as a synonym for ==
+Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts
+Task #176: Restructure enabling/disabling of MW-references
+Task #283: Integrate ogre.cfg file in settings file
+Task #290: Auto-Close MW-reference related GUI windows
+
+0.15.0
+
+Bug #5: Physics reimplementation (fixes various issues)
+Bug #258: Resizing arrow's background is not transparent
+Bug #268: Widening the stats window in X direction causes layout problems
+Bug #269: Topic pane in dialgoue window is too small for some longer topics
+Bug #271: Dialog choices are sorted incorrectly
+Bug #281: The single quote character is not rendered on dialog windows
+Bug #285: Terrain not handled properly in cells that are not predefined
+Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs
+Feature #15: Collision with Terrain
+Feature #17: Inventory-, Container- and Trade-Windows
+Feature #44: Floating Labels above Focussed Objects
+Feature #80: Tooltips
+Feature #83: Barter Dialogue
+Feature #90: Book and Scroll Windows
+Feature #156: Item Stacking in Containers
+Feature #213: Pulsating lights
+Feature #218: Feather & Burden
+Feature #256: Implement magic effect bookkeeping
+Feature #259: Add missing information to Stats window
+Feature #260: Correct case for dialogue topics
+Feature #280: GUI texture atlasing
+Feature #291: Ability to use GMST strings from GUI layout files
+Task #255: Make MWWorld::Environment into a singleton
+
+0.14.0
+
+Bug #1: Meshes rendered with wrong orientation
+Bug #6/Task #220: Picking up small objects doesn't always work
+Bug #127: tcg doesn't work
+Bug #178: Compablity problems with Ogre 1.8.0 RC 1
+Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI
+Bug #227: Terrain crashes when moving away from predefined cells
+Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces
+Bug #235: TGA texture loading problem
+Bug #246: wireframe mode does not work in water
+Feature #8/#232: Water Rendering
+Feature #13: Terrain Rendering
+Feature #37: Render Path Grid
+Feature #66: Factions
+Feature #77: Local Map
+Feature #78: Compass/Mini-Map
+Feature #97: Render Clothing/Armour
+Feature #121: Window Pinning
+Feature #205: Auto equip
+Feature #217: Contiainer should track changes to its content
+Feature #221: NPC Dialogue Window Enhancements
+Feature #233: Game settings manager
+Feature #240: Spell List and selected spell (no GUI yet)
+Feature #243: Draw State
+Task #113: Morrowind.ini Importer
+Task #215: Refactor the sound code
+Task #216: Update MyGUI
+
+0.13.0
+
+Bug #145: Fixed sound problems after cell change
+Bug #179: Pressing space in console triggers activation
+Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux
+Bug #189: ASCII 16 character added to console on it's activation on Mac OS X
+Bug #190: Case Folding fails with music files
+Bug #192: Keypresses write Text into Console no matter which gui element is active
+Bug #196: Collision shapes out of place
+Bug #202: ESMTool doesn't not work with localised ESM files anymore
+Bug #203: Torch lights only visible on short distance
+Bug #207: Ogre.log not written
+Bug #209: Sounds do not play
+Bug #210: Ogre crash at Dren plantation
+Bug #214: Unsupported file format version
+Bug #222: Launcher is writing openmw.cfg file to wrong location
+Feature #9: NPC Dialogue Window
+Feature #16/42: New sky/weather implementation
+Feature #40: Fading
+Feature #48: NPC Dialogue System
+Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet)
+Feature #161: Load REC_PGRD records
+Feature #195: Wireframe-mode
+Feature #198/199: Various sound effects
+Feature #206: Allow picking data path from launcher if non is set
+Task #108: Refactor window manager class
+Task #172: Sound Manager Cleanup
+Task #173: Create OpenEngine systems in the appropriate manager classes
+Task #184: Adjust MSVC and gcc warning levels
+Task #185: RefData rewrite
+Task #201: Workaround for transparency issues
+Task #208: silenced esm_reader.hpp warning
+
+0.12.0
+
+Bug #154: FPS Drop
+Bug #169: Local scripts continue running if associated object is deleted
+Bug #174: OpenMW fails to start if the config directory doesn't exist
+Bug #187: Missing lighting
+Bug #188: Lights without a mesh are not rendered
+Bug #191: Taking screenshot causes crash when running installed
+Feature #28: Sort out the cell load problem
+Feature #31: Allow the player to move away from pre-defined cells
+Feature #35: Use alternate storage location for modified object position
+Feature #45: NPC animations
+Feature #46: Creature Animation
+Feature #89: Basic Journal Window
+Feature #110: Automatically pick up the path of existing MW-installations
+Feature #183: More FPS display settings
+Task #19: Refactor engine class
+Task #109/Feature #162: Automate Packaging
+Task #112: Catch exceptions thrown in input handling functions
+Task #128/#168: Cleanup Configuration File Handling
+Task #131: NPC Activation doesn't work properly
+Task #144: MWRender cleanup
+Task #155: cmake cleanup
+
+0.11.1
+
+Bug #2: Resources loading doesn't work outside of bsa files
+Bug #3: GUI does not render non-English characters
+Bug #7: openmw.cfg location doesn't match
+Bug #124: The TCL alias for ToggleCollision is missing.
+Bug #125: Some command line options can't be used from a .cfg file
+Bug #126: Toggle-type script instructions are less verbose compared with original MW
+Bug #130: NPC-Record Loading fails for some NPCs
+Bug #167: Launcher sets invalid parameters in ogre config
+Feature #10: Journal
+Feature #12: Rendering Optimisations
+Feature #23: Change Launcher GUI to a tabbed interface
+Feature #24: Integrate the OGRE settings window into the launcher
+Feature #25: Determine openmw.cfg location (Launcher)
+Feature #26: Launcher Profiles
+Feature #79: MessageBox
+Feature #116: Tab-Completion in Console
+Feature #132: --data-local and multiple --data
+Feature #143: Non-Rendering Performance-Optimisations
+Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs
+Feature #157: Version Handling
+Task #14: Replace tabs with 4 spaces
+Task #18: Move components from global namespace into their own namespace
+Task #123: refactor header files in components/esm
+
+0.10.0
+
+* NPC dialogue window (not functional yet)
+* Collisions with objects
+* Refactor the PlayerPos class
+* Adjust file locations
+* CMake files and test linking for Bullet
+* Replace Ogre raycasting test for activation with something more precise
+* Adjust player movement according to collision results
+* FPS display
+* Various Portability Improvements
+* Mac OS X support is back!
+
+0.9.0
+
+* Exterior cells loading, unloading and management
+* Character Creation GUI
+* Character creation
+* Make cell names case insensitive when doing internal lookups
+* Music player
+* NPCs rendering
+
+0.8.0
+
+* GUI
+* Complete and working script engine
+* In game console
+* Sky rendering
+* Sound and music
+* Tons of smaller stuff
+
+0.7.0
+
+* This release is a complete rewrite in C++.
+* All D code has been culled, and all modules have been rewritten.
+* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase.
+
+0.6.0
+
+* Coded a GUI system using MyGUI
+* Skinned MyGUI to look like Morrowind (work in progress)
+* Integrated the Monster script engine
+* Rewrote some functions into script code
+* Very early MyGUI < > Monster binding
+* Fixed Windows sound problems (replaced old openal32.dll)
+
+0.5.0
+
+* Collision detection with Bullet
+* Experimental walk & fall character physics
+* New key bindings:
+ * t toggle physics mode (walking, flying, ghost),
+ * n night eye, brightens the scene
+* Fixed incompatability with DMD 1.032 and newer compilers
+* * (thanks to tomqyp)
+* Various minor changes and updates
+
+0.4.0
+
+* Switched from Audiere to OpenAL
+* * (BIG thanks to Chris Robinson)
+* Added complete Makefile (again) as a alternative build tool
+* More realistic lighting (thanks again to Chris Robinson)
+* Various localization fixes tested with Russian and French versions
+* Temporary workaround for the Unicode issue: invalid UTF displayed as '?'
+* Added ns option to disable sound, for debugging
+* Various bug fixes
+* Cosmetic changes to placate gdc Wall
+
+0.3.0
+
+* Built and tested on Windows XP
+* Partial support for FreeBSD (exceptions do not work)
+* You no longer have to download Monster separately
+* Made an alternative for building without DSSS (but DSSS still works)
+* Renamed main program from 'morro' to 'openmw'
+* Made the config system more robust
+* Added oc switch for showing Ogre config window on startup
+* Removed some config files, these are auto generated when missing.
+* Separated plugins.cfg into linux and windows versions.
+* Updated Makefile and sources for increased portability
+* confirmed to work against OIS 1.0.0 (Ubuntu repository package)
+
+0.2.0
+
+* Compiles with gdc
+* Switched to DSSS for building D code
+* Includes the program esmtool
+
+0.1.0
+
+first release