summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBret Curtis <psi29a@gmail.com>2014-03-04 13:02:28 +0100
committerBret Curtis <psi29a@gmail.com>2014-03-04 13:02:28 +0100
commit0f305dc522d805d09f320c8bdeb90e1e64e02858 (patch)
tree84130a0e3932a7e666de74bbf7214c8bbe11afe0
parent1c936e59aa8b5acff14d7039eeec9d434f9d0368 (diff)
Imported Upstream version 0.29.0upstream/0.29.0
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml17
-rw-r--r--CMakeLists.txt138
-rw-r--r--apps/esmtool/esmtool.cpp7
-rw-r--r--apps/esmtool/labels.cpp65
-rw-r--r--apps/esmtool/labels.hpp3
-rw-r--r--apps/esmtool/record.cpp12
-rw-r--r--apps/launcher/main.cpp1
-rw-r--r--apps/launcher/maindialog.cpp24
-rw-r--r--apps/launcher/unshieldthread.cpp6
-rw-r--r--apps/mwiniimporter/importer.cpp11
-rw-r--r--apps/opencs/CMakeLists.txt6
-rw-r--r--apps/opencs/model/doc/document.hpp5
-rw-r--r--apps/opencs/model/doc/documentmanager.hpp4
-rw-r--r--apps/opencs/model/doc/savingstages.cpp19
-rw-r--r--apps/opencs/model/doc/savingstages.hpp4
-rw-r--r--apps/opencs/model/tools/referenceablecheck.cpp1095
-rw-r--r--apps/opencs/model/tools/referenceablecheck.hpp78
-rw-r--r--apps/opencs/model/tools/reportmodel.cpp2
-rw-r--r--apps/opencs/model/tools/scriptcheck.cpp103
-rw-r--r--apps/opencs/model/tools/scriptcheck.hpp41
-rw-r--r--apps/opencs/model/tools/tools.cpp9
-rw-r--r--apps/opencs/model/world/collection.hpp17
-rw-r--r--apps/opencs/model/world/collectionbase.hpp4
-rw-r--r--apps/opencs/model/world/columnbase.hpp42
-rw-r--r--apps/opencs/model/world/columnimp.hpp22
-rw-r--r--apps/opencs/model/world/columns.cpp6
-rw-r--r--apps/opencs/model/world/commands.cpp41
-rw-r--r--apps/opencs/model/world/commands.hpp20
-rw-r--r--apps/opencs/model/world/data.cpp6
-rw-r--r--apps/opencs/model/world/idtable.cpp11
-rw-r--r--apps/opencs/model/world/idtable.hpp4
-rw-r--r--apps/opencs/model/world/idtableproxymodel.cpp4
-rw-r--r--apps/opencs/model/world/infocollection.cpp6
-rw-r--r--apps/opencs/model/world/ref.cpp3
-rw-r--r--apps/opencs/model/world/refcollection.cpp16
-rw-r--r--apps/opencs/model/world/refcollection.hpp6
-rw-r--r--apps/opencs/model/world/refidadapter.hpp1
-rw-r--r--apps/opencs/model/world/refidadapterimp.hpp8
-rw-r--r--apps/opencs/model/world/refidcollection.cpp39
-rw-r--r--apps/opencs/model/world/refidcollection.hpp7
-rw-r--r--apps/opencs/model/world/refiddata.cpp120
-rw-r--r--apps/opencs/model/world/refiddata.hpp63
-rw-r--r--apps/opencs/model/world/scriptcontext.cpp88
-rw-r--r--apps/opencs/model/world/scriptcontext.hpp17
-rw-r--r--apps/opencs/model/world/tablemimedata.cpp446
-rw-r--r--apps/opencs/model/world/tablemimedata.hpp63
-rw-r--r--apps/opencs/model/world/universalid.cpp2
-rw-r--r--apps/opencs/view/filter/editwidget.cpp145
-rw-r--r--apps/opencs/view/filter/editwidget.hpp9
-rw-r--r--apps/opencs/view/filter/filterbox.cpp28
-rw-r--r--apps/opencs/view/filter/filterbox.hpp15
-rw-r--r--apps/opencs/view/filter/recordfilterbox.cpp5
-rw-r--r--apps/opencs/view/filter/recordfilterbox.hpp4
-rw-r--r--apps/opencs/view/render/scenewidget.cpp1
-rw-r--r--apps/opencs/view/world/cellcreator.cpp24
-rw-r--r--apps/opencs/view/world/cellcreator.hpp5
-rw-r--r--apps/opencs/view/world/creator.hpp6
-rw-r--r--apps/opencs/view/world/genericcreator.cpp50
-rw-r--r--apps/opencs/view/world/genericcreator.hpp12
-rw-r--r--apps/opencs/view/world/referenceablecreator.cpp8
-rw-r--r--apps/opencs/view/world/referenceablecreator.hpp1
-rw-r--r--apps/opencs/view/world/referencecreator.cpp22
-rw-r--r--apps/opencs/view/world/referencecreator.hpp4
-rw-r--r--apps/opencs/view/world/scriptedit.cpp88
-rw-r--r--apps/opencs/view/world/scriptedit.hpp39
-rw-r--r--apps/opencs/view/world/scriptsubview.cpp3
-rw-r--r--apps/opencs/view/world/table.cpp139
-rw-r--r--apps/opencs/view/world/table.hpp28
-rw-r--r--apps/opencs/view/world/tablebottombox.cpp14
-rw-r--r--apps/opencs/view/world/tablebottombox.hpp3
-rw-r--r--apps/opencs/view/world/tablesubview.cpp58
-rw-r--r--apps/opencs/view/world/tablesubview.hpp20
-rw-r--r--apps/openmw/CMakeLists.txt16
-rw-r--r--apps/openmw/crashcatcher.cpp657
-rw-r--r--apps/openmw/engine.cpp131
-rw-r--r--apps/openmw/engine.hpp8
-rw-r--r--apps/openmw/main.cpp20
-rw-r--r--apps/openmw/mwbase/dialoguemanager.hpp16
-rw-r--r--apps/openmw/mwbase/environment.cpp19
-rw-r--r--apps/openmw/mwbase/environment.hpp11
-rw-r--r--apps/openmw/mwbase/journal.hpp16
-rw-r--r--apps/openmw/mwbase/mechanicsmanager.hpp80
-rw-r--r--apps/openmw/mwbase/scriptmanager.hpp2
-rw-r--r--apps/openmw/mwbase/soundmanager.hpp4
-rw-r--r--apps/openmw/mwbase/statemanager.hpp81
-rw-r--r--apps/openmw/mwbase/windowmanager.hpp28
-rw-r--r--apps/openmw/mwbase/world.hpp86
-rw-r--r--apps/openmw/mwclass/activator.cpp4
-rw-r--r--apps/openmw/mwclass/apparatus.cpp1
-rw-r--r--apps/openmw/mwclass/armor.cpp33
-rw-r--r--apps/openmw/mwclass/armor.hpp2
-rw-r--r--apps/openmw/mwclass/book.cpp5
-rw-r--r--apps/openmw/mwclass/book.hpp2
-rw-r--r--apps/openmw/mwclass/clothing.cpp31
-rw-r--r--apps/openmw/mwclass/clothing.hpp2
-rw-r--r--apps/openmw/mwclass/container.cpp29
-rw-r--r--apps/openmw/mwclass/container.hpp8
-rw-r--r--apps/openmw/mwclass/creature.cpp399
-rw-r--r--apps/openmw/mwclass/creature.hpp36
-rw-r--r--apps/openmw/mwclass/creaturelevlist.cpp47
-rw-r--r--apps/openmw/mwclass/creaturelevlist.hpp5
-rw-r--r--apps/openmw/mwclass/door.cpp19
-rw-r--r--apps/openmw/mwclass/ingredient.cpp4
-rw-r--r--apps/openmw/mwclass/light.cpp22
-rw-r--r--apps/openmw/mwclass/light.hpp8
-rw-r--r--apps/openmw/mwclass/lockpick.cpp1
-rw-r--r--apps/openmw/mwclass/misc.cpp15
-rw-r--r--apps/openmw/mwclass/npc.cpp405
-rw-r--r--apps/openmw/mwclass/npc.hpp34
-rw-r--r--apps/openmw/mwclass/potion.cpp6
-rw-r--r--apps/openmw/mwclass/probe.cpp1
-rw-r--r--apps/openmw/mwclass/repair.cpp1
-rw-r--r--apps/openmw/mwclass/weapon.cpp5
-rw-r--r--apps/openmw/mwclass/weapon.hpp2
-rw-r--r--apps/openmw/mwdialogue/dialoguemanagerimp.cpp79
-rw-r--r--apps/openmw/mwdialogue/dialoguemanagerimp.hpp11
-rw-r--r--apps/openmw/mwdialogue/filter.cpp60
-rw-r--r--apps/openmw/mwdialogue/journalentry.cpp72
-rw-r--r--apps/openmw/mwdialogue/journalentry.hpp37
-rw-r--r--apps/openmw/mwdialogue/journalimp.cpp157
-rw-r--r--apps/openmw/mwdialogue/journalimp.hpp14
-rw-r--r--apps/openmw/mwdialogue/quest.cpp33
-rw-r--r--apps/openmw/mwdialogue/quest.hpp15
-rw-r--r--apps/openmw/mwdialogue/topic.cpp30
-rw-r--r--apps/openmw/mwdialogue/topic.hpp18
-rw-r--r--apps/openmw/mwgui/alchemywindow.cpp7
-rw-r--r--apps/openmw/mwgui/birth.cpp18
-rw-r--r--apps/openmw/mwgui/birth.hpp1
-rw-r--r--apps/openmw/mwgui/bookwindow.cpp17
-rw-r--r--apps/openmw/mwgui/bookwindow.hpp1
-rw-r--r--apps/openmw/mwgui/charactercreation.cpp26
-rw-r--r--apps/openmw/mwgui/charactercreation.hpp4
-rw-r--r--apps/openmw/mwgui/class.cpp22
-rw-r--r--apps/openmw/mwgui/class.hpp1
-rw-r--r--apps/openmw/mwgui/companionitemmodel.cpp4
-rw-r--r--apps/openmw/mwgui/console.cpp8
-rw-r--r--apps/openmw/mwgui/container.cpp84
-rw-r--r--apps/openmw/mwgui/container.hpp7
-rw-r--r--apps/openmw/mwgui/containeritemmodel.cpp5
-rw-r--r--apps/openmw/mwgui/dialogue.cpp15
-rw-r--r--apps/openmw/mwgui/enchantingdialog.cpp40
-rw-r--r--apps/openmw/mwgui/formatting.cpp12
-rw-r--r--apps/openmw/mwgui/hud.cpp12
-rw-r--r--apps/openmw/mwgui/inventoryitemmodel.cpp11
-rw-r--r--apps/openmw/mwgui/inventorywindow.cpp132
-rw-r--r--apps/openmw/mwgui/inventorywindow.hpp8
-rw-r--r--apps/openmw/mwgui/journalbooks.cpp28
-rw-r--r--apps/openmw/mwgui/journalviewmodel.cpp79
-rw-r--r--apps/openmw/mwgui/levelupdialog.cpp13
-rw-r--r--apps/openmw/mwgui/mainmenu.cpp163
-rw-r--r--apps/openmw/mwgui/mainmenu.hpp28
-rw-r--r--apps/openmw/mwgui/mapwindow.cpp47
-rw-r--r--apps/openmw/mwgui/mapwindow.hpp20
-rw-r--r--apps/openmw/mwgui/merchantrepair.cpp18
-rw-r--r--apps/openmw/mwgui/messagebox.cpp70
-rw-r--r--apps/openmw/mwgui/messagebox.hpp5
-rw-r--r--apps/openmw/mwgui/pickpocketitemmodel.cpp2
-rw-r--r--apps/openmw/mwgui/quickkeysmenu.cpp45
-rw-r--r--apps/openmw/mwgui/race.cpp18
-rw-r--r--apps/openmw/mwgui/race.hpp1
-rw-r--r--apps/openmw/mwgui/recharge.cpp5
-rw-r--r--apps/openmw/mwgui/referenceinterface.cpp4
-rw-r--r--apps/openmw/mwgui/repair.cpp3
-rw-r--r--apps/openmw/mwgui/review.cpp14
-rw-r--r--apps/openmw/mwgui/review.hpp6
-rw-r--r--apps/openmw/mwgui/savegamedialog.cpp240
-rw-r--r--apps/openmw/mwgui/savegamedialog.hpp19
-rw-r--r--apps/openmw/mwgui/scrollwindow.cpp3
-rw-r--r--apps/openmw/mwgui/spellbuyingwindow.cpp35
-rw-r--r--apps/openmw/mwgui/spellcreationdialog.cpp21
-rw-r--r--apps/openmw/mwgui/spellicons.cpp6
-rw-r--r--apps/openmw/mwgui/spellicons.hpp3
-rw-r--r--apps/openmw/mwgui/spellwindow.cpp31
-rw-r--r--apps/openmw/mwgui/spellwindow.hpp6
-rw-r--r--apps/openmw/mwgui/statswindow.cpp41
-rw-r--r--apps/openmw/mwgui/statswindow.hpp12
-rw-r--r--apps/openmw/mwgui/tooltips.cpp3
-rw-r--r--apps/openmw/mwgui/tradeitemmodel.cpp4
-rw-r--r--apps/openmw/mwgui/tradewindow.cpp60
-rw-r--r--apps/openmw/mwgui/tradewindow.hpp4
-rw-r--r--apps/openmw/mwgui/trainingwindow.cpp30
-rw-r--r--apps/openmw/mwgui/travelwindow.cpp26
-rw-r--r--apps/openmw/mwgui/waitdialog.cpp129
-rw-r--r--apps/openmw/mwgui/waitdialog.hpp3
-rw-r--r--apps/openmw/mwgui/widgets.cpp13
-rw-r--r--apps/openmw/mwgui/widgets.hpp2
-rw-r--r--apps/openmw/mwgui/windowbase.cpp34
-rw-r--r--apps/openmw/mwgui/windowbase.hpp16
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.cpp104
-rw-r--r--apps/openmw/mwgui/windowmanagerimp.hpp23
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.cpp117
-rw-r--r--apps/openmw/mwinput/inputmanagerimp.hpp15
-rw-r--r--apps/openmw/mwmechanics/activespells.cpp30
-rw-r--r--apps/openmw/mwmechanics/activespells.hpp16
-rw-r--r--apps/openmw/mwmechanics/actors.cpp517
-rw-r--r--apps/openmw/mwmechanics/actors.hpp26
-rw-r--r--apps/openmw/mwmechanics/aiactivate.cpp132
-rw-r--r--apps/openmw/mwmechanics/aiactivate.hpp6
-rw-r--r--apps/openmw/mwmechanics/aicombat.cpp416
-rw-r--r--apps/openmw/mwmechanics/aicombat.hpp33
-rw-r--r--apps/openmw/mwmechanics/aiescort.cpp48
-rw-r--r--apps/openmw/mwmechanics/aiescort.hpp4
-rw-r--r--apps/openmw/mwmechanics/aifollow.cpp123
-rw-r--r--apps/openmw/mwmechanics/aifollow.hpp12
-rw-r--r--apps/openmw/mwmechanics/aipackage.hpp13
-rw-r--r--apps/openmw/mwmechanics/aisequence.cpp40
-rw-r--r--apps/openmw/mwmechanics/aisequence.hpp19
-rw-r--r--apps/openmw/mwmechanics/aitravel.cpp34
-rw-r--r--apps/openmw/mwmechanics/aitravel.hpp4
-rw-r--r--apps/openmw/mwmechanics/aiwander.cpp68
-rw-r--r--apps/openmw/mwmechanics/aiwander.hpp2
-rw-r--r--apps/openmw/mwmechanics/alchemy.cpp2
-rw-r--r--apps/openmw/mwmechanics/character.cpp582
-rw-r--r--apps/openmw/mwmechanics/character.hpp37
-rw-r--r--apps/openmw/mwmechanics/combat.cpp137
-rw-r--r--apps/openmw/mwmechanics/combat.hpp16
-rw-r--r--apps/openmw/mwmechanics/creaturestats.cpp171
-rw-r--r--apps/openmw/mwmechanics/creaturestats.hpp98
-rw-r--r--apps/openmw/mwmechanics/disease.hpp53
-rw-r--r--apps/openmw/mwmechanics/enchanting.cpp39
-rw-r--r--apps/openmw/mwmechanics/enchanting.hpp2
-rw-r--r--apps/openmw/mwmechanics/levelledlist.hpp83
-rw-r--r--apps/openmw/mwmechanics/magiceffects.hpp3
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.cpp320
-rw-r--r--apps/openmw/mwmechanics/mechanicsmanagerimp.hpp49
-rw-r--r--apps/openmw/mwmechanics/npcstats.cpp281
-rw-r--r--apps/openmw/mwmechanics/npcstats.hpp56
-rw-r--r--apps/openmw/mwmechanics/objects.cpp11
-rw-r--r--apps/openmw/mwmechanics/objects.hpp2
-rw-r--r--apps/openmw/mwmechanics/pathfinding.cpp303
-rw-r--r--apps/openmw/mwmechanics/pathfinding.hpp51
-rw-r--r--apps/openmw/mwmechanics/pickpocket.cpp66
-rw-r--r--apps/openmw/mwmechanics/pickpocket.hpp30
-rw-r--r--apps/openmw/mwmechanics/repair.cpp19
-rw-r--r--apps/openmw/mwmechanics/security.cpp4
-rw-r--r--apps/openmw/mwmechanics/spellcasting.cpp308
-rw-r--r--apps/openmw/mwmechanics/spellcasting.hpp175
-rw-r--r--apps/openmw/mwmechanics/spells.cpp7
-rw-r--r--apps/openmw/mwmechanics/spells.hpp4
-rw-r--r--apps/openmw/mwmechanics/stat.cpp29
-rw-r--r--apps/openmw/mwmechanics/stat.hpp87
-rw-r--r--apps/openmw/mwmechanics/steering.cpp43
-rw-r--r--apps/openmw/mwmechanics/steering.hpp19
-rw-r--r--apps/openmw/mwrender/activatoranimation.hpp4
-rw-r--r--apps/openmw/mwrender/actors.cpp13
-rw-r--r--apps/openmw/mwrender/actors.hpp6
-rw-r--r--apps/openmw/mwrender/animation.cpp160
-rw-r--r--apps/openmw/mwrender/animation.hpp51
-rw-r--r--apps/openmw/mwrender/camera.cpp16
-rw-r--r--apps/openmw/mwrender/camera.hpp2
-rw-r--r--apps/openmw/mwrender/characterpreview.cpp23
-rw-r--r--apps/openmw/mwrender/characterpreview.hpp2
-rw-r--r--apps/openmw/mwrender/creatureanimation.cpp126
-rw-r--r--apps/openmw/mwrender/creatureanimation.hpp32
-rw-r--r--apps/openmw/mwrender/debugging.cpp8
-rw-r--r--apps/openmw/mwrender/debugging.hpp4
-rw-r--r--apps/openmw/mwrender/effectmanager.cpp119
-rw-r--r--apps/openmw/mwrender/effectmanager.hpp31
-rw-r--r--apps/openmw/mwrender/externalrendering.hpp23
-rw-r--r--apps/openmw/mwrender/globalmap.cpp134
-rw-r--r--apps/openmw/mwrender/globalmap.hpp17
-rw-r--r--apps/openmw/mwrender/localmap.cpp6
-rw-r--r--apps/openmw/mwrender/localmap.hpp4
-rw-r--r--apps/openmw/mwrender/npcanimation.cpp142
-rw-r--r--apps/openmw/mwrender/npcanimation.hpp48
-rw-r--r--apps/openmw/mwrender/objects.cpp6
-rw-r--r--apps/openmw/mwrender/objects.hpp4
-rw-r--r--apps/openmw/mwrender/occlusionquery.cpp1
-rw-r--r--apps/openmw/mwrender/occlusionquery.hpp4
-rw-r--r--apps/openmw/mwrender/renderinginterface.hpp4
-rw-r--r--apps/openmw/mwrender/renderingmanager.cpp110
-rw-r--r--apps/openmw/mwrender/renderingmanager.hpp18
-rw-r--r--apps/openmw/mwrender/ripplesimulation.cpp6
-rw-r--r--apps/openmw/mwrender/shadows.cpp1
-rw-r--r--apps/openmw/mwrender/terrainstorage.cpp520
-rw-r--r--apps/openmw/mwrender/terrainstorage.hpp74
-rw-r--r--apps/openmw/mwrender/water.cpp1
-rw-r--r--apps/openmw/mwscript/aiextensions.cpp97
-rw-r--r--apps/openmw/mwscript/cellextensions.cpp20
-rw-r--r--apps/openmw/mwscript/compilercontext.cpp40
-rw-r--r--apps/openmw/mwscript/compilercontext.hpp11
-rw-r--r--apps/openmw/mwscript/containerextensions.cpp26
-rw-r--r--apps/openmw/mwscript/controlextensions.cpp47
-rw-r--r--apps/openmw/mwscript/dialogueextensions.cpp3
-rw-r--r--apps/openmw/mwscript/docs/vmformat.txt35
-rw-r--r--apps/openmw/mwscript/globalscripts.cpp127
-rw-r--r--apps/openmw/mwscript/globalscripts.hpp30
-rw-r--r--apps/openmw/mwscript/guiextensions.cpp22
-rw-r--r--apps/openmw/mwscript/interpretercontext.cpp201
-rw-r--r--apps/openmw/mwscript/interpretercontext.hpp50
-rw-r--r--apps/openmw/mwscript/locals.cpp66
-rw-r--r--apps/openmw/mwscript/locals.hpp4
-rw-r--r--apps/openmw/mwscript/miscextensions.cpp203
-rw-r--r--apps/openmw/mwscript/ref.hpp6
-rw-r--r--apps/openmw/mwscript/scriptmanagerimp.cpp53
-rw-r--r--apps/openmw/mwscript/scriptmanagerimp.hpp4
-rw-r--r--apps/openmw/mwscript/statsextensions.cpp158
-rw-r--r--apps/openmw/mwscript/transformationextensions.cpp9
-rw-r--r--apps/openmw/mwsound/openal_output.cpp35
-rw-r--r--apps/openmw/mwsound/soundmanagerimp.cpp43
-rw-r--r--apps/openmw/mwsound/soundmanagerimp.hpp4
-rw-r--r--apps/openmw/mwstate/character.cpp153
-rw-r--r--apps/openmw/mwstate/character.hpp63
-rw-r--r--apps/openmw/mwstate/charactermanager.cpp85
-rw-r--r--apps/openmw/mwstate/charactermanager.hpp46
-rw-r--r--apps/openmw/mwstate/statemanagerimp.cpp352
-rw-r--r--apps/openmw/mwstate/statemanagerimp.hpp70
-rw-r--r--apps/openmw/mwworld/actionequip.cpp16
-rw-r--r--apps/openmw/mwworld/actionopen.cpp4
-rw-r--r--apps/openmw/mwworld/actionread.cpp2
-rw-r--r--apps/openmw/mwworld/actiontake.cpp9
-rw-r--r--apps/openmw/mwworld/actionteleport.cpp33
-rw-r--r--apps/openmw/mwworld/actiontrap.cpp1
-rw-r--r--apps/openmw/mwworld/cells.cpp164
-rw-r--r--apps/openmw/mwworld/cells.hpp23
-rw-r--r--apps/openmw/mwworld/cellstore.cpp411
-rw-r--r--apps/openmw/mwworld/cellstore.hpp19
-rw-r--r--apps/openmw/mwworld/class.cpp51
-rw-r--r--apps/openmw/mwworld/class.hpp45
-rw-r--r--apps/openmw/mwworld/containerstore.cpp284
-rw-r--r--apps/openmw/mwworld/containerstore.hpp52
-rw-r--r--apps/openmw/mwworld/esmstore.cpp68
-rw-r--r--apps/openmw/mwworld/esmstore.hpp30
-rw-r--r--apps/openmw/mwworld/globals.cpp140
-rw-r--r--apps/openmw/mwworld/globals.hpp65
-rw-r--r--apps/openmw/mwworld/inventorystore.cpp165
-rw-r--r--apps/openmw/mwworld/inventorystore.hpp19
-rw-r--r--apps/openmw/mwworld/livecellref.cpp29
-rw-r--r--apps/openmw/mwworld/livecellref.hpp70
-rw-r--r--apps/openmw/mwworld/localscripts.cpp14
-rw-r--r--apps/openmw/mwworld/manualref.hpp7
-rw-r--r--apps/openmw/mwworld/physicssystem.cpp17
-rw-r--r--apps/openmw/mwworld/player.cpp112
-rw-r--r--apps/openmw/mwworld/player.hpp8
-rw-r--r--apps/openmw/mwworld/ptr.cpp8
-rw-r--r--apps/openmw/mwworld/ptr.hpp7
-rw-r--r--apps/openmw/mwworld/refdata.cpp36
-rw-r--r--apps/openmw/mwworld/refdata.hpp11
-rw-r--r--apps/openmw/mwworld/scene.cpp24
-rw-r--r--apps/openmw/mwworld/scene.hpp4
-rw-r--r--apps/openmw/mwworld/store.cpp19
-rw-r--r--apps/openmw/mwworld/store.hpp48
-rw-r--r--apps/openmw/mwworld/weather.cpp5
-rw-r--r--apps/openmw/mwworld/worldimp.cpp803
-rw-r--r--apps/openmw/mwworld/worldimp.hpp94
-rw-r--r--cmake/FindSDL.cmake177
-rw-r--r--cmake/GetGitRevisionDescription.cmake154
-rw-r--r--cmake/GetGitRevisionDescription.cmake.in38
-rw-r--r--components/CMakeLists.txt17
-rw-r--r--components/bsa/bsa_archive.cpp2
-rw-r--r--components/bsa/bsa_archive.hpp2
-rw-r--r--components/compiler/context.hpp11
-rw-r--r--components/compiler/controlparser.cpp27
-rw-r--r--components/compiler/controlparser.hpp5
-rw-r--r--components/compiler/declarationparser.cpp83
-rw-r--r--components/compiler/declarationparser.hpp43
-rw-r--r--components/compiler/errorhandler.cpp16
-rw-r--r--components/compiler/errorhandler.hpp6
-rw-r--r--components/compiler/exprparser.cpp101
-rw-r--r--components/compiler/exprparser.hpp3
-rw-r--r--components/compiler/extensions.cpp8
-rw-r--r--components/compiler/extensions.hpp10
-rw-r--r--components/compiler/extensions0.cpp28
-rw-r--r--components/compiler/generator.cpp43
-rw-r--r--components/compiler/generator.hpp6
-rw-r--r--components/compiler/lineparser.cpp148
-rw-r--r--components/compiler/lineparser.hpp6
-rw-r--r--components/compiler/locals.cpp46
-rw-r--r--components/compiler/locals.hpp20
-rw-r--r--components/compiler/opcodes.hpp32
-rw-r--r--components/compiler/parser.cpp4
-rw-r--r--components/compiler/parser.hpp6
-rw-r--r--components/compiler/quickfileparser.cpp52
-rw-r--r--components/compiler/quickfileparser.hpp39
-rw-r--r--components/compiler/scanner.cpp4
-rw-r--r--components/compiler/scriptparser.cpp10
-rw-r--r--components/compiler/scriptparser.hpp14
-rw-r--r--components/compiler/skipparser.cpp4
-rw-r--r--components/compiler/skipparser.hpp8
-rw-r--r--components/compiler/stringparser.cpp2
-rw-r--r--components/compiler/stringparser.hpp18
-rw-r--r--components/esm/aipackage.hpp2
-rw-r--r--components/esm/cellid.cpp26
-rw-r--r--components/esm/cellid.hpp28
-rw-r--r--components/esm/cellref.cpp117
-rw-r--r--components/esm/cellref.hpp24
-rw-r--r--components/esm/cellstate.cpp17
-rw-r--r--components/esm/cellstate.hpp25
-rw-r--r--components/esm/containerstate.cpp16
-rw-r--r--components/esm/containerstate.hpp20
-rw-r--r--components/esm/creaturestate.cpp20
-rw-r--r--components/esm/creaturestate.hpp22
-rw-r--r--components/esm/creaturestats.cpp20
-rw-r--r--components/esm/creaturestats.hpp27
-rw-r--r--components/esm/defs.hpp10
-rw-r--r--components/esm/dialoguestate.cpp21
-rw-r--r--components/esm/dialoguestate.hpp23
-rw-r--r--components/esm/esmreader.cpp13
-rw-r--r--components/esm/esmreader.hpp1
-rw-r--r--components/esm/esmwriter.cpp40
-rw-r--r--components/esm/esmwriter.hpp7
-rw-r--r--components/esm/globalmap.cpp26
-rw-r--r--components/esm/globalmap.hpp34
-rw-r--r--components/esm/globalscript.cpp25
-rw-r--r--components/esm/globalscript.hpp24
-rw-r--r--components/esm/inventorystate.cpp60
-rw-r--r--components/esm/inventorystate.hpp28
-rw-r--r--components/esm/journalentry.cpp39
-rw-r--r--components/esm/journalentry.hpp36
-rw-r--r--components/esm/lightstate.cpp21
-rw-r--r--components/esm/lightstate.hpp19
-rw-r--r--components/esm/loadcell.cpp209
-rw-r--r--components/esm/loadcell.hpp19
-rw-r--r--components/esm/loadcrea.hpp24
-rw-r--r--components/esm/loadinfo.hpp2
-rw-r--r--components/esm/loadland.cpp2
-rw-r--r--components/esm/loadlevlist.hpp36
-rw-r--r--components/esm/loadmgef.hpp6
-rw-r--r--components/esm/loadnpc.hpp2
-rw-r--r--components/esm/loadscpt.cpp10
-rw-r--r--components/esm/loadtes3.cpp22
-rw-r--r--components/esm/loadweap.hpp7
-rw-r--r--components/esm/locals.cpp28
-rw-r--r--components/esm/locals.hpp27
-rw-r--r--components/esm/npcstate.cpp24
-rw-r--r--components/esm/npcstate.hpp24
-rw-r--r--components/esm/npcstats.cpp133
-rw-r--r--components/esm/npcstats.hpp54
-rw-r--r--components/esm/objectstate.cpp51
-rw-r--r--components/esm/objectstate.hpp36
-rw-r--r--components/esm/player.cpp48
-rw-r--r--components/esm/player.hpp33
-rw-r--r--components/esm/queststate.cpp19
-rw-r--r--components/esm/queststate.hpp24
-rw-r--r--components/esm/savedgame.cpp46
-rw-r--r--components/esm/savedgame.hpp41
-rw-r--r--components/esm/statstate.hpp59
-rw-r--r--components/esm/variant.hpp2
-rw-r--r--components/files/lowlevelfile.cpp2
-rw-r--r--components/interpreter/context.hpp36
-rw-r--r--components/interpreter/defines.cpp6
-rw-r--r--components/interpreter/docs/vmformat.txt8
-rw-r--r--components/interpreter/installopcodes.cpp18
-rw-r--r--components/interpreter/localopcodes.hpp36
-rw-r--r--components/interpreter/miscopcodes.hpp3
-rw-r--r--components/nif/node.hpp3
-rw-r--r--components/nifbullet/bulletnifloader.cpp10
-rw-r--r--components/nifogre/material.cpp11
-rw-r--r--components/nifogre/mesh.cpp2
-rw-r--r--components/nifogre/ogrenifloader.cpp165
-rw-r--r--components/nifogre/skeleton.cpp2
-rw-r--r--components/nifogre/tests/.gitignore5
-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.cpp68
-rw-r--r--components/nifoverrides/nifoverrides.hpp19
-rw-r--r--components/ogreinit/ogreinit.cpp2
-rw-r--r--components/ogreinit/ogreplugin.cpp8
-rw-r--r--components/settings/settings.hpp4
-rw-r--r--components/terrain/chunk.cpp6
-rw-r--r--components/terrain/material.cpp4
-rw-r--r--components/terrain/quadtreenode.cpp17
-rw-r--r--components/terrain/quadtreenode.hpp2
-rw-r--r--components/terrain/storage.cpp500
-rw-r--r--components/terrain/storage.hpp43
-rw-r--r--components/terrain/world.cpp142
-rw-r--r--components/terrain/world.hpp2
-rw-r--r--components/version/version.hpp.cmake (renamed from apps/openmw/config.hpp.cmake)10
-rw-r--r--credits.txt2
-rw-r--r--extern/oics/ICSChannel.h6
-rw-r--r--extern/oics/ICSControl.h4
-rw-r--r--extern/oics/ICSInputControlSystem.h22
-rw-r--r--extern/oics/ICSInputControlSystem_joystick.cpp19
-rw-r--r--extern/oics/ICSInputControlSystem_keyboard.cpp10
-rw-r--r--extern/oics/ICSInputControlSystem_mouse.cpp12
-rw-r--r--extern/sdl4ogre/OISCompat.h4
-rw-r--r--extern/sdl4ogre/cursormanager.hpp4
-rw-r--r--extern/sdl4ogre/events.h18
-rw-r--r--extern/sdl4ogre/sdlcursormanager.hpp4
-rw-r--r--extern/sdl4ogre/sdlinputwrapper.hpp4
-rw-r--r--files/materials/objects.mat7
-rw-r--r--files/materials/objects.shader25
-rw-r--r--files/materials/terrain.shader26
-rw-r--r--files/mygui/CMakeLists.txt1
-rw-r--r--files/mygui/EBGaramond-Regular.ttfbin405476 -> 0 bytes
-rw-r--r--files/mygui/openmw_console.layout5
-rw-r--r--files/mygui/openmw_console.skin.xml13
-rw-r--r--files/mygui/openmw_container_window.layout1
-rw-r--r--files/mygui/openmw_font.xml25
-rw-r--r--files/mygui/openmw_inventory_window.layout9
-rw-r--r--files/mygui/openmw_map_window.layout2
-rw-r--r--files/mygui/openmw_savegame_dialog.layout9
-rw-r--r--files/mygui/openmw_spell_buying_window.layout2
-rw-r--r--files/mygui/openmw_spell_window.layout2
-rw-r--r--files/mygui/openmw_stats_window.layout21
-rw-r--r--files/mygui/openmw_text.skin.xml8
-rw-r--r--files/mygui/openmw_tooltips.layout3
-rw-r--r--files/mygui/openmw_travel_window.layout4
-rw-r--r--files/opencs.desktop2
-rw-r--r--files/opencs/multitype.pngbin0 -> 1708 bytes
-rw-r--r--files/opencs/resources.qrc1
-rw-r--r--files/openmw.desktop2
-rw-r--r--files/settings-default.cfg7
-rw-r--r--files/ui/mainwindow.ui42
-rw-r--r--libs/openengine/bullet/BtOgreExtras.h4
-rw-r--r--libs/openengine/bullet/BtOgreGP.h4
-rw-r--r--libs/openengine/bullet/BtOgrePG.h4
-rw-r--r--libs/openengine/bullet/BulletShapeLoader.h4
-rw-r--r--libs/openengine/bullet/btKinematicCharacterController.cpp643
-rw-r--r--libs/openengine/bullet/btKinematicCharacterController.h168
-rw-r--r--libs/openengine/bullet/physic.cpp7
-rw-r--r--libs/openengine/bullet/physic.hpp2
-rw-r--r--libs/openengine/ogre/renderer.cpp1
-rw-r--r--readme.txt82
524 files changed, 18318 insertions, 6969 deletions
diff --git a/.gitignore b/.gitignore
index c061ca637e..3975c4521b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@ resources
## generated objects
apps/openmw/config.hpp
+components/version/version.hpp
Docs/mainpage.hpp
moc_*.cxx
*.cxx_parameters
diff --git a/.travis.yml b/.travis.yml
index 04d019c0d8..5d0326a070 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,17 +4,16 @@ compiler:
branches:
only:
- master
- - next
+ - /openmw-.*$/
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/openmw
- sudo apt-get update -qq
- - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev
- - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
- - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
- - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
+ - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock
+ - sudo apt-get install -qq libqt4-dev
+ - sudo apt-get install -qq libopenal-dev
+ - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
- sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build
@@ -37,3 +36,9 @@ notifications:
email:
on_success: change
on_failure: always
+ irc:
+ channels:
+ - "chat.freenode.net#openmw"
+ on_success: change
+ on_failure: always
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 927753f1fe..07b8ce289d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,19 +6,63 @@ if (APPLE)
set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}")
endif (APPLE)
-# Macros
-
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
-include (OpenMWMacros)
-
# Version
+message(STATUS "Configuring OpenMW...")
+
+set(OPENMW_VERSION_MAJOR 0)
+set(OPENMW_VERSION_MINOR 29)
+set(OPENMW_VERSION_RELEASE 0)
+
+set(OPENMW_VERSION_COMMITHASH "")
+set(OPENMW_VERSION_TAGHASH "")
+
+set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
+
+if(EXISTS ${PROJECT_SOURCE_DIR}/.git)
+ if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+ find_package(Git)
+
+ if(GIT_FOUND)
+ include(GetGitRevisionDescription)
+ get_git_tag_revision(TAGHASH --tags --max-count=1)
+ get_git_head_revision(REFSPEC COMMITHASH)
+ git_describe(VERSION --tags ${TAGHASH})
+
+ string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}")
+ if(MATCH)
+ string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" GIT_VERSION_MAJOR "${VERSION}")
+ string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_MINOR "${VERSION}")
+ string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_RELEASE "${VERSION}")
+
+ set(GIT_VERSION "${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}.${GIT_VERSION_RELEASE}")
+
+ if(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION})
+ message(FATAL_ERROR "Silly Zini forgot to update the version again...")
+ else(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION})
+ set(OPENMW_VERSION_MAJOR ${GIT_VERSION_MAJOR})
+ set(OPENMW_VERSION_MINOR ${GIT_VERSION_MINOR})
+ set(OPENMW_VERSION_RELEASE ${GIT_VERSION_RELEASE})
+
+ set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}")
+ set(OPENMW_VERSION_TAGHASH "${TAGHASH}")
+ endif(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION})
+
+ message(STATUS "OpenMW version ${OPENMW_VERSION}")
+ else(MATCH)
+ message(WARNING "Failed to get valid version information from Git")
+ endif(MATCH)
+ else(GIT_FOUND)
+ message(WARNING "Git executable not found")
+ endif(GIT_FOUND)
+ else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+ message(STATUS "Shallow Git clone detected, not attempting to retrieve version info")
+ endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow)
+endif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
-set (OPENMW_VERSION_MAJOR 0)
-set (OPENMW_VERSION_MINOR 28)
-set (OPENMW_VERSION_RELEASE 0)
-
-set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
+# Macros
+include(OpenMWMacros)
# doxygen main page
@@ -89,8 +133,6 @@ set(OENGINE_GUI
)
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
@@ -179,10 +221,11 @@ if (WIN32)
set(Boost_USE_STATIC_LIBS ON)
set(PLATFORM_INCLUDE_DIR "platform")
add_definitions(-DBOOST_ALL_NO_LIB)
+
+ # Suppress WinMain(), provided by SDL
+ add_definitions(-DSDL_MAIN_HANDLED)
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 "")
@@ -230,7 +273,6 @@ include_directories("."
${MYGUI_INCLUDE_DIRS}
${MYGUI_PLATFORM_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
- ${UUID_INCLUDE_DIR}
${LIBDIR}
)
@@ -239,9 +281,8 @@ link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MY
if (APPLE)
# List used Ogre plugins
SET(USED_OGRE_PLUGINS ${OGRE_RenderSystem_GL_LIBRARY_REL}
- ${OGRE_Plugin_OctreeSceneManager_LIBRARY_REL}
${OGRE_Plugin_ParticleFX_LIBRARY_REL})
-
+
# Actually we must use OGRE_Plugin_CgProgramManager_FOUND but it's
# not reliable and equals TRUE even if there's no Ogre Cg plugin
if (Cg_FOUND)
@@ -263,8 +304,6 @@ if (APPLE)
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")
@@ -407,46 +446,6 @@ IF(NOT WIN32 AND NOT APPLE)
# 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 opencs;OpenCS 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)
@@ -683,7 +682,7 @@ if (APPLE)
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_MINOR ${OPENMW_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
@@ -698,18 +697,29 @@ if (APPLE)
set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS})
endforeach ()
+ install(CODE "
+ set(BU_CHMOD_BUNDLE_ITEMS ON)
+ include(BundleUtilities)
+ " COMPONENT Runtime)
+
# installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX})
# and returns list of install paths for all installed plugins
function (install_plugins_for_bundle bundle_path plugins_var)
- set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/Plugins")
- install(FILES ${ABSOLUTE_PLUGINS} DESTINATION ${RELATIVE_PLUGIN_INSTALL_BASE} COMPONENT Runtime)
+ set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/Frameworks")
set(PLUGINS "")
set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}")
foreach (PLUGIN ${ABSOLUTE_PLUGINS})
get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME)
- set(PLUGINS ${PLUGINS} "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}")
+ get_filename_component(PLUGIN_RELATIVE_WE ${PLUGIN} NAME_WE)
+
+ set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}/${PLUGIN_RELATIVE_WE}")
+ set(PLUGINS ${PLUGINS} "${PLUGIN_DYLIB_IN_BUNDLE}")
+
+ install(CODE "
+ copy_resolved_framework_into_bundle(\"${PLUGIN}/${PLUGIN_RELATIVE_WE}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\")
+ " COMPONENT Runtime)
endforeach ()
set(${plugins_var} ${PLUGINS} PARENT_SCOPE)
@@ -729,6 +739,7 @@ if (APPLE)
# Current limitations:
# 1. Handles only frameworks, not simple libs
INSTALL(CODE "
+ cmake_policy(SET CMP0009 OLD)
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})
@@ -760,9 +771,6 @@ if (APPLE)
endif()
endfunction(gp_resolve_item_override)
- cmake_policy(SET CMP0009 OLD)
- set(BU_CHMOD_BUNDLE_ITEMS ON)
- include(BundleUtilities)
fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\")
" COMPONENT Runtime)
diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp
index 27980096e6..e9731d6267 100644
--- a/apps/esmtool/esmtool.cpp
+++ b/apps/esmtool/esmtool.cpp
@@ -236,7 +236,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
// Loop through all the references
ESM::CellRef ref;
if(!quiet) std::cout << " References:\n";
- while(cell.getNextRef(esm, ref))
+
+ bool deleted = false;
+ while(cell.getNextRef(esm, ref, deleted))
{
if (save) {
info.data.mCellRefs[&cell].push_back(ref);
@@ -244,13 +246,14 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if(quiet) continue;
- std::cout << " Refnum: " << ref.mRefnum << std::endl;
+ std::cout << " Refnum: " << ref.mRefNum.mIndex << 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;
+ std::cout << " Deleted: " << deleted << std::endl;
}
}
diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp
index 7b1fc7fb21..7a42e6900f 100644
--- a/apps/esmtool/labels.cpp
+++ b/apps/esmtool/labels.cpp
@@ -680,7 +680,7 @@ std::string creatureFlags(int flags)
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::Bipedal) properties += "Bipedal ";
if (flags & ESM::Creature::Respawn) properties += "Respawn ";
if (flags & ESM::Creature::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
@@ -691,7 +691,7 @@ std::string creatureFlags(int flags)
ESM::Creature::Walks|
ESM::Creature::Swims|
ESM::Creature::Flies|
- ESM::Creature::Biped|
+ ESM::Creature::Bipedal|
ESM::Creature::Respawn|
ESM::Creature::Weapon|
ESM::Creature::Skeleton|
@@ -717,16 +717,26 @@ std::string landFlags(int flags)
return properties;
}
-std::string leveledListFlags(int flags)
+std::string itemListFlags(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 ";
+ if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels ";
+ if (flags & ESM::ItemLevList::Each) properties += "Each ";
int unused = (0xFFFFFFFF ^
- (ESM::LeveledListBase::AllLevels|
- ESM::LeveledListBase::Each));
+ (ESM::ItemLevList::AllLevels|
+ ESM::ItemLevList::Each));
+ if (flags & unused) properties += "Invalid ";
+ properties += str(boost::format("(0x%08X)") % flags);
+ return properties;
+}
+
+std::string creatureListFlags(int flags)
+{
+ std::string properties = "";
+ if (flags == 0) properties += "[None] ";
+ if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels ";
+ int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels);
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
@@ -764,34 +774,19 @@ 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::TargetAttribute) properties += "TargetAttribute ";
+ if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill ";
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 & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude ";
+ if (flags & ESM::MagicEffect::Harmful) properties += "Harmful ";
+ if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX ";
+ if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf ";
+ if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch ";
+ if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget ";
+ if (flags & ESM::MagicEffect::UncappedDamage) properties += "UncappedDamage ";
+ if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable ";
+ if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable ";
+ if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked ";
if (flags & 0xFFFC0000) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp
index 48d7b249bd..007f933164 100644
--- a/apps/esmtool/labels.hpp
+++ b/apps/esmtool/labels.hpp
@@ -50,7 +50,8 @@ 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 creatureListFlags(int flags);
+std::string itemListFlags(int flags);
std::string lightFlags(int flags);
std::string magicEffectFlags(int flags);
std::string npcFlags(int flags);
diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp
index cc09452c91..184d11bb40 100644
--- a/apps/esmtool/record.cpp
+++ b/apps/esmtool/record.cpp
@@ -13,8 +13,8 @@ void printAIPackage(ESM::AIPackage p)
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;
+ if (p.mWander.mShouldRepeat != 1)
+ std::cout << " Should repeat: " << (bool)p.mWander.mShouldRepeat << std::endl;
std::cout << " Idle: ";
for (int i = 0; i != 8; i++)
@@ -834,7 +834,7 @@ 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 << " Flags: " << creatureListFlags(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++)
@@ -846,11 +846,11 @@ 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 << " Flags: " << itemListFlags(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
+ std::cout << " Inventory: Level: " << iit->mLevel
<< " Item: " << iit->mId << std::endl;
}
@@ -958,7 +958,7 @@ void Record<ESM::MagicEffect>::print()
std::cout << " RGB Color: " << "("
<< mData.mData.mRed << ","
<< mData.mData.mGreen << ","
- << mData.mData.mGreen << ")" << std::endl;
+ << mData.mData.mBlue << ")" << std::endl;
}
template<>
diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp
index 156bbf65bc..fabf77d901 100644
--- a/apps/launcher/main.cpp
+++ b/apps/launcher/main.cpp
@@ -16,6 +16,7 @@
int main(int argc, char *argv[])
{
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
+ SDL_SetMainReady();
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp
index 9b3c4e1b02..5cf8f8a892 100644
--- a/apps/launcher/maindialog.cpp
+++ b/apps/launcher/maindialog.cpp
@@ -1,5 +1,10 @@
#include "maindialog.hpp"
+#include <components/version/version.hpp>
+
+#include <QLabel>
+#include <QDate>
+#include <QTime>
#include <QPushButton>
#include <QFontDatabase>
#include <QInputDialog>
@@ -67,6 +72,25 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ // Add version information to bottom of the window
+ QString revision(OPENMW_VERSION_COMMITHASH);
+ QString tag(OPENMW_VERSION_TAGHASH);
+
+ if (!revision.isEmpty() && !tag.isEmpty())
+ {
+ if (revision == tag) {
+ versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION));
+ } else {
+ versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10)));
+ }
+
+ // Add the compile date and time
+ versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
+ QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
+ QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
+ QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
+ }
+
createIcons();
}
diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp
index d0dbeb1bdb..52f9357108 100644
--- a/apps/launcher/unshieldthread.cpp
+++ b/apps/launcher/unshieldthread.cpp
@@ -235,7 +235,7 @@ namespace
{
for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir )
{
- if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return dir->path();
}
}
@@ -243,7 +243,7 @@ namespace
{
for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
{
- if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return dir->path();
}
}
@@ -255,7 +255,7 @@ namespace
{
for(bfs::directory_iterator end, dir(in); dir != end; ++dir)
{
- if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename)
+ if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return true;
}
diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp
index cf15891144..648ab3ebee 100644
--- a/apps/mwiniimporter/importer.cpp
+++ b/apps/mwiniimporter/importer.cpp
@@ -623,6 +623,17 @@ MwIniImporter::MwIniImporter()
"Moons:Masser Fade Out Finish",
"Moons:Script Color",
+ // blood
+ "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",
+
0
};
diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
index e2dffdbde4..6bcad1d08f 100644
--- a/apps/opencs/CMakeLists.txt
+++ b/apps/opencs/CMakeLists.txt
@@ -24,7 +24,7 @@ opencs_units (model/world
opencs_units_noqt (model/world
universalid record commands columnbase scriptcontext cell refidcollection
- refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection
+ refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata
)
opencs_hdrs_noqt (model/world
@@ -38,7 +38,7 @@ opencs_units (model/tools
opencs_units_noqt (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
- birthsigncheck spellcheck
+ birthsigncheck spellcheck referenceablecheck scriptcheck
)
@@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
- scenetoolmode infocreator
+ scenetoolmode infocreator scriptedit
)
opencs_units (view/render
diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp
index 437b0c5131..201fb4342a 100644
--- a/apps/opencs/model/doc/document.hpp
+++ b/apps/opencs/model/doc/document.hpp
@@ -71,7 +71,10 @@ namespace CSMDoc
public:
- Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_);
+ Document (const Files::ConfigurationManager& configuration,
+ const std::vector< boost::filesystem::path >& files,
+ const boost::filesystem::path& savePath,
+ const boost::filesystem::path& resDir, bool new_);
~Document();
diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp
index b80a186429..b969862e99 100644
--- a/apps/opencs/model/doc/documentmanager.hpp
+++ b/apps/opencs/model/doc/documentmanager.hpp
@@ -29,7 +29,9 @@ namespace CSMDoc
~DocumentManager();
- Document *addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_);
+ Document *addDocument (const std::vector< boost::filesystem::path >& files,
+ const boost::filesystem::path& savePath,
+ 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
diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp
index 8e9bcfc0de..d7df2117d0 100644
--- a/apps/opencs/model/doc/savingstages.cpp
+++ b/apps/opencs/model/doc/savingstages.cpp
@@ -133,16 +133,10 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector<std::
state==CSMWorld::RecordBase::State_ModifiedOnly ||
infoModified)
{
- // always write the topic record
- std::string type;
- for (int i=0; i<4; ++i)
- /// \todo make endianess agnostic (change ESMWriter interface?)
- type += reinterpret_cast<const char *> (&topic.mModified.sRecordId)[i];
-
- mState.getWriter().startRecord (type);
+ mState.getWriter().startRecord (topic.mModified.sRecordId);
mState.getWriter().writeHNCString ("NAME", topic.mModified.mId);
topic.mModified.save (mState.getWriter());
- mState.getWriter().endRecord (type);
+ mState.getWriter().endRecord (topic.mModified.sRecordId);
// write modified selected info records
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second;
@@ -178,15 +172,10 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector<std::
next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1);
}
- std::string type;
- for (int i=0; i<4; ++i)
- /// \todo make endianess agnostic (change ESMWriter interface?)
- type += reinterpret_cast<const char *> (&info.sRecordId)[i];
-
- mState.getWriter().startRecord (type);
+ mState.getWriter().startRecord (info.sRecordId);
mState.getWriter().writeHNCString ("INAM", info.mId);
info.save (mState.getWriter());
- mState.getWriter().endRecord (type);
+ mState.getWriter().endRecord (info.sRecordId);
}
}
}
diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp
index ca5586511d..b8eb0a3b30 100644
--- a/apps/opencs/model/doc/savingstages.hpp
+++ b/apps/opencs/model/doc/savingstages.hpp
@@ -104,10 +104,10 @@ namespace CSMDoc
/// \todo make endianess agnostic (change ESMWriter interface?)
type += reinterpret_cast<const char *> (&mCollection.getRecord (stage).mModified.sRecordId)[i];
- mState.getWriter().startRecord (type);
+ mState.getWriter().startRecord (mCollection.getRecord (stage).mModified.sRecordId);
mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage));
mCollection.getRecord (stage).mModified.save (mState.getWriter());
- mState.getWriter().endRecord (type);
+ mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp
new file mode 100644
index 0000000000..dab61bfffa
--- /dev/null
+++ b/apps/opencs/model/tools/referenceablecheck.cpp
@@ -0,0 +1,1095 @@
+#include "referenceablecheck.hpp"
+#include "../world/record.hpp"
+#include "../world/universalid.hpp"
+#include <components/misc/stringops.hpp>
+
+CSMTools::ReferenceableCheckStage::ReferenceableCheckStage(
+ const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection<ESM::Race >& races,
+ const CSMWorld::IdCollection<ESM::Class>& classes,
+ const CSMWorld::IdCollection<ESM::Faction>& faction)
+ :
+ mReferencables(referenceable),
+ mClasses(classes),
+ mRaces(races),
+ mFactions(faction),
+ mPlayerPresent(false)
+{
+}
+
+void CSMTools::ReferenceableCheckStage::perform(int stage, std::vector< std::string >& messages)
+{
+ //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage.
+ const int bookSize(mReferencables.getBooks().getSize());
+
+ if (stage < bookSize)
+ {
+ bookCheck(stage, mReferencables.getBooks(), messages);
+ return;
+ }
+
+ stage -= bookSize;
+
+ const int activatorSize(mReferencables.getActivators().getSize());
+
+ if (stage < activatorSize)
+ {
+ activatorCheck(stage, mReferencables.getActivators(), messages);
+ return;
+ }
+
+ stage -= activatorSize;
+
+ const int potionSize(mReferencables.getPotions().getSize());
+
+ if (stage < potionSize)
+ {
+ potionCheck(stage, mReferencables.getPotions(), messages);
+ return;
+ }
+
+ stage -= potionSize;
+
+ const int apparatusSize(mReferencables.getApparati().getSize());
+
+ if (stage < apparatusSize)
+ {
+ apparatusCheck(stage, mReferencables.getApparati(), messages);
+ return;
+ }
+
+ stage -= apparatusSize;
+
+ const int armorSize(mReferencables.getArmors().getSize());
+
+ if (stage < armorSize)
+ {
+ armorCheck(stage, mReferencables.getArmors(), messages);
+ return;
+ }
+
+ stage -= armorSize;
+
+ const int clothingSize(mReferencables.getClothing().getSize());
+
+ if (stage < clothingSize)
+ {
+ clothingCheck(stage, mReferencables.getClothing(), messages);
+ return;
+ }
+
+ stage -= clothingSize;
+
+ const int containerSize(mReferencables.getContainers().getSize());
+
+ if (stage < containerSize)
+ {
+ containerCheck(stage, mReferencables.getContainers(), messages);
+ return;
+ }
+
+ stage -= containerSize;
+
+ const int doorSize(mReferencables.getDoors().getSize());
+
+ if (stage < doorSize)
+ {
+ doorCheck(stage, mReferencables.getDoors(), messages);
+ return;
+ }
+
+ stage -= doorSize;
+
+ const int ingredientSize(mReferencables.getIngredients().getSize());
+
+ if (stage < ingredientSize)
+ {
+ ingredientCheck(stage, mReferencables.getIngredients(), messages);
+ return;
+ }
+
+ stage -= ingredientSize;
+
+ const int creatureLevListSize(mReferencables.getCreatureLevelledLists().getSize());
+
+ if (stage < creatureLevListSize)
+ {
+ creaturesLevListCheck(stage, mReferencables.getCreatureLevelledLists(), messages);
+ return;
+ }
+
+ stage -= creatureLevListSize;
+
+ const int itemLevelledListSize(mReferencables.getItemLevelledList().getSize());
+
+ if (stage < itemLevelledListSize)
+ {
+ itemLevelledListCheck(stage, mReferencables.getItemLevelledList(), messages);
+ return;
+ }
+
+ stage -= itemLevelledListSize;
+
+ const int lightSize(mReferencables.getLights().getSize());
+
+ if (stage < lightSize)
+ {
+ lightCheck(stage, mReferencables.getLights(), messages);
+ return;
+ }
+
+ stage -= lightSize;
+
+ const int lockpickSize(mReferencables.getLocpicks().getSize());
+
+ if (stage < lockpickSize)
+ {
+ lockpickCheck(stage, mReferencables.getLocpicks(), messages);
+ return;
+ }
+
+ stage -= lockpickSize;
+
+ const int miscSize(mReferencables.getMiscellaneous().getSize());
+
+ if (stage < miscSize)
+ {
+ miscCheck(stage, mReferencables.getMiscellaneous(), messages);
+ return;
+ }
+
+ stage -= miscSize;
+
+ const int npcSize(mReferencables.getNPCs().getSize());
+
+ if (stage < npcSize)
+ {
+ npcCheck(stage, mReferencables.getNPCs(), messages);
+ return;
+ }
+
+ stage -= npcSize;
+
+ const int weaponSize(mReferencables.getWeapons().getSize());
+
+ if (stage < weaponSize)
+ {
+ weaponCheck(stage, mReferencables.getWeapons(), messages);
+ return;
+ }
+
+ stage -= weaponSize;
+
+ const int probeSize(mReferencables.getProbes().getSize());
+
+ if (stage < probeSize)
+ {
+ probeCheck(stage, mReferencables.getProbes(), messages);
+ return;
+ }
+
+ stage -= probeSize;
+
+ const int repairSize(mReferencables.getRepairs().getSize());
+
+ if (stage < repairSize)
+ {
+ repairCheck(stage, mReferencables.getRepairs(), messages);
+ return;
+ }
+
+ stage -= repairSize;
+
+ const int staticSize(mReferencables.getStatics().getSize());
+
+ if (stage < staticSize)
+ {
+ staticCheck(stage, mReferencables.getStatics(), messages);
+ return;
+ }
+
+ stage -= staticSize;
+
+ const int creatureSize(mReferencables.getCreatures().getSize());
+
+ if (stage < creatureSize)
+ {
+ creatureCheck(stage, mReferencables.getCreatures(), messages);
+ return;
+ }
+// if we come that far, we are about to perform our last, final check.
+ finalCheck(messages);
+ return;
+}
+
+int CSMTools::ReferenceableCheckStage::setup()
+{
+ mPlayerPresent = false;
+ return mReferencables.getSize() + 1;
+}
+
+void CSMTools::ReferenceableCheckStage::bookCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Book >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Book, book.mId);
+
+ inventoryItemCheck<ESM::Book>(book, messages, id.toString(), true);
+}
+
+void CSMTools::ReferenceableCheckStage::activatorCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Activator >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId);
+
+ //Checking for model, IIRC all activators should have a model
+ if (activator.mModel.empty())
+ {
+ messages.push_back(id.toString() + "|" + activator.mId + " has no model");
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::potionCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Potion >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId);
+
+ inventoryItemCheck<ESM::Potion>(potion, messages, id.toString());
+ //IIRC potion can have empty effects list just fine.
+}
+
+
+void CSMTools::ReferenceableCheckStage::apparatusCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Apparatus, apparatus.mId);
+
+ inventoryItemCheck<ESM::Apparatus>(apparatus, messages, id.toString());
+
+ toolCheck<ESM::Apparatus>(apparatus, messages, id.toString());
+}
+
+void CSMTools::ReferenceableCheckStage::armorCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Armor >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Armor, armor.mId);
+
+ inventoryItemCheck<ESM::Armor>(armor, messages, id.toString(), true);
+
+ //checking for armor class, armor should have poistive armor class, but 0 is considered legal
+ if (armor.mData.mArmor < 0)
+ {
+ messages.push_back(id.toString() + "|" + armor.mId + " has negative armor class");
+ }
+
+ //checking for health. Only positive numbers are allowed, or 0 is illegal
+ if (armor.mData.mHealth <= 0)
+ {
+ messages.push_back(id.toString() + "|" + armor.mId + " has non positive health");
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::clothingCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Clothing >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Clothing, clothing.mId);
+ inventoryItemCheck<ESM::Clothing>(clothing, messages, id.toString(), true);
+}
+
+void CSMTools::ReferenceableCheckStage::containerCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Container >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId);
+
+ //Checking for model, IIRC all containers should have a model
+ if (container.mModel.empty())
+ {
+ messages.push_back(id.toString() + "|" + container.mId + " has no model");
+ }
+
+ //Checking for capacity (weight)
+ if (container.mWeight < 0) //0 is allowed
+ {
+ messages.push_back(id.toString() + "|" + container.mId + " has negative weight (capacity)");
+ }
+
+ //checking for name
+ if (container.mName.empty())
+ {
+ messages.push_back(id.toString() + "|" + container.mId + " has an empty name");
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::creatureCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Creature >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Creature& creature = (dynamic_cast<const CSMWorld::Record<ESM::Creature>&>(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Creature, creature.mId);
+
+ if (creature.mModel.empty())
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has no model");
+ }
+
+ if (creature.mName.empty())
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has an empty name");
+ }
+
+ //stats checks
+ if (creature.mData.mLevel < 1)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has non-postive level");
+ }
+
+ if (creature.mData.mStrength < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative strength");
+ }
+
+ if (creature.mData.mIntelligence < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative intelligence");
+ }
+
+ if (creature.mData.mWillpower < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative willpower");
+ }
+
+ if (creature.mData.mAgility < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative agility");
+ }
+
+ if (creature.mData.mSpeed < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative speed");
+ }
+
+ if (creature.mData.mEndurance < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative endurance");
+ }
+
+ if (creature.mData.mPersonality < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative personality");
+ }
+
+ if (creature.mData.mLuck < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative luck");
+ }
+
+ if (creature.mData.mHealth < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative health");
+ }
+
+ if (creature.mData.mSoul < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative soul value");
+ }
+
+ for (int i = 0; i < 6; ++i)
+ {
+ if (creature.mData.mAttack[i] < 0)
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative attack strength");
+ break;
+ }
+ }
+
+ //TODO, find meaning of other values
+ if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures
+ {
+ messages.push_back(id.toString() + "|" + creature.mId + " has negative gold ");
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::doorCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Door >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Door& Door = (dynamic_cast<const CSMWorld::Record<ESM::Door>&>(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Door, Door.mId);
+
+ //usual, name or model
+ if (Door.mName.empty())
+ {
+ messages.push_back(id.toString() + "|" + Door.mId + " has an empty name");
+ }
+
+ if (Door.mModel.empty())
+ {
+ messages.push_back(id.toString() + "|" + Door.mId + " has no model");
+ }
+
+ //TODO, check what static unsigned int sRecordId; is for
+}
+
+void CSMTools::ReferenceableCheckStage::ingredientCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Ingredient& Ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Ingredient, Ingredient.mId);
+
+ inventoryItemCheck<ESM::Ingredient>(Ingredient, messages, id.toString());
+}
+
+void CSMTools::ReferenceableCheckStage::creaturesLevListCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::CreatureLevList& CreatureLevList = (dynamic_cast<const CSMWorld::Record<ESM::CreatureLevList>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/
+
+ listCheck<ESM::CreatureLevList>(CreatureLevList, messages, id.toString());
+}
+
+void CSMTools::ReferenceableCheckStage::itemLevelledListCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::ItemLevList& ItemLevList = (dynamic_cast<const CSMWorld::Record<ESM::ItemLevList>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId);
+
+ listCheck<ESM::ItemLevList>(ItemLevList, messages, id.toString());
+}
+
+void CSMTools::ReferenceableCheckStage::lightCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Light >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Light& light = (dynamic_cast<const CSMWorld::Record<ESM::Light>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId);
+
+ if (light.mData.mRadius < 0)
+ {
+ messages.push_back(id.toString() + "|" + light.mId + " has negative light radius");
+ }
+
+ if (light.mData.mFlags & ESM::Light::Carry)
+ {
+ inventoryItemCheck<ESM::Light>(light, messages, id.toString());
+
+ if (light.mData.mTime == 0)
+ {
+ messages.push_back(id.toString() + "|" + light.mId + " has zero duration");
+ }
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::lockpickCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Lockpick, lockpick.mId);
+
+ inventoryItemCheck<ESM::Lockpick>(lockpick, messages, id.toString());
+
+ toolCheck<ESM::Lockpick>(lockpick, messages, id.toString(), true);
+}
+
+void CSMTools::ReferenceableCheckStage::miscCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Miscellaneous, miscellaneous.mId);
+
+ inventoryItemCheck<ESM::Miscellaneous>(miscellaneous, messages, id.toString());
+}
+
+void CSMTools::ReferenceableCheckStage::npcCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::NPC >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::NPC& npc = (dynamic_cast<const CSMWorld::Record<ESM::NPC>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc, npc.mId);
+
+ short level(npc.mNpdt52.mLevel);
+ char disposition(npc.mNpdt52.mDisposition);
+ char reputation(npc.mNpdt52.mReputation);
+ char rank(npc.mNpdt52.mRank);
+ //Don't know what unknown is for
+ int gold(npc.mNpdt52.mGold);
+
+ //Detect if player is present
+ if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl?
+ {
+ mPlayerPresent = true;
+ }
+
+ if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated
+ {
+ if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0008 = autocalculated flag
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " mNpdtType or flags mismatch!"); //should not happend?
+ return;
+ }
+
+ level = npc.mNpdt12.mLevel;
+ disposition = npc.mNpdt12.mDisposition;
+ reputation = npc.mNpdt12.mReputation;
+ rank = npc.mNpdt12.mRank;
+ gold = npc.mNpdt12.mGold;
+ }
+ else
+ {
+ if (npc.mNpdt52.mMana < 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " mana has negative value");
+ }
+
+ if (npc.mNpdt52.mFatigue < 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " fatigue has negative value");
+ }
+
+ if (npc.mNpdt52.mAgility == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " agility has zero value");
+ }
+
+ if (npc.mNpdt52.mEndurance == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " endurance has zero value");
+ }
+
+ if (npc.mNpdt52.mIntelligence == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " intelligence has zero value");
+ }
+
+ if (npc.mNpdt52.mLuck == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " luck has zero value");
+ }
+
+ if (npc.mNpdt52.mPersonality == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " personality has zero value");
+ }
+
+ if (npc.mNpdt52.mStrength == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " strength has zero value");
+ }
+
+ if (npc.mNpdt52.mSpeed == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " speed has zero value");
+ }
+
+ if (npc.mNpdt52.mWillpower == 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " willpower has zero value");
+ }
+ }
+
+ if (level < 1)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " level is non positive");
+ }
+
+ if (gold < 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " gold has negative value");
+ }
+
+ if (npc.mName.empty())
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has any empty name");
+ }
+
+ if (npc.mClass.empty())
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has any empty class");
+ }
+ else //checking if there is such class
+ {
+ if (mClasses.searchId(npc.mClass) == -1)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has invalid class");
+ }
+ }
+
+ if (npc.mRace.empty())
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has any empty race");
+ }
+ else //checking if there is a such race
+ {
+ if (mRaces.searchId(npc.mRace) == -1)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has invalid race");
+ }
+ }
+
+ if (disposition < 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has negative disposition");
+ }
+
+ if (reputation < 0) //It seems that no character in Morrowind.esm have negative reputation. I'm assuming that negative reputation is invalid
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has negative reputation");
+ }
+
+ if (npc.mFaction.empty() == false)
+ {
+ if (rank < 0)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has negative rank");
+ }
+
+ if (mFactions.searchId(npc.mFaction) == -1)
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has invalid faction");
+ }
+ }
+
+ if (npc.mHead.empty())
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has no head");
+ }
+
+ if (npc.mHair.empty())
+ {
+ messages.push_back(id.toString() + "|" + npc.mId + " has no hair");
+ }
+
+ //TODO: reputation, Disposition, rank, everything else
+}
+
+void CSMTools::ReferenceableCheckStage::weaponCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Weapon >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Weapon& weapon = (dynamic_cast<const CSMWorld::Record<ESM::Weapon>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Weapon, weapon.mId);
+
+ //TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present
+ if
+ ( //THOSE ARE HARDCODED!
+ !(weapon.mId == "VFX_Hands" ||
+ weapon.mId == "VFX_Absorb" ||
+ weapon.mId == "VFX_Reflect" ||
+ weapon.mId == "VFX_DefaultBolt" ||
+ //TODO I don't know how to get full list of effects :/
+ //DANGER!, ACHTUNG! FIXME! The following is the list of the magical bolts, valid for Morrowind.esm. However those are not hardcoded.
+ weapon.mId == "magic_bolt" ||
+ weapon.mId == "shock_bolt" ||
+ weapon.mId == "shield_bolt" ||
+ weapon.mId == "VFX_DestructBolt" ||
+ weapon.mId == "VFX_PoisonBolt" ||
+ weapon.mId == "VFX_RestoreBolt" ||
+ weapon.mId == "VFX_AlterationBolt" ||
+ weapon.mId == "VFX_ConjureBolt" ||
+ weapon.mId == "VFX_FrostBolt" ||
+ weapon.mId == "VFX_MysticismBolt" ||
+ weapon.mId == "VFX_IllusionBolt" ||
+ weapon.mId == "VFX_Multiple2" ||
+ weapon.mId == "VFX_Multiple3" ||
+ weapon.mId == "VFX_Multiple4" ||
+ weapon.mId == "VFX_Multiple5" ||
+ weapon.mId == "VFX_Multiple6" ||
+ weapon.mId == "VFX_Multiple7" ||
+ weapon.mId == "VFX_Multiple8" ||
+ weapon.mId == "VFX_Multiple9"))
+ {
+ inventoryItemCheck<ESM::Weapon>(weapon, messages, id.toString(), true);
+
+ if (!(weapon.mData.mType == ESM::Weapon::MarksmanBow ||
+ weapon.mData.mType == ESM::Weapon::MarksmanCrossbow ||
+ weapon.mData.mType == ESM::Weapon::MarksmanThrown ||
+ weapon.mData.mType == ESM::Weapon::Arrow ||
+ weapon.mData.mType == ESM::Weapon::Bolt))
+ {
+ if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1])
+ {
+ messages.push_back(id.toString() + "|" + weapon.mId + " has minimum slash damage higher than maximum");
+ }
+
+ if (weapon.mData.mThrust[0] > weapon.mData.mThrust[1])
+ {
+ messages.push_back(id.toString() + "|" + weapon.mId + " has minimum thrust damage higher than maximum");
+ }
+ }
+
+ if (weapon.mData.mChop[0] > weapon.mData.mChop[1])
+ {
+ messages.push_back(id.toString() + "|" + weapon.mId + " has minimum chop damage higher than maximum");
+ }
+
+ if (!(weapon.mData.mType == ESM::Weapon::Arrow ||
+ weapon.mData.mType == ESM::Weapon::Bolt ||
+ weapon.mData.mType == ESM::Weapon::MarksmanThrown))
+ {
+ //checking of health
+ if (weapon.mData.mHealth <= 0)
+ {
+ messages.push_back(id.toString() + "|" + weapon.mId + " has non-positivie health");
+ }
+
+ if (weapon.mData.mReach < 0)
+ {
+ messages.push_back(id.toString() + "|" + weapon.mId + " has negative reach");
+ }
+ }
+ }
+}
+
+void CSMTools::ReferenceableCheckStage::probeCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Probe >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Probe, probe.mId);
+
+ inventoryItemCheck<ESM::Probe>(probe, messages, id.toString());
+ toolCheck<ESM::Probe>(probe, messages, id.toString(), true);
+}
+
+void CSMTools::ReferenceableCheckStage::repairCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Repair >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Repair& repair = (dynamic_cast<const CSMWorld::Record<ESM::Repair>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Repair, repair.mId);
+
+ inventoryItemCheck<ESM::Repair>(repair, messages, id.toString());
+ toolCheck<ESM::Repair>(repair, messages, id.toString(), true);
+}
+
+void CSMTools::ReferenceableCheckStage::staticCheck(
+ int stage,
+ const CSMWorld::RefIdDataContainer< ESM::Static >& records,
+ std::vector< std::string >& messages)
+{
+ const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
+
+ if (baseRecord.isDeleted())
+ {
+ return;
+ }
+
+ const ESM::Static& staticElement = (dynamic_cast<const CSMWorld::Record<ESM::Static>& >(baseRecord)).get();
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Static, staticElement.mId);
+
+ if (staticElement.mModel.empty())
+ {
+ messages.push_back(id.toString() + "|" + staticElement.mId + " has no model");
+ }
+}
+
+//final check
+
+void CSMTools::ReferenceableCheckStage::finalCheck(std::vector< std::string >& messages)
+{
+ if (!mPlayerPresent)
+ {
+ CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc);
+ messages.push_back(id.toString() + "| There is no player record");
+ }
+}
+
+
+//Templates begins here
+
+template<typename ITEM> void CSMTools::ReferenceableCheckStage::inventoryItemCheck(
+ const ITEM& someItem,
+ std::vector< std::string >& messages,
+ const std::string& someID, bool enchantable)
+{
+ if (someItem.mName.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has an empty name");
+ }
+
+ //Checking for weight
+ if (someItem.mData.mWeight < 0)
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has negative weight");
+ }
+
+ //Checking for value
+ if (someItem.mData.mValue < 0)
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has negative value");
+ }
+
+//checking for model
+ if (someItem.mModel.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has no model");
+ }
+
+ //checking for icon
+ if (someItem.mIcon.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has no icon");
+ }
+
+ if (enchantable)
+ {
+ if (someItem.mData.mEnchant < 0)
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has negative enchantment");
+ }
+ }
+}
+
+template<typename ITEM> void CSMTools::ReferenceableCheckStage::inventoryItemCheck(
+ const ITEM& someItem,
+ std::vector< std::string >& messages,
+ const std::string& someID)
+{
+ if (someItem.mName.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has an empty name");
+ }
+
+ //Checking for weight
+ if (someItem.mData.mWeight < 0)
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has negative weight");
+ }
+
+ //Checking for value
+ if (someItem.mData.mValue < 0)
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has negative value");
+ }
+
+ //checking for model
+ if (someItem.mModel.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has no model");
+ }
+
+ //checking for icon
+ if (someItem.mIcon.empty())
+ {
+ messages.push_back(someID + "|" + someItem.mId + " has no icon");
+ }
+}
+
+template<typename TOOL> void CSMTools::ReferenceableCheckStage::toolCheck(
+ const TOOL& someTool,
+ std::vector< std::string >& messages,
+ const std::string& someID, bool canBeBroken)
+{
+ if (someTool.mData.mQuality <= 0)
+ {
+ messages.push_back(someID + "|" + someTool.mId + " has non-positive quality");
+ }
+
+ if (canBeBroken)
+ {
+ if (someTool.mData.mUses <= 0)
+ {
+ messages.push_back(someID + "|" + someTool.mId + " has non-positive uses count");
+ }
+ }
+}
+
+template<typename TOOL> void CSMTools::ReferenceableCheckStage::toolCheck(
+ const TOOL& someTool,
+ std::vector< std::string >& messages,
+ const std::string& someID)
+{
+ if (someTool.mData.mQuality <= 0)
+ {
+ messages.push_back(someID + "|" + someTool.mId + " has non-positive quality");
+ }
+}
+
+template<typename LIST> void CSMTools::ReferenceableCheckStage::listCheck(
+ const LIST& someList,
+ std::vector< std::string >& messages,
+ const std::string& someID)
+{
+ for (unsigned i = 0; i < someList.mList.size(); ++i)
+ {
+ if (mReferencables.searchId(someList.mList[i].mId).first == -1)
+ {
+ messages.push_back(someID + "|" + someList.mId + " contains item without referencable");
+ }
+
+ if (someList.mList[i].mLevel < 1)
+ {
+ messages.push_back(someID + "|" + someList.mId + " contains item with non-positive level");
+ }
+ }
+}
+// kate: indent-mode cstyle; indent-width 4; replace-tabs on;
diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp
new file mode 100644
index 0000000000..338983cc70
--- /dev/null
+++ b/apps/opencs/model/tools/referenceablecheck.hpp
@@ -0,0 +1,78 @@
+#ifndef REFERENCEABLECHECKSTAGE_H
+#define REFERENCEABLECHECKSTAGE_H
+
+#include "../world/universalid.hpp"
+#include "../doc/stage.hpp"
+#include "../world/data.hpp"
+#include "../world/refiddata.hpp"
+
+namespace CSMTools
+{
+ class ReferenceableCheckStage : public CSMDoc::Stage
+ {
+ public:
+ ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable,
+ const CSMWorld::IdCollection<ESM::Race>& races,
+ const CSMWorld::IdCollection<ESM::Class>& classes,
+ const CSMWorld::IdCollection<ESM::Faction>& factions);
+
+ virtual void perform(int stage, std::vector< std::string >& messages);
+ virtual int setup();
+
+ private:
+ //CONCRETE CHECKS
+ void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, std::vector< std::string >& messages);
+ void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, std::vector< std::string >& messages);
+ void potionCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Potion>& records, std::vector<std::string>& messages);
+ void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Apparatus>& records, std::vector<std::string>& messages);
+ void armorCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Armor>& records, std::vector<std::string>& messages);
+ void clothingCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Clothing>& records, std::vector<std::string>& messages);
+ void containerCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Container>& records, std::vector<std::string>& messages);
+ void creatureCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Creature>& records, std::vector<std::string>& messages);
+ void doorCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Door>& records, std::vector<std::string>& messages);
+ void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Ingredient>& records, std::vector<std::string>& messages);
+ void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::CreatureLevList>& records, std::vector<std::string>& messages);
+ void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::ItemLevList>& records, std::vector<std::string>& messages);
+ void lightCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Light>& records, std::vector<std::string>& messages);
+ void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Lockpick>& records, std::vector<std::string>& messages);
+ void miscCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Miscellaneous>& records, std::vector<std::string>& messages);
+ void npcCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::NPC>& records, std::vector<std::string>& messages);
+ void weaponCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Weapon>& records, std::vector<std::string>& messages);
+ void probeCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Probe>& records, std::vector<std::string>& messages);
+ void repairCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Repair>& records, std::vector<std::string>& messages);
+ void staticCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Static>& records, std::vector<std::string>& messages);
+
+ //FINAL CHECK
+ void finalCheck(std::vector<std::string>& messages);
+
+ //TEMPLATE CHECKS
+ template<typename ITEM> void inventoryItemCheck(const ITEM& someItem,
+ std::vector<std::string>& messages,
+ const std::string& someID,
+ bool enchantable); //for all enchantable items.
+
+ template<typename ITEM> void inventoryItemCheck(const ITEM& someItem,
+ std::vector<std::string>& messages,
+ const std::string& someID); //for non-enchantable items.
+
+ template<typename TOOL> void toolCheck(const TOOL& someTool,
+ std::vector<std::string>& messages,
+ const std::string& someID,
+ bool canbebroken); //for tools with uses.
+
+ template<typename TOOL> void toolCheck(const TOOL& someTool,
+ std::vector<std::string>& messages,
+ const std::string& someID); //for tools without uses.
+
+ template<typename LIST> void listCheck(const LIST& someList,
+ std::vector< std::string >& messages,
+ const std::string& someID);
+
+ const CSMWorld::RefIdData& mReferencables;
+ const CSMWorld::IdCollection<ESM::Race>& mRaces;
+ const CSMWorld::IdCollection<ESM::Class>& mClasses;
+ const CSMWorld::IdCollection<ESM::Faction>& mFactions;
+ bool mPlayerPresent;
+ };
+}
+#endif // REFERENCEABLECHECKSTAGE_H
diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp
index b125318755..d883617464 100644
--- a/apps/opencs/model/tools/reportmodel.cpp
+++ b/apps/opencs/model/tools/reportmodel.cpp
@@ -68,4 +68,4 @@ void CSMTools::ReportModel::add (const std::string& row)
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/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp
new file mode 100644
index 0000000000..a5154d2926
--- /dev/null
+++ b/apps/opencs/model/tools/scriptcheck.cpp
@@ -0,0 +1,103 @@
+
+#include "scriptcheck.hpp"
+
+#include <components/compiler/tokenloc.hpp>
+#include <components/compiler/scanner.hpp>
+#include <components/compiler/fileparser.hpp>
+#include <components/compiler/exception.hpp>
+#include <components/compiler/extensions0.hpp>
+
+#include "../world/data.hpp"
+
+void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc,
+ Type type)
+{
+ std::ostringstream stream;
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId);
+
+ stream << id.toString() << "|";
+
+ if (type==ErrorMessage)
+ stream << "error ";
+ else
+ stream << "warning ";
+
+ stream
+ << "script " << mFile
+ << ", line " << loc.mLine << ", column " << loc.mColumn
+ << " (" << loc.mLiteral << "): " << message;
+
+ mMessages->push_back (stream.str());
+}
+
+void CSMTools::ScriptCheckStage::report (const std::string& message, Type type)
+{
+ std::ostringstream stream;
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId);
+
+ stream << id.toString() << "|";
+
+ if (type==ErrorMessage)
+ stream << "error: ";
+ else
+ stream << "warning: ";
+
+ stream << message;
+
+ mMessages->push_back (stream.str());
+}
+
+CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMWorld::Data& data)
+: mData (data), mContext (data), mMessages (0)
+{
+ /// \todo add an option to configure warning mode
+ setWarningsMode (0);
+
+ Compiler::registerExtensions (mExtensions);
+ mContext.setExtensions (&mExtensions);
+}
+
+int CSMTools::ScriptCheckStage::setup()
+{
+ mContext.clear();
+ mMessages = 0;
+ mId.clear();
+
+ return mData.getScripts().getSize();
+}
+
+void CSMTools::ScriptCheckStage::perform (int stage, std::vector<std::string>& messages)
+{
+ mMessages = &messages;
+ mId = mData.getScripts().getId (stage);
+
+ try
+ {
+ mFile = mData.getScripts().getRecord (stage).get().mId;
+ std::istringstream input (mData.getScripts().getRecord (stage).get().mScriptText);
+
+ Compiler::Scanner scanner (*this, input, mContext.getExtensions());
+
+ Compiler::FileParser parser (*this, mContext);
+
+ scanner.scan (parser);
+ }
+ catch (const Compiler::SourceException&)
+ {
+ // error has already been reported via error handler
+ }
+ catch (const std::exception& error)
+ {
+ std::ostringstream stream;
+
+ CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId);
+
+ stream << id.toString() << "|Critical compile error: " << error.what();
+
+ messages.push_back (stream.str());
+ }
+
+ mMessages = 0;
+} \ No newline at end of file
diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp
new file mode 100644
index 0000000000..8de8e1a667
--- /dev/null
+++ b/apps/opencs/model/tools/scriptcheck.hpp
@@ -0,0 +1,41 @@
+#ifndef CSM_TOOLS_SCRIPTCHECK_H
+#define CSM_TOOLS_SCRIPTCHECK_H
+
+#include <components/compiler/errorhandler.hpp>
+#include <components/compiler/extensions.hpp>
+
+#include "../doc/stage.hpp"
+
+#include "../world/scriptcontext.hpp"
+
+namespace CSMTools
+{
+ /// \brief VerifyStage: make sure that scripts compile
+ class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler
+ {
+ const CSMWorld::Data& mData;
+ Compiler::Extensions mExtensions;
+ CSMWorld::ScriptContext mContext;
+ std::string mId;
+ std::string mFile;
+ std::vector<std::string> *mMessages;
+
+ virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type);
+ ///< Report error to the user.
+
+ virtual void report (const std::string& message, Type type);
+ ///< Report a file related error
+
+ public:
+
+ ScriptCheckStage (const CSMWorld::Data& data);
+
+ 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/tools.cpp b/apps/opencs/model/tools/tools.cpp
index cd4653280e..79a09dcb48 100644
--- a/apps/opencs/model/tools/tools.cpp
+++ b/apps/opencs/model/tools/tools.cpp
@@ -19,6 +19,8 @@
#include "regioncheck.hpp"
#include "birthsigncheck.hpp"
#include "spellcheck.hpp"
+#include "referenceablecheck.hpp"
+#include "scriptcheck.hpp"
CSMDoc::Operation *CSMTools::Tools::get (int type)
{
@@ -74,6 +76,10 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier()
mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns()));
mVerifier->appendStage (new SpellCheckStage (mData.getSpells()));
+
+ mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions()));
+
+ mVerifier->appendStage (new ScriptCheckStage (mData));
}
return mVerifier;
@@ -138,4 +144,5 @@ void CSMTools::Tools::verifierMessage (const QString& message, int type)
if (iter!=mActiveReports.end())
mReports[iter->second]->add (message.toStdString());
-} \ No newline at end of file
+}
+
diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp
index df144ba7bf..d342e88a47 100644
--- a/apps/opencs/model/world/collection.hpp
+++ b/apps/opencs/model/world/collection.hpp
@@ -98,6 +98,10 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None);
///< \param type Will be ignored, unless the collection supports multiple record types
+ virtual void cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const UniversalId::Type type);
+
virtual int searchId (const std::string& id) const;
////< Search record with \a id.
/// \return index of record (if found) or -1 (not found)
@@ -194,6 +198,19 @@ namespace CSMWorld
}
template<typename ESXRecordT, typename IdAccessorT>
+ void Collection<ESXRecordT, IdAccessorT>::cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const UniversalId::Type type)
+ {
+ Record<ESXRecordT> copy;
+ copy.mModified = getRecord(origin).get();
+ copy.mState = RecordBase::State_ModifiedOnly;
+ copy.get().mId = destination;
+
+ insertRecord(copy, getAppendIndex(destination, type));
+ }
+
+ template<typename ESXRecordT, typename IdAccessorT>
Collection<ESXRecordT, IdAccessorT>::Collection()
{}
diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp
index ab7a9ebecb..408a89d923 100644
--- a/apps/opencs/model/world/collectionbase.hpp
+++ b/apps/opencs/model/world/collectionbase.hpp
@@ -74,6 +74,10 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None) = 0;
///< If the record type does not match, an exception is thrown.
+ virtual void cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const UniversalId::Type type) = 0;
+
virtual const RecordBase& getRecord (const std::string& id) const = 0;
virtual const RecordBase& getRecord (int index) const = 0;
diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp
index 70f38c5341..e043336080 100644
--- a/apps/opencs/model/world/columnbase.hpp
+++ b/apps/opencs/model/world/columnbase.hpp
@@ -26,7 +26,49 @@ namespace CSMWorld
enum Display
{
+ Display_None, //Do not use
Display_String,
+
+ //CONCRETE TYPES STARTS HERE
+ Display_Skill,
+ Display_Class,
+ Display_Faction,
+ Display_Race,
+ Display_Sound,
+ Display_Region,
+ Display_Birthsign,
+ Display_Spell,
+ Display_Cell,
+ Display_Referenceable,
+ Display_Activator,
+ Display_Potion,
+ Display_Apparatus,
+ Display_Armor,
+ Display_Book,
+ Display_Clothing,
+ Display_Container,
+ Display_Creature,
+ Display_Door,
+ Display_Ingredient,
+ Display_CreatureLevelledList,
+ Display_ItemLevelledList,
+ Display_Light,
+ Display_Lockpick,
+ Display_Miscellaneous,
+ Display_Npc,
+ Display_Probe,
+ Display_Repair,
+ Display_Static,
+ Display_Weapon,
+ Display_Reference,
+ Display_Filter,
+ Display_Topic,
+ Display_Journal,
+ Display_TopicInfo,
+ Display_JournalInfo,
+ Display_Scene,
+ //CONCRETE TYPES ENDS HERE
+
Display_Integer,
Display_Float,
Display_Var,
diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp
index 18aac9e0be..def225018f 100644
--- a/apps/opencs/model/world/columnimp.hpp
+++ b/apps/opencs/model/world/columnimp.hpp
@@ -373,7 +373,7 @@ namespace CSMWorld
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),
+ Columns::ColumnId_Skill1) + index, ColumnBase::Display_Skill),
mIndex (index), mMajor (major)
{}
@@ -598,7 +598,7 @@ namespace CSMWorld
struct SoundFileColumn : public Column<ESXRecordT>
{
SoundFileColumn()
- : Column<ESXRecordT> (Columns::ColumnId_SoundFile, ColumnBase::Display_String)
+ : Column<ESXRecordT> (Columns::ColumnId_SoundFile, ColumnBase::Display_Sound)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
@@ -811,7 +811,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct CellColumn : public Column<ESXRecordT>
{
- CellColumn() : Column<ESXRecordT> (Columns::ColumnId_Cell, ColumnBase::Display_String) {}
+ CellColumn() : Column<ESXRecordT> (Columns::ColumnId_Cell, ColumnBase::Display_Cell) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -890,7 +890,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct OwnerColumn : public Column<ESXRecordT>
{
- OwnerColumn() : Column<ESXRecordT> (Columns::ColumnId_Owner, ColumnBase::Display_String) {}
+ OwnerColumn() : Column<ESXRecordT> (Columns::ColumnId_Owner, ColumnBase::Display_Npc) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -915,7 +915,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct SoulColumn : public Column<ESXRecordT>
{
- SoulColumn() : Column<ESXRecordT> (Columns::ColumnId_Soul, ColumnBase::Display_String) {}
+ SoulColumn() : Column<ESXRecordT> (Columns::ColumnId_Soul, ColumnBase::Display_Creature) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -940,7 +940,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct FactionColumn : public Column<ESXRecordT>
{
- FactionColumn() : Column<ESXRecordT> (Columns::ColumnId_Faction, ColumnBase::Display_String) {}
+ FactionColumn() : Column<ESXRecordT> (Columns::ColumnId_Faction, ColumnBase::Display_Faction) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -1090,7 +1090,7 @@ namespace CSMWorld
struct TeleportCellColumn : public Column<ESXRecordT>
{
TeleportCellColumn()
- : Column<ESXRecordT> (Columns::ColumnId_TeleportCell, ColumnBase::Display_String)
+ : Column<ESXRecordT> (Columns::ColumnId_TeleportCell, ColumnBase::Display_Cell)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
@@ -1146,7 +1146,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct KeyColumn : public Column<ESXRecordT>
{
- KeyColumn() : Column<ESXRecordT> (Columns::ColumnId_Key, ColumnBase::Display_String) {}
+ KeyColumn() : Column<ESXRecordT> (Columns::ColumnId_Key, ColumnBase::Display_Miscellaneous) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -1485,7 +1485,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct RaceColumn : public Column<ESXRecordT>
{
- RaceColumn() : Column<ESXRecordT> (Columns::ColumnId_Race, ColumnBase::Display_String) {}
+ RaceColumn() : Column<ESXRecordT> (Columns::ColumnId_Race, ColumnBase::Display_Race) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -1510,7 +1510,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct ClassColumn : public Column<ESXRecordT>
{
- ClassColumn() : Column<ESXRecordT> (Columns::ColumnId_Class, ColumnBase::Display_String) {}
+ ClassColumn() : Column<ESXRecordT> (Columns::ColumnId_Class, ColumnBase::Display_Class) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
@@ -1535,7 +1535,7 @@ namespace CSMWorld
template<typename ESXRecordT>
struct PcFactionColumn : public Column<ESXRecordT>
{
- PcFactionColumn() : Column<ESXRecordT> (Columns::ColumnId_PcFaction, ColumnBase::Display_String) {}
+ PcFactionColumn() : Column<ESXRecordT> (Columns::ColumnId_PcFaction, ColumnBase::Display_Faction) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp
index 7a13137205..2f3911270e 100644
--- a/apps/opencs/model/world/columns.cpp
+++ b/apps/opencs/model/world/columns.cpp
@@ -220,7 +220,7 @@ 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))
+ if (Misc::StringUtils::ciEqual(sNames[i].mName, name2))
return sNames[i].mId;
return -1;
@@ -263,7 +263,7 @@ namespace
static const char *sCreatureTypes[] =
{
- "Creature", "Deadra", "Undead", "Humanoid", 0
+ "Creature", "Daedra", "Undead", "Humanoid", 0
};
static const char *sWeaponTypes[] =
@@ -342,4 +342,4 @@ std::vector<std::string> CSMWorld::Columns::getEnums (ColumnId column)
}
return enums;
-} \ No newline at end of file
+}
diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp
index 21feb14be6..b60ffeb29c 100644
--- a/apps/opencs/model/world/commands.cpp
+++ b/apps/opencs/model/world/commands.cpp
@@ -4,10 +4,10 @@
#include <QAbstractItemModel>
#include "idtable.hpp"
-#include "idtable.hpp"
+#include <components/misc/stringops.hpp>
CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index,
- const QVariant& new_, QUndoCommand *parent)
+ const QVariant& new_, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_)
{
mOld = mModel.data (mIndex, Qt::EditRole);
@@ -25,7 +25,7 @@ void CSMWorld::ModifyCommand::undo()
mModel.setData (mIndex, mOld);
}
-CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+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());
@@ -54,7 +54,7 @@ void CSMWorld::CreateCommand::undo()
mModel.removeRow (mModel.getModelIndex (mId, 0).row());
}
-CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+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());
@@ -89,7 +89,7 @@ void CSMWorld::RevertCommand::undo()
mModel.setRecord (mId, *mOld);
}
-CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent)
+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());
@@ -126,7 +126,7 @@ void CSMWorld::DeleteCommand::undo()
CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex,
- const std::vector<int>& newOrder)
+ const std::vector<int>& newOrder)
: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder)
{}
@@ -140,8 +140,35 @@ void CSMWorld::ReorderRowsCommand::undo()
int size = static_cast<int> (mNewOrder.size());
std::vector<int> reverse (size);
- for (int i=0; i<size; ++i)
+ for (int i=0; i< size; ++i)
reverse.at (mNewOrder[i]) = i;
mModel.reorderRows (mBaseIndex, reverse);
+}
+
+CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model,
+ const std::string& idOrigin,
+ const std::string& IdDestination,
+ const CSMWorld::UniversalId::Type type,
+ QUndoCommand* parent) :
+ QUndoCommand (parent),
+ mModel (model),
+ mIdOrigin (idOrigin),
+ mIdDestination (Misc::StringUtils::lowerCase (IdDestination)),
+ mType (type)
+{
+ setText ( ("Clone record " + idOrigin + " to the " + IdDestination).c_str());
+}
+
+void CSMWorld::CloneCommand::redo()
+{
+ mModel.cloneRecord (mIdOrigin, mIdDestination, mType);
+
+ for (std::map<int, QVariant>::const_iterator iter (mValues.begin()); iter != mValues.end(); ++iter)
+ mModel.setData (mModel.getModelIndex (mIdDestination, iter->first), iter->second);
+}
+
+void CSMWorld::CloneCommand::undo()
+{
+ mModel.removeRow (mModel.getModelIndex (mIdDestination, 0).row());
} \ No newline at end of file
diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp
index f0480a369d..ec6350658f 100644
--- a/apps/opencs/model/world/commands.hpp
+++ b/apps/opencs/model/world/commands.hpp
@@ -39,6 +39,26 @@ namespace CSMWorld
virtual void undo();
};
+ class CloneCommand : public QUndoCommand
+ {
+ IdTable& mModel;
+ std::string mIdOrigin;
+ std::string mIdDestination;
+ UniversalId::Type mType;
+ std::map<int, QVariant> mValues;
+
+ public:
+
+ CloneCommand (IdTable& model, const std::string& idOrigin,
+ const std::string& IdDestination,
+ const UniversalId::Type type,
+ QUndoCommand* parent = 0);
+
+ virtual void redo();
+
+ virtual void undo();
+ };
+
class CreateCommand : public QUndoCommand
{
IdTable& mModel;
diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp
index 04e35cdaaf..8d53c4e53a 100644
--- a/apps/opencs/model/world/data.cpp
+++ b/apps/opencs/model/world/data.cpp
@@ -154,14 +154,17 @@ CSMWorld::Data::Data() : mRefs (mCells)
mTopics.addColumn (new StringIdColumn<ESM::Dialogue>);
mTopics.addColumn (new RecordStateColumn<ESM::Dialogue>);
+ mTopics.addColumn (new FixedRecordTypeColumn<ESM::Dialogue> (UniversalId::Type_Topic));
mTopics.addColumn (new DialogueTypeColumn<ESM::Dialogue>);
mJournals.addColumn (new StringIdColumn<ESM::Dialogue>);
mJournals.addColumn (new RecordStateColumn<ESM::Dialogue>);
+ mJournals.addColumn (new FixedRecordTypeColumn<ESM::Dialogue> (UniversalId::Type_Journal));
mJournals.addColumn (new DialogueTypeColumn<ESM::Dialogue> (true));
mTopicInfos.addColumn (new StringIdColumn<Info> (true));
mTopicInfos.addColumn (new RecordStateColumn<Info>);
+ mTopicInfos.addColumn (new FixedRecordTypeColumn<Info> (UniversalId::Type_TopicInfo));
mTopicInfos.addColumn (new TopicColumn<Info> (false));
mTopicInfos.addColumn (new ActorColumn<Info>);
mTopicInfos.addColumn (new RaceColumn<Info>);
@@ -178,6 +181,7 @@ CSMWorld::Data::Data() : mRefs (mCells)
mJournalInfos.addColumn (new StringIdColumn<Info> (true));
mJournalInfos.addColumn (new RecordStateColumn<Info>);
+ mJournalInfos.addColumn (new FixedRecordTypeColumn<Info> (UniversalId::Type_Journal));
mJournalInfos.addColumn (new TopicColumn<Info> (true));
mJournalInfos.addColumn (new QuestStatusTypeColumn<Info>);
mJournalInfos.addColumn (new QuestIndexColumn<Info>);
@@ -194,6 +198,7 @@ CSMWorld::Data::Data() : mRefs (mCells)
mRefs.addColumn (new StringIdColumn<CellRef> (true));
mRefs.addColumn (new RecordStateColumn<CellRef>);
+ mRefs.addColumn (new FixedRecordTypeColumn<CellRef> (UniversalId::Type_Reference));
mRefs.addColumn (new CellColumn<CellRef>);
mRefs.addColumn (new IdColumn<CellRef>);
mRefs.addColumn (new PosColumn<CellRef> (&CellRef::mPos, 0, false));
@@ -224,6 +229,7 @@ CSMWorld::Data::Data() : mRefs (mCells)
mFilters.addColumn (new StringIdColumn<CSMFilter::Filter>);
mFilters.addColumn (new RecordStateColumn<CSMFilter::Filter>);
+ mFilters.addColumn (new FixedRecordTypeColumn<CSMFilter::Filter> (UniversalId::Type_Filter));
mFilters.addColumn (new FilterColumn<CSMFilter::Filter>);
mFilters.addColumn (new DescriptionColumn<CSMFilter::Filter>);
mFilters.addColumn (new ScopeColumn<CSMFilter::Filter>);
diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp
index 809d64339c..bea307aa21 100644
--- a/apps/opencs/model/world/idtable.cpp
+++ b/apps/opencs/model/world/idtable.cpp
@@ -124,6 +124,17 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type
endInsertRows();
}
+void CSMWorld::IdTable::cloneRecord(const std::string& origin,
+ const std::string& destination,
+ CSMWorld::UniversalId::Type type)
+{
+ int index = mIdCollection->getAppendIndex (destination);
+ beginInsertRows (QModelIndex(), index, index);
+ mIdCollection->cloneRecord(origin, destination, type);
+ endInsertRows();
+}
+
+
QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const
{
return index (mIdCollection->getIndex (id), column);
diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp
index e4ae58fd04..74923867d6 100644
--- a/apps/opencs/model/world/idtable.hpp
+++ b/apps/opencs/model/world/idtable.hpp
@@ -63,6 +63,10 @@ namespace CSMWorld
void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None);
///< \param type Will be ignored, unless the collection supports multiple record types
+ void cloneRecord(const std::string& origin,
+ const std::string& destination,
+ UniversalId::Type type = UniversalId::Type_None);
+
QModelIndex getModelIndex (const std::string& id, int column) const;
void setRecord (const std::string& id, const RecordBase& record);
diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp
index 2b757adfe5..f51b7f818f 100644
--- a/apps/opencs/model/world/idtableproxymodel.cpp
+++ b/apps/opencs/model/world/idtableproxymodel.cpp
@@ -33,7 +33,9 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
: QSortFilterProxyModel (parent)
-{}
+{
+ setSortCaseSensitivity (Qt::CaseInsensitive);
+}
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
{
diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp
index 87bb925c2e..50d09f3139 100644
--- a/apps/opencs/model/world/infocollection.cpp
+++ b/apps/opencs/model/world/infocollection.cpp
@@ -67,7 +67,7 @@ int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (topic);
for (; range.first!=range.second; ++range.first)
- if (Misc::StringUtils::lowerCase (range.first->get().mId)==fullId)
+ if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId))
return std::distance (getRecords().begin(), range.first);
return -1;
@@ -177,8 +177,8 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s
RecordConstIterator end = begin;
for (; end!=getRecords().end(); ++end)
- if (Misc::StringUtils::lowerCase (end->get().mTopicId)!=topic2)
+ if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2))
break;
return Range (begin, end);
-} \ No newline at end of file
+}
diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp
index 74f60419b9..cf9e496ee6 100644
--- a/apps/opencs/model/world/ref.cpp
+++ b/apps/opencs/model/world/ref.cpp
@@ -8,6 +8,5 @@ void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string
mId = id;
mCell = cell.mId;
- if (!mDeleted)
- cell.addRef (mId);
+ cell.addRef (mId);
} \ No newline at end of file
diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp
index 696aeefaa2..9ee59bd1e0 100644
--- a/apps/opencs/model/world/refcollection.cpp
+++ b/apps/opencs/model/world/refcollection.cpp
@@ -5,6 +5,8 @@
#include "ref.hpp"
#include "cell.hpp"
+#include "universalid.hpp"
+#include "record.hpp"
void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base)
{
@@ -14,7 +16,8 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
CellRef ref;
- while (cell2.getNextRef (reader, ref))
+ bool deleted = false;
+ while (cell2.getNextRef (reader, ref, deleted))
{
/// \todo handle deleted and moved references
ref.load (reader, cell2, getNewId());
@@ -34,4 +37,15 @@ std::string CSMWorld::RefCollection::getNewId()
std::ostringstream stream;
stream << "ref#" << mNextId++;
return stream.str();
+}
+
+void CSMWorld::RefCollection::cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const CSMWorld::UniversalId::Type type,
+ const CSMWorld::UniversalId::ArgumentType argumentType)
+{
+ Record<CSMWorld::CellRef> clone(getRecord(origin));
+ clone.mState = CSMWorld::RecordBase::State_ModifiedOnly;
+ clone.get().mId = destination;
+ insertRecord(clone, getAppendIndex(destination, type), type);
} \ No newline at end of file
diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp
index b5f8c8064a..dcfd2036cd 100644
--- a/apps/opencs/model/world/refcollection.hpp
+++ b/apps/opencs/model/world/refcollection.hpp
@@ -8,6 +8,7 @@
namespace CSMWorld
{
struct Cell;
+ struct UniversalId;
/// \brief References in cells
class RefCollection : public Collection<CellRef>
@@ -25,6 +26,11 @@ namespace CSMWorld
///< Load a sequence of references.
std::string getNewId();
+
+ void cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const CSMWorld::UniversalId::Type type,
+ const CSMWorld::UniversalId::ArgumentType argumentType);
};
}
diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp
index df0ceae706..0870a2d3e6 100644
--- a/apps/opencs/model/world/refidadapter.hpp
+++ b/apps/opencs/model/world/refidadapter.hpp
@@ -31,6 +31,7 @@ namespace CSMWorld
///< If the data type does not match an exception is thrown.
virtual std::string getId (const RecordBase& record) const = 0;
+ virtual void setId(RecordBase& record, const std::string& id) = 0;
};
}
diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp
index d5d84a8f7e..bd509a86b6 100644
--- a/apps/opencs/model/world/refidadapterimp.hpp
+++ b/apps/opencs/model/world/refidadapterimp.hpp
@@ -34,6 +34,8 @@ namespace CSMWorld
BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base);
virtual std::string getId (const RecordBase& record) const;
+
+ virtual void setId (RecordBase& record, const std::string& id);
virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
const;
@@ -51,6 +53,12 @@ namespace CSMWorld
{}
template<typename RecordT>
+ void BaseRefIdAdapter<RecordT>::setId (RecordBase& record, const std::string& id)
+ {
+ (dynamic_cast<Record<RecordT>&> (record).get().mId) = id;
+ }
+
+ template<typename RecordT>
std::string BaseRefIdAdapter<RecordT>::getId (const RecordBase& record) const
{
return dynamic_cast<const Record<RecordT>&> (record).get().mId;
diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp
index 86a542c5c3..f515e34d8e 100644
--- a/apps/opencs/model/world/refidcollection.cpp
+++ b/apps/opencs/model/world/refidcollection.cpp
@@ -2,6 +2,7 @@
#include "refidcollection.hpp"
#include <stdexcept>
+#include <memory>
#include <components/esm/esmreader.hpp>
@@ -58,7 +59,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.push_back (RefIdColumn (Columns::ColumnId_Name, ColumnBase::Display_String));
nameColumns.mName = &mColumns.back();
- mColumns.push_back (RefIdColumn (Columns::ColumnId_Script, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Script, ColumnBase::Display_Script));
nameColumns.mScript = &mColumns.back();
InventoryColumns inventoryColumns (nameColumns);
@@ -181,7 +182,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
unsigned int mFlag;
} sCreatureFlagTable[] =
{
- { Columns::ColumnId_Biped, ESM::Creature::Biped },
+ { Columns::ColumnId_Biped, ESM::Creature::Bipedal },
{ Columns::ColumnId_HasWeapon, ESM::Creature::Weapon },
{ Columns::ColumnId_NoMovement, ESM::Creature::None },
{ Columns::ColumnId_Swims, ESM::Creature::Swims },
@@ -213,10 +214,10 @@ CSMWorld::RefIdCollection::RefIdCollection()
creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn));
- mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_Sound));
const RefIdColumn *openSound = &mColumns.back();
- mColumns.push_back (RefIdColumn (Columns::ColumnId_CloseSound, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_CloseSound, ColumnBase::Display_Sound));
const RefIdColumn *closeSound = &mColumns.back();
LightColumns lightColumns (inventoryColumns);
@@ -230,7 +231,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.push_back (RefIdColumn (Columns::ColumnId_Colour, ColumnBase::Display_Integer));
lightColumns.mColor = &mColumns.back();
- mColumns.push_back (RefIdColumn (Columns::ColumnId_Sound, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Sound, ColumnBase::Display_Sound));
lightColumns.mSound = &mColumns.back();
static const struct
@@ -262,13 +263,13 @@ CSMWorld::RefIdCollection::RefIdCollection()
NpcColumns npcColumns (actorsColumns);
- mColumns.push_back (RefIdColumn (Columns::ColumnId_Race, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Race, ColumnBase::Display_Race));
npcColumns.mRace = &mColumns.back();
- mColumns.push_back (RefIdColumn (Columns::ColumnId_Class, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Class, ColumnBase::Display_Class));
npcColumns.mClass = &mColumns.back();
- mColumns.push_back (RefIdColumn (Columns::ColumnId_Faction, ColumnBase::Display_String));
+ mColumns.push_back (RefIdColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction));
npcColumns.mFaction = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::Columnid_Hair, ColumnBase::Display_String));
@@ -431,7 +432,7 @@ void CSMWorld::RefIdCollection::removeRows (int index, int count)
void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type)
{
- mData.appendRecord (type, id);
+ mData.appendRecord (type, id, false);
}
int CSMWorld::RefIdCollection::searchId (const std::string& id) const
@@ -449,6 +450,16 @@ void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record)
mData.getRecord (mData.globalToLocalIndex (index)).assign (record);
}
+void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const CSMWorld::UniversalId::Type type)
+{
+ std::auto_ptr<RecordBase> newRecord(mData.getRecord(mData.searchId(origin)).clone());
+ newRecord->mState = RecordBase::State_ModifiedOnly;
+ mAdapters.find(type)->second->setId(*newRecord, destination);
+ mData.insertRecord(*newRecord, type, destination);
+}
+
void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record,
UniversalId::Type type)
{
@@ -456,7 +467,7 @@ void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record,
int index = mData.getAppendIndex (type);
- mData.appendRecord (type, id);
+ mData.appendRecord (type, id, false);
mData.getRecord (mData.globalToLocalIndex (index)).assign (record);
}
@@ -504,7 +515,7 @@ void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, Univers
{
// new record
int index = mData.getAppendIndex (type);
- mData.appendRecord (type, id);
+ mData.appendRecord (type, id, base);
RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
@@ -549,3 +560,9 @@ void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const
{
mData.save (index, writer);
}
+
+const CSMWorld::RefIdData& CSMWorld::RefIdCollection::getDataSet() const
+{
+ return mData;
+}
+
diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp
index 5ff4a70bf2..dd6213677e 100644
--- a/apps/opencs/model/world/refidcollection.hpp
+++ b/apps/opencs/model/world/refidcollection.hpp
@@ -69,6 +69,10 @@ namespace CSMWorld
virtual void removeRows (int index, int count);
+ virtual void cloneRecord(const std::string& origin,
+ const std::string& destination,
+ const UniversalId::Type type);
+
virtual void appendBlankRecord (const std::string& id, UniversalId::Type type);
///< \param type Will be ignored, unless the collection supports multiple record types
@@ -107,7 +111,10 @@ namespace CSMWorld
/// \return Success?
void save (int index, ESM::ESMWriter& writer) const;
+
+ const RefIdData& getDataSet() const; //I can't figure out a better name for this one :(
};
}
#endif
+
diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp
index 8f59b0fe74..f67ab21526 100644
--- a/apps/opencs/model/world/refiddata.cpp
+++ b/apps/opencs/model/world/refiddata.cpp
@@ -131,7 +131,7 @@ CSMWorld::RecordBase& CSMWorld::RefIdData::getRecord (const LocalIndex& index)
return iter->second->getRecord (index.first);
}
-void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::string& id)
+void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::string& id, bool base)
{
std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
mRecordContainers.find (type);
@@ -139,7 +139,7 @@ void CSMWorld::RefIdData::appendRecord (UniversalId::Type type, const std::strin
if (iter==mRecordContainers.end())
throw std::logic_error ("invalid local index type");
- iter->second->appendRecord (id);
+ iter->second->appendRecord (id, base);
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id),
LocalIndex (iter->second->getSize()-1, type)));
@@ -230,4 +230,118 @@ void CSMWorld::RefIdData::save (int index, ESM::ESMWriter& writer) const
throw std::logic_error ("invalid local index type");
iter->second->save (localIndex.first, writer);
-} \ No newline at end of file
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Book >& CSMWorld::RefIdData::getBooks() const
+{
+ return mBooks;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Activator >& CSMWorld::RefIdData::getActivators() const
+{
+ return mActivators;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Potion >& CSMWorld::RefIdData::getPotions() const
+{
+ return mPotions;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Apparatus >& CSMWorld::RefIdData::getApparati() const
+{
+ return mApparati;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Armor >& CSMWorld::RefIdData::getArmors() const
+{
+ return mArmors;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Clothing >& CSMWorld::RefIdData::getClothing() const
+{
+ return mClothing;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Container >& CSMWorld::RefIdData::getContainers() const
+{
+ return mContainers;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Creature >& CSMWorld::RefIdData::getCreatures() const
+{
+ return mCreatures;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Door >& CSMWorld::RefIdData::getDoors() const
+{
+ return mDoors;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Ingredient >& CSMWorld::RefIdData::getIngredients() const
+{
+ return mIngredients;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& CSMWorld::RefIdData::getCreatureLevelledLists() const
+{
+ return mCreatureLevelledLists;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& CSMWorld::RefIdData::getItemLevelledList() const
+{
+ return mItemLevelledLists;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Light >& CSMWorld::RefIdData::getLights() const
+{
+ return mLights;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Lockpick >& CSMWorld::RefIdData::getLocpicks() const
+{
+ return mLockpicks;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& CSMWorld::RefIdData::getMiscellaneous() const
+{
+ return mMiscellaneous;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::NPC >& CSMWorld::RefIdData::getNPCs() const
+{
+ return mNpcs;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Weapon >& CSMWorld::RefIdData::getWeapons() const
+{
+ return mWeapons;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Probe >& CSMWorld::RefIdData::getProbes() const
+{
+ return mProbes;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Repair >& CSMWorld::RefIdData::getRepairs() const
+{
+ return mRepairs;
+}
+
+const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStatics() const
+{
+ return mStatics;
+}
+
+void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::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->insertRecord(record);
+
+ mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id),
+ LocalIndex (iter->second->getSize()-1, type)));
+}
diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp
index 9595ab23b5..1b600364c7 100644
--- a/apps/opencs/model/world/refiddata.hpp
+++ b/apps/opencs/model/world/refiddata.hpp
@@ -45,7 +45,9 @@ namespace CSMWorld
virtual RecordBase& getRecord (int index)= 0;
- virtual void appendRecord (const std::string& id) = 0;
+ virtual void appendRecord (const std::string& id, bool base) = 0;
+
+ virtual void insertRecord (RecordBase& record) = 0;
virtual void load (int index, ESM::ESMReader& reader, bool base) = 0;
@@ -67,7 +69,9 @@ namespace CSMWorld
virtual RecordBase& getRecord (int index);
- virtual void appendRecord (const std::string& id);
+ virtual void appendRecord (const std::string& id, bool base);
+
+ virtual void insertRecord (RecordBase& record);
virtual void load (int index, ESM::ESMReader& reader, bool base);
@@ -79,6 +83,13 @@ namespace CSMWorld
};
template<typename RecordT>
+ void RefIdDataContainer<RecordT>::insertRecord(RecordBase& record)
+ {
+ Record<RecordT>& newRecord = dynamic_cast<Record<RecordT>& >(record);
+ mContainer.push_back(newRecord);
+ }
+
+ template<typename RecordT>
int RefIdDataContainer<RecordT>::getSize() const
{
return static_cast<int> (mContainer.size());
@@ -97,12 +108,15 @@ namespace CSMWorld
}
template<typename RecordT>
- void RefIdDataContainer<RecordT>::appendRecord (const std::string& id)
+ void RefIdDataContainer<RecordT>::appendRecord (const std::string& id, bool base)
{
Record<RecordT> record;
+
+ record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
+
+ record.mBase.mId = id;
record.mModified.mId = id;
- record.mModified.blank();
- record.mState = RecordBase::State_ModifiedOnly;
+ (base ? record.mBase : record.mModified).blank();
mContainer.push_back (record);
}
@@ -136,15 +150,10 @@ namespace CSMWorld
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly)
{
- std::string type;
- for (int i=0; i<4; ++i)
- /// \todo make endianess agnostic (change ESMWriter interface?)
- type += reinterpret_cast<const char *> (&mContainer.at (index).mModified.sRecordId)[i];
-
- writer.startRecord (type);
+ writer.startRecord (mContainer.at (index).mModified.sRecordId);
writer.writeHNCString ("NAME", getId (index));
mContainer.at (index).mModified.save (writer);
- writer.endRecord (type);
+ writer.endRecord (mContainer.at (index).mModified.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
@@ -201,11 +210,13 @@ namespace CSMWorld
void erase (int index, int count);
+ void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id);
+
const RecordBase& getRecord (const LocalIndex& index) const;
RecordBase& getRecord (const LocalIndex& index);
- void appendRecord (UniversalId::Type type, const std::string& id);
+ void appendRecord (UniversalId::Type type, const std::string& id, bool base);
int getAppendIndex (UniversalId::Type type) const;
@@ -219,7 +230,33 @@ namespace CSMWorld
/// \param listDeleted include deleted record in the list
void save (int index, ESM::ESMWriter& writer) const;
+
+ //RECORD CONTAINERS ACCESS METHODS
+ const RefIdDataContainer<ESM::Book>& getBooks() const;
+ const RefIdDataContainer<ESM::Activator>& getActivators() const;
+ const RefIdDataContainer<ESM::Potion>& getPotions() const;
+ const RefIdDataContainer<ESM::Apparatus>& getApparati() const;
+ const RefIdDataContainer<ESM::Armor>& getArmors() const;
+ const RefIdDataContainer<ESM::Clothing>& getClothing() const;
+ const RefIdDataContainer<ESM::Container>& getContainers() const;
+ const RefIdDataContainer<ESM::Creature>& getCreatures() const;
+ const RefIdDataContainer<ESM::Door>& getDoors() const;
+ const RefIdDataContainer<ESM::Ingredient>& getIngredients() const;
+ const RefIdDataContainer<ESM::CreatureLevList>& getCreatureLevelledLists() const;
+ const RefIdDataContainer<ESM::ItemLevList>& getItemLevelledList() const;
+ const RefIdDataContainer<ESM::Light>& getLights() const;
+ const RefIdDataContainer<ESM::Lockpick>& getLocpicks() const;
+ const RefIdDataContainer<ESM::Miscellaneous>& getMiscellaneous() const;
+ const RefIdDataContainer<ESM::NPC>& getNPCs() const;
+ const RefIdDataContainer<ESM::Weapon >& getWeapons() const;
+ const RefIdDataContainer<ESM::Probe >& getProbes() const;
+ const RefIdDataContainer<ESM::Repair>& getRepairs() const;
+ const RefIdDataContainer<ESM::Static>& getStatics() const;
};
}
#endif
+
+
+
+
diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp
index 86689d823b..9da49defe7 100644
--- a/apps/opencs/model/world/scriptcontext.cpp
+++ b/apps/opencs/model/world/scriptcontext.cpp
@@ -5,23 +5,92 @@
#include <components/misc/stringops.hpp>
+#include <components/compiler/quickfileparser.hpp>
+#include <components/compiler/nullerrorhandler.hpp>
+#include <components/compiler/scanner.hpp>
+
#include "data.hpp"
CSMWorld::ScriptContext::ScriptContext (const Data& data) : mData (data), mIdsUpdated (false) {}
bool CSMWorld::ScriptContext::canDeclareLocals() const
{
- return false;
+ return true;
}
char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const
{
+ int index = mData.getGlobals().searchId (name);
+
+ if (index!=-1)
+ {
+ switch (mData.getGlobals().getRecord (index).get().mValue.getType())
+ {
+ case ESM::VT_Short: return 's';
+ case ESM::VT_Long: return 'l';
+ case ESM::VT_Float: return 'f';
+
+ default: return ' ';
+ }
+ }
+
return ' ';
}
-char CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const
+std::pair<char, bool> CSMWorld::ScriptContext::getMemberType (const std::string& name,
+ const std::string& id) const
{
- return ' ';
+ /// \todo invalidate locals cache on change to scripts
+
+ std::string id2 = Misc::StringUtils::lowerCase (id);
+
+ int index = mData.getScripts().searchId (id2);
+ bool reference = false;
+
+ if (index!=-1)
+ {
+ // ID is not a script ID. Search for a matching referenceable instead.
+ index = mData.getReferenceables().searchId (id2);
+
+ if (index!=-1)
+ {
+ // Referenceable found.
+ int columnIndex = mData.getReferenceables().searchColumnIndex (Columns::ColumnId_Script);
+
+ if (columnIndex!=-1)
+ {
+ id2 = Misc::StringUtils::lowerCase (mData.getReferenceables().
+ getData (index, columnIndex).toString().toUtf8().constData());
+
+ if (!id2.empty())
+ {
+ // Referenceable has a script -> use it.
+ index = mData.getScripts().searchId (id2);
+ reference = true;
+ }
+ }
+ }
+ }
+
+ if (index==-1)
+ return std::make_pair (' ', false);
+
+ std::map<std::string, Compiler::Locals>::iterator iter = mLocals.find (id2);
+
+ if (iter==mLocals.end())
+ {
+ Compiler::Locals locals;
+
+ Compiler::NullErrorHandler errorHandler;
+ std::istringstream stream (mData.getScripts().getRecord (index).get().mScriptText);
+ Compiler::QuickFileParser parser (errorHandler, *this, locals);
+ Compiler::Scanner scanner (errorHandler, stream, getExtensions());
+ scanner.scan (parser);
+
+ iter = mLocals.insert (std::make_pair (id2, locals)).first;
+ }
+
+ return std::make_pair (iter->second.getType (Misc::StringUtils::lowerCase (name)), reference);
}
bool CSMWorld::ScriptContext::isId (const std::string& name) const
@@ -31,6 +100,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const
mIds = mData.getIds();
std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCase);
+ std::sort (mIds.begin(), mIds.end());
mIdsUpdated = true;
}
@@ -38,7 +108,19 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const
return std::binary_search (mIds.begin(), mIds.end(), Misc::StringUtils::lowerCase (name));
}
+bool CSMWorld::ScriptContext::isJournalId (const std::string& name) const
+{
+ return mData.getJournals().searchId (name)!=-1;
+}
+
void CSMWorld::ScriptContext::invalidateIds()
{
mIdsUpdated = false;
+}
+
+void CSMWorld::ScriptContext::clear()
+{
+ mIds.clear();
+ mIdsUpdated = false;
+ mLocals.clear();
} \ No newline at end of file
diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp
index b839b5a432..29ee42645a 100644
--- a/apps/opencs/model/world/scriptcontext.hpp
+++ b/apps/opencs/model/world/scriptcontext.hpp
@@ -3,8 +3,10 @@
#include <string>
#include <vector>
+#include <map>
#include <components/compiler/context.hpp>
+#include <components/compiler/locals.hpp>
namespace CSMWorld
{
@@ -15,6 +17,7 @@ namespace CSMWorld
const Data& mData;
mutable std::vector<std::string> mIds;
mutable bool mIdsUpdated;
+ mutable std::map<std::string, Compiler::Locals> mLocals;
public:
@@ -26,13 +29,23 @@ namespace CSMWorld
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 std::pair<char, bool> getMemberType (const std::string& name,
+ const std::string& id) const;
+ ///< Return type of member variable \a name in script \a id or in script of reference of
+ /// \a id
+ /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist.
+ /// second: true: script of reference
virtual bool isId (const std::string& name) const;
///< Does \a name match an ID, that can be referenced?
+ virtual bool isJournalId (const std::string& name) const;
+ ///< Does \a name match a journal ID?
+
void invalidateIds();
+
+ void clear();
+ ///< Remove all cached data.
};
}
diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp
new file mode 100644
index 0000000000..b56c9c8c25
--- /dev/null
+++ b/apps/opencs/model/world/tablemimedata.cpp
@@ -0,0 +1,446 @@
+#include "tablemimedata.hpp"
+#include <string>
+
+#include "universalid.hpp"
+#include "columnbase.hpp"
+
+CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) :
+mDocument(document)
+{
+ mUniversalId.push_back (id);
+ mObjectsFormats << QString::fromStdString ("tabledata/" + id.getTypeName());
+}
+
+CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) :
+ mUniversalId (id), mDocument(document)
+{
+ for (std::vector<UniversalId>::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it)
+ {
+ mObjectsFormats << QString::fromStdString ("tabledata/" + it->getTypeName());
+ }
+}
+
+QStringList CSMWorld::TableMimeData::formats() const
+{
+ return mObjectsFormats;
+}
+
+CSMWorld::TableMimeData::~TableMimeData()
+{
+}
+
+std::string CSMWorld::TableMimeData::getIcon() const
+{
+ if (mUniversalId.empty())
+ {
+ throw ("TableMimeData holds no UniversalId");
+ }
+
+ std::string tmpIcon;
+ bool firstIteration = true;
+
+ for (unsigned i = 0; i < mUniversalId.size(); ++i)
+ {
+ if (firstIteration)
+ {
+ firstIteration = false;
+ tmpIcon = mUniversalId[i].getIcon();
+ continue;
+ }
+
+ if (tmpIcon != mUniversalId[i].getIcon())
+ {
+ return ":/multitype.png"; //icon stolen from gnome
+ }
+
+ tmpIcon = mUniversalId[i].getIcon();
+ }
+
+ return mUniversalId.begin()->getIcon(); //All objects are of the same type;
+}
+
+std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const
+{
+ return mUniversalId;
+}
+
+
+bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const
+{
+ for (std::vector<UniversalId>::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it)
+ {
+ if (it->getType() == type)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const
+{
+ for (std::vector<UniversalId>::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it)
+ {
+ if (it->getType() == convertEnums (type))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const
+{
+ for (std::vector<UniversalId>::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it)
+ {
+ if (it->getType() == type)
+ {
+ return *it;
+ }
+ }
+
+ throw ("TableMimeData object does not hold object of the seeked type");
+}
+
+CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const
+{
+ for (std::vector<UniversalId>::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it)
+ {
+ if (it->getType() == convertEnums (type))
+ {
+ return *it;
+ }
+ }
+
+ throw ("TableMimeData object does not hold object of the seeked type");
+}
+
+bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const
+{
+ return &document == &mDocument;
+}
+
+CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (CSMWorld::ColumnBase::Display type)
+{
+ switch (type)
+ {
+ case CSMWorld::ColumnBase::Display_Race:
+ return CSMWorld::UniversalId::Type_Race;
+
+
+ case CSMWorld::ColumnBase::Display_Skill:
+ return CSMWorld::UniversalId::Type_Skill;
+
+
+ case CSMWorld::ColumnBase::Display_Class:
+ return CSMWorld::UniversalId::Type_Class;
+
+
+ case CSMWorld::ColumnBase::Display_Faction:
+ return CSMWorld::UniversalId::Type_Faction;
+
+
+ case CSMWorld::ColumnBase::Display_Sound:
+ return CSMWorld::UniversalId::Type_Sound;
+
+
+ case CSMWorld::ColumnBase::Display_Region:
+ return CSMWorld::UniversalId::Type_Region;
+
+
+ case CSMWorld::ColumnBase::Display_Birthsign:
+ return CSMWorld::UniversalId::Type_Birthsign;
+
+
+ case CSMWorld::ColumnBase::Display_Spell:
+ return CSMWorld::UniversalId::Type_Spell;
+
+
+ case CSMWorld::ColumnBase::Display_Cell:
+ return CSMWorld::UniversalId::Type_Cell;
+
+
+ case CSMWorld::ColumnBase::Display_Referenceable:
+ return CSMWorld::UniversalId::Type_Referenceable;
+
+
+ case CSMWorld::ColumnBase::Display_Activator:
+ return CSMWorld::UniversalId::Type_Activator;
+
+
+ case CSMWorld::ColumnBase::Display_Potion:
+ return CSMWorld::UniversalId::Type_Potion;
+
+
+ case CSMWorld::ColumnBase::Display_Apparatus:
+ return CSMWorld::UniversalId::Type_Apparatus;
+
+
+ case CSMWorld::ColumnBase::Display_Armor:
+ return CSMWorld::UniversalId::Type_Armor;
+
+
+ case CSMWorld::ColumnBase::Display_Book:
+ return CSMWorld::UniversalId::Type_Book;
+
+
+ case CSMWorld::ColumnBase::Display_Clothing:
+ return CSMWorld::UniversalId::Type_Clothing;
+
+
+ case CSMWorld::ColumnBase::Display_Container:
+ return CSMWorld::UniversalId::Type_Container;
+
+
+ case CSMWorld::ColumnBase::Display_Creature:
+ return CSMWorld::UniversalId::Type_Creature;
+
+
+ case CSMWorld::ColumnBase::Display_Door:
+ return CSMWorld::UniversalId::Type_Door;
+
+
+ case CSMWorld::ColumnBase::Display_Ingredient:
+ return CSMWorld::UniversalId::Type_Ingredient;
+
+
+ case CSMWorld::ColumnBase::Display_CreatureLevelledList:
+ return CSMWorld::UniversalId::Type_CreatureLevelledList;
+
+
+ case CSMWorld::ColumnBase::Display_ItemLevelledList:
+ return CSMWorld::UniversalId::Type_ItemLevelledList;
+
+
+ case CSMWorld::ColumnBase::Display_Light:
+ return CSMWorld::UniversalId::Type_Light;
+
+
+ case CSMWorld::ColumnBase::Display_Lockpick:
+ return CSMWorld::UniversalId::Type_Lockpick;
+
+
+ case CSMWorld::ColumnBase::Display_Miscellaneous:
+ return CSMWorld::UniversalId::Type_Miscellaneous;
+
+
+ case CSMWorld::ColumnBase::Display_Npc:
+ return CSMWorld::UniversalId::Type_Npc;
+
+
+ case CSMWorld::ColumnBase::Display_Probe:
+ return CSMWorld::UniversalId::Type_Probe;
+
+
+ case CSMWorld::ColumnBase::Display_Repair:
+ return CSMWorld::UniversalId::Type_Repair;
+
+
+ case CSMWorld::ColumnBase::Display_Static:
+ return CSMWorld::UniversalId::Type_Static;
+
+
+ case CSMWorld::ColumnBase::Display_Weapon:
+ return CSMWorld::UniversalId::Type_Weapon;
+
+
+ case CSMWorld::ColumnBase::Display_Reference:
+ return CSMWorld::UniversalId::Type_Reference;
+
+
+ case CSMWorld::ColumnBase::Display_Filter:
+ return CSMWorld::UniversalId::Type_Filter;
+
+
+ case CSMWorld::ColumnBase::Display_Topic:
+ return CSMWorld::UniversalId::Type_Topic;
+
+
+ case CSMWorld::ColumnBase::Display_Journal:
+ return CSMWorld::UniversalId::Type_Journal;
+
+
+ case CSMWorld::ColumnBase::Display_TopicInfo:
+ return CSMWorld::UniversalId::Type_TopicInfo;
+
+
+ case CSMWorld::ColumnBase::Display_JournalInfo:
+ return CSMWorld::UniversalId::Type_JournalInfo;
+
+
+ case CSMWorld::ColumnBase::Display_Scene:
+ return CSMWorld::UniversalId::Type_Scene;
+
+
+ case CSMWorld::ColumnBase::Display_Script:
+ return CSMWorld::UniversalId::Type_Script;
+
+
+ default:
+ return CSMWorld::UniversalId::Type_None;
+
+ }
+}
+
+CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::UniversalId::Type type)
+{
+ switch (type)
+ {
+ case CSMWorld::UniversalId::Type_Race:
+ return CSMWorld::ColumnBase::Display_Race;
+
+
+ case CSMWorld::UniversalId::Type_Skill:
+ return CSMWorld::ColumnBase::Display_Skill;
+
+
+ case CSMWorld::UniversalId::Type_Class:
+ return CSMWorld::ColumnBase::Display_Class;
+
+
+ case CSMWorld::UniversalId::Type_Faction:
+ return CSMWorld::ColumnBase::Display_Faction;
+
+
+ case CSMWorld::UniversalId::Type_Sound:
+ return CSMWorld::ColumnBase::Display_Sound;
+
+
+ case CSMWorld::UniversalId::Type_Region:
+ return CSMWorld::ColumnBase::Display_Region;
+
+
+ case CSMWorld::UniversalId::Type_Birthsign:
+ return CSMWorld::ColumnBase::Display_Birthsign;
+
+
+ case CSMWorld::UniversalId::Type_Spell:
+ return CSMWorld::ColumnBase::Display_Spell;
+
+
+ case CSMWorld::UniversalId::Type_Cell:
+ return CSMWorld::ColumnBase::Display_Cell;
+
+
+ case CSMWorld::UniversalId::Type_Referenceable:
+ return CSMWorld::ColumnBase::Display_Referenceable;
+
+
+ case CSMWorld::UniversalId::Type_Activator:
+ return CSMWorld::ColumnBase::Display_Activator;
+
+
+ case CSMWorld::UniversalId::Type_Potion:
+ return CSMWorld::ColumnBase::Display_Potion;
+
+
+ case CSMWorld::UniversalId::Type_Apparatus:
+ return CSMWorld::ColumnBase::Display_Apparatus;
+
+
+ case CSMWorld::UniversalId::Type_Armor:
+ return CSMWorld::ColumnBase::Display_Armor;
+
+
+ case CSMWorld::UniversalId::Type_Book:
+ return CSMWorld::ColumnBase::Display_Book;
+
+
+ case CSMWorld::UniversalId::Type_Clothing:
+ return CSMWorld::ColumnBase::Display_Clothing;
+
+
+ case CSMWorld::UniversalId::Type_Container:
+ return CSMWorld::ColumnBase::Display_Container;
+
+
+ case CSMWorld::UniversalId::Type_Creature:
+ return CSMWorld::ColumnBase::Display_Creature;
+
+
+ case CSMWorld::UniversalId::Type_Door:
+ return CSMWorld::ColumnBase::Display_Door;
+
+
+ case CSMWorld::UniversalId::Type_Ingredient:
+ return CSMWorld::ColumnBase::Display_Ingredient;
+
+
+ case CSMWorld::UniversalId::Type_CreatureLevelledList:
+ return CSMWorld::ColumnBase::Display_CreatureLevelledList;
+
+
+ case CSMWorld::UniversalId::Type_ItemLevelledList:
+ return CSMWorld::ColumnBase::Display_ItemLevelledList;
+
+
+ case CSMWorld::UniversalId::Type_Light:
+ return CSMWorld::ColumnBase::Display_Light;
+
+
+ case CSMWorld::UniversalId::Type_Lockpick:
+ return CSMWorld::ColumnBase::Display_Lockpick;
+
+
+ case CSMWorld::UniversalId::Type_Miscellaneous:
+ return CSMWorld::ColumnBase::Display_Miscellaneous;
+
+
+ case CSMWorld::UniversalId::Type_Npc:
+ return CSMWorld::ColumnBase::Display_Npc;
+
+
+ case CSMWorld::UniversalId::Type_Probe:
+ return CSMWorld::ColumnBase::Display_Probe;
+
+
+ case CSMWorld::UniversalId::Type_Repair:
+ return CSMWorld::ColumnBase::Display_Repair;
+
+
+ case CSMWorld::UniversalId::Type_Static:
+ return CSMWorld::ColumnBase::Display_Static;
+
+
+ case CSMWorld::UniversalId::Type_Weapon:
+ return CSMWorld::ColumnBase::Display_Weapon;
+
+
+ case CSMWorld::UniversalId::Type_Reference:
+ return CSMWorld::ColumnBase::Display_Reference;
+
+
+ case CSMWorld::UniversalId::Type_Filter:
+ return CSMWorld::ColumnBase::Display_Filter;
+
+
+ case CSMWorld::UniversalId::Type_Topic:
+ return CSMWorld::ColumnBase::Display_Topic;
+
+
+ case CSMWorld::UniversalId::Type_Journal:
+ return CSMWorld::ColumnBase::Display_Journal;
+
+
+ case CSMWorld::UniversalId::Type_TopicInfo:
+ return CSMWorld::ColumnBase::Display_TopicInfo;
+
+
+ case CSMWorld::UniversalId::Type_JournalInfo:
+ return CSMWorld::ColumnBase::Display_JournalInfo;
+
+
+ case CSMWorld::UniversalId::Type_Scene:
+ return CSMWorld::ColumnBase::Display_Scene;
+
+
+ case CSMWorld::UniversalId::Type_Script:
+ return CSMWorld::ColumnBase::Display_Script;
+
+
+ default:
+ return CSMWorld::ColumnBase::Display_None;
+ }
+} \ No newline at end of file
diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp
new file mode 100644
index 0000000000..7687f3555f
--- /dev/null
+++ b/apps/opencs/model/world/tablemimedata.hpp
@@ -0,0 +1,63 @@
+
+#ifndef TABLEMIMEDATA_H
+#define TABLEMIMEDATA_H
+
+#include <vector>
+
+#include <QtCore/QMimeData>
+#include <QStringList>
+
+#include "universalid.hpp"
+#include "columnbase.hpp"
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSMWorld
+{
+
+/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds.
+///
+/// This class provides way to construct mimedata object holding the universalid copy
+/// Universalid is used in the majority of the tables to store type, id, argument types.
+/// This way universalid grants a way to retrive record from the concrete table.
+/// Please note, that tablemimedata object can hold multiple universalIds in the vector.
+
+ class TableMimeData : public QMimeData
+ {
+ public:
+ TableMimeData(UniversalId id, const CSMDoc::Document& document);
+
+ TableMimeData(std::vector<UniversalId>& id, const CSMDoc::Document& document);
+
+ ~TableMimeData();
+
+ virtual QStringList formats() const;
+
+ std::string getIcon() const;
+
+ std::vector<UniversalId> getData() const;
+
+ bool holdsType(UniversalId::Type type) const;
+
+ bool holdsType(CSMWorld::ColumnBase::Display type) const;
+
+ bool fromDocument(const CSMDoc::Document& document) const;
+
+ UniversalId returnMatching(UniversalId::Type type) const;
+
+ UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const;
+
+ static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type);
+ static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type);
+
+ private:
+ std::vector<UniversalId> mUniversalId;
+ QStringList mObjectsFormats;
+ const CSMDoc::Document& mDocument;
+
+ };
+}
+#endif // TABLEMIMEDATA_H \ No newline at end of file
diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp
index e633f4f696..8301ebfd36 100644
--- a/apps/opencs/model/world/universalid.cpp
+++ b/apps/opencs/model/world/universalid.cpp
@@ -4,6 +4,7 @@
#include <ostream>
#include <stdexcept>
#include <sstream>
+#include <iostream>
namespace
{
@@ -186,7 +187,6 @@ CSMWorld::UniversalId::UniversalId (Type type, const std::string& id)
mClass = sIdArg[i].mClass;
return;
}
-
throw std::logic_error ("invalid ID argument UniversalId type");
}
diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp
index 708d450325..cc1578bdd9 100644
--- a/apps/opencs/view/filter/editwidget.cpp
+++ b/apps/opencs/view/filter/editwidget.cpp
@@ -2,6 +2,8 @@
#include "editwidget.hpp"
#include <QAbstractItemModel>
+#include <QString>
+#include <QApplication>
#include "../../model/world/data.hpp"
@@ -56,3 +58,146 @@ void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int s
{
textChanged (text());
}
+
+void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource,
+ Qt::DropAction action)
+{
+ const unsigned count = filterSource.size();
+ bool multipleElements = false;
+
+ switch (count) //setting multipleElements;
+ {
+ case 0: //empty
+ return; //nothing to do here
+
+ case 1: //only single
+ multipleElements = false;
+ break;
+
+ default:
+ multipleElements = true;
+ break;
+ }
+
+ Qt::KeyboardModifiers key = QApplication::keyboardModifiers();
+ QString oldContent (text());
+
+ bool replaceMode = false;
+ std::string orAnd;
+
+ switch (key) //setting replaceMode and string used to glue expressions
+ {
+ case Qt::ShiftModifier:
+ orAnd = "!or(";
+ replaceMode = false;
+ break;
+
+ case Qt::ControlModifier:
+ orAnd = "!and(";
+ replaceMode = false;
+ break;
+
+ default:
+ replaceMode = true;
+ break;
+ }
+
+ if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode
+ {
+ replaceMode = true;
+ }
+
+ if (!replaceMode)
+ {
+ oldContent.remove ('!');
+ }
+
+ std::stringstream ss;
+
+ if (multipleElements)
+ {
+ if (replaceMode)
+ {
+ ss<<"!or(";
+ } else {
+ ss << orAnd << oldContent.toStdString() << ',';
+ }
+
+ for (unsigned i = 0; i < count; ++i)
+ {
+ ss<<generateFilter (filterSource[i]);
+
+ if (i+1 != count)
+ {
+ ss<<", ";
+ }
+ }
+
+ ss<<')';
+ } else {
+ if (!replaceMode)
+ {
+ ss << orAnd << oldContent.toStdString() <<',';
+ } else {
+ ss<<'!';
+ }
+
+ ss << generateFilter (filterSource[0]);
+
+ if (!replaceMode)
+ {
+ ss<<')';
+ }
+
+ }
+
+ if (ss.str().length() >4)
+ {
+ clear();
+ insert (QString::fromUtf8(ss.str().c_str()));
+ }
+}
+
+std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const
+{
+ const unsigned columns = seekedString.second.size();
+
+ bool multipleColumns = false;
+ switch (columns)
+ {
+ case 0: //empty
+ return ""; //no column to filter
+
+ case 1: //one column to look for
+ multipleColumns = false;
+ break;
+
+ default:
+ multipleColumns = true;
+ break;
+ }
+
+ std::stringstream ss;
+ if (multipleColumns)
+ {
+ ss<<"or(";
+ for (unsigned i = 0; i < columns; ++i)
+ {
+ ss<<"string("<<'"'<<seekedString.second[i]<<'"'<<','<<'"'<<seekedString.first<<'"'<<')';
+ if (i+1 != columns)
+ ss<<',';
+ }
+ ss<<')';
+ } else {
+ ss<<"string"<<'('<<'"'<<seekedString.second[0]<<"\","<<'"'<<seekedString.first<<"\")";
+ }
+
+ return ss.str();
+}
+
+void CSVFilter::EditWidget::useFilterRequest (const std::string& idOfFilter)
+{
+ clear();
+ insert(QString::fromUtf8(idOfFilter.c_str()));
+}
+
diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp
index 31904e624f..e7e34b8e9a 100644
--- a/apps/opencs/view/filter/editwidget.hpp
+++ b/apps/opencs/view/filter/editwidget.hpp
@@ -5,6 +5,7 @@
#include <QLineEdit>
#include <QPalette>
+#include <QtCore/qnamespace.h>
#include "../../model/filter/parser.hpp"
#include "../../model/filter/node.hpp"
@@ -33,6 +34,9 @@ namespace CSVFilter
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ private:
+ std::string generateFilter(std::pair<std::string, std::vector<std::string> >& seekedString) const;
+
private slots:
void textChanged (const QString& text);
@@ -42,6 +46,11 @@ namespace CSVFilter
void filterRowsRemoved (const QModelIndex& parent, int start, int end);
void filterRowsInserted (const QModelIndex& parent, int start, int end);
+
+ void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
+ Qt::DropAction action);
+
+ void useFilterRequest(const std::string& idOfFilter);
};
}
diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp
index 2731708841..a332880252 100644
--- a/apps/opencs/view/filter/filterbox.cpp
+++ b/apps/opencs/view/filter/filterbox.cpp
@@ -2,9 +2,12 @@
#include "filterbox.hpp"
#include <QHBoxLayout>
+#include <QDragEnterEvent>
#include "recordfilterbox.hpp"
+#include <apps/opencs/model/world/tablemimedata.hpp>
+
CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent)
: QWidget (parent)
{
@@ -21,4 +24,27 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent)
connect (recordFilterBox,
SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
-} \ No newline at end of file
+
+ connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
+ recordFilterBox, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
+
+ connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&)));
+ setAcceptDrops(true);
+}
+
+void CSVFilter::FilterBox::dropEvent (QDropEvent* event)
+{
+ std::vector<CSMWorld::UniversalId> data = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData())->getData();
+
+ emit recordDropped(data, event->proposedAction());
+}
+
+void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event)
+{
+ event->acceptProposedAction();
+}
+
+void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event)
+{
+ event->accept();
+}
diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp
index 2524fa0a38..5954035fcb 100644
--- a/apps/opencs/view/filter/filterbox.hpp
+++ b/apps/opencs/view/filter/filterbox.hpp
@@ -1,9 +1,13 @@
#ifndef CSV_FILTER_FILTERBOX_H
#define CSV_FILTER_FILTERBOX_H
+#include <vector>
+
#include <QWidget>
+#include <QtCore/qnamespace.h>
#include "../../model/filter/node.hpp"
+#include "../../model/world/universalid.hpp"
namespace CSMWorld
{
@@ -16,6 +20,12 @@ namespace CSVFilter
{
Q_OBJECT
+ void dragEnterEvent (QDragEnterEvent* event);
+
+ void dropEvent (QDropEvent* event);
+
+ void dragMoveEvent(QDragMoveEvent *event);
+
public:
FilterBox (CSMWorld::Data& data, QWidget *parent = 0);
@@ -23,8 +33,13 @@ namespace CSVFilter
signals:
void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ void recordDropped (std::vector<CSMWorld::UniversalId>& types, Qt::DropAction action);
+ void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
+ Qt::DropAction action);
+ void useFilterRequest(const std::string& idOfFilter);
};
}
#endif
+
diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp
index c405177b01..2a1a1407fa 100644
--- a/apps/opencs/view/filter/recordfilterbox.cpp
+++ b/apps/opencs/view/filter/recordfilterbox.cpp
@@ -24,4 +24,9 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare
connect (
editWidget, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)));
+
+ connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
+ editWidget, SLOT(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
+
+ connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&)));
}
diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp
index 057d69518d..fa5c9c3c28 100644
--- a/apps/opencs/view/filter/recordfilterbox.hpp
+++ b/apps/opencs/view/filter/recordfilterbox.hpp
@@ -4,6 +4,7 @@
#include <boost/shared_ptr.hpp>
#include <QWidget>
+#include <QtCore/qnamespace.h>
#include <QHBoxLayout>
@@ -27,6 +28,9 @@ namespace CSVFilter
signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
+ void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
+ Qt::DropAction action);
+ void useFilterRequest(const std::string& idOfFilter);
};
}
diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp
index 8cbfc72c60..6e327bb6e0 100644
--- a/apps/opencs/view/render/scenewidget.cpp
+++ b/apps/opencs/view/render/scenewidget.cpp
@@ -6,6 +6,7 @@
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
#include <OgreEntity.h>
+#include <OgreCamera.h>
namespace CSVRender
{
diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp
index 74fb068700..cdeee56559 100644
--- a/apps/opencs/view/world/cellcreator.cpp
+++ b/apps/opencs/view/world/cellcreator.cpp
@@ -61,6 +61,7 @@ void CSVWorld::CellCreator::reset()
mX->setValue (0);
mY->setValue (0);
mType->setCurrentIndex (0);
+ setType(0);
GenericCreator::reset();
}
@@ -78,4 +79,25 @@ void CSVWorld::CellCreator::setType (int index)
void CSVWorld::CellCreator::valueChanged (int index)
{
update();
-} \ No newline at end of file
+}
+
+void CSVWorld::CellCreator::cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type)
+{
+ CSVWorld::GenericCreator::cloneMode(originId, type);
+ if (*(originId.begin()) == '#') //if originid points to the exterior cell
+ {
+ setType(1); //enable x and y controls
+ mType->setCurrentIndex(1);
+ } else {
+ setType(0);
+ mType->setCurrentIndex(0);
+ }
+}
+
+
+void CSVWorld::CellCreator::toggleWidgets(bool active)
+{
+ CSVWorld::GenericCreator::toggleWidgets(active);
+ mType->setEnabled(active);
+}
diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp
index a5473e2c97..db9fbf8a34 100644
--- a/apps/opencs/view/world/cellcreator.hpp
+++ b/apps/opencs/view/world/cellcreator.hpp
@@ -29,6 +29,11 @@ namespace CSVWorld
virtual void reset();
+ virtual void toggleWidgets(bool active = true);
+
+ virtual void cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type);
+
private slots:
void setType (int index);
diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp
index df9b116eed..88da70330d 100644
--- a/apps/opencs/view/world/creator.hpp
+++ b/apps/opencs/view/world/creator.hpp
@@ -2,6 +2,7 @@
#define CSV_WORLD_CREATOR_H
#include <QWidget>
+#include "../../model/world/universalid.hpp"
class QUndoStack;
@@ -24,8 +25,13 @@ namespace CSVWorld
virtual void reset() = 0;
+ virtual void cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type) = 0;
+
virtual void setEditLock (bool locked) = 0;
+ virtual void toggleWidgets(bool active = true) = 0;
+
signals:
void done();
diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp
index ba2b3665a9..31c216e2cb 100644
--- a/apps/opencs/view/world/genericcreator.cpp
+++ b/apps/opencs/view/world/genericcreator.cpp
@@ -57,8 +57,15 @@ const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const
}
CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack,
- const CSMWorld::UniversalId& id, bool relaxedIdRules)
-: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false)
+ const CSMWorld::UniversalId& id, bool relaxedIdRules):
+
+ mData (data),
+ mUndoStack (undoStack),
+ mListId (id),
+ mLocked (false),
+ mCloneMode(false),
+ mClonedType(CSMWorld::UniversalId::Type_None)
+
{
mLayout = new QHBoxLayout;
mLayout->setContentsMargins (0, 0, 0, 0);
@@ -89,6 +96,7 @@ void CSVWorld::GenericCreator::setEditLock (bool locked)
void CSVWorld::GenericCreator::reset()
{
+ mCloneMode = false;
mId->setText ("");
update();
}
@@ -120,16 +128,40 @@ void CSVWorld::GenericCreator::create()
{
if (!mLocked)
{
- std::string id = getId();
+ if (mCloneMode)
+ {
+ std::string id = getId();
+ std::auto_ptr<CSMWorld::CloneCommand> command (new CSMWorld::CloneCommand (
+ dynamic_cast<CSMWorld::IdTable&> (*mData.getTableModel(mListId)), mClonedId, id, mClonedType));
+
+ mUndoStack.push(command.release());
- std::auto_ptr<CSMWorld::CreateCommand> command (new CSMWorld::CreateCommand (
+ emit done();
+ emit requestFocus(id);
+ } else {
+ std::string id = getId();
+
+ std::auto_ptr<CSMWorld::CreateCommand> command (new CSMWorld::CreateCommand (
dynamic_cast<CSMWorld::IdTable&> (*mData.getTableModel (mListId)), id));
- configureCreateCommand (*command);
+ configureCreateCommand (*command);
- mUndoStack.push (command.release());
+ mUndoStack.push (command.release());
- emit done();
- emit requestFocus (id);
+ emit done();
+ emit requestFocus (id);
+ }
}
-} \ No newline at end of file
+}
+
+void CSVWorld::GenericCreator::cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type)
+{
+ mCloneMode = true;
+ mClonedId = originId;
+ mClonedType = type;
+}
+
+void CSVWorld::GenericCreator::toggleWidgets(bool active)
+{
+}
diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp
index 8dd2ca911c..714853f986 100644
--- a/apps/opencs/view/world/genericcreator.hpp
+++ b/apps/opencs/view/world/genericcreator.hpp
@@ -1,6 +1,7 @@
#ifndef CSV_WORLD_GENERICCREATOR_H
#define CSV_WORLD_GENERICCREATOR_H
+class QString;
class QPushButton;
class QLineEdit;
class QHBoxLayout;
@@ -28,6 +29,11 @@ namespace CSVWorld
std::string mErrors;
QHBoxLayout *mLayout;
bool mLocked;
+ std::string mClonedId;
+ CSMWorld::UniversalId::Type mClonedType;
+
+ protected:
+ bool mCloneMode;
protected:
@@ -57,11 +63,15 @@ namespace CSVWorld
virtual void reset();
+ virtual void toggleWidgets (bool active = true);
+
+ virtual void cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type);
+
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);
diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp
index 718fe9ca7c..7a5fca8538 100644
--- a/apps/opencs/view/world/referenceablecreator.cpp
+++ b/apps/opencs/view/world/referenceablecreator.cpp
@@ -40,4 +40,10 @@ void CSVWorld::ReferenceableCreator::reset()
{
mType->setCurrentIndex (0);
GenericCreator::reset();
-} \ No newline at end of file
+}
+
+void CSVWorld::ReferenceableCreator::toggleWidgets(bool active)
+{
+ CSVWorld::GenericCreator::toggleWidgets(active);
+ mType->setEnabled(active);
+}
diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp
index 06e0e582be..88545575e3 100644
--- a/apps/opencs/view/world/referenceablecreator.hpp
+++ b/apps/opencs/view/world/referenceablecreator.hpp
@@ -23,6 +23,7 @@ namespace CSVWorld
const CSMWorld::UniversalId& id);
virtual void reset();
+ virtual void toggleWidgets(bool active = true);
};
}
diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp
index aef25a81d0..6b8e02da0d 100644
--- a/apps/opencs/view/world/referencecreator.cpp
+++ b/apps/opencs/view/world/referencecreator.cpp
@@ -40,15 +40,20 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack&
void CSVWorld::ReferenceCreator::reset()
{
+ GenericCreator::reset();
mCell->setText ("");
mId = getData().getReferences().getNewId();
- GenericCreator::reset();
}
std::string CSVWorld::ReferenceCreator::getErrors() const
{
std::string errors = GenericCreator::getErrors();
+ if (mCloneMode)
+ {
+ return errors;
+ }
+
std::string cell = mCell->text().toUtf8().constData();
if (cell.empty())
@@ -72,4 +77,17 @@ std::string CSVWorld::ReferenceCreator::getErrors() const
void CSVWorld::ReferenceCreator::cellChanged()
{
update();
-} \ No newline at end of file
+}
+
+void CSVWorld::ReferenceCreator::toggleWidgets(bool active)
+{
+ CSVWorld::GenericCreator::toggleWidgets(active);
+ mCell->setEnabled(active);
+}
+
+void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type)
+{
+ CSVWorld::GenericCreator::cloneMode(originId, type);
+ cellChanged(); //otherwise ok button will remain disabled
+}
diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp
index 27f81564fd..12fb12dd90 100644
--- a/apps/opencs/view/world/referencecreator.hpp
+++ b/apps/opencs/view/world/referencecreator.hpp
@@ -25,7 +25,11 @@ namespace CSVWorld
ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack,
const CSMWorld::UniversalId& id);
+ virtual void cloneMode(const std::string& originId,
+ const CSMWorld::UniversalId::Type type);
+
virtual void reset();
+ virtual void toggleWidgets(bool active = true);
virtual std::string getErrors() const;
///< Return formatted error descriptions for the current state of the creator. if an empty
diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp
new file mode 100644
index 0000000000..fccac75b41
--- /dev/null
+++ b/apps/opencs/view/world/scriptedit.cpp
@@ -0,0 +1,88 @@
+#include "scriptedit.hpp"
+
+#include <algorithm>
+
+#include <QDragEnterEvent>
+#include <QRegExp>
+#include <QString>
+
+#include "../../model/world/universalid.hpp"
+#include "../../model/world/tablemimedata.hpp"
+
+CSVWorld::ScriptEdit::ScriptEdit (QWidget* parent, const CSMDoc::Document& document) :
+ QTextEdit (parent),
+ mDocument (document),
+ mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive)
+{
+ mAllowedTypes <<CSMWorld::UniversalId::Type_Journal
+ <<CSMWorld::UniversalId::Type_Global
+ <<CSMWorld::UniversalId::Type_Topic
+ <<CSMWorld::UniversalId::Type_Sound
+ <<CSMWorld::UniversalId::Type_Spell
+ <<CSMWorld::UniversalId::Type_Cell
+ <<CSMWorld::UniversalId::Type_Referenceable
+ <<CSMWorld::UniversalId::Type_Activator
+ <<CSMWorld::UniversalId::Type_Potion
+ <<CSMWorld::UniversalId::Type_Apparatus
+ <<CSMWorld::UniversalId::Type_Armor
+ <<CSMWorld::UniversalId::Type_Book
+ <<CSMWorld::UniversalId::Type_Clothing
+ <<CSMWorld::UniversalId::Type_Container
+ <<CSMWorld::UniversalId::Type_Creature
+ <<CSMWorld::UniversalId::Type_Door
+ <<CSMWorld::UniversalId::Type_Ingredient
+ <<CSMWorld::UniversalId::Type_CreatureLevelledList
+ <<CSMWorld::UniversalId::Type_ItemLevelledList
+ <<CSMWorld::UniversalId::Type_Light
+ <<CSMWorld::UniversalId::Type_Lockpick
+ <<CSMWorld::UniversalId::Type_Miscellaneous
+ <<CSMWorld::UniversalId::Type_Npc
+ <<CSMWorld::UniversalId::Type_Probe
+ <<CSMWorld::UniversalId::Type_Repair
+ <<CSMWorld::UniversalId::Type_Static
+ <<CSMWorld::UniversalId::Type_Weapon;
+}
+
+void CSVWorld::ScriptEdit::dragEnterEvent (QDragEnterEvent* event)
+{
+ setTextCursor (cursorForPosition (event->pos()));
+ event->acceptProposedAction();
+}
+
+void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event)
+{
+ setTextCursor (cursorForPosition (event->pos()));
+ event->accept();
+}
+
+void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event)
+{
+ const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData());
+
+ setTextCursor (cursorForPosition (event->pos()));
+
+ if (mime->fromDocument (mDocument))
+ {
+ std::vector<CSMWorld::UniversalId> records (mime->getData());
+
+ for (std::vector<CSMWorld::UniversalId>::iterator it = records.begin(); it != records.end(); ++it)
+ {
+ if (mAllowedTypes.contains (it->getType()))
+ {
+ if (stringNeedsQuote(it->getId()))
+ {
+ insertPlainText(QString::fromStdString ('"' + it->getId() + '"'));
+ } else {
+ insertPlainText(QString::fromStdString (it->getId()));
+ }
+ }
+ }
+ }
+}
+
+bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const
+{
+ const QString string(QString::fromStdString(id)); //<regex> is only for c++11, so let's use qregexp for now.
+ //I'm not quite sure when do we need to put quotes. To be safe we will use quotes for anything other than…
+ return !(string.contains(mWhiteListQoutes));
+}
diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp
new file mode 100644
index 0000000000..b4627c2fee
--- /dev/null
+++ b/apps/opencs/view/world/scriptedit.hpp
@@ -0,0 +1,39 @@
+#ifndef SCRIPTEDIT_H
+#define SCRIPTEDIT_H
+
+#include <qtextedit.h>
+#include <QVector>
+
+#include "../../model/world/universalid.hpp"
+
+class QWidget;
+class QRegExp;
+
+namespace CSMDoc
+{
+ class Document;
+}
+
+namespace CSVWorld
+{
+ class ScriptEdit : public QTextEdit
+ {
+ Q_OBJECT
+ public:
+ ScriptEdit (QWidget* parent, const CSMDoc::Document& document);
+
+ private:
+ QVector<CSMWorld::UniversalId::Type> mAllowedTypes;
+ const CSMDoc::Document& mDocument;
+ const QRegExp mWhiteListQoutes;
+
+ void dragEnterEvent (QDragEnterEvent* event);
+
+ void dropEvent (QDropEvent* event);
+
+ void dragMoveEvent (QDragMoveEvent* event);
+
+ bool stringNeedsQuote(const std::string& id) const;
+ };
+}
+#endif // SCRIPTEDIT_H \ No newline at end of file
diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp
index 446c34e5f0..fa41151ca3 100644
--- a/apps/opencs/view/world/scriptsubview.cpp
+++ b/apps/opencs/view/world/scriptsubview.cpp
@@ -13,6 +13,7 @@
#include "../../model/world/idtable.hpp"
#include "scripthighlighter.hpp"
+#include "scriptedit.hpp"
CSVWorld::ScriptSubView::ChangeLock::ChangeLock (ScriptSubView& view) : mView (view)
{
@@ -27,7 +28,7 @@ CSVWorld::ScriptSubView::ChangeLock::~ChangeLock()
CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document)
: SubView (id), mDocument (document), mColumn (-1), mChangeLocked (0)
{
- setWidget (mEditor = new QTextEdit (this));
+ setWidget (mEditor = new ScriptEdit (this, mDocument));
mEditor->setAcceptRichText (false);
mEditor->setLineWrapMode (QTextEdit::NoWrap);
diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp
index 71bdb9000e..edf3bc6dea 100644
--- a/apps/opencs/view/world/table.cpp
+++ b/apps/opencs/view/world/table.cpp
@@ -4,8 +4,11 @@
#include <QHeaderView>
#include <QAction>
+#include <QApplication>
#include <QMenu>
#include <QContextMenuEvent>
+#include <QString>
+#include <QtCore/qnamespace.h>
#include "../../model/world/data.hpp"
#include "../../model/world/commands.hpp"
@@ -13,6 +16,8 @@
#include "../../model/world/idtable.hpp"
#include "../../model/world/record.hpp"
#include "../../model/world/columns.hpp"
+#include "../../model/world/tablemimedata.hpp"
+#include "../../model/world/tablemimedata.hpp"
#include "recordstatusdelegate.hpp"
#include "util.hpp"
@@ -28,7 +33,11 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
if (!mEditLock)
{
if (selectedRows.size()==1)
+ {
menu.addAction (mEditAction);
+ if (mCreateAction)
+ menu.addAction(mCloneAction);
+ }
if (mCreateAction)
menu.addAction (mCreateAction);
@@ -84,7 +93,7 @@ std::vector<std::string> CSVWorld::Table::listRevertableSelectedIds() const
QModelIndexList selectedRows = selectionModel()->selectedRows();
for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end();
- ++iter)
+ ++iter)
{
QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0));
@@ -154,8 +163,8 @@ std::vector<std::string> CSVWorld::Table::listDeletableSelectedIds() const
}
CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack,
- bool createAndDelete, bool sorting)
- : mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0)
+ bool createAndDelete, bool sorting, const CSMDoc::Document& document)
+ : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document)
{
mModel = &dynamic_cast<CSMWorld::IdTable&> (*data.getTableModel (id));
@@ -199,6 +208,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q
mCreateAction = new QAction (tr ("Add Record"), this);
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
addAction (mCreateAction);
+
+ mCloneAction = new QAction (tr ("Clone Record"), this);
+ connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
+ addAction(mCloneAction);
}
mRevertAction = new QAction (tr ("Revert Record"), this);
@@ -227,6 +240,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q
connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)),
this, SLOT (selectionSizeUpdate ()));
+
+ setAcceptDrops(true);
}
void CSVWorld::Table::setEditLock (bool locked)
@@ -295,6 +310,19 @@ void CSVWorld::Table::editRecord()
}
}
+void CSVWorld::Table::cloneRecord()
+{
+ if (!mEditLock)
+ {
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+ const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row());
+ if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted())
+ {
+ emit cloneRequest (toClone);
+ }
+ }
+}
+
void CSVWorld::Table::moveUpRecord()
{
QModelIndexList selectedRows = selectionModel()->selectedRows();
@@ -411,3 +439,108 @@ void CSVWorld::Table::recordFilterChanged (boost::shared_ptr<CSMFilter::Node> fi
{
mProxyModel->setFilter (filter);
}
+
+void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event)
+{
+ if (event->buttons() & Qt::LeftButton)
+ {
+ QModelIndexList selectedRows = selectionModel()->selectedRows();
+
+ if (selectedRows.size() == 0)
+ {
+ return;
+ }
+
+ QDrag* drag = new QDrag (this);
+ CSMWorld::TableMimeData* mime = NULL;
+
+ if (selectedRows.size() == 1)
+ {
+ mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument);
+ }
+ else
+ {
+ std::vector<CSMWorld::UniversalId> idToDrag;
+
+ foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW.
+ {
+ idToDrag.push_back (getUniversalId (it.row()));
+ }
+
+ mime = new CSMWorld::TableMimeData (idToDrag, mDocument);
+ }
+
+ drag->setMimeData (mime);
+ drag->setPixmap (QString::fromStdString (mime->getIcon()));
+
+ Qt::DropActions action = Qt::IgnoreAction;
+ switch (QApplication::keyboardModifiers())
+ {
+ case Qt::ControlModifier:
+ action = Qt::CopyAction;
+ break;
+
+ case Qt::ShiftModifier:
+ action = Qt::MoveAction;
+ break;
+ }
+
+ drag->exec(action);
+ }
+
+}
+
+void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event)
+{
+ event->acceptProposedAction();
+}
+
+void CSVWorld::Table::dropEvent(QDropEvent *event)
+{
+ QModelIndex index = indexAt (event->pos());
+
+ if (!index.isValid())
+ {
+ return;
+ }
+
+ const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData());
+ if (mime->fromDocument (mDocument))
+ {
+ CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display>
+ (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
+
+ if (mime->holdsType (display))
+ {
+ CSMWorld::UniversalId record (mime->returnMatching (display));
+
+ std::auto_ptr<CSMWorld::ModifyCommand> command (new CSMWorld::ModifyCommand
+ (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str()))));
+
+ mUndoStack.push (command.release());
+ }
+ } //TODO handle drops from different document
+}
+
+void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event)
+{
+ event->accept();
+}
+
+std::vector<std::string> CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const
+{
+ const int count = mModel->columnCount();
+
+ std::vector<std::string> titles;
+ for (int i = 0; i < count; ++i)
+ {
+ CSMWorld::ColumnBase::Display columndisplay = static_cast<CSMWorld::ColumnBase::Display>
+ (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
+
+ if (display == columndisplay)
+ {
+ titles.push_back(mModel->headerData (i, Qt::Horizontal).toString().toStdString());
+ }
+ }
+ return titles;
+} \ No newline at end of file
diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp
index 889e2847ac..615a31b4d7 100644
--- a/apps/opencs/view/world/table.hpp
+++ b/apps/opencs/view/world/table.hpp
@@ -5,8 +5,14 @@
#include <string>
#include <QTableView>
+#include <QtGui/qevent.h>
#include "../../model/filter/node.hpp"
+#include "../../model/world/columnbase.hpp"
+
+namespace CSMDoc {
+ class Document;
+}
class QUndoStack;
class QAction;
@@ -32,6 +38,7 @@ namespace CSVWorld
QUndoStack& mUndoStack;
QAction *mEditAction;
QAction *mCreateAction;
+ QAction *mCloneAction;
QAction *mRevertAction;
QAction *mDeleteAction;
QAction *mMoveUpAction;
@@ -41,6 +48,10 @@ namespace CSVWorld
bool mEditLock;
int mRecordStatusDisplay;
+ /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you
+ /// should NOT use it for anything else.
+ const CSMDoc::Document& mDocument;
+
private:
void contextMenuEvent (QContextMenuEvent *event);
@@ -49,9 +60,19 @@ namespace CSVWorld
std::vector<std::string> listDeletableSelectedIds() const;
+ void mouseMoveEvent(QMouseEvent *event);
+
+ void dragEnterEvent(QDragEnterEvent *event);
+
+ void dragMoveEvent(QDragMoveEvent *event);
+
+ void dropEvent(QDropEvent *event);
+
public:
- Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting);
+ Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete,
+ bool sorting, const CSMDoc::Document& document);
+
///< \param createAndDelete Allow creation and deletion of records.
/// \param sorting Allow changing order of rows in the view via column headers.
@@ -61,6 +82,8 @@ namespace CSVWorld
void updateEditorSetting (const QString &settingName, const QString &settingValue);
+ std::vector<std::string> getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const;
+
signals:
void editRequest (int row);
@@ -73,6 +96,7 @@ namespace CSVWorld
/// \param modified Number of added and modified records
void createRequest();
+ void cloneRequest(const CSMWorld::UniversalId&);
private slots:
@@ -82,6 +106,8 @@ namespace CSVWorld
void editRecord();
+ void cloneRecord();
+
void moveUpRecord();
void moveDownRecord();
diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp
index 3edf9af317..239c7410fb 100644
--- a/apps/opencs/view/world/tablebottombox.cpp
+++ b/apps/opencs/view/world/tablebottombox.cpp
@@ -153,7 +153,19 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi
void CSVWorld::TableBottomBox::createRequest()
{
mCreator->reset();
+ mCreator->toggleWidgets(true);
mLayout->setCurrentWidget (mCreator);
setVisible (true);
mCreating = true;
-} \ No newline at end of file
+}
+
+void CSVWorld::TableBottomBox::cloneRequest(const std::string& id,
+ const CSMWorld::UniversalId::Type type)
+{
+ mCreator->reset();
+ mCreator->cloneMode(id, type);
+ mLayout->setCurrentWidget(mCreator);
+ mCreator->toggleWidgets(false);
+ setVisible (true);
+ mCreating = true;
+}
diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp
index a5ae5e0bd9..8f0d851632 100644
--- a/apps/opencs/view/world/tablebottombox.hpp
+++ b/apps/opencs/view/world/tablebottombox.hpp
@@ -2,6 +2,7 @@
#define CSV_WORLD_BOTTOMBOX_H
#include <QWidget>
+#include <apps/opencs/model/world/universalid.hpp>
class QLabel;
class QStackedLayout;
@@ -76,6 +77,8 @@ namespace CSVWorld
/// \param modified Number of added and modified records
void createRequest();
+ void cloneRequest(const std::string& id,
+ const CSMWorld::UniversalId::Type type);
};
}
diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp
index 55ded09de2..e330d47758 100644
--- a/apps/opencs/view/world/tablesubview.cpp
+++ b/apps/opencs/view/world/tablesubview.cpp
@@ -2,11 +2,12 @@
#include "tablesubview.hpp"
#include <QVBoxLayout>
+#include <QEvent>
#include "../../model/doc/document.hpp"
+#include "../../model/world/tablemimedata.hpp"
#include "../filter/filterbox.hpp"
-
#include "table.hpp"
#include "tablebottombox.hpp"
#include "creator.hpp"
@@ -23,7 +24,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0);
layout->insertWidget (0, mTable =
- new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting), 2);
+ new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2);
CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this);
@@ -44,16 +45,34 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
mTable->tableSizeUpdate();
mTable->selectionSizeUpdate();
+ mTable->viewport()->installEventFilter(this);
+ mBottom->installEventFilter(this);
+ filterBox->installEventFilter(this);
if (mBottom->canCreateAndDelete())
+ {
connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest()));
+ connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this,
+ SLOT(cloneRequest(const CSMWorld::UniversalId&)));
+
+ connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)),
+ mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)));
+ }
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>)));
+
+ connect(filterBox, SIGNAL(recordDropped(std::vector<CSMWorld::UniversalId>&, Qt::DropAction)),
+ this, SLOT(createFilterRequest(std::vector<CSMWorld::UniversalId>&, Qt::DropAction)));
+
+ connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&)));
+
+ connect(this, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)),
+ filterBox, SIGNAL(createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >&, Qt::DropAction)));
}
void CSVWorld::TableSubView::setEditLock (bool locked)
@@ -75,4 +94,39 @@ void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, con
void CSVWorld::TableSubView::setStatusBar (bool show)
{
mBottom->setStatusBar (show);
+}
+
+void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone)
+{
+ emit cloneRequest(toClone.getId(), toClone.getType());
+}
+
+void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action)
+{
+ std::vector<std::pair<std::string, std::vector<std::string> > > filterSource;
+
+ for (std::vector<CSMWorld::UniversalId>::iterator it = types.begin(); it != types.end(); ++it)
+ {
+ std::pair<std::string, std::vector<std::string> > pair( //splited long line
+ std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType()))));
+
+ filterSource.push_back(pair);
+ }
+ emit createFilterRequest(filterSource, action);
+}
+
+bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event)
+{
+ if (event->type() == QEvent::Drop)
+ {
+ QDropEvent* drop = dynamic_cast<QDropEvent*>(event);
+ const CSMWorld::TableMimeData* data = dynamic_cast<const CSMWorld::TableMimeData*>(drop->mimeData());
+ bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter);
+ if (handled)
+ {
+ emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId());
+ }
+ return handled;
+ }
+ return false;
} \ No newline at end of file
diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp
index 56a441a4d5..b3c253919b 100644
--- a/apps/opencs/view/world/tablesubview.hpp
+++ b/apps/opencs/view/world/tablesubview.hpp
@@ -3,8 +3,15 @@
#include "../doc/subview.hpp"
+#include <QtCore/qnamespace.h>
+
class QModelIndex;
+namespace CSMWorld
+{
+ class IdTable;
+}
+
namespace CSMDoc
{
class Document;
@@ -34,9 +41,22 @@ namespace CSVWorld
virtual void setStatusBar (bool show);
+ protected:
+ bool eventFilter(QObject* object, QEvent *event);
+
+ signals:
+ void cloneRequest(const std::string&,
+ const CSMWorld::UniversalId::Type);
+ void createFilterRequest(std::vector<std::pair<std::string, std::vector<std::string> > >& filterSource,
+ Qt::DropAction action);
+ void useFilterRequest(const std::string& idOfFilter);
+
private slots:
void editRequest (int row);
+ void cloneRequest (const CSMWorld::UniversalId& toClone);
+ void createFilterRequest(std::vector< CSMWorld::UniversalId >& types,
+ Qt::DropAction action);
};
}
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index 57773507fe..84d116848e 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -1,7 +1,3 @@
-
-# config file
-configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp")
-
# local files
set(GAME
main.cpp
@@ -12,15 +8,14 @@ if(NOT WIN32)
endif()
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
- characterpreview externalrendering globalmap videoplayer ripplesimulation refraction
- terrainstorage renderconst
+ characterpreview globalmap videoplayer ripplesimulation refraction
+ terrainstorage renderconst effectmanager
)
add_openmw_dir (mwinput
@@ -74,11 +69,16 @@ add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
+ disease pickpocket levelledlist combat steering
+ )
+
+add_openmw_dir (mwstate
+ statemanagerimp charactermanager character
)
add_openmw_dir (mwbase
environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager
- inputmanager windowmanager
+ inputmanager windowmanager statemanager
)
# Main executable
diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp
index 6663306663..65a0369193 100644
--- a/apps/openmw/crashcatcher.cpp
+++ b/apps/openmw/crashcatcher.cpp
@@ -6,6 +6,7 @@
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/ucontext.h>
+#include <sys/utsname.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
@@ -42,78 +43,78 @@ static char altstack[SIGSTKSZ];
static struct {
- int signum;
- pid_t pid;
- int has_siginfo;
- siginfo_t siginfo;
- char buf[1024];
+ int signum;
+ pid_t pid;
+ int has_siginfo;
+ siginfo_t siginfo;
+ char buf[1024];
} crash_info;
static const struct {
- const char *name;
- int signum;
+ const char *name;
+ int signum;
} signals[] = {
- { "Segmentation fault", SIGSEGV },
- { "Illegal instruction", SIGILL },
- { "FPU exception", SIGFPE },
- { "System BUS error", SIGBUS },
- { NULL, 0 }
+{ "Segmentation fault", SIGSEGV },
+{ "Illegal instruction", SIGILL },
+{ "FPU exception", SIGFPE },
+{ "System BUS error", SIGBUS },
+{ NULL, 0 }
};
static const struct {
- int code;
- const char *name;
+ int code;
+ const char *name;
} sigill_codes[] = {
-#ifndef __FreeBSD__
- { ILL_ILLOPC, "Illegal opcode" },
- { ILL_ILLOPN, "Illegal operand" },
- { ILL_ILLADR, "Illegal addressing mode" },
- { ILL_ILLTRP, "Illegal trap" },
- { ILL_PRVOPC, "Privileged opcode" },
- { ILL_PRVREG, "Privileged register" },
- { ILL_COPROC, "Coprocessor error" },
- { ILL_BADSTK, "Internal stack error" },
-#endif
- { 0, NULL }
+ #ifndef __FreeBSD__
+ { ILL_ILLOPC, "Illegal opcode" },
+ { ILL_ILLOPN, "Illegal operand" },
+ { ILL_ILLADR, "Illegal addressing mode" },
+ { ILL_ILLTRP, "Illegal trap" },
+ { ILL_PRVOPC, "Privileged opcode" },
+ { ILL_PRVREG, "Privileged register" },
+ { ILL_COPROC, "Coprocessor error" },
+ { ILL_BADSTK, "Internal stack error" },
+ #endif
+ { 0, NULL }
};
static const struct {
- int code;
- const char *name;
+ int code;
+ const char *name;
} sigfpe_codes[] = {
- { FPE_INTDIV, "Integer divide by zero" },
- { FPE_INTOVF, "Integer overflow" },
- { FPE_FLTDIV, "Floating point divide by zero" },
- { FPE_FLTOVF, "Floating point overflow" },
- { FPE_FLTUND, "Floating point underflow" },
- { FPE_FLTRES, "Floating point inexact result" },
- { FPE_FLTINV, "Floating point invalid operation" },
- { FPE_FLTSUB, "Subscript out of range" },
- { 0, NULL }
+ { FPE_INTDIV, "Integer divide by zero" },
+ { FPE_INTOVF, "Integer overflow" },
+ { FPE_FLTDIV, "Floating point divide by zero" },
+ { FPE_FLTOVF, "Floating point overflow" },
+ { FPE_FLTUND, "Floating point underflow" },
+ { FPE_FLTRES, "Floating point inexact result" },
+ { FPE_FLTINV, "Floating point invalid operation" },
+ { FPE_FLTSUB, "Subscript out of range" },
+ { 0, NULL }
};
static const struct {
- int code;
- const char *name;
+ int code;
+ const char *name;
} sigsegv_codes[] = {
-#ifndef __FreeBSD__
- { SEGV_MAPERR, "Address not mapped to object" },
- { SEGV_ACCERR, "Invalid permissions for mapped object" },
-#endif
- { 0, NULL }
+ #ifndef __FreeBSD__
+ { SEGV_MAPERR, "Address not mapped to object" },
+ { SEGV_ACCERR, "Invalid permissions for mapped object" },
+ #endif
+ { 0, NULL }
};
static const struct {
- int code;
- const char *name;
+ int code;
+ const char *name;
} sigbus_codes[] = {
-#ifndef __FreeBSD__
- { BUS_ADRALN, "Invalid address alignment" },
- { BUS_ADRERR, "Non-existent physical address" },
- { BUS_OBJERR, "Object specific hardware error" },
-#endif
- { 0, NULL }
+ #ifndef __FreeBSD__
+ { BUS_ADRALN, "Invalid address alignment" },
+ { BUS_ADRERR, "Non-existent physical address" },
+ { BUS_OBJERR, "Object specific hardware error" },
+ #endif
+ { 0, NULL }
};
static int (*cc_user_info)(char*, char*);
@@ -121,314 +122,318 @@ static int (*cc_user_info)(char*, char*);
static void gdb_info(pid_t pid)
{
- char respfile[64];
- char cmd_buf[128];
- FILE *f;
- int fd;
-
- /* Create a temp file to put gdb commands into */
- strcpy(respfile, "gdb-respfile-XXXXXX");
- if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL)
- {
- fprintf(f, "attach %d\n"
- "shell echo \"\"\n"
- "shell echo \"* Loaded Libraries\"\n"
- "info sharedlibrary\n"
- "shell echo \"\"\n"
- "shell echo \"* Threads\"\n"
- "info threads\n"
- "shell echo \"\"\n"
- "shell echo \"* FPU Status\"\n"
- "info float\n"
- "shell echo \"\"\n"
- "shell echo \"* Registers\"\n"
- "info registers\n"
- "shell echo \"\"\n"
- "shell echo \"* Backtrace\"\n"
- "thread apply all backtrace full\n"
- "detach\n"
- "quit\n", pid);
- fclose(f);
-
- /* Run gdb and print process info. */
- snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile);
- printf("Executing: %s\n", cmd_buf);
- fflush(stdout);
-
- system(cmd_buf);
- /* Clean up */
- remove(respfile);
- }
- else
- {
- /* Error creating temp file */
- if(fd >= 0)
- {
- close(fd);
- remove(respfile);
- }
- printf("!!! Could not create gdb command file\n");
- }
- fflush(stdout);
+ char respfile[64];
+ char cmd_buf[128];
+ FILE *f;
+ int fd;
+
+ /* Create a temp file to put gdb commands into */
+ strcpy(respfile, "gdb-respfile-XXXXXX");
+ if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL)
+ {
+ fprintf(f, "attach %d\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Loaded Libraries\"\n"
+ "info sharedlibrary\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Threads\"\n"
+ "info threads\n"
+ "shell echo \"\"\n"
+ "shell echo \"* FPU Status\"\n"
+ "info float\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Registers\"\n"
+ "info registers\n"
+ "shell echo \"\"\n"
+ "shell echo \"* Backtrace\"\n"
+ "thread apply all backtrace full\n"
+ "detach\n"
+ "quit\n", pid);
+ fclose(f);
+
+ /* Run gdb and print process info. */
+ snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile);
+ printf("Executing: %s\n", cmd_buf);
+ fflush(stdout);
+
+ system(cmd_buf);
+ /* Clean up */
+ remove(respfile);
+ }
+ else
+ {
+ /* Error creating temp file */
+ if(fd >= 0)
+ {
+ close(fd);
+ remove(respfile);
+ }
+ printf("!!! Could not create gdb command file\n");
+ }
+ fflush(stdout);
}
static void sys_info(void)
{
#ifdef __unix__
- system("echo \"System: `uname -a`\"");
- putchar('\n');
- fflush(stdout);
+ struct utsname info;
+ if(uname(&info))
+ printf("!!! Failed to get system information\n");
+ else
+ printf("System: %s %s %s %s %s\n",
+ info.sysname, info.nodename, info.release, info.version, info.machine);
+
+ fflush(stdout);
#endif
}
-
static size_t safe_write(int fd, const void *buf, size_t len)
{
- size_t ret = 0;
- while(ret < len)
- {
- ssize_t rem;
- if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1)
- {
- if(errno == EINTR)
- continue;
- break;
- }
- ret += rem;
- }
- return ret;
+ size_t ret = 0;
+ while(ret < len)
+ {
+ ssize_t rem;
+ if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1)
+ {
+ if(errno == EINTR)
+ continue;
+ break;
+ }
+ ret += rem;
+ }
+ return ret;
}
static void crash_catcher(int signum, siginfo_t *siginfo, void *context)
{
//ucontext_t *ucontext = (ucontext_t*)context;
- pid_t dbg_pid;
- int fd[2];
-
- /* Make sure the effective uid is the real uid */
- if(getuid() != geteuid())
- {
- raise(signum);
- return;
- }
-
- safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1);
- if(pipe(fd) == -1)
- {
- safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1);
- raise(signum);
- return;
- }
-
- crash_info.signum = signum;
- crash_info.pid = getpid();
- crash_info.has_siginfo = !!siginfo;
- if(siginfo)
- crash_info.siginfo = *siginfo;
- if(cc_user_info)
- cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf));
-
- /* Fork off to start a crash handler */
- switch((dbg_pid=fork()))
- {
- /* Error */
- case -1:
- safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1);
- raise(signum);
- return;
-
- case 0:
- dup2(fd[0], STDIN_FILENO);
- close(fd[0]);
- close(fd[1]);
-
- execl(argv0, argv0, crash_switch, NULL);
-
- safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1);
- _exit(1);
-
- default:
+ pid_t dbg_pid;
+ int fd[2];
+
+ /* Make sure the effective uid is the real uid */
+ if(getuid() != geteuid())
+ {
+ raise(signum);
+ return;
+ }
+
+ safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1);
+ if(pipe(fd) == -1)
+ {
+ safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1);
+ raise(signum);
+ return;
+ }
+
+ crash_info.signum = signum;
+ crash_info.pid = getpid();
+ crash_info.has_siginfo = !!siginfo;
+ if(siginfo)
+ crash_info.siginfo = *siginfo;
+ if(cc_user_info)
+ cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf));
+
+ /* Fork off to start a crash handler */
+ switch((dbg_pid=fork()))
+ {
+ /* Error */
+ case -1:
+ safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1);
+ raise(signum);
+ return;
+
+ case 0:
+ dup2(fd[0], STDIN_FILENO);
+ close(fd[0]);
+ close(fd[1]);
+
+ execl(argv0, argv0, crash_switch, NULL);
+
+ safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1);
+ _exit(1);
+
+ default:
#ifdef __linux__
- prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0);
+ prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0);
#endif
- safe_write(fd[1], &crash_info, sizeof(crash_info));
- close(fd[0]);
- close(fd[1]);
-
- /* Wait; we'll be killed when gdb is done */
- do {
- int status;
- if(waitpid(dbg_pid, &status, 0) == dbg_pid &&
- (WIFEXITED(status) || WIFSIGNALED(status)))
- {
- /* The debug process died before it could kill us */
- raise(signum);
- break;
- }
- } while(1);
- }
+ safe_write(fd[1], &crash_info, sizeof(crash_info));
+ close(fd[0]);
+ close(fd[1]);
+
+ /* Wait; we'll be killed when gdb is done */
+ do {
+ int status;
+ if(waitpid(dbg_pid, &status, 0) == dbg_pid &&
+ (WIFEXITED(status) || WIFSIGNALED(status)))
+ {
+ /* The debug process died before it could kill us */
+ raise(signum);
+ break;
+ }
+ } while(1);
+ }
}
static void crash_handler(const char *logfile)
{
- const char *sigdesc = "";
+ const char *sigdesc = "";
int i;
- if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1)
- {
- fprintf(stderr, "!!! Failed to retrieve info from crashed process\n");
- exit(1);
- }
-
- /* Get the signal description */
- for(i = 0;signals[i].name;++i)
- {
- if(signals[i].signum == crash_info.signum)
- {
- sigdesc = signals[i].name;
- break;
- }
- }
-
- if(crash_info.has_siginfo)
- {
- switch(crash_info.signum)
- {
- case SIGSEGV:
- for(i = 0;sigsegv_codes[i].name;++i)
- {
- if(sigsegv_codes[i].code == crash_info.siginfo.si_code)
- {
- sigdesc = sigsegv_codes[i].name;
- break;
- }
- }
- break;
-
- case SIGFPE:
- for(i = 0;sigfpe_codes[i].name;++i)
- {
- if(sigfpe_codes[i].code == crash_info.siginfo.si_code)
- {
- sigdesc = sigfpe_codes[i].name;
- break;
- }
- }
- break;
-
- case SIGILL:
- for(i = 0;sigill_codes[i].name;++i)
- {
- if(sigill_codes[i].code == crash_info.siginfo.si_code)
- {
- sigdesc = sigill_codes[i].name;
- break;
- }
- }
- break;
-
- case SIGBUS:
- for(i = 0;sigbus_codes[i].name;++i)
- {
- if(sigbus_codes[i].code == crash_info.siginfo.si_code)
- {
- sigdesc = sigbus_codes[i].name;
- break;
- }
- }
- break;
- }
- }
- fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum);
- if(crash_info.has_siginfo)
- fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr);
- fputc('\n', stderr);
-
- if(logfile)
- {
- /* Create crash log file and redirect shell output to it */
- if(freopen(logfile, "wa", stdout) != stdout)
- {
- fprintf(stderr, "!!! Could not create %s following signal\n", logfile);
- exit(1);
- }
- fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid);
-
- printf("*** Fatal Error ***\n"
- "%s (signal %i)\n", sigdesc, crash_info.signum);
- if(crash_info.has_siginfo)
- printf("Address: %p\n", crash_info.siginfo.si_addr);
- fputc('\n', stdout);
- fflush(stdout);
- }
-
- sys_info();
-
- crash_info.buf[sizeof(crash_info.buf)-1] = '\0';
- printf("%s\n", crash_info.buf);
- fflush(stdout);
-
- if(crash_info.pid > 0)
- {
- gdb_info(crash_info.pid);
- kill(crash_info.pid, SIGKILL);
- }
-
- if(logfile)
- {
+ if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1)
+ {
+ fprintf(stderr, "!!! Failed to retrieve info from crashed process\n");
+ exit(1);
+ }
+
+ /* Get the signal description */
+ for(i = 0;signals[i].name;++i)
+ {
+ if(signals[i].signum == crash_info.signum)
+ {
+ sigdesc = signals[i].name;
+ break;
+ }
+ }
+
+ if(crash_info.has_siginfo)
+ {
+ switch(crash_info.signum)
+ {
+ case SIGSEGV:
+ for(i = 0;sigsegv_codes[i].name;++i)
+ {
+ if(sigsegv_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigsegv_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGFPE:
+ for(i = 0;sigfpe_codes[i].name;++i)
+ {
+ if(sigfpe_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigfpe_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGILL:
+ for(i = 0;sigill_codes[i].name;++i)
+ {
+ if(sigill_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigill_codes[i].name;
+ break;
+ }
+ }
+ break;
+
+ case SIGBUS:
+ for(i = 0;sigbus_codes[i].name;++i)
+ {
+ if(sigbus_codes[i].code == crash_info.siginfo.si_code)
+ {
+ sigdesc = sigbus_codes[i].name;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum);
+ if(crash_info.has_siginfo)
+ fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr);
+ fputc('\n', stderr);
+
+ if(logfile)
+ {
+ /* Create crash log file and redirect shell output to it */
+ if(freopen(logfile, "wa", stdout) != stdout)
+ {
+ fprintf(stderr, "!!! Could not create %s following signal\n", logfile);
+ exit(1);
+ }
+ fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid);
+
+ printf("*** Fatal Error ***\n"
+ "%s (signal %i)\n", sigdesc, crash_info.signum);
+ if(crash_info.has_siginfo)
+ printf("Address: %p\n", crash_info.siginfo.si_addr);
+ fputc('\n', stdout);
+ fflush(stdout);
+ }
+
+ sys_info();
+
+ crash_info.buf[sizeof(crash_info.buf)-1] = '\0';
+ printf("%s\n", crash_info.buf);
+ fflush(stdout);
+
+ if(crash_info.pid > 0)
+ {
+ gdb_info(crash_info.pid);
+ kill(crash_info.pid, SIGKILL);
+ }
+
+ if(logfile)
+ {
char cwd[MAXPATHLEN];
getcwd(cwd, MAXPATHLEN);
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL);
- }
- exit(0);
+ }
+ exit(0);
}
int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
{
- struct sigaction sa;
- stack_t altss;
- int retval;
-
- if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
- crash_handler(logfile);
-
- cc_user_info = user_info;
-
- if(argv[0][0] == '/')
- snprintf(argv0, sizeof(argv0), "%s", argv[0]);
- else
- {
- getcwd(argv0, sizeof(argv0));
- retval = strlen(argv0);
- snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
- }
-
- /* Set an alternate signal stack so SIGSEGVs caused by stack overflows
- * still run */
- altss.ss_sp = altstack;
- altss.ss_flags = 0;
- altss.ss_size = sizeof(altstack);
- sigaltstack(&altss, NULL);
-
- memset(&sa, 0, sizeof(sa));
- sa.sa_sigaction = crash_catcher;
- sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK;
- sigemptyset(&sa.sa_mask);
-
- retval = 0;
- while(num_signals--)
- {
+ struct sigaction sa;
+ stack_t altss;
+ int retval;
+
+ if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
+ crash_handler(logfile);
+
+ cc_user_info = user_info;
+
+ if(argv[0][0] == '/')
+ snprintf(argv0, sizeof(argv0), "%s", argv[0]);
+ else
+ {
+ getcwd(argv0, sizeof(argv0));
+ retval = strlen(argv0);
+ snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
+ }
+
+ /* Set an alternate signal stack so SIGSEGVs caused by stack overflows
+ * still run */
+ altss.ss_sp = altstack;
+ altss.ss_flags = 0;
+ altss.ss_size = sizeof(altstack);
+ sigaltstack(&altss, NULL);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = crash_catcher;
+ sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK;
+ sigemptyset(&sa.sa_mask);
+
+ retval = 0;
+ while(num_signals--)
+ {
if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT &&
- *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1)
- {
- *signals = 0;
- retval = -1;
- }
- ++signals;
- }
- return retval;
+ *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1)
+ {
+ *signals = 0;
+ retval = -1;
+ }
+ ++signals;
+ }
+ return retval;
}
diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp
index 3c2423345b..af8d494261 100644
--- a/apps/openmw/engine.cpp
+++ b/apps/openmw/engine.cpp
@@ -7,6 +7,8 @@
#include <MyGUI_WidgetManager.h>
+#include <SDL.h>
+
#include <components/compiler/extensions0.hpp>
#include <components/bsa/bsa_archive.hpp>
@@ -41,8 +43,7 @@
#include "mwmechanics/mechanicsmanagerimp.hpp"
-
-#include <SDL.h>
+#include "mwstate/statemanagerimp.hpp"
void OMW::Engine::executeLocalScripts()
{
@@ -88,31 +89,47 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
if (mUseSound)
MWBase::Environment::get().getSoundManager()->update(frametime);
- // global scripts
- MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
+ bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode();
- bool changed = MWBase::Environment::get().getWorld()->hasCellChanged();
+ // update game state
+ MWBase::Environment::get().getStateManager()->update (frametime);
- // 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.
+ if (MWBase::Environment::get().getStateManager()->getState()==
+ MWBase::StateManager::State_Running)
+ {
+ // global scripts
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
+
+ bool changed = MWBase::Environment::get().getWorld()->hasCellChanged();
- // passing of time
- if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
- MWBase::Environment::get().getWorld()->advanceTime(
- frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600);
+ // 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.
+ if (changed) // keep change flag for another frame, if cell changed happened in local script
+ MWBase::Environment::get().getWorld()->markCellAsUnchanged();
+
+ if (!paused)
+ 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());
+ paused);
+
+ if (MWBase::Environment::get().getStateManager()->getState()==
+ MWBase::StateManager::State_Running)
+ {
+ MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr();
+ if(!paused && player.getClass().getCreatureStats(player).isDead())
+ MWBase::Environment::get().getStateManager()->endGame();
+ }
// update world
- MWBase::Environment::get().getWorld()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode());
+ MWBase::Environment::get().getWorld()->update(frametime, paused);
// update GUI
Ogre::RenderWindow* window = mOgre->getWindow();
@@ -135,9 +152,10 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
: mOgre (0)
, mFpsLevel(0)
, mVerboseScripts (false)
- , mNewGame (false)
+ , mSkipMenu (false)
, mUseSound (true)
, mCompileAll (false)
+ , mWarningsMode (1)
, mScriptContext (0)
, mFSStrict (false)
, mScriptConsoleMode (false)
@@ -157,6 +175,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
//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");
+ SDL_SetMainReady();
if(SDL_Init(flags) != 0)
{
throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError()));
@@ -264,12 +283,7 @@ void OMW::Engine::setCell (const std::string& cellName)
void OMW::Engine::addContentFile(const std::string& file)
{
- if (file.find_last_of(".") == std::string::npos)
- {
- throw std::runtime_error("Missing extension in content file!");
- }
-
- mContentFiles.push_back(file);
+ mContentFiles.push_back(file);
}
void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity)
@@ -277,9 +291,9 @@ void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity)
mVerboseScripts = scriptsVerbosity;
}
-void OMW::Engine::setNewGame(bool newGame)
+void OMW::Engine::setSkipMenu (bool skipMenu)
{
- mNewGame = newGame;
+ mSkipMenu = skipMenu;
}
std::string OMW::Engine::loadSettings (Settings::Manager & settings)
@@ -309,18 +323,25 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings)
// 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);
+ std::string transparencyOverrides = "/transparency-overrides.cfg";
+ std::string materialOverrides = "/material-overrides.cfg";
+ if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + transparencyOverrides))
+ nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + transparencyOverrides);
+ else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + transparencyOverrides))
+ nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + transparencyOverrides);
+ if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + materialOverrides))
+ nifOverrides.loadMaterialOverrides(mCfgMgr.getLocalPath().string() + materialOverrides);
+ else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + materialOverrides))
+ nifOverrides.loadMaterialOverrides(mCfgMgr.getGlobalPath().string() + materialOverrides);
return settingspath;
}
void OMW::Engine::prepareEngine (Settings::Manager & settings)
{
+ mEnvironment.setStateManager (
+ new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0)));
+
Nif::NIFFile::CacheLock cachelock;
std::string renderSystem = settings.getString("render system", "Video");
@@ -387,10 +408,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
input->setPlayer(&mEnvironment.getWorld()->getPlayer());
window->initUI();
- if (mNewGame)
- // still redundant work here: recreate CharacterCreation(),
- // double update visibility etc.
- window->setNewGame(true);
window->renderWorldMap();
//Load translation data
@@ -398,7 +415,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
for (size_t i = 0; i < mContentFiles.size(); i++)
mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]);
- Compiler::registerExtensions (mExtensions);
+ Compiler::registerExtensions (mExtensions);
// Create sound system
mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound));
@@ -408,7 +425,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mScriptContext->setExtensions (&mExtensions);
mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(),
- mVerboseScripts, *mScriptContext));
+ mVerboseScripts, *mScriptContext, mWarningsMode));
// Create game mechanics system
MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
@@ -422,12 +439,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mechanics->buildPlayer();
window->updatePlayer();
- if (!mNewGame)
- {
- // load cell
- ESM::Position pos;
- MWBase::World *world = MWBase::Environment::get().getWorld();
+ // load cell
+ ESM::Position pos;
+ MWBase::World *world = MWBase::Environment::get().getWorld();
+ if (!mCellName.empty())
+ {
if (world->findExteriorPosition(mCellName, pos)) {
world->changeToExteriorCell (pos);
}
@@ -437,7 +454,11 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
}
}
else
- mEnvironment.getWorld()->startNewGame();
+ {
+ pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
+ pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
+ world->changeToExteriorCell (pos);
+ }
Ogre::FrameEvent event;
event.timeSinceLastEvent = 0;
@@ -463,7 +484,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
void OMW::Engine::go()
{
- assert (!mCellName.empty());
assert (!mContentFiles.empty());
assert (!mOgre);
@@ -484,8 +504,14 @@ void OMW::Engine::go()
if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript);
+ // start in main menu
+ if (!mSkipMenu)
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
+ else
+ MWBase::Environment::get().getStateManager()->newGame (true);
+
// Start the main rendering loop
- while (!mEnvironment.getRequestExit())
+ while (!mEnvironment.get().getStateManager()->hasQuitRequest())
Ogre::Root::getSingleton().renderOneFrame();
// Save user settings
@@ -504,16 +530,19 @@ void OMW::Engine::activate()
if (ptr.isEmpty())
return;
+ if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated
+ 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());
+ MWWorld::Class::get (ptr).activate (ptr, MWBase::Environment::get().getWorld()->getPlayerPtr());
interpreterContext.activate (ptr, action);
std::string script = MWWorld::Class::get (ptr).getScript (ptr);
- MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayerPtr());
if (!script.empty())
{
@@ -584,8 +613,12 @@ void OMW::Engine::setStartupScript (const std::string& path)
mStartupScript = path;
}
-
void OMW::Engine::setActivationDistanceOverride (int distance)
{
mActivationDistanceOverride = distance;
}
+
+void OMW::Engine::setWarningsMode (int mode)
+{
+ mWarningsMode = mode;
+}
diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp
index 9292e81bb3..5c15ddf6fc 100644
--- a/apps/openmw/engine.hpp
+++ b/apps/openmw/engine.hpp
@@ -71,9 +71,10 @@ namespace OMW
std::vector<std::string> mContentFiles;
int mFpsLevel;
bool mVerboseScripts;
- bool mNewGame;
+ bool mSkipMenu;
bool mUseSound;
bool mCompileAll;
+ int mWarningsMode;
std::string mFocusName;
std::map<std::string,std::string> mFallbackMap;
bool mScriptConsoleMode;
@@ -151,8 +152,7 @@ namespace OMW
/// Disable or enable all sounds
void setSoundUsage(bool soundUsage);
- /// Start as a new game.
- void setNewGame(bool newGame);
+ void setSkipMenu (bool skipMenu);
void setGrabMouse(bool grab) { mGrab = grab; }
@@ -182,6 +182,8 @@ namespace OMW
/// Override the game setting specified activation distance.
void setActivationDistanceOverride (int distance);
+ void setWarningsMode (int mode);
+
private:
Files::ConfigurationManager& mCfgMgr;
};
diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp
index 2bf48c1bb9..3098d953ec 100644
--- a/apps/openmw/main.cpp
+++ b/apps/openmw/main.cpp
@@ -1,6 +1,7 @@
#include <iostream>
#include <cstdio>
+#include <components/version/version.hpp>
#include <components/files/configurationmanager.hpp>
#include <SDL.h>
@@ -11,6 +12,7 @@
#include <boost/iostreams/stream_buffer.hpp>
// For OutputDebugString
+#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// makes __argc and __argv available on windows
#include <cstdlib>
@@ -29,8 +31,6 @@ extern int is_debugger_attached(void);
#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
@@ -115,7 +115,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("resources", bpo::value<std::string>()->default_value("resources"),
"set resources directory")
- ("start", bpo::value<std::string>()->default_value("Beshara"),
+ ("start", bpo::value<std::string>()->default_value(""),
"set initial cell")
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
@@ -136,8 +136,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("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")
+ ("script-warn", bpo::value<int>()->implicit_value (1)
+ ->default_value (1),
+ "handling of warnings when compiling scripts\n"
+ "\t0 - ignore warning\n"
+ "\t1 - show warning but consider script as correctly compiled anyway\n"
+ "\t2 - treat warnings as errors")
+
+ ("skip-menu", bpo::value<bool>()->implicit_value(true)
+ ->default_value(false), "skip main menu on game startup")
("fs-strict", bpo::value<bool>()->implicit_value(true)
->default_value(false), "strict file system handling (no case folding)")
@@ -231,7 +238,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
// startup-settings
engine.setCell(variables["start"].as<std::string>());
- engine.setNewGame(variables["new-game"].as<bool>());
+ engine.setSkipMenu (variables["skip-menu"].as<bool>());
// other settings
engine.setSoundUsage(!variables["no-sound"].as<bool>());
@@ -241,6 +248,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.setScriptConsoleMode (variables["script-console"].as<bool>());
engine.setStartupScript (variables["script-run"].as<std::string>());
engine.setActivationDistanceOverride (variables["activate-dist"].as<int>());
+ engine.setWarningsMode (variables["script-warn"].as<int>());
return true;
}
diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp
index 58731d1c78..33bba07e15 100644
--- a/apps/openmw/mwbase/dialoguemanager.hpp
+++ b/apps/openmw/mwbase/dialoguemanager.hpp
@@ -3,6 +3,14 @@
#include <string>
+#include <libs/platform/stdint.h>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+}
+
namespace MWWorld
{
class Ptr;
@@ -51,7 +59,13 @@ namespace MWBase
virtual void persuade (int type) = 0;
virtual int getTemporaryDispositionChange () const = 0;
- virtual void applyTemporaryDispositionChange (int delta) = 0;
+ virtual void applyDispositionChange (int delta) = 0;
+
+ virtual int countSavedGameRecords() const = 0;
+
+ virtual void write (ESM::ESMWriter& writer) const = 0;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
};
}
diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp
index 4db0b45b96..3bc15746ec 100644
--- a/apps/openmw/mwbase/environment.cpp
+++ b/apps/openmw/mwbase/environment.cpp
@@ -11,13 +11,14 @@
#include "mechanicsmanager.hpp"
#include "inputmanager.hpp"
#include "windowmanager.hpp"
+#include "statemanager.hpp"
MWBase::Environment *MWBase::Environment::sThis = 0;
-bool MWBase::Environment::sExit = false;
MWBase::Environment::Environment()
: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
- mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0)
+ mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0),
+ mStateManager (0)
{
assert (!sThis);
sThis = this;
@@ -69,6 +70,11 @@ void MWBase::Environment::setInputManager (InputManager *inputManager)
mInputManager = inputManager;
}
+void MWBase::Environment::setStateManager (StateManager *stateManager)
+{
+ mStateManager = stateManager;
+}
+
void MWBase::Environment::setFrameDuration (float duration)
{
mFrameDuration = duration;
@@ -122,6 +128,12 @@ MWBase::InputManager *MWBase::Environment::getInputManager() const
return mInputManager;
}
+MWBase::StateManager *MWBase::Environment::getStateManager() const
+{
+ assert (mStateManager);
+ return mStateManager;
+}
+
float MWBase::Environment::getFrameDuration() const
{
return mFrameDuration;
@@ -152,6 +164,9 @@ void MWBase::Environment::cleanup()
delete mInputManager;
mInputManager = 0;
+
+ delete mStateManager;
+ mStateManager = 0;
}
const MWBase::Environment& MWBase::Environment::get()
diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp
index 4663029077..eb636ea2fb 100644
--- a/apps/openmw/mwbase/environment.hpp
+++ b/apps/openmw/mwbase/environment.hpp
@@ -11,6 +11,7 @@ namespace MWBase
class MechanicsManager;
class InputManager;
class WindowManager;
+ class StateManager;
/// \brief Central hub for mw-subsystems
///
@@ -30,10 +31,9 @@ namespace MWBase
DialogueManager *mDialogueManager;
Journal *mJournal;
InputManager *mInputManager;
+ StateManager *mStateManager;
float mFrameDuration;
- static bool sExit;
-
Environment (const Environment&);
///< not implemented
@@ -46,9 +46,6 @@ namespace MWBase
~Environment();
- static void setRequestExit () { sExit = true; }
- static bool getRequestExit () { return sExit; }
-
void setWorld (World *world);
void setSoundManager (SoundManager *soundManager);
@@ -65,6 +62,8 @@ namespace MWBase
void setInputManager (InputManager *inputManager);
+ void setStateManager (StateManager *stateManager);
+
void setFrameDuration (float duration);
///< Set length of current frame in seconds.
@@ -84,6 +83,8 @@ namespace MWBase
InputManager *getInputManager() const;
+ StateManager *getStateManager() const;
+
float getFrameDuration() const;
void cleanup();
diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp
index 51e51edda9..56d9601fc5 100644
--- a/apps/openmw/mwbase/journal.hpp
+++ b/apps/openmw/mwbase/journal.hpp
@@ -5,10 +5,18 @@
#include <deque>
#include <map>
+#include <libs/platform/stdint.h>
+
#include "../mwdialogue/journalentry.hpp"
#include "../mwdialogue/topic.hpp"
#include "../mwdialogue/quest.hpp"
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+}
+
namespace MWBase
{
/// \brief Interface for the player's journal (implemented in MWDialogue)
@@ -46,7 +54,7 @@ namespace MWBase
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 void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0;
virtual TEntryIter begin() const = 0;
///< Iterator pointing to the begin of the main journal.
@@ -69,6 +77,12 @@ namespace MWBase
virtual TTopicIter topicEnd() const = 0;
///< Iterator pointing past the last topic.
+
+ virtual int countSavedGameRecords() const = 0;
+
+ virtual void write (ESM::ESMWriter& writer) const = 0;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
};
}
diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp
index 3ab234de1c..22dda0ce09 100644
--- a/apps/openmw/mwbase/mechanicsmanager.hpp
+++ b/apps/openmw/mwbase/mechanicsmanager.hpp
@@ -3,6 +3,7 @@
#include <string>
#include <vector>
+#include <list>
namespace Ogre
{
@@ -76,8 +77,12 @@ namespace MWBase
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 void rest(bool sleep) = 0;
+ ///< If the player is sleeping or waiting, this should be called every hour.
+ /// @param sleep is the player sleeping or waiting?
+
+ virtual int getHoursToRest() const = 0;
+ ///< Calculate how many hours the player needs to rest in order to be fully healed
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.
@@ -88,6 +93,36 @@ namespace MWBase
virtual int countDeaths (const std::string& id) const = 0;
///< Return the number of deaths for actors with the given ID.
+ /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
+ virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0;
+
+ enum OffenseType
+ {
+ OT_Theft, // Taking items owned by an NPC or a faction you are not a member of
+ OT_Assault, // Attacking a peaceful NPC
+ OT_Murder, // Murdering a peaceful NPC
+ OT_Trespassing, // Staying in a cell you are not allowed in (where is this defined?)
+ OT_SleepingInOwnedBed, // Sleeping in a bed owned by an NPC or a faction you are not a member of
+ OT_Pickpocket // Entering pickpocket mode, leaving it, and being detected. Any items stolen are a separate crime (Theft)
+ };
+ /**
+ * @brief Commit a crime. If any actors witness the crime and report it,
+ * reportCrime will be called automatically.
+ * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen.
+ * @return was the crime reported?
+ */
+ virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
+ OffenseType type, int arg=0) = 0;
+ virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
+ OffenseType type, int arg=0) = 0;
+ /// Utility to check if taking this item is illegal and calling commitCrime if so
+ virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count) = 0;
+ /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
+ virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0;
+ /// Attempt sleeping in a bed. If this is illegal, call commitCrime.
+ /// @return was it illegal, and someone saw you doing it?
+ virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0;
+
enum PersuasionType
{
PT_Admire,
@@ -101,28 +136,35 @@ namespace MWBase
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 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;
- 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
+ /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
+ /// paused we may want to do it manually (after equipping permanent enchantment)
+ virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;
- virtual void 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 void toggleAI() = 0;
+ virtual bool isAIActive() = 0;
- virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
+ virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
- /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
- /// paused we may want to do it manually (after equipping permanent enchantment)
- virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;
+ ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor)
+ virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0;
- virtual void toggleAI() = 0;
- virtual bool isAIActive() = 0;
+ virtual void playerLoaded() = 0;
};
}
diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp
index 32df2bfa3b..ae146e0646 100644
--- a/apps/openmw/mwbase/scriptmanager.hpp
+++ b/apps/openmw/mwbase/scriptmanager.hpp
@@ -35,8 +35,6 @@ namespace MWBase
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)
diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp
index 4d764597cf..f3973153a2 100644
--- a/apps/openmw/mwbase/soundmanager.hpp
+++ b/apps/openmw/mwbase/soundmanager.hpp
@@ -147,6 +147,10 @@ namespace MWBase
virtual void update(float duration) = 0;
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0;
+
+ virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0;
+
+ virtual void clear() = 0;
};
}
diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp
new file mode 100644
index 0000000000..cd907408a5
--- /dev/null
+++ b/apps/openmw/mwbase/statemanager.hpp
@@ -0,0 +1,81 @@
+#ifndef GAME_MWSTATE_STATEMANAGER_H
+#define GAME_MWSTATE_STATEMANAGER_H
+
+#include <vector>
+#include <string>
+
+namespace MWState
+{
+ struct Slot;
+ class Character;
+}
+
+namespace MWBase
+{
+ /// \brief Interface for game state manager (implemented in MWState)
+ class StateManager
+ {
+ public:
+
+ enum State
+ {
+ State_NoGame,
+ State_Ended,
+ State_Running
+ };
+
+ typedef std::vector<MWState::Character>::const_iterator CharacterIterator;
+
+ private:
+
+ StateManager (const StateManager&);
+ ///< not implemented
+
+ StateManager& operator= (const StateManager&);
+ ///< not implemented
+
+ public:
+
+ StateManager() {}
+
+ virtual ~StateManager() {}
+
+ virtual void requestQuit() = 0;
+
+ virtual bool hasQuitRequest() const = 0;
+
+ virtual void askLoadRecent() = 0;
+
+ virtual State getState() const = 0;
+
+ virtual void newGame (bool bypass = false) = 0;
+ ///< Start a new game.
+ ///
+ /// \param bypass Skip new game mechanics.
+
+ virtual void endGame() = 0;
+
+ virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0;
+ ///< Write a saved game to \a slot or create a new slot if \a slot == 0.
+ ///
+ /// \note Slot must belong to the current character.
+
+ virtual void loadGame (const MWState::Character *character, const MWState::Slot *slot) = 0;
+ ///< Load a saved game file from \a slot.
+ ///
+ /// \note \a slot must belong to \a character.
+
+ virtual MWState::Character *getCurrentCharacter (bool create = true) = 0;
+ ///< \param create Create a new character, if there is no current character.
+
+ virtual CharacterIterator characterBegin() = 0;
+ ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned
+ /// iterator.
+
+ virtual CharacterIterator characterEnd() = 0;
+
+ virtual void update (float duration) = 0;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp
index 1300efc753..4fce19e337 100644
--- a/apps/openmw/mwbase/windowmanager.hpp
+++ b/apps/openmw/mwbase/windowmanager.hpp
@@ -33,6 +33,8 @@ namespace OEngine
namespace ESM
{
struct Class;
+ class ESMReader;
+ class ESMWriter;
}
namespace MWWorld
@@ -55,6 +57,12 @@ namespace MWGui
class InventoryWindow;
class ContainerWindow;
class DialogueWindow;
+
+ enum ShowInDialogueMode {
+ ShowInDialogueMode_IfPossible,
+ ShowInDialogueMode_Only,
+ ShowInDialogueMode_Never
+ };
}
namespace SFO
@@ -137,8 +145,8 @@ namespace MWBase
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::AttributeValue& value) = 0;
+ virtual void setValue (int parSkill, const MWMechanics::SkillValue& 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;
@@ -224,20 +232,17 @@ namespace MWBase
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 messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 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 std::map<int, MWMechanics::SkillValue > getPlayerSkillValues() = 0;
+ virtual std::map<int, MWMechanics::AttributeValue > getPlayerAttributeValues() = 0;
virtual SkillList getPlayerMinorSkills() = 0;
virtual SkillList getPlayerMajorSkills() = 0;
@@ -282,12 +287,19 @@ namespace MWBase
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
+ /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0;
virtual Loading::Listener* getLoadingScreen() = 0;
/// Should the cursor be visible?
virtual bool getCursorVisible() = 0;
+
+ /// Clear all savegame-specific data
+ virtual void clear() = 0;
+
+ virtual void write (ESM::ESMWriter& writer) = 0;
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0;
};
}
diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp
index 3a88971149..3d033838cc 100644
--- a/apps/openmw/mwbase/world.hpp
+++ b/apps/openmw/mwbase/world.hpp
@@ -2,6 +2,7 @@
#define GAME_MWBASE_WORLD_H
#include <vector>
+#include <map>
#include <components/settings/settings.hpp>
@@ -30,17 +31,18 @@ namespace OEngine
namespace ESM
{
class ESMReader;
+ class ESMWriter;
struct Position;
struct Cell;
struct Class;
struct Potion;
struct Spell;
struct NPC;
+ struct CellId;
}
namespace MWRender
{
- class ExternalRendering;
class Animation;
}
@@ -95,13 +97,26 @@ namespace MWBase
virtual void startNewGame() = 0;
+ virtual void clear() = 0;
+
+ virtual int countSavedGameRecords() const = 0;
+
+ virtual void write (ESM::ESMWriter& writer) const = 0;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type,
+ const std::map<int, int>& contentFileMap) = 0;
+
virtual OEngine::Render::Fader* getFader() = 0;
- ///< \ŧodo remove this function. Rendering details should not be exposed.
+ ///< \todo 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 MWWorld::CellStore *getCell (const ESM::CellId& id) = 0;
+
+ virtual void useDeathCamera() = 0;
+
virtual void setWaterHeight(const float height) = 0;
virtual void toggleWater() = 0;
@@ -113,6 +128,7 @@ namespace MWBase
virtual const MWWorld::Fallback *getFallback () const = 0;
virtual MWWorld::Player& getPlayer() = 0;
+ virtual MWWorld::Ptr getPlayerPtr() = 0;
virtual const MWWorld::ESMStore& getStore() const = 0;
@@ -139,16 +155,26 @@ namespace MWBase
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 void setGlobalInt (const std::string& name, int value) = 0;
+ ///< Set value independently from real type.
- virtual MWWorld::Globals::Data getGlobalVariable (const std::string& name) const = 0;
+ virtual void setGlobalFloat (const std::string& name, float value) = 0;
+ ///< Set value independently from real type.
+
+ virtual int getGlobalInt (const std::string& name) const = 0;
+ ///< Get value independently from real type.
+
+ virtual float getGlobalFloat (const std::string& name) const = 0;
+ ///< Get value independently from real type.
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 std::string getCellName (const MWWorld::CellStore *cell = 0) const = 0;
+ ///< Return name of the cell.
+ ///
+ /// \note If cell==0, the cell the player is currently in will be used instead to
+ /// generate a name.
virtual void removeRefScript (MWWorld::RefData *ref) = 0;
//< Remove the script attached to ref from mLocalScripts
@@ -157,6 +183,10 @@ namespace MWBase
///< Return a pointer to a liveCellRef with the given name.
/// \param activeOnly do non search inactive cells.
+ virtual MWWorld::Ptr searchPtr (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.
@@ -181,8 +211,12 @@ namespace MWBase
virtual void setDay (int day) = 0;
///< Set in-game time day.
- virtual int getDay() = 0;
- virtual int getMonth() = 0;
+ virtual int getDay() const = 0;
+ virtual int getMonth() const = 0;
+ virtual int getYear() const = 0;
+
+ virtual std::string getMonthName (int month = -1) const = 0;
+ ///< Return name of month (-1: current month)
virtual MWWorld::TimeStamp getTimeStamp() const = 0;
///< Return current in-game time stamp.
@@ -211,6 +245,8 @@ namespace MWBase
virtual void changeToExteriorCell (const ESM::Position& position) = 0;
///< Move to exterior cell.
+ virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position) = 0;
+
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.
@@ -221,7 +257,7 @@ namespace MWBase
/// 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.
+ /// use the "Head" node, or alternatively the "Bip01 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;
@@ -332,7 +368,7 @@ namespace MWBase
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 isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const = 0;
virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0;
virtual void togglePOV() = 0;
@@ -366,8 +402,6 @@ namespace MWBase
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
@@ -382,6 +416,7 @@ namespace MWBase
virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
virtual void stopVideo() = 0;
virtual void frameStarted (float dt, bool paused) = 0;
+ virtual void screenshot (Ogre::Image& image, int w, int h) = 0;
/// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise
@@ -426,6 +461,8 @@ namespace MWBase
virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
+ virtual const std::vector<std::string>& getContentFiles() const = 0;
+
virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0;
// Are we in an exterior or pseudo-exterior cell and it's night?
@@ -433,11 +470,10 @@ namespace MWBase
virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) = 0;
- /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
- /// closest to \a worldPos.
+ /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
/// @note id must be lower case
virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr,
- const std::string& id, Ogre::Vector3 worldPos) = 0;
+ const std::string& id) = 0;
enum DetectionType
{
@@ -450,6 +486,24 @@ namespace MWBase
/// @note This also works for references in containers.
virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector<MWWorld::Ptr>& out,
DetectionType type) = 0;
+
+ /// Update the value of some globals according to the world state, which may be used by dialogue entries.
+ /// This should be called when initiating a dialogue.
+ virtual void updateDialogueGlobals() = 0;
+
+ /// Moves all stolen items from \a ptr to the closest evidence chest.
+ virtual void confiscateStolenItems(const MWWorld::Ptr& ptr) = 0;
+
+ virtual void goToJail () = 0;
+
+ /// Spawn a random creature from a levelled list next to the player
+ virtual void spawnRandomCreature(const std::string& creatureList) = 0;
+
+ /// Spawn a blood effect for \a ptr at \a worldPosition
+ virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
+
+ virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
+ const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0;
};
}
diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp
index 583cb08d39..8bc104d254 100644
--- a/apps/openmw/mwclass/activator.cpp
+++ b/apps/openmw/mwclass/activator.cpp
@@ -96,7 +96,11 @@ namespace MWClass
std::string text;
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
+ {
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
+ }
info.text = text;
return info;
diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp
index 53c62273dc..b3a1af288f 100644
--- a/apps/openmw/mwclass/apparatus.cpp
+++ b/apps/openmw/mwclass/apparatus.cpp
@@ -128,6 +128,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
info.text = text;
diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp
index 0fae1b05c3..e3974f243a 100644
--- a/apps/openmw/mwclass/armor.cpp
+++ b/apps/openmw/mwclass/armor.cpp
@@ -17,7 +17,6 @@
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/nullaction.hpp"
#include "../mwworld/containerstore.hpp"
-#include "../mwworld/player.hpp"
#include "../mwrender/objects.hpp"
#include "../mwrender/renderinginterface.hpp"
@@ -252,6 +251,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -289,7 +289,7 @@ namespace MWClass
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);
+ MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
if (ptr.getCellRef().mCharge == 0)
return std::make_pair(0, "#{sInventoryMessage1}");
@@ -300,20 +300,23 @@ namespace MWClass
if (slots_.first.empty())
return std::make_pair(0, "");
- std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
-
- // 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)
+ if (npc.getClass().isNpc())
{
- std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
+ std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
+ // 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)
{
- if((*itr).mPart == ESM::PRT_Head)
- return std::make_pair(0, "#{sNotifyMessage13}");
- if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
- return std::make_pair(0, "#{sNotifyMessage14}");
+ std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
+
+ 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((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
+ return std::make_pair(0, "#{sNotifyMessage14}");
+ }
}
}
@@ -363,12 +366,12 @@ namespace MWClass
return MWWorld::Ptr(&cell.mArmors.insert(*ref), &cell);
}
- float Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ int Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Armor> *ref =
ptr.get<ESM::Armor>();
- return ref->mBase->mData.mEnchant/10.f;
+ return ref->mBase->mData.mEnchant;
}
bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const
diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp
index d8d09d5bb0..17cfca4534 100644
--- a/apps/openmw/mwclass/armor.hpp
+++ b/apps/openmw/mwclass/armor.hpp
@@ -79,7 +79,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::Ptr &ptr) const;
- virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const;
};
diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp
index 1da9209706..0e6506514c 100644
--- a/apps/openmw/mwclass/book.cpp
+++ b/apps/openmw/mwclass/book.cpp
@@ -140,6 +140,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -188,12 +189,12 @@ namespace MWClass
return MWWorld::Ptr(&cell.mBooks.insert(*ref), &cell);
}
- float Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ int Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Book> *ref =
ptr.get<ESM::Book>();
- return ref->mBase->mData.mEnchant/10.f;
+ return ref->mBase->mData.mEnchant;
}
bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const
diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp
index 7fb8a95077..79b823fa9f 100644
--- a/apps/openmw/mwclass/book.hpp
+++ b/apps/openmw/mwclass/book.hpp
@@ -58,7 +58,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::Ptr &ptr) const;
- virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
virtual float getWeight (const MWWorld::Ptr& ptr) const;
diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp
index ffa96260df..ab98d05ae8 100644
--- a/apps/openmw/mwclass/clothing.cpp
+++ b/apps/openmw/mwclass/clothing.cpp
@@ -14,7 +14,6 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/nullaction.hpp"
-#include "../mwworld/player.hpp"
#include "../mwgui/tooltips.hpp"
@@ -195,6 +194,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -238,20 +238,23 @@ namespace MWClass
if (slots_.first.empty())
return std::make_pair(0, "");
- std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
-
- // 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)
+ if (npc.getClass().isNpc())
{
- std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
+ std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
- for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
+ // 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)
{
- if((*itr).mPart == ESM::PRT_Head)
- return std::make_pair(0, "#{sNotifyMessage13}");
- if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
- return std::make_pair(0, "#{sNotifyMessage15}");
+ std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
+
+ 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((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
+ return std::make_pair(0, "#{sNotifyMessage15}");
+ }
}
}
@@ -276,12 +279,12 @@ namespace MWClass
return MWWorld::Ptr(&cell.mClothes.insert(*ref), &cell);
}
- float Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ int Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Clothing> *ref =
ptr.get<ESM::Clothing>();
- return ref->mBase->mData.mEnchant/10.f;
+ return ref->mBase->mData.mEnchant;
}
bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const
diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp
index e2e1188a1a..a73b2c1907 100644
--- a/apps/openmw/mwclass/clothing.hpp
+++ b/apps/openmw/mwclass/clothing.hpp
@@ -71,7 +71,7 @@ namespace MWClass
virtual std::string getModel(const MWWorld::Ptr &ptr) const;
- virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
virtual float getWeight (const MWWorld::Ptr& ptr) const;
diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp
index 55b15a7d29..546d6538c9 100644
--- a/apps/openmw/mwclass/container.cpp
+++ b/apps/openmw/mwclass/container.cpp
@@ -2,6 +2,7 @@
#include "container.hpp"
#include <components/esm/loadcont.hpp>
+#include <components/esm/containerstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -16,7 +17,6 @@
#include "../mwworld/actionopen.hpp"
#include "../mwworld/actiontrap.hpp"
#include "../mwworld/physicssystem.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwgui/tooltips.hpp"
@@ -53,7 +53,7 @@ namespace MWClass
ptr.get<ESM::Container>();
data->mContainerStore.fill(
- ref->mBase->mInventory, ptr.getCellRef().mOwner, MWBase::Environment::get().getWorld()->getStore());
+ ref->mBase->mInventory, ptr.getCellRef().mOwner, ptr.getCellRef().mFaction, MWBase::Environment::get().getWorld()->getStore());
// store
ptr.getRefData().setCustomData (data.release());
@@ -108,7 +108,7 @@ namespace MWClass
const std::string lockedSound = "LockedChest";
const std::string trapActivationSound = "Disarm Trap Fail";
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player);
bool needKey = ptr.getCellRef().mLockLevel>0;
@@ -216,6 +216,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -258,4 +259,26 @@ namespace MWClass
return MWWorld::Ptr(&cell.mContainers.insert(*ref), &cell);
}
+
+ void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const
+ {
+ const ESM::ContainerState& state2 = dynamic_cast<const ESM::ContainerState&> (state);
+
+ ensureCustomData (ptr);
+
+ dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
+ readState (state2.mInventory);
+ }
+
+ void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const
+ {
+ ESM::ContainerState& state2 = dynamic_cast<ESM::ContainerState&> (state);
+
+ ensureCustomData (ptr);
+
+ dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
+ writeState (state2.mInventory);
+ }
}
diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp
index 006e4bd223..c97867d35a 100644
--- a/apps/openmw/mwclass/container.hpp
+++ b/apps/openmw/mwclass/container.hpp
@@ -54,6 +54,14 @@ namespace MWClass
virtual void unlock (const MWWorld::Ptr& ptr) const;
///< Unlock object
+ virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const;
+ ///< Read additional state from \a state into \a ptr.
+
+ virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const;
+ ///< Write additional state from \a ptr into \a state.
+
static void registerSelf();
virtual std::string getModel(const MWWorld::Ptr &ptr) const;
diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp
index b225441aaf..81ca0ce2bf 100644
--- a/apps/openmw/mwclass/creature.cpp
+++ b/apps/openmw/mwclass/creature.cpp
@@ -2,10 +2,12 @@
#include "creature.hpp"
#include <components/esm/loadcrea.hpp>
+#include <components/esm/creaturestate.hpp>
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/magiceffects.hpp"
#include "../mwmechanics/movement.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@@ -26,22 +28,30 @@
#include "../mwgui/tooltips.hpp"
+#include "../mwworld/inventorystore.hpp"
+
#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/combat.hpp"
namespace
{
struct CustomData : public MWWorld::CustomData
{
MWMechanics::CreatureStats mCreatureStats;
- MWWorld::ContainerStore mContainerStore;
+ MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
MWMechanics::Movement mMovement;
virtual MWWorld::CustomData *clone() const;
+
+ CustomData() : mContainerStore(0) {}
+ virtual ~CustomData() { delete mContainerStore; }
};
MWWorld::CustomData *CustomData::clone() const
{
- return new CustomData (*this);
+ CustomData* cloned = new CustomData (*this);
+ cloned->mContainerStore = mContainerStore->clone();
+ return cloned;
}
}
@@ -61,6 +71,17 @@ namespace MWClass
fMinWalkSpeedCreature = gmst.find("fMinWalkSpeedCreature");
fMaxWalkSpeedCreature = gmst.find("fMaxWalkSpeedCreature");
+ 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");
+ fKnockDownMult = gmst.find("fKnockDownMult");
+ iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
+ iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
inited = true;
}
@@ -84,10 +105,10 @@ namespace MWClass
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);
+ data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
+ data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
+ data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
+ data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
// spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
@@ -95,13 +116,23 @@ namespace MWClass
data->mCreatureStats.getSpells().add (*iter);
// inventory
- data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr),
- MWBase::Environment::get().getWorld()->getStore());
-
- data->mContainerStore.add("gold_001", ref->mBase->mData.mGold, ptr);
+ if (ref->mBase->mFlags & ESM::Creature::Weapon)
+ data->mContainerStore = new MWWorld::InventoryStore();
+ else
+ data->mContainerStore = new MWWorld::ContainerStore();
// store
ptr.getRefData().setCustomData (data.release());
+
+ getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
+ MWBase::Environment::get().getWorld()->getStore());
+
+ // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
+ // (except for gold you gave him)
+ getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
+
+ if (ref->mBase->mFlags & ESM::Creature::Weapon)
+ getInventoryStore(ptr).autoEquip(ptr);
}
}
@@ -120,8 +151,10 @@ namespace MWClass
void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
{
+ MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
+
MWRender::Actors& actors = renderingInterface.getActors();
- actors.insertCreature(ptr);
+ actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon);
}
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
@@ -163,6 +196,144 @@ namespace MWClass
void Creature::hit(const MWWorld::Ptr& ptr, int type) const
{
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+ const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
+
+ // Get the weapon used (if hand-to-hand, weapon = inv.end())
+ MWWorld::Ptr weapon;
+ if (ptr.getClass().hasInventoryStore(ptr))
+ {
+ MWWorld::InventoryStore &inv = getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name())
+ weapon = *weaponslot;
+ }
+
+ // Reduce fatigue
+ // somewhat of a guess, but using the weapon weight makes sense
+ const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
+ const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
+ const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
+ MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
+ const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
+ float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
+ if (!weapon.isEmpty())
+ fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult;
+ fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
+ stats.setFatigue(fatigue);
+
+ // TODO: where is the distance defined?
+ float dist = 200.f;
+ if (!weapon.isEmpty())
+ {
+ const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
+ dist = fCombatDistance * weapon.get<ESM::Weapon>()->mBase->mData.mReach;
+ }
+ std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist);
+ if (result.first.isEmpty())
+ return; // Didn't hit anything
+
+ MWWorld::Ptr victim = result.first;
+
+ if (!victim.getClass().isActor())
+ return; // Can't hit non-actors
+
+ Ogre::Vector3 hitPosition = result.second;
+
+ MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
+ const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
+ float hitchance = ref->mBase->mData.mCombat +
+ (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
+ (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
+ hitchance *= stats.getFatigueTerm();
+ hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
+ mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
+ hitchance -= otherstats.getEvasion();
+
+ if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
+ {
+ victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false);
+ return;
+ }
+
+ int min,max;
+ switch (type)
+ {
+ case 0:
+ min = ref->mBase->mData.mAttack[0];
+ max = ref->mBase->mData.mAttack[1];
+ break;
+ case 1:
+ min = ref->mBase->mData.mAttack[2];
+ max = ref->mBase->mData.mAttack[3];
+ break;
+ case 2:
+ default:
+ min = ref->mBase->mData.mAttack[4];
+ max = ref->mBase->mData.mAttack[5];
+ break;
+ }
+
+ // I think this should be random, since attack1-3 animations don't have an attack strength like NPCs do
+ float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0);
+
+ if (!weapon.isEmpty())
+ {
+ const bool weaphashealth = get(weapon).hasItemHealth(weapon);
+ const unsigned char *attack = NULL;
+ if(type == ESM::Weapon::AT_Chop)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
+ else if(type == ESM::Weapon::AT_Slash)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
+ else if(type == ESM::Weapon::AT_Thrust)
+ attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
+ if(attack)
+ {
+ float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
+ weaponDamage *= 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;
+ weaponDamage *= float(weapon.getCellRef().mCharge) / weapmaxhealth;
+ }
+
+ if (!MWBase::Environment::get().getWorld()->getGodModeState())
+ weapon.getCellRef().mCharge -= std::min(std::max(1,
+ (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
+
+ // Weapon broken? unequip it
+ if (weapon.getCellRef().mCharge == 0)
+ weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr);
+
+ damage += weaponDamage;
+ }
+
+ // Apply "On hit" enchanted weapons
+ std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
+ if (!enchantmentName.empty())
+ {
+ const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
+ enchantmentName);
+ if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
+ {
+ MWMechanics::CastSpell cast(ptr, victim);
+ cast.mHitPosition = hitPosition;
+ cast.cast(weapon);
+ }
+ }
+ }
+
+ if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
+ damage = 0;
+
+ if (damage > 0)
+ MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
+
+ victim.getClass().onHit(victim, damage, true, weapon, ptr, true);
}
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
@@ -189,18 +360,60 @@ namespace MWClass
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
}
- if(ishealth)
+ if (damage > 0.0f && !object.isEmpty())
+ MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
+
+ if (damage > 0.f)
{
- if(damage > 0.0f)
+ // Check for knockdown
+ float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
+ float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
+ * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
+ {
+ getCreatureStats(ptr).setKnockedDown(true);
+
+ }
+ else
+ getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
+
+ if(ishealth)
+ {
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
- float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
- setActorHealth(ptr, health, attacker);
+ float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
+ setActorHealth(ptr, health, attacker);
+ }
+ else
+ {
+ MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
+ fatigue.setCurrent(fatigue.getCurrent() - damage, true);
+ getCreatureStats(ptr).setFatigue(fatigue);
+ }
}
- else
+ }
+
+ void Creature::block(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::InventoryStore& inv = getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ if (shield == inv.end())
+ return;
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ switch(shield->getClass().getEquipmentSkill(*shield))
{
- MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
- fatigue.setCurrent(fatigue.getCurrent() - damage, true);
- getCreatureStats(ptr).setFatigue(fatigue);
+ 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;
+ default:
+ return;
}
}
@@ -243,18 +456,33 @@ namespace MWClass
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
- MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr)
- const
+ MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
{
ensureCustomData (ptr);
- return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
+ return *dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
+ }
+
+ MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
+
+ if (ref->mBase->mFlags & ESM::Creature::Weapon)
+ return dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr));
+ else
+ throw std::runtime_error("this creature has no inventory store");
+ }
+
+ bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
+
+ return (ref->mBase->mFlags & ESM::Creature::Weapon);
}
std::string Creature::getScript (const MWWorld::Ptr& ptr) const
{
- MWWorld::LiveCellRef<ESM::Creature> *ref =
- ptr.get<ESM::Creature>();
+ MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
return ref->mBase->mScript;
}
@@ -284,10 +512,51 @@ namespace MWClass
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;
+
+ const MWBase::World *world = MWBase::Environment::get().getWorld();
+ const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
+
+ const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
+
+ bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
+
+ float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) *
+ fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
+
+ float moveSpeed;
+ if(normalizedEncumbrance >= 1.0f)
+ moveSpeed = 0.0f;
+ else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
+ world->isLevitationEnabled()))
+ {
+ float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
+ mageffects.get(ESM::MagicEffect::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(running)
+ swimSpeed = runSpeed;
+ swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude;
+ swimSpeed *= fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) *
+ fSwimRunAthleticsMult->getFloat();
+ moveSpeed = swimSpeed;
+ }
+ else if(running)
+ moveSpeed = runSpeed;
+ else
+ moveSpeed = walkSpeed;
+ if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
+ moveSpeed *= 0.75f;
+
+ return moveSpeed;
}
MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const
@@ -413,6 +682,14 @@ namespace MWClass
return MWWorld::Ptr(&cell.mCreatures.insert(*ref), &cell);
}
+ bool Creature::isFlying(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ return ref->mBase->mFlags & ESM::Creature::Flies;
+ }
+
int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name)
{
if(name == "left")
@@ -451,6 +728,76 @@ namespace MWClass
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
}
+ int Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref =
+ ptr.get<ESM::Creature>();
+
+ const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skill);
+
+ switch (skillRecord->mData.mSpecialization)
+ {
+ case ESM::Class::Combat:
+ return ref->mBase->mData.mCombat;
+ case ESM::Class::Magic:
+ return ref->mBase->mData.mMagic;
+ case ESM::Class::Stealth:
+ return ref->mBase->mData.mStealth;
+ default:
+ throw std::runtime_error("invalid specialisation");
+ }
+ }
+
+ int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
+
+ if (ref->mBase->mFlags & ESM::Creature::Skeleton)
+ return 1;
+ if (ref->mBase->mFlags & ESM::Creature::Metal)
+ return 2;
+ return 0;
+ }
+
+ void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const
+ {
+ const ESM::CreatureState& state2 = dynamic_cast<const ESM::CreatureState&> (state);
+
+ ensureCustomData (ptr);
+
+ CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
+
+ customData.mContainerStore->readState (state2.mInventory);
+ customData.mCreatureStats.readState (state2.mCreatureStats);
+
+ }
+
+ void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const
+ {
+ ESM::CreatureState& state2 = dynamic_cast<ESM::CreatureState&> (state);
+
+ ensureCustomData (ptr);
+
+ CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
+
+ customData.mContainerStore->writeState (state2.mInventory);
+ customData.mCreatureStats.writeState (state2.mCreatureStats);
+ }
+
const ESM::GameSetting* Creature::fMinWalkSpeedCreature;
const ESM::GameSetting* Creature::fMaxWalkSpeedCreature;
+ const ESM::GameSetting *Creature::fEncumberedMoveEffect;
+ const ESM::GameSetting *Creature::fSneakSpeedMultiplier;
+ const ESM::GameSetting *Creature::fAthleticsRunBonus;
+ const ESM::GameSetting *Creature::fBaseRunMultiplier;
+ const ESM::GameSetting *Creature::fMinFlySpeed;
+ const ESM::GameSetting *Creature::fMaxFlySpeed;
+ const ESM::GameSetting *Creature::fSwimRunBase;
+ const ESM::GameSetting *Creature::fSwimRunAthleticsMult;
+ const ESM::GameSetting *Creature::fKnockDownMult;
+ const ESM::GameSetting *Creature::iKnockDownOddsMult;
+ const ESM::GameSetting *Creature::iKnockDownOddsBase;
+
}
diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp
index 0d8694ff81..adaf62a96e 100644
--- a/apps/openmw/mwclass/creature.hpp
+++ b/apps/openmw/mwclass/creature.hpp
@@ -16,6 +16,18 @@ namespace MWClass
static const ESM::GameSetting *fMinWalkSpeedCreature;
static const ESM::GameSetting *fMaxWalkSpeedCreature;
+ 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 *fKnockDownMult;
+ static const ESM::GameSetting *iKnockDownOddsMult;
+ static const ESM::GameSetting *iKnockDownOddsBase;
+
public:
@@ -44,6 +56,8 @@ namespace MWClass
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
+ virtual void block(const MWWorld::Ptr &ptr) 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;
@@ -56,6 +70,11 @@ namespace MWClass
const MWWorld::Ptr& ptr) const;
///< Return container store
+ virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
+ ///< Return inventory store
+
+ virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const;
+
virtual std::string getScript (const MWWorld::Ptr& ptr) const;
///< Return name of the script attached to ptr
@@ -72,7 +91,7 @@ namespace MWClass
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;
@@ -99,6 +118,21 @@ namespace MWClass
isActor() const {
return true;
}
+
+ virtual bool isFlying (const MWWorld::Ptr &ptr) const;
+
+ virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const;
+
+ /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
+ virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
+
+ virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const;
+ ///< Read additional state from \a state into \a ptr.
+
+ virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const;
+ ///< Write additional state from \a ptr into \a state.
};
}
diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp
index 53dd34bb48..ea586e5b63 100644
--- a/apps/openmw/mwclass/creaturelevlist.cpp
+++ b/apps/openmw/mwclass/creaturelevlist.cpp
@@ -3,6 +3,24 @@
#include <components/esm/loadlevlist.hpp>
+#include "../mwmechanics/levelledlist.hpp"
+
+#include "../mwworld/customdata.hpp"
+
+namespace
+{
+ struct CustomData : public MWWorld::CustomData
+ {
+ // TODO: save the creature we spawned here
+ virtual MWWorld::CustomData *clone() const;
+ };
+
+ MWWorld::CustomData *CustomData::clone() const
+ {
+ return new CustomData (*this);
+ }
+}
+
namespace MWClass
{
std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
@@ -16,4 +34,33 @@ namespace MWClass
registerClass (typeid (ESM::CreatureLevList).name(), instance);
}
+
+ void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const
+ {
+ ensureCustomData(ptr);
+ }
+
+ void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const
+ {
+ if (!ptr.getRefData().getCustomData())
+ {
+ std::auto_ptr<CustomData> data (new CustomData);
+
+ MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
+ ptr.get<ESM::CreatureLevList>();
+
+ std::string id = MWMechanics::getLevelledItem(ref->mBase, true);
+
+ if (!id.empty())
+ {
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ MWWorld::ManualRef ref(store, id);
+ ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos;
+ // TODO: hold on to this for respawn purposes later
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos);
+ }
+
+ ptr.getRefData().setCustomData(data.release());
+ }
+ }
}
diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp
index 81965efd58..d2c02043eb 100644
--- a/apps/openmw/mwclass/creaturelevlist.hpp
+++ b/apps/openmw/mwclass/creaturelevlist.hpp
@@ -7,6 +7,8 @@ namespace MWClass
{
class CreatureLevList : public MWWorld::Class
{
+ void ensureCustomData (const MWWorld::Ptr& ptr) const;
+
public:
virtual std::string getName (const MWWorld::Ptr& ptr) const;
@@ -14,6 +16,9 @@ namespace MWClass
/// can return an empty string.
static void registerSelf();
+
+ virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
+ ///< Add reference into a cell for rendering
};
}
diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp
index 3adb4f6fc3..e9ac39f1d1 100644
--- a/apps/openmw/mwclass/door.cpp
+++ b/apps/openmw/mwclass/door.cpp
@@ -8,7 +8,6 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/nullaction.hpp"
#include "../mwworld/failedaction.hpp"
@@ -97,7 +96,7 @@ namespace MWClass
if (needKey && hasKey)
{
- if(actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
+ if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}");
ptr.getCellRef().mLockLevel = 0;
// using a key disarms the trap
@@ -116,21 +115,11 @@ namespace MWClass
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));
+ boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTeleport (ref->mRef.mDestCell, ref->mRef.mDoorDest));
- action->setSound(openSound);
+ action->setSound(openSound);
- return action;
- }
- else
- {
- // another NPC or a creature is using the door
- return boost::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction);
- }
+ return action;
}
else
{
diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp
index 4296d4e1b6..faf29bc83c 100644
--- a/apps/openmw/mwclass/ingredient.cpp
+++ b/apps/openmw/mwclass/ingredient.cpp
@@ -12,7 +12,6 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/physicssystem.hpp"
#include "../mwworld/actioneat.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/nullaction.hpp"
#include "../mwmechanics/npcstats.hpp"
@@ -149,10 +148,11 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp
index 6a6133cb92..ddb2c16d66 100644
--- a/apps/openmw/mwclass/light.cpp
+++ b/apps/openmw/mwclass/light.cpp
@@ -2,6 +2,7 @@
#include "light.hpp"
#include <components/esm/loadligh.hpp>
+#include <components/esm/lightstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -187,6 +188,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -268,4 +270,24 @@ namespace MWClass
}
return std::make_pair(1,"");
}
+
+ void Light::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const
+ {
+ const ESM::LightState& state2 = dynamic_cast<const ESM::LightState&> (state);
+
+ ensureCustomData (ptr);
+
+ dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime = state2.mTime;
+ }
+
+ void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const
+ {
+ ESM::LightState& state2 = dynamic_cast<ESM::LightState&> (state);
+
+ ensureCustomData (ptr);
+
+ state2.mTime = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime;
+ }
}
diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp
index c15228a6a0..5568e1727e 100644
--- a/apps/openmw/mwclass/light.hpp
+++ b/apps/openmw/mwclass/light.hpp
@@ -71,6 +71,14 @@ namespace MWClass
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;
+
+ virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const;
+ ///< Read additional state from \a state into \a ptr.
+
+ virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const;
+ ///< Write additional state from \a ptr into \a state.
};
}
diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp
index e1dc5b2e14..795b660527 100644
--- a/apps/openmw/mwclass/lockpick.cpp
+++ b/apps/openmw/mwclass/lockpick.cpp
@@ -145,6 +145,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp
index d211891035..e58716f1cf 100644
--- a/apps/openmw/mwclass/misc.cpp
+++ b/apps/openmw/mwclass/misc.cpp
@@ -93,7 +93,9 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
ptr.get<ESM::Miscellaneous>();
- int value = (ptr.getCellRef().mGoldValue == 1) ? ref->mBase->mData.mValue : ptr.getCellRef().mGoldValue;
+ int value = ref->mBase->mData.mValue;
+ if (ptr.getCellRef().mGoldValue > 1 && ptr.getRefData().getCount() == 1)
+ value = ptr.getCellRef().mGoldValue;
if (ptr.getCellRef().mSoul != "")
{
@@ -153,11 +155,8 @@ namespace MWClass
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;
+ if (gold)
+ count *= getValue(ptr);
std::string countString;
if (!gold)
@@ -184,6 +183,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -201,7 +201,7 @@ namespace MWClass
MWBase::Environment::get().getWorld()->getStore();
if (isGold(ptr)) {
- int goldAmount = ptr.getRefData().getCount();
+ int goldAmount = getValue(ptr) * ptr.getRefData().getCount();
std::string base = "Gold_001";
if (goldAmount >= 100)
@@ -220,6 +220,7 @@ namespace MWClass
newRef.getPtr().get<ESM::Miscellaneous>();
newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell);
newPtr.getCellRef().mGoldValue = goldAmount;
+ newPtr.getRefData().setCount(1);
} else {
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
ptr.get<ESM::Miscellaneous>();
diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp
index f4e15423d3..3a95f3c294 100644
--- a/apps/openmw/mwclass/npc.cpp
+++ b/apps/openmw/mwclass/npc.cpp
@@ -3,12 +3,11 @@
#include <memory>
-#include <boost/algorithm/string.hpp>
-
#include <OgreSceneNode.h>
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadnpc.hpp>
+#include <components/esm/npcstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -21,6 +20,8 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/spellcasting.hpp"
+#include "../mwmechanics/disease.hpp"
+#include "../mwmechanics/combat.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/actiontalk.hpp"
@@ -37,9 +38,6 @@
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;
@@ -145,7 +143,7 @@ namespace
*
* and by adding class, race, specialization bonus.
*/
- void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
+ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
{
const ESM::Class *class_ =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
@@ -179,29 +177,41 @@ namespace
for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
{
- if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
- {
- raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
- break;
- }
+ if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
+ {
+ raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
+ break;
+ }
}
for (int k = 0; k < 5; ++k)
{
- // is this a minor or major skill?
- if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
- {
- majorMultiplier = 1.0f;
- break;
- }
+ // is this a minor or major skill?
+ if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
+ {
+ majorMultiplier = 1.0f;
+ break;
+ }
+ if (class_->mData.mSkills[k][1] == skillIndex)
+ {
+ // Major skill -> add starting spells for this skill if existing
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+ MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
+ for (; it != store.get<ESM::Spell>().end(); ++it)
+ {
+ if (it->mData.mFlags & ESM::Spell::F_Autocalc
+ && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex)
+ npcStats.getSpells().add(it->mId);
+ }
+ }
}
// is this skill in the same Specialization as the class?
const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex);
if (skill->mData.mSpecialization == class_->mData.mSpecialization)
{
- specMultiplier = 0.5f;
- specBonus = 5;
+ specMultiplier = 0.5f;
+ specBonus = 5;
}
npcStats.getSkill(skillIndex).setBase(
@@ -210,7 +220,7 @@ namespace
+ 5
+ raceBonus
+ specBonus
- + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100.0f));
+ + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100));
}
}
}
@@ -241,6 +251,11 @@ namespace MWClass
fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
fWereWolfRunMult = gmst.find("fWereWolfRunMult");
+ fKnockDownMult = gmst.find("fKnockDownMult");
+ iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
+ iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
+ fDamageStrengthBase = gmst.find("fDamageStrengthBase");
+ fDamageStrengthMult = gmst.find("fDamageStrengthMult");
inited = true;
}
@@ -303,15 +318,34 @@ namespace MWClass
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats);
- autoCalculateSkills(ref->mBase, data->mNpcStats);
+ autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
+ }
+
+ // race powers
+ const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
+ for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
+ iter!=race->mPowers.mList.end(); ++iter)
+ {
+ data->mNpcStats.getSpells().add (*iter);
+ }
+
+ if (data->mNpcStats.getFactionRanks().size())
+ {
+ static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("iAutoRepFacMod")->getInt();
+ static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("iAutoRepLevMod")->getInt();
+ int rank = data->mNpcStats.getFactionRanks().begin()->second;
+
+ data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1));
}
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);
+ data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
+ data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
+ data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
+ data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
// spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
@@ -319,13 +353,15 @@ namespace MWClass
data->mNpcStats.getSpells().add (*iter);
// inventory
- data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr),
+ data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "",
MWBase::Environment::get().getWorld()->getStore());
// store
ptr.getRefData().setCustomData (data.release());
- getContainerStore(ptr).add("gold_001", gold, ptr);
+ // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
+ // (except for gold you gave him)
+ getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr);
getInventoryStore(ptr).autoEquip(ptr);
}
@@ -422,11 +458,28 @@ namespace MWClass
if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name())
weapon = MWWorld::Ptr();
- float dist = 100.0f * (!weapon.isEmpty() ?
+ // Reduce fatigue
+ // somewhat of a guess, but using the weapon weight makes sense
+ const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
+ const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
+ const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
+ MWMechanics::DynamicStat<float> fatigue = getCreatureStats(ptr).getFatigue();
+ const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
+ float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
+ if (!weapon.isEmpty())
+ fatigueLoss += weapon.getClass().getWeight(weapon) * getNpcStats(ptr).getAttackStrength() * fWeaponFatigueMult;
+ fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
+ getCreatureStats(ptr).setFatigue(fatigue);
+
+ const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
+ float dist = fCombatDistance * (!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;
+
+ // TODO: Use second to work out the hit angle
+ std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
+ MWWorld::Ptr victim = result.first;
+ Ogre::Vector3 hitPosition = result.second;
if(victim.isEmpty()) // Didn't hit anything
return;
@@ -450,8 +503,8 @@ namespace MWClass
(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 += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
+ mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
hitchance -= otherstats.getEvasion();
if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
@@ -466,16 +519,17 @@ namespace MWClass
{
const bool weaphashealth = get(weapon).hasItemHealth(weapon);
const unsigned char *attack = NULL;
- if(type == MWMechanics::CreatureStats::AT_Chop)
+ if(type == ESM::Weapon::AT_Chop)
attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
- else if(type == MWMechanics::CreatureStats::AT_Slash)
+ else if(type == ESM::Weapon::AT_Slash)
attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
- else if(type == MWMechanics::CreatureStats::AT_Thrust)
+ else if(type == ESM::Weapon::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);
+ damage *= fDamageStrengthBase->getFloat() +
+ (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1);
if(weaphashealth)
{
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
@@ -483,13 +537,7 @@ namespace MWClass
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);
- }
-
+
if (!MWBase::Environment::get().getWorld()->getGodModeState())
weapon.getCellRef().mCharge -= std::min(std::max(1,
(int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
@@ -510,14 +558,9 @@ namespace MWClass
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);
+ healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f)
+ || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0);
if(stats.isWerewolf())
{
healthdmg = true;
@@ -541,6 +584,16 @@ namespace MWClass
if(ptr.getRefData().getHandle() == "player")
skillUsageSucceeded(ptr, weapskill, 0);
+ bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
+ if(!detected)
+ {
+ damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
+ MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
+ }
+ if (othercls.getCreatureStats(victim).getKnockedDown())
+ damage *= gmst.find("fCombatKODamageMult")->getFloat();
+
// Apply "On hit" enchanted weapons
std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
if (!enchantmentName.empty())
@@ -549,31 +602,18 @@ namespace MWClass
enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{
- // Check if we have enough charges
- const float enchantCost = enchantment->mData.mCost;
- int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
- const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
-
- if (weapon.getCellRef().mEnchantmentCharge == -1)
- weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
- if (weapon.getCellRef().mEnchantmentCharge < castCost)
- {
- if (ptr.getRefData().getHandle() == "player")
- MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
- }
- else
- {
- weapon.getCellRef().mEnchantmentCharge -= castCost;
-
- MWMechanics::CastSpell cast(ptr, victim);
- cast.cast(weapon);
-
- if (ptr.getRefData().getHandle() == "player")
- skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
- }
+ MWMechanics::CastSpell cast(ptr, victim);
+ cast.mHitPosition = hitPosition;
+ cast.cast(weapon);
}
}
+ if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
+ damage = 0;
+
+ if (healthdmg && damage > 0)
+ MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
+
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
}
@@ -583,6 +623,10 @@ namespace MWClass
// NOTE: 'object' and/or 'attacker' may be empty.
+ // Attacking peaceful NPCs is a crime
+ if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30)
+ MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);
+
if(!successful)
{
// TODO: Handle HitAttemptOnMe script function
@@ -597,18 +641,44 @@ namespace MWClass
if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
{
- const std::string &script = ptr.get<ESM::NPC>()->mBase->mScript;
+ const std::string &script = ptr.getClass().getScript(ptr);
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
if(!script.empty())
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
}
+ if (!attacker.isEmpty())
+ MWMechanics::diseaseContact(ptr, attacker);
+
+ if (damage > 0.0f && !object.isEmpty())
+ MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
+
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");
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < chance)
+ {
+ MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
+ }
+ getCreatureStats(ptr).setAttacked(true);
+
+ // Check for knockdown
+ float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
+ float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
+ * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
+ roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
+ {
+ getCreatureStats(ptr).setKnockedDown(true);
+
+ }
+ else
+ getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
if(object.isEmpty())
{
@@ -654,6 +724,9 @@ namespace MWClass
if (armorref.mCharge == 0)
inv.unequipItem(armor, ptr);
+ if (ptr.getRefData().getHandle() == "player")
+ skillUsageSucceeded(ptr, get(armor).getEquipmentSkill(armor), 0);
+
switch(get(armor).getEquipmentSkill(armor))
{
case ESM::Skill::LightArmor:
@@ -667,6 +740,8 @@ namespace MWClass
break;
}
}
+ else if(ptr.getRefData().getHandle() == "player")
+ skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
}
}
@@ -685,6 +760,30 @@ namespace MWClass
}
}
+ void Npc::block(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::InventoryStore& inv = getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ if (shield == inv.end())
+ return;
+
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ switch(shield->getClass().getEquipmentSkill(*shield))
+ {
+ 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;
+ default:
+ return;
+ }
+ }
+
void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
{
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
@@ -720,10 +819,10 @@ namespace MWClass
}
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
if(get(ptr).getCreatureStats(ptr).isHostile())
return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
+ if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
+ return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
@@ -751,80 +850,6 @@ namespace MWClass
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();
@@ -833,11 +858,14 @@ namespace MWClass
const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);
+ bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
+ bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
+
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))
+ if(sneaking)
walkSpeed *= fSneakSpeedMultiplier->getFloat();
float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
@@ -848,11 +876,11 @@ namespace MWClass
float moveSpeed;
if(normalizedEncumbrance >= 1.0f)
moveSpeed = 0.0f;
- else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0 &&
+ else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
world->isLevitationEnabled())
{
float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
- mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
+ mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);
flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat());
flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance;
flySpeed = std::max(0.0f, flySpeed);
@@ -861,14 +889,14 @@ namespace MWClass
else if(world->isSwimming(ptr))
{
float swimSpeed = walkSpeed;
- if(Npc::getStance(ptr, Run, false))
+ if(running)
swimSpeed = runSpeed;
- swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude;
+ swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).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))
+ else if(running && !sneaking)
moveSpeed = runSpeed;
else
moveSpeed = walkSpeed;
@@ -897,10 +925,10 @@ namespace MWClass
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 += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64;
x *= encumbranceTerm;
- if(Npc::getStance(ptr, Run, false))
+ if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
x *= fJumpRunMultiplier->getFloat();
x *= npcdata->mNpcStats.getFatigueTerm();
x -= -627.2f;/*gravity constant*/
@@ -920,7 +948,7 @@ namespace MWClass
{
const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified();
const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
- const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude;
+ const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude;
const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat();
const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat();
const float fallDistanceBase = gmst.find("fFallDistanceBase")->getFloat();
@@ -974,7 +1002,7 @@ namespace MWClass
return ref->mBase->mFlags & ESM::NPC::Essential;
}
-
+
void Npc::registerSelf()
{
boost::shared_ptr<Class> instance (new Npc);
@@ -1012,7 +1040,8 @@ namespace MWClass
float Npc::getCapacity (const MWWorld::Ptr& ptr) const
{
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
- return stats.getAttribute(0).getModified()*5;
+ static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->getFloat();
+ return stats.getAttribute(0).getModified()*fEncumbranceStrMult;
}
float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const
@@ -1025,8 +1054,8 @@ namespace MWClass
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;
+ weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).mMagnitude;
+ weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).mMagnitude;
if(weight < 0.0f)
weight = 0.0f;
}
@@ -1226,6 +1255,50 @@ namespace MWClass
return MWWorld::Ptr(&cell.mNpcs.insert(*ref), &cell);
}
+ int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
+ {
+ return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
+ }
+
+ int Npc::getBloodTexture(const MWWorld::Ptr &ptr) const
+ {
+ MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
+
+ if (ref->mBase->mFlags & ESM::NPC::Skeleton)
+ return 1;
+ if (ref->mBase->mFlags & ESM::NPC::Metal)
+ return 2;
+ return 0;
+ }
+
+ void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const
+ {
+ const ESM::NpcState& state2 = dynamic_cast<const ESM::NpcState&> (state);
+
+ ensureCustomData (ptr);
+
+ CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
+
+ customData.mInventoryStore.readState (state2.mInventory);
+ customData.mNpcStats.readState (state2.mNpcStats);
+ static_cast<MWMechanics::CreatureStats&> (customData.mNpcStats).readState (state2.mCreatureStats);
+ }
+
+ void Npc::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const
+ {
+ ESM::NpcState& state2 = dynamic_cast<ESM::NpcState&> (state);
+
+ ensureCustomData (ptr);
+
+ CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
+
+ customData.mInventoryStore.writeState (state2.mInventory);
+ customData.mNpcStats.writeState (state2.mNpcStats);
+ static_cast<const MWMechanics::CreatureStats&> (customData.mNpcStats).writeState (state2.mCreatureStats);
+ }
+
const ESM::GameSetting *Npc::fMinWalkSpeed;
const ESM::GameSetting *Npc::fMaxWalkSpeed;
const ESM::GameSetting *Npc::fEncumberedMoveEffect;
@@ -1242,4 +1315,10 @@ namespace MWClass
const ESM::GameSetting *Npc::fJumpAcroMultiplier;
const ESM::GameSetting *Npc::fJumpRunMultiplier;
const ESM::GameSetting *Npc::fWereWolfRunMult;
+ const ESM::GameSetting *Npc::fKnockDownMult;
+ const ESM::GameSetting *Npc::iKnockDownOddsMult;
+ const ESM::GameSetting *Npc::iKnockDownOddsBase;
+ const ESM::GameSetting *Npc::fDamageStrengthBase;
+ const ESM::GameSetting *Npc::fDamageStrengthMult;
+
}
diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp
index c39ca42ef4..c54dd339a4 100644
--- a/apps/openmw/mwclass/npc.hpp
+++ b/apps/openmw/mwclass/npc.hpp
@@ -33,6 +33,11 @@ namespace MWClass
static const ESM::GameSetting *fJumpAcroMultiplier;
static const ESM::GameSetting *fJumpRunMultiplier;
static const ESM::GameSetting *fWereWolfRunMult;
+ static const ESM::GameSetting *fKnockDownMult;
+ static const ESM::GameSetting *iKnockDownOddsMult;
+ static const ESM::GameSetting *iKnockDownOddsBase;
+ static const ESM::GameSetting *fDamageStrengthBase;
+ static const ESM::GameSetting *fDamageStrengthMult;
public:
@@ -68,10 +73,14 @@ namespace MWClass
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
///< Return inventory store
+ virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; }
+
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 block(const MWWorld::Ptr &ptr) 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,
@@ -81,16 +90,6 @@ namespace MWClass
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.
@@ -138,7 +137,7 @@ namespace MWClass
///< 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;
@@ -147,6 +146,11 @@ namespace MWClass
virtual std::string getModel(const MWWorld::Ptr &ptr) const;
+ virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
+
+ /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
+ virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
+
virtual bool isActor() const {
return true;
}
@@ -154,6 +158,14 @@ namespace MWClass
virtual bool isNpc() const {
return true;
}
+
+ virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const;
+ ///< Read additional state from \a state into \a ptr.
+
+ virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const;
+ ///< Write additional state from \a ptr into \a state.
};
}
diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp
index e276c58aa5..d68db4e454 100644
--- a/apps/openmw/mwclass/potion.cpp
+++ b/apps/openmw/mwclass/potion.cpp
@@ -13,7 +13,6 @@
#include "../mwworld/cellstore.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/physicssystem.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/nullaction.hpp"
#include "../mwgui/tooltips.hpp"
@@ -133,7 +132,7 @@ namespace MWClass
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();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
int i=0;
@@ -153,6 +152,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -166,7 +166,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Potion> *ref =
ptr.get<ESM::Potion>();
- MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayerPtr();
// remove used potion (assume it is present in inventory)
ptr.getContainerStore()->remove(ptr, 1, actor);
diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp
index b54464acdc..4209c1431b 100644
--- a/apps/openmw/mwclass/probe.cpp
+++ b/apps/openmw/mwclass/probe.cpp
@@ -144,6 +144,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp
index ce2b4ff10c..5f2065c3cc 100644
--- a/apps/openmw/mwclass/repair.cpp
+++ b/apps/openmw/mwclass/repair.cpp
@@ -148,6 +148,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp
index 5e93e0d81b..af0234cd54 100644
--- a/apps/openmw/mwclass/weapon.cpp
+++ b/apps/openmw/mwclass/weapon.cpp
@@ -355,6 +355,7 @@ namespace MWClass
if (MWBase::Environment::get().getWindowManager()->getFullHelp()) {
text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner");
+ text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction");
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
}
@@ -428,12 +429,12 @@ namespace MWClass
return MWWorld::Ptr(&cell.mWeapons.insert(*ref), &cell);
}
- float Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ int Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Weapon> *ref =
ptr.get<ESM::Weapon>();
- return ref->mBase->mData.mEnchant/10.f;
+ return ref->mBase->mData.mEnchant;
}
bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const
diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp
index 181c637db6..db44cd2b71 100644
--- a/apps/openmw/mwclass/weapon.hpp
+++ b/apps/openmw/mwclass/weapon.hpp
@@ -84,7 +84,7 @@ namespace MWClass
virtual float getWeight (const MWWorld::Ptr& ptr) const;
- virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
+ virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const;
};
}
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
index 3951cedcbc..c9e8ad9551 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp
@@ -8,6 +8,7 @@
#include <components/esm/loaddial.hpp>
#include <components/esm/loadinfo.hpp>
+#include <components/esm/dialoguestate.hpp>
#include <components/compiler/exception.hpp>
#include <components/compiler/errorhandler.hpp>
@@ -30,7 +31,6 @@
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
-#include "../mwworld/player.hpp"
#include "../mwgui/dialogue.hpp"
@@ -126,6 +126,8 @@ namespace MWDialogue
void DialogueManager::startDialogue (const MWWorld::Ptr& actor)
{
mLastTopic = "";
+ mPermanentDispositionChange = 0;
+ mTemporaryDispositionChange = 0;
mChoice = -1;
mIsInChoice = false;
@@ -142,6 +144,7 @@ namespace MWDialogue
//setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI
updateTopics();
+ updateGlobals();
//greeting
const MWWorld::Store<ESM::Dialogue> &dialogs =
@@ -251,7 +254,7 @@ namespace MWDialogue
}
}
- void DialogueManager::executeTopic (const std::string& topic, bool randomResponse)
+ void DialogueManager::executeTopic (const std::string& topic)
{
Filter filter (mActor, mChoice, mTalkedTo);
@@ -262,12 +265,9 @@ namespace MWDialogue
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 = filter.search(dialogue, true);
+ if (info)
{
- const ESM::DialInfo* info = infos[randomResponse ? std::rand() % infos.size() : 0];
-
parseText (info->mResponse);
std::string title;
@@ -287,7 +287,7 @@ namespace MWDialogue
MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor);
win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title);
- MWBase::Environment::get().getJournal()->addTopic (topic, info->mId);
+ MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor));
executeScript (info->mResultScript);
@@ -300,6 +300,11 @@ namespace MWDialogue
}
}
+ void DialogueManager::updateGlobals()
+ {
+ MWBase::Environment::get().getWorld()->updateDialogueGlobals();
+ }
+
void DialogueManager::updateTopics()
{
std::list<std::string> keywordList;
@@ -416,7 +421,7 @@ namespace MWDialogue
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
// Apply disposition change to NPC's base disposition
- if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor);
npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
@@ -447,7 +452,7 @@ namespace MWDialogue
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);
+ MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor));
executeScript (info->mResultScript);
}
}
@@ -494,7 +499,7 @@ namespace MWDialogue
else if (curDisp + mTemporaryDispositionChange > 100)
mTemporaryDispositionChange = 100 - curDisp;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1);
std::string text;
@@ -509,7 +514,7 @@ namespace MWDialogue
text = "Bribe";
}
- executeTopic (text + (success ? " Success" : " Fail"), true);
+ executeTopic (text + (success ? " Success" : " Fail"));
}
int DialogueManager::getTemporaryDispositionChange() const
@@ -517,9 +522,19 @@ namespace MWDialogue
return mTemporaryDispositionChange;
}
- void DialogueManager::applyTemporaryDispositionChange(int delta)
+ void DialogueManager::applyDispositionChange(int delta)
{
+ int oldTemp = mTemporaryDispositionChange;
mTemporaryDispositionChange += delta;
+ // don't allow increasing beyond 100 or decreasing below 0
+ int curDisp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor);
+ if (curDisp + mTemporaryDispositionChange < 0)
+ mTemporaryDispositionChange = -curDisp;
+ else if (curDisp + mTemporaryDispositionChange > 100)
+ mTemporaryDispositionChange = 100 - curDisp;
+
+ int diff = mTemporaryDispositionChange - oldTemp;
+ mPermanentDispositionChange += diff;
}
bool DialogueManager::checkServiceRefused()
@@ -572,7 +587,43 @@ namespace MWDialogue
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
if(winMgr->getSubtitlesEnabled())
winMgr->messageBox(info->mResponse);
- sndMgr->say(actor, info->mSound);
+ if (!info->mSound.empty())
+ sndMgr->say(actor, info->mSound);
+ }
+ }
+
+ int DialogueManager::countSavedGameRecords() const
+ {
+ return 1; // known topics
+ }
+
+ void DialogueManager::write (ESM::ESMWriter& writer) const
+ {
+ ESM::DialogueState state;
+
+ for (std::map<std::string, bool>::const_iterator iter (mKnownTopics.begin());
+ iter!=mKnownTopics.end(); ++iter)
+ if (iter->second)
+ state.mKnownTopics.push_back (iter->first);
+
+ writer.startRecord (ESM::REC_DIAS);
+ state.save (writer);
+ writer.endRecord (ESM::REC_DIAS);
+ }
+
+ void DialogueManager::readRecord (ESM::ESMReader& reader, int32_t type)
+ {
+ if (type==ESM::REC_DIAS)
+ {
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ ESM::DialogueState state;
+ state.load (reader);
+
+ for (std::vector<std::string>::const_iterator iter (state.mKnownTopics.begin());
+ iter!=state.mKnownTopics.end(); ++iter)
+ if (store.get<ESM::Dialogue>().search (*iter))
+ mKnownTopics.insert (std::make_pair (*iter, true));
}
}
diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
index 1b7abed45a..cf8ea11764 100644
--- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
+++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp
@@ -40,11 +40,12 @@ namespace MWDialogue
void parseText (const std::string& text);
void updateTopics();
+ void updateGlobals();
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);
+ void executeTopic (const std::string& topic);
public:
@@ -76,7 +77,13 @@ namespace MWDialogue
virtual void persuade (int type);
virtual int getTemporaryDispositionChange () const;
- virtual void applyTemporaryDispositionChange (int delta);
+ virtual void applyDispositionChange (int delta);
+
+ virtual int countSavedGameRecords() const;
+
+ virtual void write (ESM::ESMWriter& writer) const;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type);
};
diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp
index 11dccde42d..3d45b85ce0 100644
--- a/apps/openmw/mwdialogue/filter.cpp
+++ b/apps/openmw/mwdialogue/filter.cpp
@@ -5,9 +5,9 @@
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp"
@@ -24,7 +24,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
// actor id
if (!info.mActor.empty())
{
- if ( Misc::StringUtils::lowerCase (info.mActor)!=MWWorld::Class::get (mActor).getId (mActor))
+ if ( !Misc::StringUtils::ciEqual(info.mActor, MWWorld::Class::get (mActor).getId (mActor)))
return false;
}
else if (isCreature)
@@ -41,7 +41,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
MWWorld::LiveCellRef<ESM::NPC> *cellRef = mActor.get<ESM::NPC>();
- if (Misc::StringUtils::lowerCase (info.mRace)!= Misc::StringUtils::lowerCase (cellRef->mBase->mRace))
+ if (!Misc::StringUtils::ciEqual(info.mRace, cellRef->mBase->mRace))
return false;
}
@@ -53,7 +53,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
MWWorld::LiveCellRef<ESM::NPC> *cellRef = mActor.get<ESM::NPC>();
- if ( Misc::StringUtils::lowerCase (info.mClass)!= Misc::StringUtils::lowerCase (cellRef->mBase->mClass))
+ if ( !Misc::StringUtils::ciEqual(info.mClass, cellRef->mBase->mClass))
return false;
}
@@ -92,7 +92,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const
bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const
{
- const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// check player faction
if (!info.mPcFaction.empty())
@@ -110,7 +110,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const
// check cell
if (!info.mCell.empty())
- if (Misc::StringUtils::lowerCase (player.getCell()->mCell->mName) != Misc::StringUtils::lowerCase (info.mCell))
+ if (!Misc::StringUtils::ciEqual(player.getCell()->mCell->mName, info.mCell))
return false;
return true;
@@ -133,7 +133,8 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert
if (isCreature)
return true;
- int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor);
+ int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor)
+ + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange();
// 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);
@@ -170,7 +171,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
// internally all globals are float :(
return select.selectCompare (
- MWBase::Environment::get().getWorld()->getGlobalVariable (select.getName()).mFloat);
+ MWBase::Environment::get().getWorld()->getGlobalFloat (select.getName()));
case SelectWrapper::Function_Local:
{
@@ -187,7 +188,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
int i = 0;
for (; i<static_cast<int> (script->mVarNames.size()); ++i)
- if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name)
+ if (Misc::StringUtils::ciEqual(script->mVarNames[i], name))
break;
if (i>=static_cast<int> (script->mVarNames.size()))
@@ -210,17 +211,17 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
case SelectWrapper::Function_PcHealthPercent:
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
- return select.selectCompare (ratio);
+ return select.selectCompare (static_cast<int>(ratio*100));
}
case SelectWrapper::Function_PcDynamicStat:
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
float value = MWWorld::Class::get (player).getCreatureStats (player).
getDynamic (select.getArgument()).getCurrent();
@@ -244,7 +245,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
switch (select.getFunction())
{
@@ -261,7 +262,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con
std::string name = select.getName();
for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter)
- if (Misc::StringUtils::lowerCase(iter->getCellRef().mRefID) == name)
+ if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, name))
sum += iter->getRefData().getCount();
return sum;
@@ -277,7 +278,8 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con
case SelectWrapper::Function_AiSetting:
- return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (select.getArgument());
+ return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (
+ (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified();
case SelectWrapper::Function_PcAttribute:
@@ -417,7 +419,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con
bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
switch (select.getFunction())
{
@@ -427,23 +429,23 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
case SelectWrapper::Function_NotId:
- return select.getName()!=Misc::StringUtils::lowerCase (MWWorld::Class::get (mActor).getId (mActor));
+ return !Misc::StringUtils::ciEqual(MWWorld::Class::get (mActor).getId (mActor), select.getName());
case SelectWrapper::Function_NotFaction:
- return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mFaction)!=select.getName();
+ return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mFaction, select.getName());
case SelectWrapper::Function_NotClass:
- return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mClass)!=select.getName();
+ return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mClass, select.getName());
case SelectWrapper::Function_NotRace:
- return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mRace)!=select.getName();
+ return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mRace, select.getName());
case SelectWrapper::Function_NotCell:
- return Misc::StringUtils::lowerCase (mActor.getCell()->mCell->mName)!=select.getName();
+ return !Misc::StringUtils::ciEqual(mActor.getCell()->mCell->mName, select.getName());
case SelectWrapper::Function_NotLocal:
{
@@ -460,7 +462,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
int i = 0;
for (; i < static_cast<int> (script->mVarNames.size()); ++i)
- if (Misc::StringUtils::lowerCase(script->mVarNames[i]) == name)
+ if (Misc::StringUtils::ciEqual(script->mVarNames[i], name))
break;
if (i >= static_cast<int> (script->mVarNames.size()))
@@ -476,8 +478,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
case SelectWrapper::Function_SameRace:
- return Misc::StringUtils::lowerCase (mActor.get<ESM::NPC>()->mBase->mRace)!=
- Misc::StringUtils::lowerCase (player.get<ESM::NPC>()->mBase->mRace);
+ return !Misc::StringUtils::ciEqual(mActor.get<ESM::NPC>()->mBase->mRace, player.get<ESM::NPC>()->mBase->mRace);
case SelectWrapper::Function_SameFaction:
@@ -505,14 +506,13 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
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();
+ return player.getClass().getNpcStats(player).getExpelled(faction);
}
case SelectWrapper::Function_PcVampire:
- return MWWorld::Class::get (player).getNpcStats (player).isVampire();
+ return MWWorld::Class::get (player).getCreatureStats(player).getMagicEffects().
+ get(ESM::MagicEffect::Vampirism).mMagnitude > 0;
case SelectWrapper::Function_TalkedToPc:
@@ -524,7 +524,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
case SelectWrapper::Function_Detected:
- return MWWorld::Class::get (mActor).hasDetected (mActor, player);
+ return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor);
case SelectWrapper::Function_Attacked:
@@ -536,7 +536,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
case SelectWrapper::Function_CreatureTargetted:
- return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted();
+ return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted();
case SelectWrapper::Function_Werewolf:
diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp
index 5ffde54991..9463e4c45b 100644
--- a/apps/openmw/mwdialogue/journalentry.cpp
+++ b/apps/openmw/mwdialogue/journalentry.cpp
@@ -3,6 +3,8 @@
#include <stdexcept>
+#include <components/esm/journalentry.hpp>
+
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -10,23 +12,55 @@
namespace MWDialogue
{
- JournalEntry::JournalEntry() {}
-
- JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId)
- : mTopic (topic), mInfoId (infoId)
- {}
+ Entry::Entry() {}
- std::string JournalEntry::getText (const MWWorld::ESMStore& store) const
+ Entry::Entry (const std::string& topic, const std::string& infoId)
+ : mInfoId (infoId)
{
const ESM::Dialogue *dialogue =
- store.get<ESM::Dialogue>().find (mTopic);
+ 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->mId == mInfoId)
- return iter->mResponse;
+ {
+ /// \todo text replacement
+ mText = iter->mResponse;
+ return;
+ }
+
+ throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + topic);
+ }
+
+ Entry::Entry (const ESM::JournalEntry& record) : mInfoId (record.mInfo), mText (record.mText), mActorName(record.mActorName) {}
+
+ std::string Entry::getText() const
+ {
+ return mText;
+ }
+
+ void Entry::write (ESM::JournalEntry& entry) const
+ {
+ entry.mInfo = mInfoId;
+ entry.mText = mText;
+ entry.mActorName = mActorName;
+ }
+
+
+ JournalEntry::JournalEntry() {}
+
+ JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId)
+ : Entry (topic, infoId), mTopic (topic)
+ {}
+
+ JournalEntry::JournalEntry (const ESM::JournalEntry& record)
+ : Entry (record), mTopic (record.mTopic)
+ {}
- throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + mTopic);
+ void JournalEntry::write (ESM::JournalEntry& entry) const
+ {
+ Entry::write (entry);
+ entry.mTopic = mTopic;
}
JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index)
@@ -49,6 +83,7 @@ namespace MWDialogue
throw std::runtime_error ("unknown journal index for topic " + topic);
}
+
StampedJournalEntry::StampedJournalEntry()
: mDay (0), mMonth (0), mDayOfMonth (0)
{}
@@ -58,11 +93,24 @@ namespace MWDialogue
: JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth)
{}
+ StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record)
+ : JournalEntry (record), mDay (record.mDay), mMonth (record.mMonth),
+ mDayOfMonth (record.mDayOfMonth)
+ {}
+
+ void StampedJournalEntry::write (ESM::JournalEntry& entry) const
+ {
+ JournalEntry::write (entry);
+ entry.mDay = mDay;
+ entry.mMonth = mMonth;
+ entry.mDayOfMonth = mDayOfMonth;
+ }
+
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;
+ int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed");
+ int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month");
+ int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day");
return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth);
}
diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp
index 9d009b48b2..a77ba4f7cf 100644
--- a/apps/openmw/mwdialogue/journalentry.hpp
+++ b/apps/openmw/mwdialogue/journalentry.hpp
@@ -3,24 +3,45 @@
#include <string>
-namespace MWWorld
+namespace ESM
{
- struct ESMStore;
+ struct JournalEntry;
}
namespace MWDialogue
{
- /// \brief A quest or dialogue entry
- struct JournalEntry
+ /// \brief Basic quest/dialogue/topic entry
+ struct Entry
{
- std::string mTopic;
std::string mInfoId;
+ std::string mText;
+ std::string mActorName; // optional
+
+ Entry();
+
+ Entry (const std::string& topic, const std::string& infoId);
+
+ Entry (const ESM::JournalEntry& record);
+
+ std::string getText() const;
+
+ void write (ESM::JournalEntry& entry) const;
+ };
+
+ /// \brief A dialogue entry
+ ///
+ /// Same as entry, but store TopicID
+ struct JournalEntry : public Entry
+ {
+ std::string mTopic;
JournalEntry();
JournalEntry (const std::string& topic, const std::string& infoId);
- std::string getText (const MWWorld::ESMStore& store) const;
+ JournalEntry (const ESM::JournalEntry& record);
+
+ void write (ESM::JournalEntry& entry) const;
static JournalEntry makeFromQuest (const std::string& topic, int index);
@@ -39,6 +60,10 @@ namespace MWDialogue
StampedJournalEntry (const std::string& topic, const std::string& infoId,
int day, int month, int dayOfMonth);
+ StampedJournalEntry (const ESM::JournalEntry& record);
+
+ void write (ESM::JournalEntry& entry) const;
+
static StampedJournalEntry makeFromQuest (const std::string& topic, int index);
};
}
diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp
index 23cfb5fdd1..26383b3a7b 100644
--- a/apps/openmw/mwdialogue/journalimp.cpp
+++ b/apps/openmw/mwdialogue/journalimp.cpp
@@ -1,6 +1,13 @@
#include "journalimp.hpp"
+#include <iterator>
+
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/esmreader.hpp>
+#include <components/esm/queststate.hpp>
+#include <components/esm/journalentry.hpp>
+
#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
@@ -26,6 +33,38 @@ namespace MWDialogue
return iter->second;
}
+ Topic& Journal::getTopic (const std::string& id)
+ {
+ TTopicContainer::iterator iter = mTopics.find (id);
+
+ if (iter==mTopics.end())
+ {
+ std::pair<TTopicContainer::iterator, bool> result
+ = mTopics.insert (std::make_pair (id, Topic (id)));
+
+ iter = result.first;
+ }
+
+ return iter->second;
+ }
+
+ bool Journal::isThere (const std::string& topicId, const std::string& infoId) const
+ {
+ if (const ESM::Dialogue *dialogue =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().search (topicId))
+ {
+ if (infoId.empty())
+ return true;
+
+ for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
+ iter!=dialogue->mInfo.end(); ++iter)
+ if (iter->mId == infoId)
+ return true;
+ }
+
+ return false;
+ }
+
Journal::Journal()
{}
@@ -64,19 +103,13 @@ namespace MWDialogue
quest.setIndex (index);
}
- void Journal::addTopic (const std::string& topicId, const std::string& infoId)
+ void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName)
{
- 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;
- }
+ Topic& topic = getTopic (topicId);
- iter->second.addEntry (JournalEntry (topicId, infoId));
+ JournalEntry entry(topicId, infoId);
+ entry.mActorName = actorName;
+ topic.addEntry (entry);
}
int Journal::getJournalIndex (const std::string& id) const
@@ -118,4 +151,106 @@ namespace MWDialogue
{
return mTopics.end();
}
+
+ int Journal::countSavedGameRecords() const
+ {
+ int count = static_cast<int> (mQuests.size());
+
+ for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter)
+ count += std::distance (iter->second.begin(), iter->second.end());
+
+ count += std::distance (mJournal.begin(), mJournal.end());
+
+ for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter)
+ count += std::distance (iter->second.begin(), iter->second.end());
+
+ return count;
+ }
+
+ void Journal::write (ESM::ESMWriter& writer) const
+ {
+ for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter)
+ {
+ const Quest& quest = iter->second;
+
+ ESM::QuestState state;
+ quest.write (state);
+ writer.startRecord (ESM::REC_QUES);
+ state.save (writer);
+ writer.endRecord (ESM::REC_QUES);
+
+ for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter)
+ {
+ ESM::JournalEntry entry;
+ entry.mType = ESM::JournalEntry::Type_Quest;
+ entry.mTopic = quest.getTopic();
+ iter->write (entry);
+ writer.startRecord (ESM::REC_JOUR);
+ entry.save (writer);
+ writer.endRecord (ESM::REC_JOUR);
+ }
+ }
+
+ for (TEntryIter iter (mJournal.begin()); iter!=mJournal.end(); ++iter)
+ {
+ ESM::JournalEntry entry;
+ entry.mType = ESM::JournalEntry::Type_Journal;
+ iter->write (entry);
+ writer.startRecord (ESM::REC_JOUR);
+ entry.save (writer);
+ writer.endRecord (ESM::REC_JOUR);
+ }
+
+ for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter)
+ {
+ const Topic& topic = iter->second;
+
+ for (Topic::TEntryIter iter (topic.begin()); iter!=topic.end(); ++iter)
+ {
+ ESM::JournalEntry entry;
+ entry.mType = ESM::JournalEntry::Type_Topic;
+ entry.mTopic = topic.getTopic();
+ iter->write (entry);
+ writer.startRecord (ESM::REC_JOUR);
+ entry.save (writer);
+ writer.endRecord (ESM::REC_JOUR);
+ }
+ }
+ }
+
+ void Journal::readRecord (ESM::ESMReader& reader, int32_t type)
+ {
+ if (type==ESM::REC_JOUR)
+ {
+ ESM::JournalEntry record;
+ record.load (reader);
+
+ if (isThere (record.mTopic, record.mInfo))
+ switch (record.mType)
+ {
+ case ESM::JournalEntry::Type_Quest:
+
+ getQuest (record.mTopic).insertEntry (record);
+ break;
+
+ case ESM::JournalEntry::Type_Journal:
+
+ mJournal.push_back (record);
+ break;
+
+ case ESM::JournalEntry::Type_Topic:
+
+ getTopic (record.mTopic).insertEntry (record);
+ break;
+ }
+ }
+ else if (type==ESM::REC_QUES)
+ {
+ ESM::QuestState record;
+ record.load (reader);
+
+ if (isThere (record.mTopic))
+ mQuests.insert (std::make_pair (record.mTopic, record));
+ }
+ }
}
diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp
index f4f8eb1c29..1b4803ba21 100644
--- a/apps/openmw/mwdialogue/journalimp.hpp
+++ b/apps/openmw/mwdialogue/journalimp.hpp
@@ -15,8 +15,14 @@ namespace MWDialogue
TQuestContainer mQuests;
TTopicContainer mTopics;
+ private:
+
Quest& getQuest (const std::string& id);
+ Topic& getTopic (const std::string& id);
+
+ bool isThere (const std::string& topicId, const std::string& infoId = "") const;
+
public:
Journal();
@@ -32,7 +38,7 @@ namespace MWDialogue
virtual int getJournalIndex (const std::string& id) const;
///< Get the journal index.
- virtual void addTopic (const std::string& topicId, const std::string& infoId);
+ virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName);
virtual TEntryIter begin() const;
///< Iterator pointing to the begin of the main journal.
@@ -55,6 +61,12 @@ namespace MWDialogue
virtual TTopicIter topicEnd() const;
///< Iterator pointing past the last topic.
+
+ virtual int countSavedGameRecords() const;
+
+ virtual void write (ESM::ESMWriter& writer) const;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type);
};
}
diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp
index 5e2739be16..a5411b7479 100644
--- a/apps/openmw/mwdialogue/quest.cpp
+++ b/apps/openmw/mwdialogue/quest.cpp
@@ -1,6 +1,8 @@
#include "quest.hpp"
+#include <components/esm/queststate.hpp>
+
#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
@@ -16,7 +18,11 @@ namespace MWDialogue
: Topic (topic), mIndex (0), mFinished (false)
{}
- const std::string Quest::getName() const
+ Quest::Quest (const ESM::QuestState& state)
+ : Topic (state.mTopic), mIndex (state.mState), mFinished (state.mFinished!=0)
+ {}
+
+ std::string Quest::getName() const
{
const ESM::Dialogue *dialogue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
@@ -39,21 +45,24 @@ namespace MWDialogue
const ESM::Dialogue *dialogue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
+ bool found=false;
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;
+ found = true;
+ // Don't return here. Quest status may actually be in a different info record, since we don't merge these (yet?)
}
- throw std::runtime_error ("unknown journal index for topic " + mTopic);
+ if (found)
+ mIndex = index;
+ else
+ throw std::runtime_error ("unknown journal index for topic " + mTopic);
}
bool Quest::isFinished() const
@@ -79,12 +88,20 @@ namespace MWDialogue
if (index==-1)
throw std::runtime_error ("unknown journal entry for topic " + mTopic);
- setIndex (index);
+ if (index > mIndex)
+ setIndex (index);
for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
- if (*iter==entry.mInfoId)
+ if (iter->mInfoId==entry.mInfoId)
return;
- mEntries.push_back (entry.mInfoId);
+ mEntries.push_back (entry); // we want slicing here
+ }
+
+ void Quest::write (ESM::QuestState& state) const
+ {
+ state.mTopic = getTopic();
+ state.mState = mIndex;
+ state.mFinished = mFinished;
}
}
diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp
index 3afa81fac0..40824f398e 100644
--- a/apps/openmw/mwdialogue/quest.hpp
+++ b/apps/openmw/mwdialogue/quest.hpp
@@ -3,9 +3,14 @@
#include "topic.hpp"
+namespace ESM
+{
+ struct QuestState;
+}
+
namespace MWDialogue
{
- /// \brief A quest in progress or a compelted quest
+ /// \brief A quest in progress or a completed quest
class Quest : public Topic
{
int mIndex;
@@ -17,13 +22,15 @@ namespace MWDialogue
Quest (const std::string& topic);
- const std::string getName() const;
+ Quest (const ESM::QuestState& state);
+
+ virtual 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.
+ ///< Calling this function with a non-existent index will throw an exception.
bool isFinished() const;
@@ -31,6 +38,8 @@ namespace MWDialogue
///< Add entry and adjust index accordingly.
///
/// \note Redundant entries are ignored, but the index is still adjusted.
+
+ void write (ESM::QuestState& state) const;
};
}
diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp
index 3253b20d66..f7df305c70 100644
--- a/apps/openmw/mwdialogue/topic.cpp
+++ b/apps/openmw/mwdialogue/topic.cpp
@@ -1,6 +1,9 @@
#include "topic.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
#include "../mwworld/esmstore.hpp"
namespace MWDialogue
@@ -9,7 +12,8 @@ namespace MWDialogue
{}
Topic::Topic (const std::string& topic)
- : mTopic (topic)
+ : mTopic (topic), mName (
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (topic)->mId)
{}
Topic::~Topic()
@@ -20,11 +24,29 @@ namespace MWDialogue
if (entry.mTopic!=mTopic)
throw std::runtime_error ("topic does not match: " + mTopic);
- for (TEntryIter iter = begin(); iter!=end(); ++iter)
- if (*iter==entry.mInfoId)
+ // bail out if we already have heard this
+ for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it)
+ {
+ if (it->mInfoId == entry.mInfoId)
return;
+ }
+
+ mEntries.push_back (entry); // we want slicing here
+ }
+
+ void Topic::insertEntry (const ESM::JournalEntry& entry)
+ {
+ mEntries.push_back (entry);
+ }
- mEntries.push_back (entry.mInfoId);
+ std::string Topic::getTopic() const
+ {
+ return mTopic;
+ }
+
+ std::string Topic::getName() const
+ {
+ return mName;
}
Topic::TEntryIter Topic::begin() const
diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp
index c3f0baabc2..02fa6d5246 100644
--- a/apps/openmw/mwdialogue/topic.hpp
+++ b/apps/openmw/mwdialogue/topic.hpp
@@ -6,6 +6,11 @@
#include "journalentry.hpp"
+namespace ESM
+{
+ struct JournalEntry;
+}
+
namespace MWDialogue
{
/// \brief Collection of seen responses for a topic
@@ -13,13 +18,14 @@ namespace MWDialogue
{
public:
- typedef std::vector<std::string> TEntryContainer;
+ typedef std::vector<Entry> TEntryContainer;
typedef TEntryContainer::const_iterator TEntryIter;
protected:
std::string mTopic;
- TEntryContainer mEntries; // info-IDs
+ std::string mName;
+ TEntryContainer mEntries;
public:
@@ -34,7 +40,13 @@ namespace MWDialogue
///
/// \note Redundant entries are ignored.
- std::string const & getName () const { return mTopic; }
+ void insertEntry (const ESM::JournalEntry& entry);
+ ///< Add entry without checking for redundant entries or modifying the state of the
+ /// topic otherwise
+
+ std::string getTopic() const;
+
+ virtual std::string getName() const;
TEntryIter begin() const;
///< Iterator pointing to the begin of the journal for this topic.
diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp
index 09f692e4f5..ddbd3f120d 100644
--- a/apps/openmw/mwgui/alchemywindow.cpp
+++ b/apps/openmw/mwgui/alchemywindow.cpp
@@ -8,7 +8,6 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "inventoryitemmodel.hpp"
@@ -143,9 +142,9 @@ namespace MWGui
void AlchemyWindow::open()
{
- mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr());
- InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr());
mSortModel = new SortFilterItemModel(model);
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
mItemView->setModel (mSortModel);
@@ -154,7 +153,7 @@ namespace MWGui
int index = 0;
- mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr());
for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools());
iter!=mAlchemy.endTools() && index<static_cast<int> (mApparatus.size()); ++iter, ++index)
diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp
index 9656067097..7f58309ba0 100644
--- a/apps/openmw/mwgui/birth.cpp
+++ b/apps/openmw/mwgui/birth.cpp
@@ -1,6 +1,5 @@
#include "birth.hpp"
-#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include "../mwbase/environment.hpp"
@@ -34,8 +33,7 @@ namespace MWGui
getWidget(mBirthList, "BirthsignList");
mBirthList->setScrollVisible(true);
- mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
- mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
+ mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onAccept);
mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
MyGUI::Button* backButton;
@@ -77,7 +75,7 @@ namespace MWGui
size_t count = mBirthList->getItemCount();
for (size_t i = 0; i < count; ++i)
{
- if (boost::iequals(*mBirthList->getItemDataAt<std::string>(i), birthId))
+ if (Misc::StringUtils::ciEqual(*mBirthList->getItemDataAt<std::string>(i), birthId))
{
mBirthList->setIndexSelected(i);
MyGUI::Button* okButton;
@@ -98,6 +96,14 @@ namespace MWGui
eventDone(this);
}
+ void BirthDialog::onAccept(MyGUI::ListBox *_sender, size_t _index)
+ {
+ onSelectBirth(_sender, _index);
+ if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
void BirthDialog::onBackClicked(MyGUI::Widget* _sender)
{
eventBack();
@@ -112,7 +118,7 @@ namespace MWGui
getWidget(okButton, "OKButton");
const std::string *birthId = mBirthList->getItemDataAt<std::string>(_index);
- if (boost::iequals(mCurrentBirthId, *birthId))
+ if (Misc::StringUtils::ciEqual(mCurrentBirthId, *birthId))
return;
mCurrentBirthId = *birthId;
@@ -148,7 +154,7 @@ namespace MWGui
mBirthList->setIndexSelected(index);
mCurrentBirthId = it2->first;
}
- else if (boost::iequals(it2->first, mCurrentBirthId))
+ else if (Misc::StringUtils::ciEqual(it2->first, mCurrentBirthId))
{
mBirthList->setIndexSelected(index);
}
diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp
index cc958ddcaa..20a64c78ce 100644
--- a/apps/openmw/mwgui/birth.hpp
+++ b/apps/openmw/mwgui/birth.hpp
@@ -38,6 +38,7 @@ namespace MWGui
protected:
void onSelectBirth(MyGUI::ListBox* _sender, size_t _index);
+ void onAccept(MyGUI::ListBox* _sender, size_t index);
void onOkClicked(MyGUI::Widget* _sender);
void onBackClicked(MyGUI::Widget* _sender);
diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp
index efe089689b..98d963b229 100644
--- a/apps/openmw/mwgui/bookwindow.cpp
+++ b/apps/openmw/mwgui/bookwindow.cpp
@@ -8,7 +8,6 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/actiontake.hpp"
-#include "../mwworld/player.hpp"
#include "formatting.hpp"
@@ -44,6 +43,11 @@ namespace MWGui
adjustButton(mNextPageButton);
adjustButton(mPrevPageButton);
+ mLeftPage->setNeedMouseFocus(true);
+ mLeftPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel);
+ mRightPage->setNeedMouseFocus(true);
+ mRightPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel);
+
if (mNextPageButton->getSize().width == 64)
{
// english button has a 7 pixel wide strip of garbage on its right edge
@@ -54,6 +58,14 @@ namespace MWGui
center();
}
+ void BookWindow::onMouseWheel(MyGUI::Widget *_sender, int _rel)
+ {
+ if (_rel < 0)
+ nextPage();
+ else
+ prevPage();
+ }
+
void BookWindow::clearPages()
{
for (std::vector<MyGUI::Widget*>::iterator it=mPages.begin();
@@ -89,6 +101,7 @@ namespace MWGui
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));
+ pageWidget->setNeedMouseFocus(false);
parser.parsePage(*it, pageWidget, mLeftPage->getSize().width);
mPages.push_back(pageWidget);
++i;
@@ -124,7 +137,7 @@ namespace MWGui
MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0);
MWWorld::ActionTake take(mBook);
- take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr());
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book);
}
diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp
index ef87dd9c4c..f8821ab50b 100644
--- a/apps/openmw/mwgui/bookwindow.hpp
+++ b/apps/openmw/mwgui/bookwindow.hpp
@@ -25,6 +25,7 @@ namespace MWGui
void onPrevPageButtonClicked (MyGUI::Widget* sender);
void onCloseButtonClicked (MyGUI::Widget* sender);
void onTakeButtonClicked (MyGUI::Widget* sender);
+ void onMouseWheel(MyGUI::Widget* _sender, int _rel);
void updatePages();
void clearPages();
diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp
index b829f219d7..5526bd26df 100644
--- a/apps/openmw/mwgui/charactercreation.cpp
+++ b/apps/openmw/mwgui/charactercreation.cpp
@@ -10,10 +10,9 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/fallback.hpp"
-#include "../mwworld/player.hpp"
namespace
{
@@ -47,10 +46,9 @@ namespace
void updatePlayerHealth()
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats(player);
-
- creatureStats.updateHealth();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player);
+ npcStats.updateHealth();
}
}
@@ -75,7 +73,7 @@ namespace MWGui
mGenerateClassSpecializations[2] = 0;
}
- void CharacterCreation::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value)
{
if (mReviewDialog)
{
@@ -113,7 +111,7 @@ namespace MWGui
}
}
- void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value)
+ void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value)
{
if (mReviewDialog)
mReviewDialog->setSkillValue(parSkill, value);
@@ -220,8 +218,8 @@ namespace MWGui
mReviewDialog->setBirthSign(mPlayerBirthSignId);
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- MWMechanics::CreatureStats stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
mReviewDialog->setHealth ( stats.getHealth() );
mReviewDialog->setMagicka( stats.getMagicka() );
@@ -229,8 +227,8 @@ namespace MWGui
}
{
- std::map<int, MWMechanics::Stat<int> > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
- for (std::map<int, MWMechanics::Stat<int> >::iterator it = attributes.begin();
+ std::map<int, MWMechanics::AttributeValue > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues();
+ for (std::map<int, MWMechanics::AttributeValue >::iterator it = attributes.begin();
it != attributes.end(); ++it)
{
mReviewDialog->setAttribute(static_cast<ESM::Attribute::AttributeID> (it->first), it->second);
@@ -238,8 +236,8 @@ namespace MWGui
}
{
- std::map<int, MWMechanics::Stat<float> > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues();
- for (std::map<int, MWMechanics::Stat<float> >::iterator it = skills.begin();
+ std::map<int, MWMechanics::SkillValue > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues();
+ for (std::map<int, MWMechanics::SkillValue >::iterator it = skills.begin();
it != skills.end(); ++it)
{
mReviewDialog->setSkillValue(static_cast<ESM::Skill::SkillEnum> (it->first), it->second);
diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp
index b80aaae41c..924f40c282 100644
--- a/apps/openmw/mwgui/charactercreation.hpp
+++ b/apps/openmw/mwgui/charactercreation.hpp
@@ -31,9 +31,9 @@ namespace MWGui
//Show a dialog
void spawnDialog(const char id);
- void setValue (const std::string& id, const MWMechanics::Stat<int>& value);
+ void setValue (const std::string& id, const MWMechanics::AttributeValue& 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 setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void configureSkills (const SkillList& major, const SkillList& minor);
void doRenderUpdate();
diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp
index 6c46f21763..1c8cc78403 100644
--- a/apps/openmw/mwgui/class.cpp
+++ b/apps/openmw/mwgui/class.cpp
@@ -1,7 +1,5 @@
#include "class.hpp"
-#include <boost/algorithm/string.hpp>
-
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -29,11 +27,12 @@ namespace MWGui
MyGUI::Button* backButton;
getWidget(backButton, "BackButton");
+ backButton->setCaptionWithReplacing("#{sMessageQuestionAnswer3}");
backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked);
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
- okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
+ okButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}");
okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked);
}
@@ -83,8 +82,7 @@ namespace MWGui
getWidget(mClassList, "ClassList");
mClassList->setScrollVisible(true);
- mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
- mClassList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
+ mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onAccept);
mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass);
getWidget(mClassImage, "ClassImage");
@@ -127,7 +125,7 @@ namespace MWGui
size_t count = mClassList->getItemCount();
for (size_t i = 0; i < count; ++i)
{
- if (boost::iequals(*mClassList->getItemDataAt<std::string>(i), classId))
+ if (Misc::StringUtils::ciEqual(*mClassList->getItemDataAt<std::string>(i), classId))
{
mClassList->setIndexSelected(i);
MyGUI::Button* okButton;
@@ -153,6 +151,14 @@ namespace MWGui
eventBack();
}
+ void PickClassDialog::onAccept(MyGUI::ListBox* _sender, size_t _index)
+ {
+ onSelectClass(_sender, _index);
+ if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index)
{
if (_index == MyGUI::ITEM_NONE)
@@ -162,7 +168,7 @@ namespace MWGui
getWidget(okButton, "OKButton");
const std::string *classId = mClassList->getItemDataAt<std::string>(_index);
- if (boost::iequals(mCurrentClassId, *classId))
+ if (Misc::StringUtils::ciEqual(mCurrentClassId, *classId))
return;
mCurrentClassId = *classId;
@@ -192,7 +198,7 @@ namespace MWGui
mCurrentClassId = id;
mClassList->setIndexSelected(index);
}
- else if (boost::iequals(id, mCurrentClassId))
+ else if (Misc::StringUtils::ciEqual(id, mCurrentClassId))
{
mClassList->setIndexSelected(index);
}
diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp
index e74370a4cd..f78f7541b2 100644
--- a/apps/openmw/mwgui/class.hpp
+++ b/apps/openmw/mwgui/class.hpp
@@ -111,6 +111,7 @@ namespace MWGui
protected:
void onSelectClass(MyGUI::ListBox* _sender, size_t _index);
+ void onAccept(MyGUI::ListBox* _sender, size_t _index);
void onOkClicked(MyGUI::Widget* _sender);
void onBackClicked(MyGUI::Widget* _sender);
diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp
index 3212ed7018..bb6cf28006 100644
--- a/apps/openmw/mwgui/companionitemmodel.cpp
+++ b/apps/openmw/mwgui/companionitemmodel.cpp
@@ -12,7 +12,7 @@ namespace MWGui
void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
{
- if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
@@ -23,7 +23,7 @@ namespace MWGui
void CompanionItemModel::removeItem (const ItemStack& item, size_t count)
{
- if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp
index b8d20709d9..f3805b255a 100644
--- a/apps/openmw/mwgui/console.cpp
+++ b/apps/openmw/mwgui/console.cpp
@@ -119,8 +119,6 @@ namespace MWGui
// Set up the log window
mHistory->setOverflowToTheLeft(true);
- mHistory->setEditStatic(true);
- mHistory->setVisibleVScroll(true);
// compiler
Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts);
@@ -215,7 +213,7 @@ namespace MWGui
{
std::vector<std::string> matches;
listNames();
- mCommandLine->setCaption(complete( mCommandLine->getCaption(), matches ));
+ mCommandLine->setCaption(complete( mCommandLine->getOnlyText(), matches ));
#if 0
int i = 0;
for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end(); ++it,++i )
@@ -234,7 +232,7 @@ namespace MWGui
{
// If the user was editing a string, store it for later
if(mCurrent == mCommandHistory.end())
- mEditString = mCommandLine->getCaption();
+ mEditString = mCommandLine->getOnlyText();
if(mCurrent != mCommandHistory.begin())
{
@@ -259,7 +257,7 @@ namespace MWGui
void Console::acceptCommand(MyGUI::EditBox* _sender)
{
- const std::string &cm = mCommandLine->getCaption();
+ const std::string &cm = mCommandLine->getOnlyText();
if(cm.empty()) return;
// Add the command to the history, and set the current pointer to
diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp
index b7c6e3367d..7e7ad5ec26 100644
--- a/apps/openmw/mwgui/container.cpp
+++ b/apps/openmw/mwgui/container.cpp
@@ -6,9 +6,13 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
+#include "../mwworld/containerstore.hpp"
+
+#include "../mwmechanics/pickpocket.hpp"
#include "countdialog.hpp"
#include "tradewindow.hpp"
@@ -123,6 +127,7 @@ namespace MWGui
, mSelectedItem(-1)
, mModel(NULL)
, mSortModel(NULL)
+ , mPickpocketDetected(false)
{
getWidget(mDisposeCorpseButton, "DisposeCorpseButton");
getWidget(mTakeButton, "TakeButton");
@@ -134,6 +139,7 @@ namespace MWGui
mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked);
mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked);
+ mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ContainerWindow::onKeyPressed);
mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked);
setCoord(200,0,600,300);
@@ -171,6 +177,9 @@ namespace MWGui
void ContainerWindow::dragItem(MyGUI::Widget* sender, int count)
{
+ if (!onTakeItem(mModel->getItem(mSelectedItem), count))
+ return;
+
mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count);
}
@@ -208,12 +217,13 @@ namespace MWGui
void ContainerWindow::open(const MWWorld::Ptr& container, bool loot)
{
+ mPickpocketDetected = false;
mPtr = container;
if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot)
{
// we are stealing stuff
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mModel = new PickpocketItemModel(player, new InventoryItemModel(container));
}
else
@@ -225,11 +235,46 @@ namespace MWGui
mItemView->setModel (mSortModel);
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton);
+
// Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last
// or we end up using a possibly invalid model.
setTitle(MWWorld::Class::get(container).getName(container));
}
+ void ContainerWindow::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char)
+ {
+ if (_key == MyGUI::KeyCode::Space)
+ onCloseButtonClicked(mCloseButton);
+ if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter)
+ onTakeAllButtonClicked(mTakeButton);
+ }
+
+ void ContainerWindow::close()
+ {
+ WindowBase::close();
+
+ if (dynamic_cast<PickpocketItemModel*>(mModel)
+ // Make sure we were actually closed, rather than just temporarily hidden (e.g. console or main menu opened)
+ && !MWBase::Environment::get().getWindowManager()->containsMode(GM_Container)
+ // If it was already detected while taking an item, no need to check now
+ && !mPickpocketDetected
+ )
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ MWMechanics::Pickpocket pickpocket(player, mPtr);
+ if (pickpocket.finish())
+ {
+ MWBase::Environment::get().getMechanicsManager()->reportCrime(
+ player, mPtr, MWBase::MechanicsManager::OT_Pickpocket);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container);
+ MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
+ mPickpocketDetected = true;
+ return;
+ }
+ }
+ }
+
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
{
if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop)
@@ -255,8 +300,13 @@ namespace MWGui
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);
+ const ItemStack& item = mModel->getItem(i);
+
+ if (!onTakeItem(item, item.mCount))
+ break;
+
+ playerModel->copyItem(item, item.mCount);
+ mModel->removeItem(item, item.mCount);
}
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
@@ -283,4 +333,30 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
}
+ bool ContainerWindow::onTakeItem(const ItemStack &item, int count)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ if (dynamic_cast<PickpocketItemModel*>(mModel))
+ {
+ MWMechanics::Pickpocket pickpocket(player, mPtr);
+ if (pickpocket.pick(item.mBase, count))
+ {
+ int value = item.mBase.getClass().getValue(item.mBase) * count;
+ MWBase::Environment::get().getMechanicsManager()->reportCrime(
+ player, MWWorld::Ptr(), MWBase::MechanicsManager::OT_Theft, value);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container);
+ MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
+ mPickpocketDetected = true;
+ return false;
+ }
+ else
+ player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1);
+ }
+ else
+ {
+ MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count);
+ }
+ return true;
+ }
+
}
diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp
index 243f77aa56..ce4707af6b 100644
--- a/apps/openmw/mwgui/container.hpp
+++ b/apps/openmw/mwgui/container.hpp
@@ -52,10 +52,13 @@ namespace MWGui
ContainerWindow(DragAndDrop* dragAndDrop);
void open(const MWWorld::Ptr& container, bool loot=false);
+ virtual void close();
private:
DragAndDrop* mDragAndDrop;
+ bool mPickpocketDetected;
+
MWGui::ItemView* mItemView;
SortFilterItemModel* mSortModel;
ItemModel* mModel;
@@ -72,6 +75,10 @@ namespace MWGui
void onCloseButtonClicked(MyGUI::Widget* _sender);
void onTakeAllButtonClicked(MyGUI::Widget* _sender);
void onDisposeCorpseButtonClicked(MyGUI::Widget* sender);
+ void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char);
+
+ /// @return is taking the item allowed?
+ bool onTakeItem(const ItemStack& item, int count);
virtual void onReferenceUnavailable();
};
diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp
index 6b0fbd8903..bcb8440bfb 100644
--- a/apps/openmw/mwgui/containeritemmodel.cpp
+++ b/apps/openmw/mwgui/containeritemmodel.cpp
@@ -76,10 +76,7 @@ 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);
+ source.getClass().getContainerStore(source).add(item.mBase, count, source);
}
void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index 914302d84e..481c983142 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -12,7 +12,6 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwdialogue/dialoguemanagerimp.hpp"
@@ -21,7 +20,6 @@
#include "list.hpp"
#include "tradewindow.hpp"
#include "spellbuyingwindow.hpp"
-#include "inventorywindow.hpp"
#include "travelwindow.hpp"
#include "bookpage.hpp"
@@ -69,24 +67,24 @@ namespace MWGui
void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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)
{
- player.getClass().getContainerStore(player).remove("gold_001", 10, player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player);
type = MWBase::MechanicsManager::PT_Bribe10;
}
else if (sender == mBribe100Button)
{
- player.getClass().getContainerStore(player).remove("gold_001", 100, player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player);
type = MWBase::MechanicsManager::PT_Bribe100;
}
else /*if (sender == mBribe1000Button)*/
{
- player.getClass().getContainerStore(player).remove("gold_001", 1000, player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player);
type = MWBase::MechanicsManager::PT_Bribe1000;
}
@@ -100,7 +98,8 @@ namespace MWGui
WindowModal::open();
center();
- int playerGold = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
mBribe10Button->setEnabled (playerGold >= 10);
mBribe100Button->setEnabled (playerGold >= 100);
@@ -546,7 +545,7 @@ namespace MWGui
for (size_t i=0; i<mTopicsList->getItemCount(); ++i)
{
std::string item = mTopicsList->getItemNameAt(i);
- if (Misc::StringUtils::lowerCase(item) == title)
+ if (Misc::StringUtils::ciEqual(item, title))
{
realTitle = item;
break;
diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp
index d2e914d17e..92205c3e99 100644
--- a/apps/openmw/mwgui/enchantingdialog.cpp
+++ b/apps/openmw/mwgui/enchantingdialog.cpp
@@ -5,13 +5,13 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
-#include "../mwworld/player.hpp"
-#include "../mwworld/manualref.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/containerstore.hpp"
#include "itemselection.hpp"
#include "container.hpp"
-#include "inventorywindow.hpp"
#include "sortfilteritemmodel.hpp"
@@ -106,7 +106,7 @@ namespace MWGui
void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mEnchanting.setSelfEnchanting(true);
mEnchanting.setEnchanter(player);
@@ -149,7 +149,7 @@ namespace MWGui
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->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr());
mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable);
}
@@ -236,7 +236,7 @@ namespace MWGui
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->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr());
mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones);
//MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}");
@@ -259,7 +259,7 @@ namespace MWGui
{
if (mEffects.size() <= 0)
{
- MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}");
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu11}");
return;
}
@@ -290,12 +290,36 @@ namespace MWGui
mEnchanting.setNewItemName(mName->getCaption());
mEnchanting.setEffect(mEffectList);
- if (mEnchanting.getEnchantPrice() > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+ if (mEnchanting.getEnchantPrice() > playerGold)
{
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
return;
}
+ // check if the player is attempting to use a soulstone or item that was stolen from this actor
+ if (mPtr != player)
+ {
+ for (int i=0; i<2; ++i)
+ {
+ MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem();
+ if (Misc::StringUtils::ciEqual(item.getCellRef().mOwner, mPtr.getCellRef().mRefID))
+ {
+ std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString();
+ if (msg.find("%s") != std::string::npos)
+ msg.replace(msg.find("%s"), 2, item.getClass().getName(item));
+ MWBase::Environment::get().getWindowManager()->messageBox(msg);
+ MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
+ MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
+ item.getClass().getValue(item));
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
+ return;
+ }
+ }
+ }
+
int result = mEnchanting.create();
if(result==1)
diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp
index bd75c078c0..4d3d04ced7 100644
--- a/apps/openmw/mwgui/formatting.cpp
+++ b/apps/openmw/mwgui/formatting.cpp
@@ -12,6 +12,8 @@
#include <boost/range/algorithm/copy.hpp>
#include <OgreUTFString.h>
+#include <OgreResourceGroupManager.h>
+
namespace
{
int convertFromHex(std::string hex)
@@ -288,6 +290,16 @@ namespace MWGui
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()));
+
+ // Apparently a bug with some morrowind versions, they reference the image without the size suffix.
+ // So if the image isn't found, try appending the size.
+ if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("bookart\\"+image))
+ {
+ std::stringstream str;
+ str << image.substr(0, image.rfind(".")) << "_" << width << "_" << height << image.substr(image.rfind("."));
+ image = str.str();
+ }
+
box->setImageTexture("bookart\\" + image);
box->setProperty("NeedMouse", "false");
}
diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp
index 8ef5e59d08..a6ad43ce58 100644
--- a/apps/openmw/mwgui/hud.cpp
+++ b/apps/openmw/mwgui/hud.cpp
@@ -6,7 +6,6 @@
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/creaturestats.hpp"
@@ -242,7 +241,7 @@ namespace MWGui
if (world->canPlaceObject(mouseX, mouseY))
world->placeObject(object, mouseX, mouseY, mDragAndDrop->mDraggedCount);
else
- world->dropObjectOnGround(world->getPlayer().getPlayer(), object, mDragAndDrop->mDraggedCount);
+ world->dropObjectOnGround(world->getPlayerPtr(), object, mDragAndDrop->mDraggedCount);
MWBase::Environment::get().getWindowManager()->changePointer("arrow");
@@ -268,7 +267,8 @@ namespace MWGui
else if ((mode == GM_Container) || (mode == GM_Inventory))
{
// pick up object
- MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
+ if (!object.isEmpty())
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object);
}
}
}
@@ -320,7 +320,7 @@ namespace MWGui
void HUD::onWeaponClicked(MyGUI::Widget* _sender)
{
- const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}");
@@ -332,7 +332,7 @@ namespace MWGui
void HUD::onMagicClicked(MyGUI::Widget* _sender)
{
- const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}");
@@ -517,7 +517,7 @@ namespace MWGui
mWeapStatus->setProgressPosition(0);
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf())
mWeapImage->setImageTexture("icons\\k\\tx_werewolf_hand.dds");
else
diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp
index 672ea9c16f..97e1e9a2b1 100644
--- a/apps/openmw/mwgui/inventoryitemmodel.cpp
+++ b/apps/openmw/mwgui/inventoryitemmodel.cpp
@@ -42,10 +42,7 @@ 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);
+ mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor);
}
@@ -72,14 +69,14 @@ void InventoryItemModel::update()
// 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")
+ if(item.getCellRef().mRefID == "werewolfrobe")
continue;
ItemStack newItem (item, this, item.getRefData().getCount());
- if (mActor.getTypeName() == typeid(ESM::NPC).name())
+ if (mActor.getClass().hasInventoryStore(mActor))
{
- MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor);
+ MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor);
for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
{
MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp
index c14971f6e2..2ea09db61e 100644
--- a/apps/openmw/mwgui/inventorywindow.cpp
+++ b/apps/openmw/mwgui/inventorywindow.cpp
@@ -8,11 +8,13 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/action.hpp"
+#include "../mwscript/interpretercontext.hpp"
+#include "../mwbase/scriptmanager.hpp"
#include "bookwindow.hpp"
#include "scrollwindow.hpp"
@@ -33,7 +35,7 @@ namespace MWGui
, mTrading(false)
, mLastXSize(0)
, mLastYSize(0)
- , mPreview(MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ())
+ , mPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())
, mPreviewDirty(true)
, mDragAndDrop(dragAndDrop)
, mSelectedItem(-1)
@@ -53,7 +55,7 @@ namespace MWGui
getWidget(mRightPane, "RightPane");
getWidget(mArmorRating, "ArmorRating");
- mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
+ mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
getWidget(mItemView, "ItemView");
mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected);
@@ -74,17 +76,18 @@ namespace MWGui
void InventoryWindow::adjustPanes()
{
- const float aspect = 0.5; // fixed aspect ratio for the left pane
- mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 );
- mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4,
+ const float aspect = 0.5; // fixed aspect ratio for the avatar image
+ float leftPaneWidth = (mMainWidget->getSize().height-44-mArmorRating->getHeight()) * aspect;
+ mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 );
+ mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4,
mRightPane->getPosition().top,
- mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15,
+ mMainWidget->getSize().width - 12 - leftPaneWidth - 15,
mMainWidget->getSize().height-44 );
}
void InventoryWindow::updatePlayer()
{
- mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr());
mSortModel = new SortFilterItemModel(mTradeModel);
mItemView->setModel(mSortModel);
@@ -276,7 +279,7 @@ namespace MWGui
void InventoryWindow::open()
{
- mPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
updateEncumbranceBar();
@@ -351,6 +354,48 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned);
}
+ void InventoryWindow::useItem(const MWWorld::Ptr &ptr)
+ {
+ const std::string& script = ptr.getClass().getScript(ptr);
+
+ // If the item has a script, set its OnPcEquip to 1
+ if (!script.empty()
+ // Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards,
+ // the next time it is equipped will work normally, but will not set onpcequip
+ && (ptr != mSkippedToEquip || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1))
+ ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
+
+ // Give the script a chance to run once before we do anything else
+ // this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this)
+ if (!script.empty())
+ {
+ MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
+ MWBase::Environment::get().getScriptManager()->run (script, interpreterContext);
+ }
+
+ if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0)
+ {
+ boost::shared_ptr<MWWorld::Action> action = MWWorld::Class::get(ptr).use(ptr);
+
+ action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr());
+
+ // 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);
+
+ mSkippedToEquip = MWWorld::Ptr();
+ }
+ else
+ mSkippedToEquip = ptr;
+
+ mItemView->update();
+
+ notifyContentChanged();
+ }
+
void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender)
{
if (mDragAndDrop->mIsOnDragAndDrop)
@@ -364,36 +409,19 @@ namespace MWGui
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);
+ it = invStore.add(ptr, mDragAndDrop->mDraggedCount, mPtr);
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();
+ useItem(ptr);
}
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 );
+ MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition ();
+ int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f );
+ int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f );
MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY);
if (itemSelected.isEmpty ())
@@ -425,7 +453,7 @@ namespace MWGui
// 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")
+ if(item.getCellRef().mRefID == "werewolfrobe")
return MWWorld::Ptr();
return item;
}
@@ -435,7 +463,7 @@ namespace MWGui
void InventoryWindow::updateEncumbranceBar()
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
float capacity = MWWorld::Class::get(player).getCapacity(player);
float encumbrance = MWWorld::Class::get(player).getEncumbrance(player);
@@ -450,19 +478,6 @@ namespace MWGui
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;
@@ -473,11 +488,18 @@ namespace MWGui
if (mPreviewDirty)
{
mPreviewDirty = false;
- MyGUI::IntSize size = mAvatar->getSize();
+ MyGUI::IntSize size = mAvatarImage->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");
+ mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height)));
+ mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height)));
+
+ mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
+ + boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
+ if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
+ mArmorRating->setCaptionWithReplacing (boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
}
}
@@ -488,9 +510,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells();
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)
@@ -515,13 +534,11 @@ namespace MWGui
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);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player);
// remove from world
MWBase::Environment::get().getWorld()->deleteObject (object);
@@ -536,10 +553,7 @@ namespace MWGui
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 ();
+ MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count);
}
}
diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp
index 94ecfd4c8a..7ef168e988 100644
--- a/apps/openmw/mwgui/inventorywindow.hpp
+++ b/apps/openmw/mwgui/inventorywindow.hpp
@@ -31,10 +31,6 @@ namespace MWGui
void pickUpObject (MWWorld::Ptr object);
- int getPlayerGold();
-
- MyGUI::IntCoord getAvatarScreenCoord();
-
MWWorld::Ptr getAvatarSelectedItem(int x, int y);
void rebuildAvatar() {
@@ -48,6 +44,8 @@ namespace MWGui
void updatePlayer();
+ void useItem(const MWWorld::Ptr& ptr);
+
void setGuiMode(GuiMode mode);
private:
@@ -76,6 +74,8 @@ namespace MWGui
MyGUI::Button* mFilterMagic;
MyGUI::Button* mFilterMisc;
+ MWWorld::Ptr mSkippedToEquip;
+
GuiMode mGuiMode;
int mLastXSize;
diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp
index dbea10e775..8caea770ee 100644
--- a/apps/openmw/mwgui/journalbooks.cpp
+++ b/apps/openmw/mwgui/journalbooks.cpp
@@ -196,34 +196,6 @@ book JournalBooks::createEmptyJournalBook ()
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 ();
}
diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp
index 79a77070ae..c6bd6d15d2 100644
--- a/apps/openmw/mwgui/journalviewmodel.cpp
+++ b/apps/openmw/mwgui/journalviewmodel.cpp
@@ -20,8 +20,6 @@ namespace MWGui {
struct JournalViewModelImpl;
-static void injectMonthName (std::ostream & os, int month);
-
struct JournalViewModelImpl : JournalViewModel
{
typedef KeywordSearch <std::string, intptr_t> KeywordSearchT;
@@ -206,10 +204,12 @@ struct JournalViewModelImpl : JournalViewModel
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));
+ const MWDialogue::Quest& quest = i->second;
+ // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal.
+ // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not supposed
+ // to appear in the quest book.
+ if (!quest.getName().empty())
+ visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (quest.getName()));
}
}
@@ -235,21 +235,21 @@ struct JournalViewModelImpl : JournalViewModel
std::string getText () const
{
- return itr->getText(MWBase::Environment::get().getWorld()->getStore());
+ return itr->getText();
}
Utf8Span timestamp () const
{
if (timestamp_buffer.empty ())
{
- std::ostringstream os;
+ std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}");
- os << itr->mDayOfMonth << ' ';
-
- injectMonthName (os, itr->mMonth);
+ std::ostringstream os;
- const std::string& dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}");
- os << " (" << dayStr << " " << (itr->mDay + 1) << ')';
+ os
+ << itr->mDayOfMonth << ' '
+ << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth)
+ << " (" << dayStr << " " << (itr->mDay + 1) << ')';
timestamp_buffer = os.str ();
}
@@ -270,7 +270,7 @@ struct JournalViewModelImpl : JournalViewModel
{
for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j)
{
- if (i->mInfoId == *j)
+ if (i->mInfoId == j->mInfoId)
visitor (JournalEntryImpl <MWBase::Journal::TEntryIter> (this, i));
}
}
@@ -290,9 +290,7 @@ struct JournalViewModelImpl : JournalViewModel
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));
+ visitor (toUtf8Span (topic.getName()));
}
void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const
@@ -304,10 +302,7 @@ struct JournalViewModelImpl : JournalViewModel
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));
+ visitor (TopicId (&i->second), toUtf8Span (i->second.getName()));
}
}
@@ -316,24 +311,18 @@ struct JournalViewModelImpl : JournalViewModel
{
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());
-
+ return itr->getText();
}
Utf8Span source () const
{
- if (source_buffer.empty ())
- source_buffer = "someone";
- return toUtf8Span (source_buffer);
+ return toUtf8Span (itr->mActorName);
}
};
@@ -349,38 +338,6 @@ struct JournalViewModelImpl : JournalViewModel
}
};
-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/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
index a772b3a15a..f56d80883a 100644
--- a/apps/openmw/mwgui/levelupdialog.cpp
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -6,7 +6,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/fallback.hpp"
@@ -59,7 +58,7 @@ namespace MWGui
void LevelupDialog::setAttributeValues()
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
@@ -115,7 +114,7 @@ namespace MWGui
void LevelupDialog::open()
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
@@ -155,8 +154,7 @@ namespace MWGui
void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
- MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
if (mSpentAttributes.size() < 3)
@@ -166,15 +164,14 @@ namespace MWGui
// increase attributes
for (int i=0; i<3; ++i)
{
- MWMechanics::Stat<int> attribute = creatureStats.getAttribute(mSpentAttributes[i]);
+ MWMechanics::AttributeValue attribute = pcStats.getAttribute(mSpentAttributes[i]);
attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i]));
if (attribute.getBase() >= 100)
attribute.setBase(100);
- creatureStats.setAttribute(mSpentAttributes[i], attribute);
+ pcStats.setAttribute(mSpentAttributes[i], attribute);
}
- creatureStats.levelUp();
pcStats.levelUp ();
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Levelup);
diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp
index fa7ed2aceb..da1992474b 100644
--- a/apps/openmw/mwgui/mainmenu.cpp
+++ b/apps/openmw/mwgui/mainmenu.cpp
@@ -1,13 +1,14 @@
#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"
+#include "../mwbase/statemanager.hpp"
+
+#include "../mwstate/character.hpp"
#include "savegamedialog.hpp"
@@ -16,90 +17,132 @@ namespace MWGui
MainMenu::MainMenu(int w, int h)
: OEngine::GUI::Layout("openmw_mainmenu.layout")
- , mButtonBox(0)
+ , mButtonBox(0), mWidth (w), mHeight (h)
+ , mSaveGameDialog(NULL)
+ {
+ updateMenu();
+ }
+
+ MainMenu::~MainMenu()
{
- onResChange(w,h);
+ delete mSaveGameDialog;
}
void MainMenu::onResChange(int w, int h)
{
- setCoord(0,0,w,h);
+ mWidth = w;
+ mHeight = h;
+
+ updateMenu();
+ }
+
+ void MainMenu::setVisible (bool visible)
+ {
+ if (visible)
+ updateMenu();
+
+ OEngine::GUI::Layout::setVisible (visible);
+ }
+
+ void MainMenu::onButtonClicked(MyGUI::Widget *sender)
+ {
+ std::string name = *sender->getUserData<std::string>();
+ MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ if (name == "return")
+ {
+ MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx);
+ MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu);
+ }
+ else if (name == "options")
+ MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings);
+ else if (name == "exitgame")
+ MWBase::Environment::get().getStateManager()->requestQuit();
+ else if (name == "newgame")
+ {
+ MWBase::Environment::get().getStateManager()->newGame();
+ }
+
+ else
+ {
+ if (!mSaveGameDialog)
+ mSaveGameDialog = new SaveGameDialog();
+ if (name == "loadgame")
+ mSaveGameDialog->setLoadOrSave(true);
+ else if (name == "savegame")
+ mSaveGameDialog->setLoadOrSave(false);
+ mSaveGameDialog->setVisible(true);
+ }
+ }
+
+ void MainMenu::updateMenu()
+ {
+ setCoord(0,0, mWidth, mHeight);
- if (mButtonBox)
- MyGUI::Gui::getInstance ().destroyWidget(mButtonBox);
+ if (!mButtonBox)
+ mButtonBox = mMainWidget->createWidget<MyGUI::Widget>("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
- mButtonBox = mMainWidget->createWidget<MyGUI::Widget>("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
int curH = 0;
+ MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState();
+
std::vector<std::string> buttons;
- buttons.push_back("return");
+
+ if (state==MWBase::StateManager::State_Running)
+ buttons.push_back("return");
+
buttons.push_back("newgame");
- //buttons.push_back("loadgame");
- //buttons.push_back("savegame");
+
+ if (MWBase::Environment::get().getStateManager()->characterBegin()!=
+ MWBase::Environment::get().getStateManager()->characterEnd())
+ buttons.push_back("loadgame");
+
+ if (state==MWBase::StateManager::State_Running &&
+ MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1)
+ buttons.push_back("savegame");
+
buttons.push_back("options");
//buttons.push_back("credits");
buttons.push_back("exitgame");
- int maxwidth = 0;
-
- mButtons.clear();
+ // Create new buttons if needed
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;
+ if (mButtons.find(*it) == mButtons.end())
+ {
+ 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");
+ button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked);
+ button->setUserData(std::string(*it));
+ mButtons[*it] = button;
+ }
}
+
+ // Start by hiding all buttons
+ int maxwidth = 0;
for (std::map<std::string, MWGui::ImageButton*>::iterator it = mButtons.begin(); it != mButtons.end(); ++it)
{
+ it->second->setVisible(false);
MyGUI::IntSize requested = it->second->getRequestedSize();
- it->second->setCoord((maxwidth-requested.width) / 2, it->second->getTop(), requested.width, requested.height);
+ if (requested.width > maxwidth)
+ maxwidth = requested.width;
}
- 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().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx);
- MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu);
- }
- else if (sender == mButtons["options"])
- MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings);
- else if (sender == mButtons["exitgame"])
- MWBase::Environment::get().setRequestExit();
- else if (sender == mButtons["newgame"])
+ // Now show and position the ones we want
+ for (std::vector<std::string>::iterator it = buttons.begin(); it != buttons.end(); ++it)
{
- MWBase::Environment::get().getWorld()->startNewGame();
- MWBase::Environment::get().getWindowManager()->setNewGame(true);
- MWBase::Environment::get().getDialogueManager()->clear();
- MWBase::Environment::get().getJournal()->clear();
+ assert(mButtons.find(*it) != mButtons.end());
+ MWGui::ImageButton* button = mButtons[*it];
+ button->setVisible(true);
+ MyGUI::IntSize requested = button->getRequestedSize();
+ button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, requested.height);
+ curH += requested.height;
}
- else if (sender == mButtons["loadgame"])
- {
- MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog();
- dialog->setLoadOrSave(true);
- dialog->setVisible(true);
- }
- else if (sender == mButtons["savegame"])
- {
- MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog();
- dialog->setLoadOrSave(false);
- dialog->setVisible(true);
- }
- }
+ mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH);
+ }
}
diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp
index 4e76a64df7..6d52f26d5d 100644
--- a/apps/openmw/mwgui/mainmenu.hpp
+++ b/apps/openmw/mwgui/mainmenu.hpp
@@ -5,19 +5,33 @@
namespace MWGui
{
+ class SaveGameDialog;
+
class MainMenu : public OEngine::GUI::Layout
{
- public:
- MainMenu(int w, int h);
+ int mWidth;
+ int mHeight;
+
+ public:
+
+ MainMenu(int w, int h);
+ ~MainMenu();
+
+ void onResChange(int w, int h);
+
+ virtual void setVisible (bool visible);
+
+ private:
+
+ MyGUI::Widget* mButtonBox;
- void onResChange(int w, int h);
+ std::map<std::string, MWGui::ImageButton*> mButtons;
- private:
- MyGUI::Widget* mButtonBox;
+ void onButtonClicked (MyGUI::Widget* sender);
- std::map<std::string, MWGui::ImageButton*> mButtons;
+ void updateMenu();
- void onButtonClicked (MyGUI::Widget* sender);
+ SaveGameDialog* mSaveGameDialog;
};
}
diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp
index 2f34df2360..1b4af6d828 100644
--- a/apps/openmw/mwgui/mapwindow.cpp
+++ b/apps/openmw/mwgui/mapwindow.cpp
@@ -294,7 +294,7 @@ namespace MWGui
std::vector<MWWorld::Ptr> markers;
MWBase::World* world = MWBase::Environment::get().getWorld();
world->listDetectedReferences(
- world->getPlayer().getPlayer(),
+ world->getPlayerPtr(),
markers, MWBase::World::DetectionType(type));
if (markers.empty())
return;
@@ -373,8 +373,9 @@ namespace MWGui
// ------------------------------------------------------------------------------------------
- MapWindow::MapWindow(const std::string& cacheDir)
- : MWGui::WindowPinnableBase("openmw_map_window.layout")
+ MapWindow::MapWindow(DragAndDrop* drag, const std::string& cacheDir)
+ : WindowPinnableBase("openmw_map_window.layout")
+ , NoDrop(drag, mMainWidget)
, mGlobal(false)
, mGlobalMap(0)
, mGlobalMapRender(0)
@@ -434,7 +435,7 @@ namespace MWGui
static int _counter=0;
- MyGUI::Button* markerWidget = mGlobalMapImage->createWidget<MyGUI::Button>("ButtonImage",
+ MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget<MyGUI::Button>("ButtonImage",
widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast<std::string>(_counter));
markerWidget->setImageResource("DoorMarker");
markerWidget->setUserString("ToolTipType", "Layout");
@@ -499,10 +500,11 @@ namespace MWGui
mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight());
mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight());
- for (unsigned int i=0; i<mGlobalMapImage->getChildCount (); ++i)
+ // force markers to foreground
+ for (unsigned int i=0; i<mGlobalMapOverlay->getChildCount (); ++i)
{
- if (mGlobalMapImage->getChildAt (i)->getName().substr(0,4) == "Door")
- mGlobalMapImage->getChildAt (i)->castType<MyGUI::Button>()->setImageResource("DoorMarker");
+ if (mGlobalMapOverlay->getChildAt (i)->getName().substr(0,4) == "Door")
+ mGlobalMapOverlay->getChildAt (i)->castType<MyGUI::Button>()->setImageResource("DoorMarker");
}
globalMapUpdatePlayer();
@@ -515,8 +517,8 @@ namespace MWGui
// For interiors, position is set by WindowManager via setGlobalMapPlayerPosition
if (MWBase::Environment::get().getWorld ()->isCellExterior ())
{
- Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition ();
- Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation ();
+ Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedPosition ();
+ Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedOrientation ();
Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y);
float worldX, worldY;
@@ -573,4 +575,31 @@ namespace MWGui
mGlobalMap->setViewOffset(viewoffs);
}
+ void MapWindow::clear()
+ {
+ mGlobalMapRender->clear();
+
+ while (mEventBoxGlobal->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mEventBoxGlobal->getChildAt(0));
+ while (mGlobalMapOverlay->getChildCount())
+ MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0));
+ }
+
+ void MapWindow::write(ESM::ESMWriter &writer)
+ {
+ mGlobalMapRender->write(writer);
+ }
+
+ void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type)
+ {
+ std::vector<std::pair<int, int> > exploredCells;
+ mGlobalMapRender->readRecord(reader, type, exploredCells);
+
+ for (std::vector<std::pair<int, int> >::iterator it = exploredCells.begin(); it != exploredCells.end(); ++it)
+ {
+ const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Cell>().search(it->first, it->second);
+ if (cell && !cell->mName.empty())
+ addVisitedLocation(cell->mName, it->first, it->second);
+ }
+ }
}
diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp
index 7df2105dcf..1e52ff26af 100644
--- a/apps/openmw/mwgui/mapwindow.hpp
+++ b/apps/openmw/mwgui/mapwindow.hpp
@@ -1,6 +1,8 @@
#ifndef MWGUI_MAPWINDOW_H
#define MWGUI_MAPWINDOW_H
+#include <libs/platform/stdint.h>
+
#include "windowpinnablebase.hpp"
namespace MWRender
@@ -8,6 +10,12 @@ namespace MWRender
class GlobalMap;
}
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+}
+
namespace Loading
{
class Listener;
@@ -75,10 +83,10 @@ namespace MWGui
float mLastDirectionY;
};
- class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase
+ class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop
{
public:
- MapWindow(const std::string& cacheDir);
+ MapWindow(DragAndDrop* drag, const std::string& cacheDir);
virtual ~MapWindow();
void setCellName(const std::string& cellName);
@@ -92,6 +100,14 @@ namespace MWGui
virtual void open();
+ void onFrame(float dt) { NoDrop::onFrame(dt); }
+
+ /// Clear all savegame-specific data
+ void clear();
+
+ void write (ESM::ESMWriter& writer);
+ void readRecord (ESM::ESMReader& reader, int32_t type);
+
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);
diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp
index 4da1668209..3c3335d8b0 100644
--- a/apps/openmw/mwgui/merchantrepair.cpp
+++ b/apps/openmw/mwgui/merchantrepair.cpp
@@ -8,12 +8,9 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
-#include "inventorywindow.hpp"
-
namespace MWGui
{
@@ -36,7 +33,9 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
int currentY = 0;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
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));
@@ -69,8 +68,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
MyGUI::Button* button =
- mList->createWidget<MyGUI::Button>(
- (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton",
+ mList->createWidget<MyGUI::Button>("SandTextButton",
0,
currentY,
0,
@@ -80,7 +78,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
currentY += 18;
- button->setEnabled(price<=MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold());
+ button->setEnabled(price<=playerGold);
button->setUserString("Price", boost::lexical_cast<std::string>(price));
button->setUserData(*iter);
button->setCaptionWithReplacing(name);
@@ -93,7 +91,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
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()));
+ + boost::lexical_cast<std::string>(playerGold));
}
void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel)
@@ -119,8 +117,8 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender)
int price = boost::lexical_cast<int>(sender->getUserString("Price"));
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
- player.getClass().getContainerStore(player).remove("gold_001", price, player);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
startRepair(mActor);
}
diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp
index 378e76e467..1ce167c339 100644
--- a/apps/openmw/mwgui/messagebox.cpp
+++ b/apps/openmw/mwgui/messagebox.cpp
@@ -8,12 +8,12 @@
namespace MWGui
{
- MessageBoxManager::MessageBoxManager ()
+ MessageBoxManager::MessageBoxManager (float timePerChar)
{
- mMessageBoxSpeed = 0.1;
mInterMessageBoxe = NULL;
mStaticMessageBox = NULL;
mLastButtonPressed = -1;
+ mMessageBoxSpeed = timePerChar;
}
MessageBoxManager::~MessageBoxManager ()
@@ -62,7 +62,8 @@ namespace MWGui
{
MessageBox *box = new MessageBox(*this, message);
box->mCurrentTime = 0;
- box->mMaxTime = message.length()*mMessageBoxSpeed;
+ std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message);
+ box->mMaxTime = realMessage.length()*mMessageBoxSpeed;
if(stat)
mStaticMessageBox = box;
@@ -126,12 +127,6 @@ namespace MWGui
mMessageBoxSpeed = speed;
}
- void MessageBoxManager::okayPressed ()
- {
- if(mInterMessageBoxe != NULL)
- mInterMessageBoxe->okayPressed();
- }
-
int MessageBoxManager::readPressedButton ()
{
int pressed = mLastButtonPressed;
@@ -250,11 +245,11 @@ namespace MWGui
}
mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding;
- MyGUI::IntCoord absCoord;
- absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
- absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
+ MyGUI::IntPoint absPos;
+ absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
+ absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
- mMainWidget->setCoord(absCoord);
+ mMainWidget->setPosition(absPos);
mMainWidget->setSize(mainWidgetSize);
MyGUI::IntCoord messageWidgetCoord;
@@ -292,24 +287,6 @@ namespace MWGui
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);
@@ -331,24 +308,41 @@ namespace MWGui
top += buttonSize.height + 2*buttonTopPadding;
}
- }
- }
+ mainWidgetSize.height = top + buttonMainPadding;
+ mMainWidget->setSize(mainWidgetSize);
- void InteractiveMessageBox::okayPressed()
- {
+ MyGUI::IntPoint absPos;
+ absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
+ absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
+
+ mMainWidget->setPosition(absPos);
+
+ MyGUI::IntCoord messageWidgetCoord;
+ messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
+ messageWidgetCoord.top = textPadding;
+ messageWidgetCoord.width = textSize.width;
+ messageWidgetCoord.height = textSize.height;
+ mMessageWidget->setCoord(messageWidgetCoord);
+ }
+ // Set key focus to "Ok" button
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)
+ if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok))
{
- buttonActivated(*button);
- MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f);
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button);
+ (*button)->eventKeyButtonPressed += MyGUI::newDelegate(this, &InteractiveMessageBox::onKeyPressed);
break;
}
}
+ }
+ void InteractiveMessageBox::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char)
+ {
+ if (_key == MyGUI::KeyCode::Return || _key == MyGUI::KeyCode::NumpadEnter || _key == MyGUI::KeyCode::Space)
+ buttonActivated(_sender);
}
void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed)
diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp
index 0288f366ce..caa37008ca 100644
--- a/apps/openmw/mwgui/messagebox.hpp
+++ b/apps/openmw/mwgui/messagebox.hpp
@@ -22,7 +22,7 @@ namespace MWGui
class MessageBoxManager
{
public:
- MessageBoxManager ();
+ MessageBoxManager (float timePerChar);
~MessageBoxManager ();
void onFrame (float frameDuration);
void createMessageBox (const std::string& message, bool stat = false);
@@ -33,7 +33,6 @@ namespace MWGui
bool removeMessageBox (MessageBox *msgbox);
void setMessageBoxSpeed (int speed);
- void okayPressed();
int readPressedButton ();
typedef MyGUI::delegates::CMultiDelegate1<int> EventHandle_Int;
@@ -74,7 +73,6 @@ namespace MWGui
{
public:
InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons);
- void okayPressed ();
void mousePressed (MyGUI::Widget* _widget);
int readPressedButton ();
@@ -82,6 +80,7 @@ namespace MWGui
private:
void buttonActivated (MyGUI::Widget* _widget);
+ void onKeyPressed(MyGUI::Widget* _sender, MyGUI::KeyCode _key, MyGUI::Char _char);
MessageBoxManager& mMessageBoxManager;
MyGUI::EditBox* mMessageWidget;
diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp
index 13ee4396d0..0196bf02d3 100644
--- a/apps/openmw/mwgui/pickpocketitemmodel.cpp
+++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp
@@ -9,7 +9,7 @@ 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();
+ int chance = thief.getClass().getSkill(thief, ESM::Skill::Sneak);
mSourceModel->update();
for (size_t i = 0; i<mSourceModel->getItemCount(); ++i)
diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp
index b8f52cd1fb..ff13ae1afa 100644
--- a/apps/openmw/mwgui/quickkeysmenu.cpp
+++ b/apps/openmw/mwgui/quickkeysmenu.cpp
@@ -2,10 +2,16 @@
#include <boost/lexical_cast.hpp>
-#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
-#include "../mwworld/actionequip.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
#include "../mwmechanics/spellcasting.hpp"
+#include "../mwmechanics/spells.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
#include "../mwgui/inventorywindow.hpp"
#include "../mwgui/bookwindow.hpp"
#include "../mwgui/scrollwindow.hpp"
@@ -18,8 +24,8 @@ 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));
+ int cmp = left.getClass().getName(left).compare(
+ right.getClass().getName(right));
return cmp < 0;
}
@@ -126,7 +132,7 @@ namespace MWGui
mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel);
}
mItemSelectionDialog->setVisible(true);
- mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr());
mAssignDialog->setVisible (false);
}
@@ -267,7 +273,7 @@ namespace MWGui
QuickKeyType type = *button->getUserData<QuickKeyType>();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
if (type == Type_Item || type == Type_MagicItem)
@@ -302,6 +308,12 @@ namespace MWGui
if (type == Type_Magic)
{
std::string spellId = button->getChildAt(0)->getUserString("Spell");
+
+ // Make sure the player still has this spell
+ MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
+ MWMechanics::Spells& spells = stats.getSpells();
+ if (!spells.hasSpell(spellId))
+ return;
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
}
@@ -309,19 +321,7 @@ namespace MWGui
{
MWWorld::Ptr item = *button->getChildAt (0)->getUserData<MWWorld::Ptr>();
- 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();
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
}
else if (type == Type_MagicItem)
{
@@ -341,10 +341,7 @@ namespace MWGui
// 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 ());
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
}
store.setSelectedEnchantItem(it);
@@ -430,7 +427,7 @@ namespace MWGui
const int spellHeight = 18;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp
index 2c73226e3d..3dff1b7e41 100644
--- a/apps/openmw/mwgui/race.cpp
+++ b/apps/openmw/mwgui/race.cpp
@@ -1,6 +1,5 @@
#include "race.hpp"
-#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
@@ -71,8 +70,7 @@ namespace MWGui
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->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept);
mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace);
setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus"));
@@ -140,7 +138,7 @@ namespace MWGui
size_t count = mRaceList->getItemCount();
for (size_t i = 0; i < count; ++i)
{
- if (boost::iequals(*mRaceList->getItemDataAt<std::string>(i), raceId))
+ if (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt<std::string>(i), raceId))
{
mRaceList->setIndexSelected(i);
MyGUI::Button* okButton;
@@ -230,7 +228,7 @@ namespace MWGui
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
const std::string *raceId = mRaceList->getItemDataAt<std::string>(_index);
- if (boost::iequals(mCurrentRaceId, *raceId))
+ if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId))
return;
mCurrentRaceId = *raceId;
@@ -242,6 +240,14 @@ namespace MWGui
updateSpellPowers();
}
+ void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index)
+ {
+ onSelectRace(_sender, _index);
+ if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE)
+ return;
+ eventDone(this);
+ }
+
void RaceDialog::getBodyParts (int part, std::vector<std::string>& out)
{
out.clear();
@@ -320,7 +326,7 @@ namespace MWGui
continue;
mRaceList->addItem(it->mName, it->mId);
- if (boost::iequals(it->mId, mCurrentRaceId))
+ if (Misc::StringUtils::ciEqual(it->mId, mCurrentRaceId))
mRaceList->setIndexSelected(index);
++index;
}
diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp
index 914ae80964..340dcfa27b 100644
--- a/apps/openmw/mwgui/race.hpp
+++ b/apps/openmw/mwgui/race.hpp
@@ -67,6 +67,7 @@ namespace MWGui
void onSelectNextHair(MyGUI::Widget* _sender);
void onSelectRace(MyGUI::ListBox* _sender, size_t _index);
+ void onAccept(MyGUI::ListBox* _sender, size_t _index);
void onOkClicked(MyGUI::Widget* _sender);
void onBackClicked(MyGUI::Widget* _sender);
diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp
index b700360bac..683406d9eb 100644
--- a/apps/openmw/mwgui/recharge.cpp
+++ b/apps/openmw/mwgui/recharge.cpp
@@ -7,7 +7,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
@@ -85,7 +84,7 @@ void Recharge::updateView()
int currentY = 0;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
for (MWWorld::ContainerStoreIterator iter (store.begin());
iter!=store.end(); ++iter)
@@ -141,7 +140,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender)
MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp
index 86a85be18e..2ea0db64ac 100644
--- a/apps/openmw/mwgui/referenceinterface.cpp
+++ b/apps/openmw/mwgui/referenceinterface.cpp
@@ -3,8 +3,6 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
-#include "../mwworld/player.hpp"
-
namespace MWGui
{
ReferenceInterface::ReferenceInterface()
@@ -18,7 +16,7 @@ namespace MWGui
void ReferenceInterface::checkReferenceAvailable()
{
- MWWorld::Ptr::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+ MWWorld::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell();
// check if player has changed cell, or count of the reference has become 0
if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL)
diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp
index 0bd4b0995f..d729ee7fa0 100644
--- a/apps/openmw/mwgui/repair.cpp
+++ b/apps/openmw/mwgui/repair.cpp
@@ -6,7 +6,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
@@ -88,7 +87,7 @@ void Repair::updateRepairView()
int currentY = 0;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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));
diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp
index dfc86a547b..e27e40ae64 100644
--- a/apps/openmw/mwgui/review.cpp
+++ b/apps/openmw/mwgui/review.cpp
@@ -65,7 +65,7 @@ namespace MWGui
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));
+ attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue());
}
// Setup skills
@@ -74,7 +74,7 @@ namespace MWGui
for (int i = 0; i < ESM::Skill::Length; ++i)
{
- mSkillValues.insert(std::make_pair(i, MWMechanics::Stat<float>()));
+ mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue()));
mSkillWidgetMap.insert(std::make_pair(i, static_cast<MyGUI::TextBox*> (0)));
}
@@ -152,7 +152,7 @@ namespace MWGui
mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
}
- void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat<int>& value)
+ void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value)
{
std::map<int, Widgets::MWAttributePtr>::iterator attr = mAttributeWidgets.find(static_cast<int>(attributeId));
if (attr == mAttributeWidgets.end())
@@ -161,7 +161,7 @@ namespace MWGui
attr->second->setAttributeValue(value);
}
- void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat<float>& value)
+ void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value)
{
mSkillValues[skillId] = value;
MyGUI::TextBox* widget = mSkillWidgetMap[skillId];
@@ -279,9 +279,9 @@ namespace MWGui
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();
+ const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second;
+ int base = stat.getBase();
+ int modified = stat.getModified();
std::string state = "normal";
if (modified > base)
diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp
index 1c24fec745..5d0767d217 100644
--- a/apps/openmw/mwgui/review.hpp
+++ b/apps/openmw/mwgui/review.hpp
@@ -38,10 +38,10 @@ namespace MWGui
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 setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value);
void configureSkills(const SkillList& major, const SkillList& minor);
- void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat<float>& value);
+ void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value);
virtual void open();
@@ -85,7 +85,7 @@ namespace MWGui
std::map<int, Widgets::MWAttributePtr> mAttributeWidgets;
SkillList mMajorSkills, mMinorSkills, mMiscSkills;
- std::map<int, MWMechanics::Stat<float> > mSkillValues;
+ std::map<int, MWMechanics::SkillValue > mSkillValues;
std::map<int, MyGUI::TextBox*> mSkillWidgetMap;
std::string mName, mRaceId, mBirthSignId;
ESM::Class mKlass;
diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp
index a1acd35884..77ad98121b 100644
--- a/apps/openmw/mwgui/savegamedialog.cpp
+++ b/apps/openmw/mwgui/savegamedialog.cpp
@@ -1,12 +1,28 @@
#include "savegamedialog.hpp"
#include "widgets.hpp"
+#include <OgreImage.h>
+#include <OgreTextureManager.h>
+
+#include <components/misc/stringops.hpp>
+
+#include <components/settings/settings.hpp>
+
+#include "../mwbase/statemanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+#include "../mwstate/character.hpp"
+
+#include "confirmationdialog.hpp"
namespace MWGui
{
-
SaveGameDialog::SaveGameDialog()
: WindowModal("openmw_savegame_dialog.layout")
+ , mSaving(true)
+ , mCurrentCharacter(NULL)
{
getWidget(mScreenshot, "Screenshot");
getWidget(mCharacterSelection, "SelectCharacter");
@@ -18,21 +34,89 @@ namespace MWGui
getWidget(mSpacer, "Spacer");
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked);
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
+ mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
+ mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected);
+ mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated);
+ mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept);
+ mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged);
+ }
+ void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos)
+ {
+ onSlotSelected(sender, pos);
+ accept();
+ }
+
+ void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender)
+ {
+ // This might have previously been a save slot from the list. If so, that is no longer the case
+ mSaveList->setIndexSelected(MyGUI::ITEM_NONE);
+ onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
+ }
+
+ void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender)
+ {
+ accept();
}
void SaveGameDialog::open()
{
+ WindowModal::open();
+
+ mSaveNameEdit->setCaption ("");
+ if (mSaving)
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit);
+
center();
+
+ MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
+ if (mgr->characterBegin() == mgr->characterEnd())
+ return;
+
+ mCurrentCharacter = mgr->getCurrentCharacter (false);
+
+ std::string directory =
+ Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves"));
+
+ mCharacterSelection->removeAllItems();
+
+ for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it)
+ {
+ if (it->begin()!=it->end())
+ {
+ std::stringstream title;
+ title << it->getSignature().mPlayerName;
+ title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClass << ")";
+
+ mCharacterSelection->addItem (title.str());
+
+ if (mCurrentCharacter == &*it ||
+ (!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase (
+ it->begin()->mPath.parent_path().filename().string())))
+ {
+ mCurrentCharacter = &*it;
+ mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1);
+ }
+ }
+ }
+
+ fillSaveList();
+
}
void SaveGameDialog::setLoadOrSave(bool load)
{
+ mSaving = !load;
mSaveNameEdit->setVisible(!load);
mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
mCharacterSelection->setVisible(load);
mSpacer->setUserString("Hidden", load ? "false" : "true");
+ if (!load)
+ {
+ mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false);
+ }
+
center();
}
@@ -41,9 +125,161 @@ namespace MWGui
setVisible(false);
}
- void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender)
+ void SaveGameDialog::onConfirmationGiven()
+ {
+ accept(true);
+ }
+
+ void SaveGameDialog::accept(bool reallySure)
{
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
+
+ // Get the selected slot, if any
+ unsigned int i=0;
+ const MWState::Slot* slot = NULL;
+
+ if (mCurrentCharacter)
+ {
+ for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it,++i)
+ {
+ if (i == mSaveList->getIndexSelected())
+ slot = &*it;
+ }
+ }
+
+ if (mSaving)
+ {
+ // If overwriting an existing slot, ask for confirmation first
+ if (slot != NULL && !reallySure)
+ {
+ ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog();
+ dialog->open("#{sMessage4}");
+ dialog->eventOkClicked.clear();
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven);
+ dialog->eventCancelClicked.clear();
+ return;
+ }
+ if (mSaveNameEdit->getCaption().empty())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}");
+ return;
+ }
+ MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot);
+ }
+ else
+ {
+ if (mCurrentCharacter && slot)
+ MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot);
+ }
+
setVisible(false);
+
+ if (MWBase::Environment::get().getStateManager()->getState()==
+ MWBase::StateManager::State_NoGame)
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
+ }
}
+ void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender)
+ {
+ accept();
+ }
+
+ void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos)
+ {
+ MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
+
+ unsigned int i=0;
+ const MWState::Character* character = NULL;
+ for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i)
+ {
+ if (i == pos)
+ character = &*it;
+ }
+ assert(character && "Can't find selected character");
+
+ mCurrentCharacter = character;
+ fillSaveList();
+ }
+
+ void SaveGameDialog::fillSaveList()
+ {
+ mSaveList->removeAllItems();
+ if (!mCurrentCharacter)
+ return;
+ for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it)
+ {
+ mSaveList->addItem(it->mProfile.mDescription);
+ }
+ onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
+ }
+
+ void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos)
+ {
+ if (pos == MyGUI::ITEM_NONE)
+ {
+ mInfoText->setCaption("");
+ mScreenshot->setImageTexture("");
+ return;
+ }
+
+ if (mSaving)
+ mSaveNameEdit->setCaption(sender->getItemNameAt(pos));
+
+ const MWState::Slot* slot = NULL;
+ unsigned int i=0;
+ for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i)
+ {
+ if (i == pos)
+ slot = &*it;
+ }
+ assert(slot && "Can't find selected slot");
+
+ std::stringstream text;
+ time_t time = slot->mTimeStamp;
+ struct tm* timeinfo;
+ timeinfo = localtime(&time);
+
+ text << asctime(timeinfo) << "\n";
+ text << "Level " << slot->mProfile.mPlayerLevel << "\n";
+ text << slot->mProfile.mPlayerCell << "\n";
+ // text << "Time played: " << slot->mProfile.mTimePlayed << "\n";
+
+ int hour = int(slot->mProfile.mInGameTime.mGameHour);
+ bool pm = hour >= 12;
+ if (hour >= 13) hour -= 12;
+ if (hour == 0) hour = 12;
+
+ text
+ << slot->mProfile.mInGameTime.mDay << " "
+ << MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth)
+ << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}");
+
+ mInfoText->setCaptionWithReplacing(text.str());
+
+ // Decode screenshot
+ std::vector<char> data = slot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :(
+ Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
+ Ogre::Image image;
+ image.load(stream, "jpg");
+
+ const std::string textureName = "@savegame_screenshot";
+ Ogre::TexturePtr texture;
+ texture = Ogre::TextureManager::getSingleton().getByName(textureName);
+ mScreenshot->setImageTexture("");
+ if (texture.isNull())
+ {
+ texture = Ogre::TextureManager::getSingleton().createManual(textureName,
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
+ Ogre::TEX_TYPE_2D,
+ image.getWidth(), image.getHeight(), 0, Ogre::PF_BYTE_RGBA, Ogre::TU_DYNAMIC_WRITE_ONLY);
+ }
+ texture->unload();
+ texture->setWidth(image.getWidth());
+ texture->setHeight(image.getHeight());
+ texture->loadImage(image);
+
+ mScreenshot->setImageTexture(textureName);
+ }
}
diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp
index 1a3178ef30..8d09a1cbc1 100644
--- a/apps/openmw/mwgui/savegamedialog.hpp
+++ b/apps/openmw/mwgui/savegamedialog.hpp
@@ -3,6 +3,11 @@
#include "windowbase.hpp"
+namespace MWState
+{
+ class Character;
+}
+
namespace MWGui
{
@@ -15,12 +20,22 @@ namespace MWGui
void setLoadOrSave(bool load);
+ private:
void onCancelButtonClicked (MyGUI::Widget* sender);
void onOkButtonClicked (MyGUI::Widget* sender);
+ void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos);
+ void onSlotSelected (MyGUI::ListBox* sender, size_t pos);
+ void onSlotActivated (MyGUI::ListBox* sender, size_t pos);
+ void onEditSelectAccept (MyGUI::EditBox* sender);
+ void onSaveNameChanged (MyGUI::EditBox* sender);
+ void onConfirmationGiven();
+ void accept(bool reallySure=false);
+
+ void fillSaveList();
- private:
MyGUI::ImageBox* mScreenshot;
+ bool mSaving;
MyGUI::ComboBox* mCharacterSelection;
MyGUI::EditBox* mInfoText;
@@ -30,6 +45,8 @@ namespace MWGui
MyGUI::EditBox* mSaveNameEdit;
MyGUI::Widget* mSpacer;
+ const MWState::Character* mCurrentCharacter;
+
};
}
diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp
index 48931b18e7..e1970004ce 100644
--- a/apps/openmw/mwgui/scrollwindow.cpp
+++ b/apps/openmw/mwgui/scrollwindow.cpp
@@ -6,7 +6,6 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/actiontake.hpp"
-#include "../mwworld/player.hpp"
#include "formatting.hpp"
@@ -90,7 +89,7 @@ namespace MWGui
MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0);
MWWorld::ActionTake take(mScroll);
- take.execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr());
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll);
}
diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp
index bbd28b2de6..68aecf28dd 100644
--- a/apps/openmw/mwgui/spellbuyingwindow.cpp
+++ b/apps/openmw/mwgui/spellbuyingwindow.cpp
@@ -8,14 +8,11 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
-#include "inventorywindow.hpp"
-
namespace MWGui
{
const int SpellBuyingWindow::sLineHeight = 18;
@@ -43,15 +40,19 @@ namespace MWGui
int price = spell->mData.mCost*store.get<ESM::GameSetting>().find("fSpellValueMult")->getFloat();
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true);
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
MyGUI::Button* toAdd =
mSpellsView->createWidget<MyGUI::Button>(
- (price>MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()) ? "SandTextGreyedOut" : "SandTextButton",
+ "SandTextButton",
0,
mCurrentY,
200,
sLineHeight,
MyGUI::Align::Default
);
+ toAdd->setEnabled(price<=playerGold);
mCurrentY += sLineHeight;
@@ -103,7 +104,7 @@ namespace MWGui
bool SpellBuyingWindow::playerHasSpell(const std::string &id)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells();
for (MWMechanics::Spells::TIterator it = playerSpells.begin(); it != playerSpells.end(); ++it)
{
@@ -117,17 +118,14 @@ namespace MWGui
{
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);
- player.getClass().getContainerStore(player).remove("gold_001", price, player);
- startSpellBuying(mPtr);
-
- MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
- }
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
+ MWMechanics::Spells& spells = stats.getSpells();
+ spells.add (mSpellsWidgetMap.find(_sender)->second);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
+ startSpellBuying(mPtr);
+
+ MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
}
void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender)
@@ -137,7 +135,10 @@ namespace MWGui
void SpellBuyingWindow::updateLabels()
{
- mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(playerGold));
mPlayerGold->setCoord(8,
mPlayerGold->getTop(),
mPlayerGold->getTextSize().width,
diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp
index b9324fea17..469d7188ec 100644
--- a/apps/openmw/mwgui/spellcreationdialog.cpp
+++ b/apps/openmw/mwgui/spellcreationdialog.cpp
@@ -3,18 +3,20 @@
#include <boost/lexical_cast.hpp>
#include "../mwbase/windowmanager.hpp"
-
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
+#include "../mwworld/class.hpp"
#include "../mwmechanics/spellcasting.hpp"
+#include "../mwmechanics/spells.hpp"
+#include "../mwmechanics/creaturestats.hpp"
#include "tooltips.hpp"
#include "class.hpp"
-#include "inventorywindow.hpp"
namespace
{
@@ -334,7 +336,10 @@ namespace MWGui
return;
}
- if (boost::lexical_cast<int>(mPriceLabel->getCaption()) > MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold())
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
+ if (boost::lexical_cast<int>(mPriceLabel->getCaption()) > playerGold)
{
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
return;
@@ -342,9 +347,7 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
-
- player.getClass().getContainerStore(player).remove("gold_001", boost::lexical_cast<int>(mPriceLabel->getCaption()), player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, boost::lexical_cast<int>(mPriceLabel->getCaption()), player);
MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
@@ -414,7 +417,7 @@ namespace MWGui
mPriceLabel->setCaption(boost::lexical_cast<std::string>(int(price)));
- float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayerPtr());
mSuccessChance->setCaption(boost::lexical_cast<std::string>(int(chance)));
}
@@ -441,7 +444,7 @@ namespace MWGui
{
// get the list of magic effects that are known to the player
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp
index e93e96c4b2..0cd665a871 100644
--- a/apps/openmw/mwgui/spellicons.cpp
+++ b/apps/openmw/mwgui/spellicons.cpp
@@ -9,7 +9,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
@@ -22,7 +21,8 @@ namespace MWGui
{
void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
- const std::string& sourceName, float magnitude, float remainingTime)
+ const std::string& sourceName, const std::string& casterHandle,
+ float magnitude, float remainingTime)
{
MagicEffectInfo newEffectSource;
newEffectSource.mKey = key;
@@ -39,7 +39,7 @@ namespace MWGui
{
// TODO: Tracking add/remove/expire would be better than force updating every frame
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp
index a29e2a00ab..1bb80f3d4c 100644
--- a/apps/openmw/mwgui/spellicons.hpp
+++ b/apps/openmw/mwgui/spellicons.hpp
@@ -43,7 +43,8 @@ namespace MWGui
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
virtual void visit (MWMechanics::EffectKey key,
- const std::string& sourceName, float magnitude, float remainingTime = -1);
+ const std::string& sourceName, const std::string& casterHandle,
+ float magnitude, float remainingTime = -1);
};
class SpellIcons
diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp
index e2817c38b8..6b261a799c 100644
--- a/apps/openmw/mwgui/spellwindow.cpp
+++ b/apps/openmw/mwgui/spellwindow.cpp
@@ -4,12 +4,15 @@
#include <boost/format.hpp>
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
-#include "../mwworld/actionequip.hpp"
+#include "../mwworld/class.hpp"
#include "../mwmechanics/spellcasting.hpp"
+#include "../mwmechanics/spells.hpp"
+#include "../mwmechanics/creaturestats.hpp"
#include "spellicons.hpp"
#include "inventorywindow.hpp"
@@ -39,8 +42,9 @@ namespace
namespace MWGui
{
- SpellWindow::SpellWindow()
+ SpellWindow::SpellWindow(DragAndDrop* drag)
: WindowPinnableBase("openmw_spell_window.layout")
+ , NoDrop(drag, mMainWidget)
, mHeight(0)
, mWidth(0)
{
@@ -81,7 +85,7 @@ namespace MWGui
// 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::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
@@ -232,8 +236,7 @@ namespace MWGui
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
float enchantCost = enchant->mData.mCost;
- MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
- int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+ int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant);
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
std::string cost = boost::lexical_cast<std::string>(castCost);
@@ -298,7 +301,7 @@ namespace MWGui
void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
@@ -317,13 +320,7 @@ namespace MWGui
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();
+ MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
}
store.setSelectedEnchantItem(it);
@@ -335,7 +332,7 @@ namespace MWGui
void SpellWindow::onSpellSelected(MyGUI::Widget* _sender)
{
std::string spellId = _sender->getUserString("Spell");
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
if (MyGUI::InputManager::getInstance().isShiftPressed())
@@ -389,11 +386,11 @@ namespace MWGui
void SpellWindow::onDeleteSpellAccept()
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
- if (spells.getSelectedSpell() == mSpellToDelete)
+ if (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == mSpellToDelete)
MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
spells.remove(mSpellToDelete);
diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp
index 521e73d767..38a7619318 100644
--- a/apps/openmw/mwgui/spellwindow.hpp
+++ b/apps/openmw/mwgui/spellwindow.hpp
@@ -7,14 +7,16 @@ namespace MWGui
{
class SpellIcons;
- class SpellWindow : public WindowPinnableBase
+ class SpellWindow : public WindowPinnableBase, public NoDrop
{
public:
- SpellWindow();
+ SpellWindow(DragAndDrop* drag);
virtual ~SpellWindow();
void updateSpells();
+ void onFrame(float dt) { NoDrop::onFrame(dt); }
+
protected:
MyGUI::ScrollView* mSpellView;
MyGUI::Widget* mEffectBox;
diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
index ab6096b7e6..3d4c741a3e 100644
--- a/apps/openmw/mwgui/statswindow.cpp
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -6,8 +6,8 @@
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/player.hpp"
#include "../mwmechanics/npcstats.hpp"
@@ -18,8 +18,9 @@ namespace MWGui
const int StatsWindow::sLineHeight = 18;
- StatsWindow::StatsWindow ()
+ StatsWindow::StatsWindow (DragAndDrop* drag)
: WindowPinnableBase("openmw_stats_window.layout")
+ , NoDrop(drag, mMainWidget)
, mSkillView(NULL)
, mMajorSkills()
, mMinorSkills()
@@ -61,7 +62,7 @@ namespace MWGui
for (int i = 0; i < ESM::Skill::Length; ++i)
{
- mSkillValues.insert(std::pair<int, MWMechanics::Stat<float> >(i, MWMechanics::Stat<float>()));
+ mSkillValues.insert(std::pair<int, MWMechanics::SkillValue >(i, MWMechanics::SkillValue()));
mSkillWidgetMap.insert(std::pair<int, MyGUI::TextBox*>(i, (MyGUI::TextBox*)NULL));
}
@@ -102,7 +103,7 @@ namespace MWGui
adjustWindowCaption();
}
- void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value)
{
static const char *ids[] =
{
@@ -179,14 +180,14 @@ namespace MWGui
}
}
- void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat<float>& value)
+ void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& 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));
+ int modified = value.getModified(), base = value.getBase();
+ std::string text = boost::lexical_cast<std::string>(modified);
std::string state = "normal";
if (modified > base)
state = "increased";
@@ -219,21 +220,26 @@ namespace MWGui
updateSkillArea();
}
- void StatsWindow::onFrame ()
+ void StatsWindow::onFrame (float dt)
{
if (!mMainWidget->getVisible())
return;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ NoDrop::onFrame(dt);
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player);
// level progress
MyGUI::Widget* levelWidget;
for (int i=0; i<2; ++i)
{
+ int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->getInt();
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");
+ levelWidget->setUserString("Range_LevelProgress", boost::lexical_cast<std::string>(max));
+ levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast<std::string>(PCstats.getLevelProgress()) + "/"
+ + boost::lexical_cast<std::string>(max));
}
setFactions(PCstats.getFactionRanks());
@@ -358,22 +364,20 @@ namespace MWGui
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 MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second;
+ int base = stat.getBase();
+ int modified = stat.getModified();
+ int progressPercent = stat.getProgress() * 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)
@@ -426,7 +430,7 @@ namespace MWGui
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::ESMStore &store = world->getStore();
const ESM::NPC *player =
- world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ world->getPlayerPtr().get<ESM::NPC>()->mBase;
// race tooltip
const ESM::Race* playerRace = store.get<ESM::Race>().find(player->mRace);
@@ -454,7 +458,7 @@ namespace MWGui
if (!mSkillWidgets.empty())
addSeparator(coord1, coord2);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player);
const std::set<std::string> &expelled = PCstats.getExpelled();
@@ -484,7 +488,6 @@ namespace MWGui
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);
diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp
index 49b44a2e16..d90c16be92 100644
--- a/apps/openmw/mwgui/statswindow.hpp
+++ b/apps/openmw/mwgui/statswindow.hpp
@@ -10,27 +10,27 @@ namespace MWGui
{
class WindowManager;
- class StatsWindow : public WindowPinnableBase
+ class StatsWindow : public WindowPinnableBase, public NoDrop
{
public:
typedef std::map<std::string, int> FactionList;
typedef std::vector<int> SkillList;
- StatsWindow();
+ StatsWindow(DragAndDrop* drag);
/// automatically updates all the data in the stats window, but only if it has changed.
- void onFrame();
+ void onFrame(float dt);
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::AttributeValue& 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 setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void configureSkills (const SkillList& major, const SkillList& minor);
void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; }
@@ -61,7 +61,7 @@ namespace MWGui
MyGUI::ScrollView* mSkillView;
SkillList mMajorSkills, mMinorSkills, mMiscSkills;
- std::map<int, MWMechanics::Stat<float> > mSkillValues;
+ std::map<int, MWMechanics::SkillValue > 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
diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp
index b52c8e3ddb..8716c4dea8 100644
--- a/apps/openmw/mwgui/tooltips.cpp
+++ b/apps/openmw/mwgui/tooltips.cpp
@@ -182,7 +182,7 @@ namespace MWGui
}
else if (type == "AvatarItemSelection")
{
- MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord ();
+ MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord();
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 );
@@ -211,6 +211,7 @@ namespace MWGui
params.mMagnMin = it->mMagnMin;
params.mMagnMax = it->mMagnMax;
params.mRange = it->mRange;
+ params.mArea = it->mArea;
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
params.mNoTarget = false;
effects.push_back(params);
diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp
index 5c12843da0..88c14b7913 100644
--- a/apps/openmw/mwgui/tradeitemmodel.cpp
+++ b/apps/openmw/mwgui/tradeitemmodel.cpp
@@ -149,7 +149,7 @@ namespace MWGui
if(!mMerchant.isEmpty())
{
MWWorld::Ptr base = item.mBase;
- if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, "gold_001"))
+ if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, MWWorld::ContainerStore::sGoldId))
continue;
if(!MWWorld::Class::get(base).canSell(base, services))
continue;
@@ -162,7 +162,7 @@ namespace MWGui
}
// don't show equipped items
- if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
+ if(mMerchant.getClass().hasInventoryStore(mMerchant))
{
bool isEquipped = false;
MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);
diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp
index d544b83cf4..92ba9470d4 100644
--- a/apps/openmw/mwgui/tradewindow.cpp
+++ b/apps/openmw/mwgui/tradewindow.cpp
@@ -14,9 +14,6 @@
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
-#include "../mwmechanics/npcstats.hpp"
-
-#include "../mwworld/player.hpp"
#include "inventorywindow.hpp"
#include "itemview.hpp"
@@ -24,6 +21,7 @@
#include "containeritemmodel.hpp"
#include "tradeitemmodel.hpp"
#include "countdialog.hpp"
+#include "dialogue.hpp"
namespace MWGui
{
@@ -211,11 +209,11 @@ namespace MWGui
if (amount > 0)
{
- store.add("gold_001", amount, actor);
+ store.add(MWWorld::ContainerStore::sGoldId, amount, actor);
}
else
{
- store.remove("gold_001", - amount, actor);
+ store.remove(MWWorld::ContainerStore::sGoldId, - amount, actor);
}
}
@@ -252,8 +250,11 @@ namespace MWGui
return;
}
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
// check if the player can afford this
- if (mCurrentBalance < 0 && MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold() < std::abs(mCurrentBalance))
+ if (mCurrentBalance < 0 && playerGold < std::abs(mCurrentBalance))
{
// user notification
MWBase::Environment::get().getWindowManager()->
@@ -270,7 +271,24 @@ namespace MWGui
return;
}
- MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ // check if the player is attempting to sell back an item stolen from this actor
+ for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it)
+ {
+ if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().mOwner, mPtr.getCellRef().mRefID))
+ {
+ std::string msg = gmst.find("sNotifyMessage49")->getString();
+ if (msg.find("%s") != std::string::npos)
+ msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase));
+ MWBase::Environment::get().getWindowManager()->messageBox(msg);
+ MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
+ MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
+ it->mBase.getClass().getValue(it->mBase)
+ * it->mCount);
+ onCancelButtonClicked(mCancelButton);
+ MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
+ return;
+ }
+ }
if(mCurrentBalance > mCurrentMerchantOffer)
{
@@ -293,13 +311,13 @@ namespace MWGui
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);
- const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
+ const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
+ const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
- float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
+ float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100);
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 d1 = std::min(mPtr.getClass().getSkill(mPtr, ESM::Skill::Mercantile), 100);
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);
@@ -318,16 +336,18 @@ namespace MWGui
messageBox("#{sNotifyMessage9}");
int iBarterFailDisposition = gmst.find("iBarterFailDisposition")->getInt();
- MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterFailDisposition);
+ if (mPtr.getClass().isNpc())
+ MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterFailDisposition);
return;
}
//skill use!
- MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0);
+ player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0);
}
int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt();
- MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition);
+ if (mPtr.getClass().isNpc())
+ MWBase::Environment::get().getDialogueManager()->applyDispositionChange(iBarterSuccessDisposition);
// make the item transfer
mTradeModel->transferItems();
@@ -336,10 +356,13 @@ namespace MWGui
// transfer the gold
if (mCurrentBalance != 0)
{
- addOrRemoveGold(mCurrentBalance, playerPtr);
+ addOrRemoveGold(mCurrentBalance, player);
addOrRemoveGold(-mCurrentBalance, mPtr);
}
+ MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse(
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sBarterDialog5")->getString());
+
std::string sound = "Item Gold Up";
MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
@@ -394,7 +417,10 @@ namespace MWGui
void TradeWindow::updateLabels()
{
- mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
+ mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast<std::string>(playerGold));
if (mCurrentBalance > 0)
{
@@ -443,7 +469,7 @@ namespace MWGui
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
- if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
+ if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, MWWorld::ContainerStore::sGoldId))
merchantGold += it->getRefData().getCount();
}
return merchantGold;
diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp
index 7c11bd5394..1a8999e6e5 100644
--- a/apps/openmw/mwgui/tradewindow.hpp
+++ b/apps/openmw/mwgui/tradewindow.hpp
@@ -28,8 +28,6 @@ namespace MWGui
void startTrade(const MWWorld::Ptr& actor);
- void addOrRemoveGold(int gold, const MWWorld::Ptr& actor);
-
void onFrame(float frameDuration);
void borrowItem (int index, size_t count);
@@ -95,6 +93,8 @@ namespace MWGui
void onIncreaseButtonTriggered();
void onDecreaseButtonTriggered();
+ void addOrRemoveGold(int gold, const MWWorld::Ptr& actor);
+
void updateLabels();
virtual void onReferenceUnavailable();
diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp
index 04eddcb173..bee76992af 100644
--- a/apps/openmw/mwgui/trainingwindow.cpp
+++ b/apps/openmw/mwgui/trainingwindow.cpp
@@ -9,13 +9,11 @@
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/npcstats.hpp"
-#include "inventorywindow.hpp"
#include "tooltips.hpp"
namespace MWGui
@@ -41,7 +39,10 @@ namespace MWGui
{
mPtr = actor;
- mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(playerGold));
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats (actor);
@@ -72,7 +73,6 @@ namespace MWGui
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 =
@@ -83,11 +83,10 @@ namespace MWGui
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::Button* button = mTrainingOptions->createWidget<MyGUI::Button>("SandTextButton",
MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default);
+ button->setEnabled(price <= playerGold);
button->setUserData(bestSkills[i].first);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected);
@@ -115,7 +114,7 @@ namespace MWGui
{
int skillId = *sender->getUserData<int>();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player);
const MWWorld::ESMStore &store =
@@ -124,9 +123,6 @@ namespace MWGui
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 ())
{
@@ -134,6 +130,14 @@ namespace MWGui
return;
}
+ // You can not train a skill above its governing attribute
+ const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillId);
+ if (pcStats.getSkill(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage17}");
+ return;
+ }
+
// increase skill
MWWorld::LiveCellRef<ESM::NPC> *playerRef = player.get<ESM::NPC>();
@@ -142,7 +146,7 @@ namespace MWGui
pcStats.increaseSkill (skillId, *class_, true);
// remove gold
- player.getClass().getContainerStore(player).remove("gold_001", price, player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
// go back to game mode
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
@@ -150,6 +154,8 @@ namespace MWGui
// advance time
MWBase::Environment::get().getWorld ()->advanceTime (2);
+ MWBase::Environment::get().getMechanicsManager()->rest(false);
+ MWBase::Environment::get().getMechanicsManager()->rest(false);
MWBase::Environment::get().getWorld ()->getFader()->fadeOut(0.25);
mFadeTimeRemaining = 0.5;
diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp
index dd5da4522f..c314ce1fda 100644
--- a/apps/openmw/mwgui/travelwindow.cpp
+++ b/apps/openmw/mwgui/travelwindow.cpp
@@ -2,6 +2,8 @@
#include <boost/lexical_cast.hpp>
+#include <OgreVector3.h>
+
#include <libs/openengine/ogre/fader.hpp>
#include "../mwbase/environment.hpp"
@@ -9,12 +11,9 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
-#include "inventorywindow.hpp"
-
namespace MWGui
{
const int TravelWindow::sLineHeight = 18;
@@ -51,13 +50,15 @@ namespace MWGui
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
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();
@@ -65,7 +66,8 @@ namespace MWGui
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);
+ MyGUI::Button* toAdd = mDestinationsView->createWidget<MyGUI::Button>("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default);
+ toAdd->setEnabled(price<=playerGold);
mCurrentY += sLineHeight;
if(interior)
toAdd->setUserString("interior","y");
@@ -121,13 +123,14 @@ namespace MWGui
int price;
iss >> price;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
- if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()<price)
+ if (playerGold<price)
return;
- player.getClass().getContainerStore(player).remove("gold_001", price, player);
+ player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1);
ESM::Position pos = *_sender->getUserData<ESM::Position>();
@@ -145,7 +148,7 @@ namespace MWGui
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().getMechanicsManager ()->rest (true);
}
MWBase::Environment::get().getWorld()->advanceTime(hours);
@@ -166,7 +169,10 @@ namespace MWGui
void TravelWindow::updateLabels()
{
- mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()));
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
+ int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
+
+ mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast<std::string>(playerGold));
mPlayerGold->setCoord(8,
mPlayerGold->getTop(),
mPlayerGold->getTextSize().width,
diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp
index 2467f6c40d..3c1a4b3faf 100644
--- a/apps/openmw/mwgui/waitdialog.cpp
+++ b/apps/openmw/mwgui/waitdialog.cpp
@@ -9,7 +9,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/creaturestats.hpp"
@@ -49,6 +48,7 @@ namespace MWGui
, mRemainingTime(0.05)
, mCurHour(0)
, mManualHours(1)
+ , mInterruptAt(-1)
{
getWidget(mDateTimeText, "DateTimeText");
getWidget(mRestText, "RestText");
@@ -87,49 +87,7 @@ namespace MWGui
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;
- }
+ std::string month = MWBase::Environment::get().getWorld ()->getMonthName();
int hour = MWBase::Environment::get().getWorld ()->getTimeStamp ().getHour ();
bool pm = hour >= 12;
if (hour >= 13) hour -= 12;
@@ -145,43 +103,7 @@ namespace MWGui
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
+ int autoHours = MWBase::Environment::get().getMechanicsManager()->getHoursToRest();
startWaiting(autoHours);
}
@@ -193,7 +115,8 @@ namespace MWGui
void WaitDialog::startWaiting(int hoursToWait)
{
- MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.2);
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ world->getFader ()->fadeOut(0.2);
setVisible(false);
mProgressBar.setVisible (true);
@@ -201,6 +124,30 @@ namespace MWGui
mCurHour = 0;
mHours = hoursToWait;
+ // FIXME: move this somewhere else?
+ mInterruptAt = -1;
+ MWWorld::Ptr player = world->getPlayerPtr();
+ if (mSleeping && player.getCell()->isExterior())
+ {
+ std::string regionstr = player.getCell()->mCell->mRegion;
+ if (!regionstr.empty())
+ {
+ const ESM::Region *region = world->getStore().get<ESM::Region>().find (regionstr);
+ if (!region->mSleepList.empty())
+ {
+ float fSleepRandMod = world->getStore().get<ESM::GameSetting>().find("fSleepRandMod")->getFloat();
+ int x = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * hoursToWait; // [0, hoursRested]
+ float y = fSleepRandMod * hoursToWait;
+ if (x > y)
+ {
+ float fSleepRestMod = world->getStore().get<ESM::GameSetting>().find("fSleepRestMod")->getFloat();
+ mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait);
+ mInterruptCreatureList = region->mSleepList;
+ }
+ }
+ }
+ }
+
mRemainingTime = 0.05;
mProgressBar.setProgress (0, mHours);
}
@@ -218,7 +165,7 @@ namespace MWGui
void WaitDialog::setCanRest (bool canRest)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified())
&& (stats.getHealth().getCurrent() >= stats.getHealth().getModified())
@@ -243,6 +190,13 @@ namespace MWGui
if (!mWaiting)
return;
+ if (mCurHour == mInterruptAt)
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sSleepInterrupt}");
+ MWBase::Environment::get().getWorld()->spawnRandomCreature(mInterruptCreatureList);
+ stopWaiting();
+ }
+
mRemainingTime -= dt;
while (mRemainingTime < 0)
@@ -254,8 +208,7 @@ namespace MWGui
if (mCurHour <= mHours)
{
MWBase::Environment::get().getWorld ()->advanceTime (1);
- if (mSleeping)
- MWBase::Environment::get().getMechanicsManager ()->restoreDynamicStats ();
+ MWBase::Environment::get().getMechanicsManager ()->rest (mSleeping);
}
}
@@ -272,11 +225,13 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed);
mWaiting = false;
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::NpcStats &pcstats = MWWorld::Class::get(player).getNpcStats(player);
// trigger levelup if possible
- if (mSleeping && pcstats.getLevelProgress () >= 10)
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt())
{
MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup);
}
diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp
index 2723f7a805..d96649af6f 100644
--- a/apps/openmw/mwgui/waitdialog.hpp
+++ b/apps/openmw/mwgui/waitdialog.hpp
@@ -51,6 +51,9 @@ namespace MWGui
int mManualHours; // stores the hours to rest selected via slider
float mRemainingTime;
+ int mInterruptAt;
+ std::string mInterruptCreatureList;
+
WaitDialogProgressBar mProgressBar;
void onUntilHealedButtonClicked(MyGUI::Widget* sender);
diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp
index c37ae15fa1..b30cf2bae8 100644
--- a/apps/openmw/mwgui/widgets.cpp
+++ b/apps/openmw/mwgui/widgets.cpp
@@ -178,7 +178,7 @@ namespace MWGui
}
if (mAttributeValueWidget)
{
- AttributeValue::Type modified = mValue.getModified(), base = mValue.getBase();
+ int modified = mValue.getModified(), base = mValue.getBase();
static_cast<MyGUI::TextBox*>(mAttributeValueWidget)->setCaption(boost::lexical_cast<std::string>(modified));
if (modified > base)
mAttributeValueWidget->_setWidgetState("increased");
@@ -528,14 +528,9 @@ namespace MWGui
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("");
+ std::stringstream out;
+ out << mValue << "/" << mMax;
+ static_cast<MyGUI::TextBox*>(mBarTextWidget)->setCaption(out.str().c_str());
}
}
void MWDynamicStat::setTitle(const std::string& text)
diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp
index 1630ab3c9f..adc56f423f 100644
--- a/apps/openmw/mwgui/widgets.hpp
+++ b/apps/openmw/mwgui/widgets.hpp
@@ -133,7 +133,7 @@ namespace MWGui
public:
MWAttribute();
- typedef MWMechanics::Stat<int> AttributeValue;
+ typedef MWMechanics::AttributeValue AttributeValue;
void setAttributeId(int attributeId);
void setAttributeValue(const AttributeValue& value);
diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp
index cc74579abf..87b26b814a 100644
--- a/apps/openmw/mwgui/windowbase.cpp
+++ b/apps/openmw/mwgui/windowbase.cpp
@@ -1,6 +1,7 @@
#include "windowbase.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "container.hpp"
using namespace MWGui;
@@ -50,3 +51,36 @@ void WindowModal::close()
{
MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget);
}
+
+NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget)
+ : mDrag(drag), mWidget(widget), mTransparent(false)
+{
+}
+
+void NoDrop::onFrame(float dt)
+{
+ MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition();
+
+ if (mDrag->mIsOnDragAndDrop)
+ {
+ MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget();
+ while (focus && focus != mWidget)
+ focus = focus->getParent();
+
+ if (focus == mWidget)
+ mTransparent = true;
+ }
+ if (!mWidget->getAbsoluteCoord().inside(mousePos))
+ mTransparent = false;
+
+ if (mTransparent)
+ {
+ mWidget->setNeedMouseFocus(false); // Allow click-through
+ mWidget->setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5));
+ }
+ else
+ {
+ mWidget->setNeedMouseFocus(true);
+ mWidget->setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5));
+ }
+}
diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp
index 2c014baf0b..48de9ea87c 100644
--- a/apps/openmw/mwgui/windowbase.hpp
+++ b/apps/openmw/mwgui/windowbase.hpp
@@ -11,6 +11,7 @@ namespace MWBase
namespace MWGui
{
class WindowManager;
+ class DragAndDrop;
class WindowBase: public OEngine::GUI::Layout
{
@@ -42,6 +43,21 @@ namespace MWGui
virtual void open();
virtual void close();
};
+
+ /// A window that cannot be the target of a drag&drop action.
+ /// When hovered with a drag item, the window will become transparent and allow click-through.
+ class NoDrop
+ {
+ public:
+ NoDrop(DragAndDrop* drag, MyGUI::Widget* widget);
+
+ void onFrame(float dt);
+
+ private:
+ MyGUI::Widget* mWidget;
+ DragAndDrop* mDrag;
+ bool mTransparent;
+ };
}
#endif
diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp
index 90ced66d3c..5448bc3c42 100644
--- a/apps/openmw/mwgui/windowmanagerimp.cpp
+++ b/apps/openmw/mwgui/windowmanagerimp.cpp
@@ -14,6 +14,7 @@
#include <extern/sdl4ogre/sdlcursormanager.hpp>
#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/statemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
@@ -200,14 +201,15 @@ namespace MWGui
mRecharge = new Recharge();
mMenu = new MainMenu(w,h);
- mMap = new MapWindow("");
+ mMap = new MapWindow(mDragAndDrop, "");
trackWindow(mMap, "map");
- mStatsWindow = new StatsWindow();
+ mStatsWindow = new StatsWindow(mDragAndDrop);
trackWindow(mStatsWindow, "stats");
mConsole = new Console(w,h, mConsoleOnlyScripts);
trackWindow(mConsole, "console");
mJournal = JournalWindow::create(JournalViewModel::create ());
- mMessageBoxManager = new MessageBoxManager();
+ mMessageBoxManager = new MessageBoxManager(
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMessageTimePerChar")->getFloat());
mInventoryWindow = new InventoryWindow(mDragAndDrop);
mTradeWindow = new TradeWindow();
trackWindow(mTradeWindow, "barter");
@@ -226,7 +228,7 @@ namespace MWGui
mConfirmationDialog = new ConfirmationDialog();
mAlchemyWindow = new AlchemyWindow();
trackWindow(mAlchemyWindow, "alchemy");
- mSpellWindow = new SpellWindow();
+ mSpellWindow = new SpellWindow(mDragAndDrop);
trackWindow(mSpellWindow, "spells");
mQuickKeysMenu = new QuickKeysMenu();
mLevelupDialog = new LevelupDialog();
@@ -249,12 +251,12 @@ namespace MWGui
// Setup player stats
for (int i = 0; i < ESM::Attribute::Length; ++i)
{
- mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::Stat<int>()));
+ mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue()));
}
for (int i = 0; i < ESM::Skill::Length; ++i)
{
- mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat<float>()));
+ mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue()));
}
// Set up visibility
@@ -544,7 +546,7 @@ namespace MWGui
}
}
- void WindowManager::setValue (const std::string& id, const MWMechanics::Stat<int>& value)
+ void WindowManager::setValue (const std::string& id, const MWMechanics::AttributeValue& value)
{
mStatsWindow->setValue (id, value);
mCharGen->setValue(id, value);
@@ -575,7 +577,7 @@ namespace MWGui
}
- void WindowManager::setValue (int parSkill, const MWMechanics::Stat<float>& value)
+ void WindowManager::setValue (int parSkill, const MWMechanics::SkillValue& value)
{
/// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we
/// allow custom skills.
@@ -646,19 +648,14 @@ namespace MWGui
mGarbageDialogs.push_back(dialog);
}
- void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, bool showInDialogueModeOnly)
+ void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode)
{
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) {
+ if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) {
mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message));
- } else {
- if (showInDialogueModeOnly) {
- if (getMode() == GM_Dialogue)
- mMessageBoxManager->createMessageBox(message);
- } else {
- mMessageBoxManager->createMessageBox(message);
- }
+ } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) {
+ mMessageBoxManager->createMessageBox(message);
}
} else {
mMessageBoxManager->createInteractiveMessageBox(message, buttons);
@@ -676,17 +673,6 @@ namespace MWGui
mMessageBoxManager->removeStaticMessageBox();
}
- void WindowManager::enterPressed ()
- {
- mMessageBoxManager->okayPressed();
- }
-
- void WindowManager::activateKeyPressed ()
- {
- mMessageBoxManager->okayPressed();
- mCountDialog->cancel();
- }
-
int WindowManager::readPressedButton ()
{
return mMessageBoxManager->readPressedButton();
@@ -709,6 +695,10 @@ namespace MWGui
mToolTips->onFrame(frameDuration);
+ if (MWBase::Environment::get().getStateManager()->getState()==
+ MWBase::StateManager::State_NoGame)
+ return;
+
if (mDragAndDrop->mIsOnDragAndDrop)
{
assert(mDragAndDrop->mDraggedWidget);
@@ -719,7 +709,9 @@ namespace MWGui
mInventoryWindow->onFrame();
- mStatsWindow->onFrame();
+ mStatsWindow->onFrame(frameDuration);
+ mMap->onFrame(frameDuration);
+ mSpellWindow->onFrame(frameDuration);
mWaitDialog->onFrame(frameDuration);
@@ -740,31 +732,20 @@ namespace MWGui
mCompanionWindow->onFrame();
}
- void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell)
+ void WindowManager::changeCell(MWWorld::CellStore* cell)
{
+ std::string name = MWBase::Environment::get().getWorld()->getCellName (cell);
+
+ mMap->setCellName( name );
+ mHud->setCellName( name );
+
if (cell->mCell->isExterior())
{
- std::string name;
- if (cell->mCell->mName != "")
- {
- name = cell->mCell->mName;
+ if (!cell->mCell->mName.empty())
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() );
@@ -772,8 +753,6 @@ namespace MWGui
}
else
{
- mMap->setCellName( cell->mCell->mName );
- mHud->setCellName( cell->mCell->mName );
mMap->setCellPrefix( cell->mCell->mName );
mHud->setCellPrefix( cell->mCell->mName );
@@ -784,7 +763,6 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos);
mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y);
}
-
}
void WindowManager::setInteriorMapTexture(const int x, const int y)
@@ -1052,6 +1030,11 @@ namespace MWGui
{
mSelectedSpell = "";
mHud->unsetSelectedSpell();
+
+ MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer();
+ if (player->getDrawState() == MWMechanics::DrawState_Spell)
+ player->setDrawState(MWMechanics::DrawState_Nothing);
+
mSpellWindow->setTitle("#{sNone}");
}
@@ -1178,12 +1161,12 @@ namespace MWGui
return mGuiModes.back();
}
- std::map<int, MWMechanics::Stat<float> > WindowManager::getPlayerSkillValues()
+ std::map<int, MWMechanics::SkillValue > WindowManager::getPlayerSkillValues()
{
return mPlayerSkillValues;
}
- std::map<int, MWMechanics::Stat<int> > WindowManager::getPlayerAttributeValues()
+ std::map<int, MWMechanics::AttributeValue > WindowManager::getPlayerAttributeValues()
{
return mPlayerAttributes;
}
@@ -1247,7 +1230,7 @@ namespace MWGui
bool WindowManager::getRestEnabled()
{
//Enable rest dialogue if character creation finished
- if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalVariable ("chargenstate").mFloat==-1)
+ if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1)
mRestAllowed=true;
return mRestAllowed;
}
@@ -1400,4 +1383,19 @@ namespace MWGui
Settings::Manager::setFloat(setting + " h", "Windows", h);
}
+ void WindowManager::clear()
+ {
+ mMap->clear();
+ }
+
+ void WindowManager::write(ESM::ESMWriter &writer)
+ {
+ mMap->write(writer);
+ }
+
+ void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type)
+ {
+ mMap->readRecord(reader, type);
+ }
+
}
diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp
index b332ffc364..dafb65e47b 100644
--- a/apps/openmw/mwgui/windowmanagerimp.hpp
+++ b/apps/openmw/mwgui/windowmanagerimp.hpp
@@ -105,6 +105,7 @@ namespace MWGui
*/
virtual void update();
+ /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
virtual void setKeyFocusWidget (MyGUI::Widget* widget);
virtual void setNewGame(bool newgame);
@@ -152,8 +153,8 @@ namespace MWGui
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::AttributeValue& value);
+ virtual void setValue (int parSkill, const MWMechanics::SkillValue& 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);
@@ -219,18 +220,16 @@ namespace MWGui
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 messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible);
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 std::map<int, MWMechanics::SkillValue > getPlayerSkillValues();
+ virtual std::map<int, MWMechanics::AttributeValue > getPlayerAttributeValues();
virtual SkillList getPlayerMinorSkills();
virtual SkillList getPlayerMajorSkills();
@@ -282,6 +281,12 @@ namespace MWGui
virtual bool getCursorVisible();
+ /// Clear all savegame-specific data
+ virtual void clear();
+
+ virtual void write (ESM::ESMWriter& writer);
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type);
+
private:
bool mConsoleOnlyScripts;
@@ -346,9 +351,9 @@ namespace MWGui
// Various stats about player as needed by window manager
std::string mPlayerName;
std::string mPlayerRaceId;
- std::map<int, MWMechanics::Stat<int> > mPlayerAttributes;
+ std::map<int, MWMechanics::AttributeValue > mPlayerAttributes;
SkillList mPlayerMajorSkills, mPlayerMinorSkills;
- std::map<int, MWMechanics::Stat<float> > mPlayerSkillValues;
+ std::map<int, MWMechanics::SkillValue > mPlayerSkillValues;
MyGUI::Gui *mGui; // Gui
std::vector<GuiMode> mGuiModes;
diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp
index 1ad4095155..8bc6facea0 100644
--- a/apps/openmw/mwinput/inputmanagerimp.cpp
+++ b/apps/openmw/mwinput/inputmanagerimp.cpp
@@ -9,6 +9,7 @@
#include <MyGUI_RenderManager.h>
#include <MyGUI_Widget.h>
#include <MyGUI_Button.h>
+#include <MyGUI_EditBox.h>
#include <openengine/ogre/renderer.hpp>
@@ -16,10 +17,11 @@
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
-#include "../mwgui/bookwindow.hpp"
+#include "../mwbase/statemanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
using namespace ICS;
@@ -159,20 +161,6 @@ namespace MWInput
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)
@@ -181,7 +169,9 @@ namespace MWInput
switch (action)
{
case A_GameMenu:
- toggleMainMenu ();
+ if(!(MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running
+ && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu))
+ toggleMainMenu ();
break;
case A_Screenshot:
screenshot();
@@ -195,14 +185,7 @@ namespace MWInput
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
+ if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
activate();
break;
case A_Journal:
@@ -269,8 +252,6 @@ namespace MWInput
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)
@@ -301,7 +282,9 @@ namespace MWInput
return;
// Disable movement in Gui mode
- if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
+ if (MWBase::Environment::get().getWindowManager()->isGuiMode()
+ || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)
+ return;
// Configure player movement according to keyboard input. Actual movement will
@@ -355,7 +338,7 @@ namespace MWInput
// 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 ();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
mOverencumberedMessageDelay -= dt;
if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player))
{
@@ -469,7 +452,7 @@ namespace MWInput
mInputBinder->adjustMouseRegion(width, height);
}
- bool InputManager::keyPressed( const SDL_KeyboardEvent &arg )
+ void InputManager::keyPressed( const SDL_KeyboardEvent &arg )
{
// Cut, copy & paste
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
@@ -497,6 +480,9 @@ namespace MWInput
edit->deleteTextSelection();
}
}
+ }
+ if (edit && !edit->getEditStatic())
+ {
if (arg.keysym.sym == SDLK_c && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL)))
{
std::string text = edit->getTextSelection();
@@ -508,18 +494,10 @@ namespace MWInput
mInputBinder->keyPressed (arg);
- if((arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER)
- && MWBase::Environment::get().getWindowManager()->isGuiMode())
- {
- // Pressing enter when a messagebox is prompting for "ok" will activate the ok button
- 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)
@@ -530,47 +508,41 @@ namespace MWInput
MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it);
}
- bool InputManager::keyReleased(const SDL_KeyboardEvent &arg )
+ void 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 )
+ void 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
+ return; // 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)
+ if (b && b->getEnabled())
{
MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f);
}
}
-
- return true;
}
- bool InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id )
+ void 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 )
+ void InputManager::mouseMoved(const SFO::MouseMotionEvent &arg )
{
mInputBinder->mouseMoved (arg);
@@ -591,15 +563,6 @@ namespace MWInput
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)
@@ -608,8 +571,6 @@ namespace MWInput
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;
@@ -619,8 +580,8 @@ namespace MWInput
// 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);
+ mPlayer->yaw(x);
+ mPlayer->pitch(-y);
}
if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change
@@ -629,8 +590,6 @@ namespace MWInput
MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true);
}
}
-
- return true;
}
void InputManager::windowFocusChange(bool have_focus)
@@ -649,7 +608,7 @@ namespace MWInput
void InputManager::windowClosed()
{
- MWBase::Environment::setRequestExit();
+ MWBase::Environment::get().getStateManager()->requestQuit();
}
void InputManager::toggleMainMenu()
@@ -676,7 +635,13 @@ namespace MWInput
if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
// Not allowed before the magic window is accessible
- if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Magic))
+ if (!mControlSwitch["playermagic"])
+ return;
+
+ // Not allowed if no spell selected
+ MWWorld::InventoryStore& inventory = MWWorld::Class::get(mPlayer->getPlayer()).getInventoryStore(mPlayer->getPlayer());
+ if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() &&
+ inventory.getSelectedEnchantItem() == inventory.end())
return;
MWMechanics::DrawState_ state = mPlayer->getDrawState();
@@ -691,7 +656,7 @@ namespace MWInput
if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return;
// Not allowed before the inventory window is accessible
- if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
+ if (!mControlSwitch["playerfighting"])
return;
MWMechanics::DrawState_ state = mPlayer->getDrawState();
@@ -736,21 +701,6 @@ namespace MWInput
// .. 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())
@@ -796,7 +746,8 @@ namespace MWInput
void InputManager::showQuickKeysMenu()
{
- if (!MWBase::Environment::get().getWindowManager()->isGuiMode ())
+ if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()
+ && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1)
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);
diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp
index d4693ff28c..bd3f4954b1 100644
--- a/apps/openmw/mwinput/inputmanagerimp.hpp
+++ b/apps/openmw/mwinput/inputmanagerimp.hpp
@@ -1,5 +1,5 @@
-#ifndef _MWINPUT_MWINPUTMANAGERIMP_H
-#define _MWINPUT_MWINPUTMANAGERIMP_H
+#ifndef MWINPUT_MWINPUTMANAGERIMP_H
+#define MWINPUT_MWINPUTMANAGERIMP_H
#include "../mwgui/mode.hpp"
@@ -86,13 +86,13 @@ namespace MWInput
virtual void resetToDefaultBindings();
public:
- virtual bool keyPressed(const SDL_KeyboardEvent &arg );
- virtual bool keyReleased( const SDL_KeyboardEvent &arg );
+ virtual void keyPressed(const SDL_KeyboardEvent &arg );
+ virtual void 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 mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id );
+ virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id );
+ virtual void mouseMoved( const SFO::MouseMotionEvent &arg );
virtual void windowVisibilityChange( bool visible );
virtual void windowFocusChange( bool have_focus );
@@ -173,7 +173,6 @@ namespace MWInput
void toggleSpell();
void toggleWeapon();
void toggleInventory();
- void toggleContainer();
void toggleConsole();
void screenshot();
void toggleJournal();
diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp
index 05877e454f..994798b0bb 100644
--- a/apps/openmw/mwmechanics/activespells.cpp
+++ b/apps/openmw/mwmechanics/activespells.cpp
@@ -126,7 +126,8 @@ namespace MWMechanics
return mSpells;
}
- void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects, const std::string &displayName)
+ void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects,
+ const std::string &displayName, const std::string& casterHandle)
{
bool exists = false;
for (TContainer::const_iterator it = begin(); it != end(); ++it)
@@ -139,6 +140,7 @@ namespace MWMechanics
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
params.mEffects = effects;
params.mDisplayName = displayName;
+ params.mCasterHandle = casterHandle;
if (!exists || stack)
mSpells.insert (std::make_pair(id, params));
@@ -148,6 +150,12 @@ namespace MWMechanics
mSpellsChanged = true;
}
+ void ActiveSpells::removeEffects(const std::string &id)
+ {
+ mSpells.erase(Misc::StringUtils::lowerCase(id));
+ mSpellsChanged = true;
+ }
+
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
{
for (TContainer::const_iterator it = begin(); it != end(); ++it)
@@ -164,7 +172,7 @@ namespace MWMechanics
float magnitude = effectIt->mMagnitude;
if (magnitude)
- visitor.visit(effectIt->mKey, name, magnitude, remainingTime);
+ visitor.visit(effectIt->mKey, name, it->second.mCasterHandle, magnitude, remainingTime);
}
}
}
@@ -197,4 +205,22 @@ namespace MWMechanics
}
mSpellsChanged = true;
}
+
+ void ActiveSpells::purge(const std::string &actorHandle)
+ {
+ for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
+ {
+ for (std::vector<Effect>::iterator effectIt = it->second.mEffects.begin();
+ effectIt != it->second.mEffects.end();)
+ {
+ const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectIt->mKey.mId);
+ if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked
+ && it->second.mCasterHandle == actorHandle)
+ effectIt = it->second.mEffects.erase(effectIt);
+ else
+ effectIt++;
+ }
+ }
+ mSpellsChanged = true;
+ }
}
diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp
index 92faf790f2..7a40afb4cb 100644
--- a/apps/openmw/mwmechanics/activespells.hpp
+++ b/apps/openmw/mwmechanics/activespells.hpp
@@ -37,8 +37,8 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeStamp;
std::string mDisplayName;
- // TODO: To handle CASTER_LINKED flag (spell is purged when caster dies),
- // we should probably store a handle to the caster here.
+ // Handle to the caster that that inflicted this spell on us
+ std::string mCasterHandle;
};
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
@@ -76,15 +76,23 @@ namespace MWMechanics
/// \param stack If false, the spell is not added if one with the same ID exists already.
/// \param effects
/// \param displayName Name for display in magic menu.
+ /// \param casterHandle
///
- void addSpell (const std::string& id, bool stack, std::vector<Effect> effects, const std::string& displayName);
+ void addSpell (const std::string& id, bool stack, std::vector<Effect> effects,
+ const std::string& displayName, const std::string& casterHandle);
- /// Remove all active effects with this id
+ /// Removes the active effects from this spell/potion/.. with \a id
+ void removeEffects (const std::string& id);
+
+ /// Remove all active effects with this effect id
void purgeEffect (short effectId);
/// Remove all active effects, if roll succeeds (for each effect)
void purgeAll (float chance);
+ /// Remove all effects with CASTER_LINKED flag that were cast by \a actorHandle
+ void purge (const std::string& actorHandle);
+
bool isSpellActive (std::string id) const;
///< case insensitive
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 79dd985628..1fb22ce639 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -11,9 +11,9 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/actionequip.hpp"
+#include "../mwworld/player.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@@ -28,6 +28,7 @@
#include "../mwbase/mechanicsmanager.hpp"
#include "aicombat.hpp"
+#include "aifollow.hpp"
namespace
{
@@ -48,8 +49,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
{
- // TODO: remove this check once creatures support inventory store
- if (ptr.getTypeName() == typeid(ESM::NPC).name())
+ if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item =
@@ -80,14 +80,95 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
return true;
}
}
- return true;
+ return false;
}
+void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
+{
+ MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
+ const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).mMagnitude > 0;
+ int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
+
+ health = 0.1 * endurance;
+
+ magicka = 0;
+ if (!stunted)
+ {
+ float fRestMagicMult = settings.find("fRestMagicMult")->getFloat ();
+ magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
+ }
+}
}
namespace MWMechanics
{
+
+ class SoulTrap : public MWMechanics::EffectSourceVisitor
+ {
+ MWWorld::Ptr mCreature;
+ MWWorld::Ptr mActor;
+ public:
+ SoulTrap(MWWorld::Ptr trappedCreature)
+ : mCreature(trappedCreature) {}
+
+ virtual void visit (MWMechanics::EffectKey key,
+ const std::string& sourceName, const std::string& casterHandle,
+ float magnitude, float remainingTime = -1)
+ {
+ if (key.mId != ESM::MagicEffect::Soultrap)
+ return;
+ if (magnitude <= 0)
+ return;
+
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+
+ MWWorld::Ptr caster = world->searchPtrViaHandle(casterHandle);
+ if (caster.isEmpty() || !caster.getClass().isActor())
+ return;
+
+ static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->getFloat();
+
+ float creatureSoulValue = mCreature.get<ESM::Creature>()->mBase->mData.mSoul;
+ if (creatureSoulValue == 0)
+ return;
+
+ // Use the smallest soulgem that is large enough to hold the soul
+ MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
+ MWWorld::ContainerStoreIterator gem = container.end();
+ float gemCapacity = std::numeric_limits<float>().max();
+ std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/
+ for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous);
+ it != container.end(); ++it)
+ {
+ const std::string& id = it->getCellRef().mRefID;
+ if (id.size() >= soulgemFilter.size()
+ && id.substr(0,soulgemFilter.size()) == soulgemFilter)
+ {
+ float thisGemCapacity = it->get<ESM::Miscellaneous>()->mBase->mData.mValue * fSoulgemMult;
+ if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity
+ && it->getCellRef().mSoul.empty())
+ {
+ gem = it;
+ gemCapacity = thisGemCapacity;
+ }
+ }
+ }
+
+ if (gem == container.end())
+ return;
+
+ // Set the soul on just one of the gems, not the whole stack
+ gem->getContainerStore()->unstack(*gem, caster);
+ gem->getCellRef().mSoul = mCreature.getCellRef().mRefID;
+
+ if (caster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}");
+ }
+ };
+
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{
// magic effects
@@ -96,50 +177,49 @@ namespace MWMechanics
calculateDynamicStats (ptr);
calculateCreatureStatModifiers (ptr, duration);
- if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
+ // AI
+ if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
{
- // AI
- if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
+ CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
+ //engage combat or not?
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ if(ptr != player && !creatureStats.isHostile())
{
- CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
- //engage combat or not?
- if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile())
+ ESM::Position playerpos = player.getRefData().getPosition();
+ ESM::Position actorpos = ptr.getRefData().getPosition();
+ float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0])
+ +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
+ +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
+ float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified();
+ float disp = 100; //creatures don't have disposition, so set it to 100 by default
+ if(ptr.getTypeName() == typeid(ESM::NPC).name())
{
- ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition();
- ESM::Position actorpos = ptr.getRefData().getPosition();
- float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0])
- +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
- +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
- float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1);
- float disp = 100; //creatures don't have disposition, so set it to 100 by default
- if(ptr.getTypeName() == typeid(ESM::NPC).name())
- {
- disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
- }
- bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
- if( ( (fight == 100 )
- || (fight >= 95 && d <= 3000)
- || (fight >= 90 && d <= 2000)
- || (fight >= 80 && d <= 1000)
- || (fight >= 80 && disp <= 40)
- || (fight >= 70 && disp <= 35 && d <= 1000)
- || (fight >= 60 && disp <= 30 && d <= 1000)
- || (fight >= 50 && disp == 0)
- || (fight >= 40 && disp <= 10 && d <= 500) )
- && LOS
- )
- {
- creatureStats.getAiSequence().stack(AiCombat("player"));
- creatureStats.setHostile(true);
- }
+ disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
+ }
+ bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player)
+ && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr);
+ if( ( (fight == 100 )
+ || (fight >= 95 && d <= 3000)
+ || (fight >= 90 && d <= 2000)
+ || (fight >= 80 && d <= 1000)
+ || (fight >= 80 && disp <= 40)
+ || (fight >= 70 && disp <= 35 && d <= 1000)
+ || (fight >= 60 && disp <= 30 && d <= 1000)
+ || (fight >= 50 && disp == 0)
+ || (fight >= 40 && disp <= 10 && d <= 500) )
+ && LOS
+ )
+ {
+ creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()));
+ creatureStats.setHostile(true);
}
-
- creatureStats.getAiSequence().execute (ptr,duration);
}
- // fatigue restoration
- calculateRestoration(ptr, duration);
+ creatureStats.getAiSequence().execute (ptr,duration);
}
+
+ // fatigue restoration
+ calculateRestoration(ptr, duration, false);
}
void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused)
@@ -166,7 +246,7 @@ namespace MWMechanics
now += creatureStats.getActiveSpells().getMagicEffects();
- MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now);
+ //MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now);
creatureStats.setMagicEffects(now);
@@ -197,44 +277,37 @@ namespace MWMechanics
creatureStats.setFatigue(fatigue);
}
- void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration)
+ void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep)
{
if (ptr.getClass().getCreatureStats(ptr).isDead())
return;
- CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
- const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
- int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
-
- 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;
+ MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
+ const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
- if (duration == 3600)
+ if (sleep)
{
- // the actor is sleeping, restore health and magicka
+ float health, magicka;
+ getRestorationPerHourOfSleep(ptr, health, magicka);
- bool stunted = stats.getMagicEffects ().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0;
+ DynamicStat<float> stat = stats.getHealth();
+ stat.setCurrent(stat.getCurrent() + health);
+ stats.setHealth(stat);
- DynamicStat<float> health = stats.getHealth();
- health.setCurrent (health.getCurrent() + 0.1 * endurance);
- stats.setHealth (health);
+ stat = stats.getMagicka();
+ stat.setCurrent(stat.getCurrent() + magicka);
+ stats.setMagicka(stat);
+ }
- if (!stunted)
- {
- float fRestMagicMult = settings.find("fRestMagicMult")->getFloat ();
+ int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
- DynamicStat<float> magicka = stats.getMagicka();
- magicka.setCurrent (magicka.getCurrent()
- + fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified());
- stats.setMagicka (magicka);
- }
- }
+ float capacity = ptr.getClass().getCapacity(ptr);
+ float encumbrance = ptr.getClass().getEncumbrance(ptr);
+ float normalizedEncumbrance = (capacity == 0 ? 1 : encumbrance/capacity);
+ if (normalizedEncumbrance > 1)
+ normalizedEncumbrance = 1;
// restore fatigue
-
float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat ();
float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat ();
float fEndFatigueMult = settings.find("fEndFatigueMult")->getFloat ();
@@ -245,6 +318,7 @@ namespace MWMechanics
DynamicStat<float> fatigue = stats.getFatigue();
fatigue.setCurrent (fatigue.getCurrent() + duration * x);
stats.setFatigue (fatigue);
+
}
void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
@@ -255,7 +329,7 @@ namespace MWMechanics
// attributes
for(int i = 0;i < ESM::Attribute::Length;++i)
{
- Stat<int> stat = creatureStats.getAttribute(i);
+ AttributeValue stat = creatureStats.getAttribute(i);
stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude -
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude -
effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude);
@@ -267,23 +341,47 @@ namespace MWMechanics
for(int i = 0;i < 3;++i)
{
DynamicStat<float> stat = creatureStats.getDynamic(i);
- stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude -
- effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude);
+ stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).mMagnitude -
+ effects.get(ESM::MagicEffect::DrainHealth+i).mMagnitude);
- float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude
- - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude
- - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude;
- stat.setCurrent(stat.getCurrent() + currentDiff * duration);
+ float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).mMagnitude
+ - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).mMagnitude
+ - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).mMagnitude;
+ stat.setCurrent(stat.getCurrent() + currentDiff * duration, i == 2);
creatureStats.setDynamic(i, stat);
}
+ // AI setting modifiers
+ int creature = !ptr.getClass().isNpc();
+ if (creature && ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Humanoid)
+ creature = false;
+ // Note: the Creature variants only work on normal creatures, not on daedra or undead creatures.
+ if (!creature || ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Creatures)
+ {
+ Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Fight);
+ stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid+creature).mMagnitude
+ - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).mMagnitude);
+ creatureStats.setAiSetting(CreatureStats::AI_Fight, stat);
+
+ stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
+ stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid+creature).mMagnitude
+ - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).mMagnitude);
+ creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
+ }
+ if (creature && ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Undead)
+ {
+ Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
+ stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).mMagnitude);
+ creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
+ }
+
// Apply disintegration (reduces item health)
- float disintegrateWeapon = effects.get(EffectKey(ESM::MagicEffect::DisintegrateWeapon)).mMagnitude;
+ float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).mMagnitude;
if (disintegrateWeapon > 0)
disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration);
- float disintegrateArmor = effects.get(EffectKey(ESM::MagicEffect::DisintegrateArmor)).mMagnitude;
+ float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).mMagnitude;
if (disintegrateArmor > 0)
{
// According to UESP
@@ -315,7 +413,7 @@ namespace MWMechanics
DynamicStat<float> health = creatureStats.getHealth();
for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
{
- float magnitude = creatureStats.getMagicEffects().get(EffectKey(damageEffects[i])).mMagnitude;
+ float magnitude = creatureStats.getMagicEffects().get(damageEffects[i]).mMagnitude;
if (damageEffects[i] == ESM::MagicEffect::SunDamage)
{
@@ -346,30 +444,32 @@ namespace MWMechanics
static std::map<int, std::string> boundItemsMap;
if (boundItemsMap.empty())
{
- boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe";
- boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots";
- boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass";
- boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger";
- boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below
- boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm";
- boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow";
- boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword";
- boundItemsMap[ESM::MagicEffect::BoundMace] = "mace";
- boundItemsMap[ESM::MagicEffect::BoundShield] = "shield";
- boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear";
+ boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID";
+ boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID";
+ boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID";
+ boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID";
+ boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below)
+ boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID";
+ boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID";
+ boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID";
+ boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID";
+ boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID";
+ boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID";
}
for (std::map<int, std::string>::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it)
{
bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end();
- int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
+ int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude;
if (found != (magnitude > 0))
{
- std::string item = "bound_" + it->second;
+ std::string itemGmst = it->second;
+ std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ itemGmst)->getString();
if (it->first == ESM::MagicEffect::BoundGloves)
{
- adjustBoundItem(item + "_left", magnitude > 0, ptr);
- adjustBoundItem(item + "_right", magnitude > 0, ptr);
+ adjustBoundItem("sMagicBoundLeftGauntletID", magnitude > 0, ptr);
+ adjustBoundItem("sMagicBoundRightGauntletID", magnitude > 0, ptr);
}
else
adjustBoundItem(item, magnitude > 0, ptr);
@@ -385,38 +485,40 @@ namespace MWMechanics
static std::map<int, std::string> summonMap;
if (summonMap.empty())
{
- summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon";
- summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon";
- summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon";
- summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon";
- summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon";
- summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon";
- summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon";
- summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon";
- summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon";
- summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon";
- summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon";
- summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon";
- summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon";
- summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ";
- summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon";
- summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon";
- summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon";
- summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon";
- summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon";
- summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon";
+ summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID";
+ summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID";
+ summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID";
+ summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID";
+ summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID";
+ summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID";
+ summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID";
+ summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID";
+ summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID";
+ summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID";
+ summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID";
+ summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID";
+ summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID";
+ summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID";
+ summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID";
+ summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID";
+ summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID";
+ summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID";
+ summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID";
+ summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID";
+ summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID";
+ summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID";
}
for (std::map<int, std::string>::iterator it = summonMap.begin(); it != summonMap.end(); ++it)
{
bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end();
- int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
+ int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude;
if (found != (magnitude > 0))
{
if (magnitude > 0)
{
ESM::Position ipos = ptr.getRefData().getPosition();
- Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]);
+ Ogre::Vector3 pos(ipos.pos);
Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
const float distance = 50;
pos = pos + distance*rot.yAxis();
@@ -427,15 +529,20 @@ namespace MWMechanics
ipos.rot[1] = 0;
ipos.rot[2] = 0;
- MWWorld::CellStore* store = ptr.getCell();
- MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1);
- ref.getPtr().getCellRef().mPos = ipos;
-
- // TODO: Add AI to follow player and fight for him
-
- creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
- MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle()));
+ std::string creatureID =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->getString();
+ if (!creatureID.empty())
+ {
+ MWWorld::CellStore* store = ptr.getCell();
+ MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1);
+ ref.getPtr().getCellRef().mPos = ipos;
+
+ // TODO: Add AI to follow player and fight for him
+ // TODO: VFX_SummonStart, VFX_SummonEnd
+ creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
+ MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle()));
+ }
}
else
{
@@ -461,7 +568,7 @@ namespace MWMechanics
// skills
for(int i = 0;i < ESM::Skill::Length;++i)
{
- Stat<float>& skill = npcStats.getSkill(i);
+ SkillValue& skill = npcStats.getSkill(i);
skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude -
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude -
effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude);
@@ -488,10 +595,11 @@ namespace MWMechanics
if(timeLeft == 0.0f)
{
// If drowning, apply 3 points of damage per second
- ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - 3.0f*duration);
+ static const float fSuffocationDamage = world->getStore().get<ESM::GameSetting>().find("fSuffocationDamage")->getFloat();
+ ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - fSuffocationDamage*duration);
// Play a drowning sound as necessary for the player
- if(ptr == world->getPlayer().getPlayer())
+ if(ptr == world->getPlayerPtr())
{
MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager();
if(!sndmgr->getSoundPlaying(MWWorld::Ptr(), "drown"))
@@ -500,7 +608,10 @@ namespace MWMechanics
}
}
else
- stats.setTimeToStartDrowning(20);
+ {
+ static const float fHoldBreathTime = world->getStore().get<ESM::GameSetting>().find("fHoldBreathTime")->getFloat();
+ stats.setTimeToStartDrowning(fHoldBreathTime);
+ }
}
void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration)
@@ -646,7 +757,7 @@ namespace MWMechanics
}
}
- void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore, const MWWorld::Ptr& ignore)
+ void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore)
{
PtrControllerMap::iterator iter = mActors.begin();
while(iter != mActors.end())
@@ -663,77 +774,130 @@ namespace MWMechanics
void Actors::update (float duration, bool paused)
{
- if (!paused)
+ if(!paused)
{
+ // Reset data from previous frame
+ for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
+ {
+ // Reset last hit object, which is only valid for one frame
+ // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation
+ // (below)
+ iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
+ }
+
+ // AI and magic effects update
+ for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ {
+ if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
+ {
+ updateActor(iter->first, duration);
+ if(iter->first.getTypeName() == typeid(ESM::NPC).name())
+ updateNpc(iter->first, duration, paused);
+ }
+ }
+
+ // Looping magic VFX update
+ // Note: we need to do this before any of the animations are updated.
+ // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
+ // so updating VFX immediately after that would just remove the particle effects instantly.
+ // There needs to be a magic effect update in between.
+ for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ iter->second->updateContinuousVfx();
+
+ // Animation/movement update
+ for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ {
+ if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get(
+ ESM::MagicEffect::Paralyze).mMagnitude > 0)
+ iter->second->skipAnim();
+ iter->second->update(duration);
+ }
+
+ // Kill dead actors
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, duration);
- if(iter->first.getTypeName() == typeid(ESM::NPC).name())
- updateNpc(iter->first, duration, paused);
-
if(!stats.isDead())
continue;
}
// If it's the player and God Mode is turned on, keep it alive
- if(iter->first.getRefData().getHandle()=="player" &&
+ if(iter->first.getRefData().getHandle()=="player" &&
MWBase::Environment::get().getWorld()->getGodModeState())
{
- MWMechanics::DynamicStat<float> stat(stats.getHealth());
+ MWMechanics::DynamicStat<float> stat (stats.getHealth());
- if(stat.getModified()<1)
+ if (stat.getModified()<1)
{
stat.setModified(1, 0);
stats.setHealth(stat);
}
-
stats.resurrect();
continue;
}
- if(iter->second->isDead())
- continue;
-
- iter->second->kill();
+ // Make sure spell effects with CasterLinked flag are removed
+ // TODO: would be nice not to do this all the time...
+ for(PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2)
+ {
+ MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells();
+ spells.purge(iter->first.getRefData().getHandle());
+ }
- // Reset magic effects and recalculate derived effects
- // One case where we need this is to make sure bound items are removed upon death
- stats.setMagicEffects(MWMechanics::MagicEffects());
- calculateCreatureStatModifiers(iter->first, 0);
+ // FIXME: see http://bugs.openmw.org/issues/869
+ MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false);
- ++mDeathCount[cls.getId(iter->first)];
+ if (iter->second->kill())
+ {
+ ++mDeathCount[cls.getId(iter->first)];
- if(cls.isEssential(iter->first))
- MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
- }
- }
+ // Apply soultrap
+ if (iter->first.getTypeName() == typeid(ESM::Creature).name())
+ {
+ SoulTrap soulTrap (iter->first);
+ stats.getActiveSpells().visitEffectSources(soulTrap);
+ }
- if(!paused)
- {
- // Note: we need to do this before any of the animations are updated.
- // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
- // so updating VFX immediately after that would just remove the particle effects instantly.
- // There needs to be a magic effect update in between.
- for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
- iter->second->updateContinuousVfx();
+ // Reset magic effects and recalculate derived effects
+ // One case where we need this is to make sure bound items are removed upon death
+ stats.setMagicEffects(MWMechanics::MagicEffects());
+ calculateCreatureStatModifiers(iter->first, 0);
- for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
- iter->second->update(duration);
+ if(cls.isEssential(iter->first))
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
+ }
+ }
}
}
- void Actors::restoreDynamicStats()
+ void Actors::restoreDynamicStats(bool sleep)
{
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
- calculateRestoration(iter->first, 3600);
+ calculateRestoration(iter->first, 3600, sleep);
+ }
+
+ int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const
+ {
+ float healthPerHour, magickaPerHour;
+ getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour);
+
+ CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
+
+ float healthHours = healthPerHour >= 0
+ ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour
+ : 1.0f;
+ float magickaHours = magickaPerHour >= 0
+ ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour
+ : 1.0f;
+
+ int autoHours = std::ceil(std::max(1.f, std::max(healthHours, magickaHours)));
+ return autoHours;
}
int Actors::countDeaths (const std::string& id) const
@@ -771,4 +935,31 @@ namespace MWMechanics
return iter->second->isAnimPlaying(groupName);
return false;
}
+
+ void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
+ {
+ for (PtrControllerMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
+ {
+ if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
+ out.push_back(iter->first);
+ }
+ }
+
+ std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
+ {
+ std::list<MWWorld::Ptr> list;
+ 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);
+
+ if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow)
+ {
+ MWMechanics::AiFollow* package = static_cast<MWMechanics::AiFollow*>(stats.getAiSequence().getActivePackage());
+ if(package->getFollowedActor() == actor.getCellRef().mRefID)
+ list.push_front(iter->first);
+ }
+ }
+ return list;
+ }
}
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
index 83aff63e3c..4b18ac862a 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -25,9 +25,6 @@ namespace MWMechanics
{
class Actors
{
- typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
- PtrControllerMap mActors;
-
std::map<std::string, int> mDeathCount;
void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused);
@@ -39,7 +36,7 @@ namespace MWMechanics
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
void calculateNpcStatModifiers (const MWWorld::Ptr& ptr);
- void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
+ void calculateRestoration (const MWWorld::Ptr& ptr, float duration, bool sleep);
void updateDrowning (const MWWorld::Ptr& ptr, float duration);
@@ -50,6 +47,11 @@ namespace MWMechanics
Actors();
~Actors();
+ typedef std::map<MWWorld::Ptr,CharacterController*> PtrControllerMap;
+
+ PtrControllerMap::const_iterator begin() { return mActors.begin(); }
+ PtrControllerMap::const_iterator end() { return mActors.end(); }
+
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)
void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); }
@@ -77,9 +79,12 @@ namespace MWMechanics
///< 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();
+ void restoreDynamicStats(bool sleep);
///< If the player is sleeping, this should be called every hour.
-
+
+ int getHoursToRest(const MWWorld::Ptr& ptr) const;
+ ///< Calculate how many hours the given actor needs to rest in order to be fully healed
+
int countDeaths (const std::string& id) const;
///< Return the number of deaths for actors with the given ID.
@@ -88,6 +93,15 @@ namespace MWMechanics
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);
+
+ void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
+
+ std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
+ ///<return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor)
+
+ private:
+ PtrControllerMap mActors;
+
};
}
diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp
index ee0dcf96e5..1f3c585216 100644
--- a/apps/openmw/mwmechanics/aiactivate.cpp
+++ b/apps/openmw/mwmechanics/aiactivate.cpp
@@ -1,21 +1,113 @@
#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,float duration)
-{
- std::cout << "AiActivate completed.\n";
- return true;
-}
-
-int MWMechanics::AiActivate::getTypeId() const
-{
- return 4;
-}
+#include <iostream>
+
+#include "movement.hpp"
+
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwworld/action.hpp"
+
+#include "steering.hpp"
+
+namespace
+{
+ float sgn(float a)
+ {
+ if(a > 0)
+ return 1.0;
+ return -1.0;
+ }
+}
+
+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,float duration)
+{
+ 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->getPlayerPtr();
+ 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;
+ }
+ }
+
+ MWWorld::Ptr target = world->getPtr(mObjectId,false);
+ ESM::Position targetPos = target.getRefData().getPosition();
+
+ bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
+ if(!mPathFinder.isPathConstructed() || cellChange)
+ {
+ mCellX = cell->mData.mX;
+ mCellY = cell->mData.mY;
+
+ ESM::Pathgrid::Point dest;
+ dest.mX = targetPos.pos[0];
+ dest.mY = targetPos.pos[1];
+ dest.mZ = targetPos.pos[2];
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ mPathFinder.buildPath(start, dest, actor.getCell(), true);
+ }
+
+ if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
+ (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
+ (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200)
+ {
+ movement.mPosition[1] = 0;
+ MWWorld::Ptr target = world->getPtr(mObjectId,false);
+ MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
+ return true;
+ }
+
+ if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
+ {
+ movement.mPosition[1] = 0;
+ MWWorld::Ptr target = world->getPtr(mObjectId,false);
+ MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
+ return true;
+ }
+
+ float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
+ zTurn(actor, Ogre::Degree(zAngle));
+ MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
+ movement.mPosition[1] = 1;
+
+ return false;
+}
+
+int MWMechanics::AiActivate::getTypeId() const
+{
+ return TypeIdActivate;
+}
diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp
index f922e238c2..7c94c25896 100644
--- a/apps/openmw/mwmechanics/aiactivate.hpp
+++ b/apps/openmw/mwmechanics/aiactivate.hpp
@@ -4,6 +4,8 @@
#include "aipackage.hpp"
#include <string>
+#include "pathfinding.hpp"
+
namespace MWMechanics
{
@@ -18,6 +20,10 @@ namespace MWMechanics
private:
std::string mObjectId;
+
+ PathFinder mPathFinder;
+ int mCellX;
+ int mCellY;
};
}
#endif // GAME_MWMECHANICS_AIACTIVATE_H
diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp
index 39a97df6c1..17a624f0ff 100644
--- a/apps/openmw/mwmechanics/aicombat.cpp
+++ b/apps/openmw/mwmechanics/aicombat.cpp
@@ -1,138 +1,357 @@
#include "aicombat.hpp"
-#include "movement.hpp"
+#include <OgreMath.h>
+#include <OgreVector3.h>
+
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/timestamp.hpp"
-#include "../mwbase/world.hpp"
+#include "../mwworld/inventorystore.hpp"
+
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
-#include "creaturestats.hpp"
-#include "npcstats.hpp"
-#include "OgreMath.h"
+#include "creaturestats.hpp"
+#include "steering.hpp"
+#include "movement.hpp"
+#include "character.hpp" // fixme: for getActiveWeapon
namespace
{
- static float sgn(float a)
+ static float sgn(Ogre::Radian a)
{
- if(a > 0)
+ if(a.valueDegrees() > 0)
return 1.0;
return -1.0;
}
+
+ //chooses an attack depending on probability to avoid uniformity
+ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
}
namespace MWMechanics
{
-
- AiCombat::AiCombat(const std::string &targetId)
- :mTargetId(targetId),mTimer(0),mTimer2(0)
+ AiCombat::AiCombat(const MWWorld::Ptr& actor) :
+ mTarget(actor),
+ mTimerAttack(0),
+ mTimerReact(0),
+ mTimerCombatMove(0),
+ mFollowTarget(false),
+ mReadyToAttack(false),
+ mStrike(false),
+ mCombatMove(false),
+ mRotate(false),
+ mMovement(),
+ mTargetAngle(0)
{
}
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
{
- if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true;
-
- const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false);
+ //General description
+ if(!actor.getClass().getCreatureStats(actor).isHostile()
+ || actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
+ return true;
- if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true;
+ if(mTarget.getClass().getCreatureStats(mTarget).isDead())
+ return true;
- if(actor.getTypeName() == typeid(ESM::NPC).name())
+ //Update every frame
+ if(mCombatMove)
{
- MWWorld::Class::get(actor).
- MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true);
- MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState();
- if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
- MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
- //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
+ mTimerCombatMove -= duration;
+ if( mTimerCombatMove <= 0)
+ {
+ mTimerCombatMove = 0;
+ mMovement.mPosition[1] = mMovement.mPosition[0] = 0;
+ mCombatMove = false;
+ }
}
- ESM::Position pos = actor.getRefData().getPosition();
- const ESM::Pathgrid *pathgrid =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
- float xCell = 0;
- float yCell = 0;
+ actor.getClass().getMovementSettings(actor) = mMovement;
- if (actor.getCell()->mCell->isExterior())
+ if (mRotate)
{
- xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE;
- yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE;
+ if (zTurn(actor, Ogre::Degree(mTargetAngle)))
+ mRotate = false;
}
+
- ESM::Pathgrid::Point dest;
- dest.mX = target.getRefData().getPosition().pos[0];
- dest.mY = target.getRefData().getPosition().pos[1];
- dest.mZ = target.getRefData().getPosition().pos[2];
+ mTimerAttack -= duration;
+ actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike);
- ESM::Pathgrid::Point start;
- start.mX = pos.pos[0];
- start.mY = pos.pos[1];
- start.mZ = pos.pos[2];
+ float tReaction = 0.25f;
+ if(mTimerReact < tReaction)
+ {
+ mTimerReact += duration;
+ return false;
+ }
- mTimer2 = mTimer2 + duration;
+ //Update with period = tReaction
- if(!mPathFinder.isPathConstructed())
- mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
- else
+ mTimerReact = 0;
+
+ //actual attacking logic
+ //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
+ float attackPeriod = 1.0f;
+ if(mReadyToAttack)
{
- mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true);
- ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
- if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() ||
- (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200))
+ if(mTimerAttack <= -attackPeriod)
{
- mTimer2 = 0;
- mPathFinder = mPathFinder2;
+ //TODO: should depend on time between 'start' to 'min attack'
+ //for better controlling of NPCs' attack strength.
+ //Also it seems that this time is different for slash/thrust/chop
+ mTimerAttack = 0.35f * static_cast<float>(rand())/RAND_MAX;
+ mStrike = true;
+
+ //say a provoking combat phrase
+ if (actor.getClass().isNpc())
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt();
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < chance)
+ {
+ MWBase::Environment::get().getDialogueManager()->say(actor, "attack");
+ }
+ }
}
+ else if (mTimerAttack <= 0)
+ mStrike = false;
}
+ else
+ {
+ mTimerAttack = -attackPeriod;
+ mStrike = false;
+ }
+
+ const MWWorld::Class &cls = actor.getClass();
+ const ESM::Weapon *weapon = NULL;
+ MWMechanics::WeaponType weaptype;
+ float weapRange, weapSpeed = 1.0f;
- mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
+ actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
- 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;
+ if (actor.getClass().hasInventoryStore(actor))
+ {
+ MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState();
+ if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
+ actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
+ //Get weapon speed and range
+ MWWorld::ContainerStoreIterator weaponSlot =
+ MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype);
+ if (weaptype == WeapType_HandToHand)
+ {
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ weapRange = gmst.find("fHandToHandReach")->getFloat();
+ }
+ else
+ {
+ weapon = weaponSlot->get<ESM::Weapon>()->mBase;
+ weapRange = weapon->mData.mReach;
+ weapSpeed = weapon->mData.mSpeed;
+ }
+ weapRange *= 100.0f;
+ }
+ else //is creature
+ {
+ weaptype = WeapType_HandToHand; //doesn't matter, should only reflect if it is melee or distant weapon
+ weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
+ }
- float range = 100;
- MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
- if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ)
- < range*range)
+ ESM::Position pos = actor.getRefData().getPosition();
+
+ float rangeMelee;
+ float rangeCloseUp;
+ bool distantCombat = false;
+ if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown)
+ {
+ rangeMelee = 1000; // TODO: should depend on archer skill
+ rangeCloseUp = 0; //doesn't needed when attacking from distance
+ distantCombat = true;
+ }
+ else
{
- float directionX = dest.mX - start.mX;
- float directionY = dest.mY - start.mY;
- float directionResult = sqrt(directionX * directionX + directionY * directionY);
+ rangeMelee = weapRange;
+ rangeCloseUp = 300;
+ }
- zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees();
- MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
+ Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]);
+ ESM::Position targetPos = mTarget.getRefData().getPosition();
+ Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]);
+ Ogre::Vector3 vDir = vDest - vStart;
+ float distBetween = vDir.length();
- mPathFinder.clearPath();
+ if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) )
+ {
+ //Melee and Close-up combat
+ vDir.z = 0;
+ float dirLen = vDir.length();
+ mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
+ mRotate = true;
- if(mTimer == 0)
+ //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
+ if (mFollowTarget && distBetween > rangeMelee)
{
- MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
- //mTimer = mTimer + duration;
+ //Close-up combat: just run up on target
+ mMovement.mPosition[1] = 1;
}
- if( mTimer > 1)
+ else
{
- MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
- mTimer = 0;
+ //Melee: stop running and attack
+ mMovement.mPosition[1] = 0;
+
+ // When attacking with a weapon, choose between slash, thrust or chop
+ if (actor.getClass().hasInventoryStore(actor))
+ chooseBestAttack(weapon, mMovement);
+
+ if(mMovement.mPosition[0] || mMovement.mPosition[1])
+ {
+ mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
+ mCombatMove = true;
+ }
+ else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5)))
+ {
+ //apply sideway movement (kind of dodging) with some probability
+ if(static_cast<float>(rand())/RAND_MAX < 0.25)
+ {
+ mMovement.mPosition[0] = static_cast<float>(rand())/RAND_MAX < 0.5? 1: -1;
+ mTimerCombatMove = 0.05f + 0.15f * static_cast<float>(rand())/RAND_MAX;
+ mCombatMove = true;
+ }
+ }
+
+ if(distantCombat && distBetween < rangeMelee/4)
+ {
+ mMovement.mPosition[1] = -1;
+ }
+
+ mReadyToAttack = true;
+ //only once got in melee combat, actor is allowed to use close-up shortcutting
+ mFollowTarget = true;
}
- else
+ }
+ else
+ {
+ //target is at far distance: build path to target OR follow target (if previously actor had reached it once)
+ mFollowTarget = false;
+
+ buildNewPath(actor); //may fail to build a path, check before use
+
+ //delete visited path node
+ mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
+
+ //if no new path leave mTargetAngle unchanged
+ if(!mPathFinder.getPath().empty())
{
- mTimer = mTimer + duration;
+ //try shortcut
+ if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))
+ mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees();
+ else
+ mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
+ mRotate = true;
}
+
+ mMovement.mPosition[1] = 1;
+ mReadyToAttack = false;
+ }
+
+ if(distBetween > rangeMelee)
+ {
+ //special run attack; it shouldn't affect melee combat tactics
+ if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1)
+ {
+ //check if actor can overcome the distance = distToTarget - attackerWeapRange
+ //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing)
+ //then start attacking
+ float speed1 = cls.getSpeed(actor);
+ float speed2 = mTarget.getClass().getSpeed(mTarget);
+ if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0
+ && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0)
+ speed2 = 0;
- MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
- //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell());
+ float s1 = distBetween - weapRange;
+ float t = s1/speed1;
+ float s2 = speed2 * t;
+ float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags
+ if (t + s2/speed1 <= t_swing)
+ {
+ mReadyToAttack = true;
+ if(mTimerAttack <= -attackPeriod)
+ {
+ mTimerAttack = 0.3f*static_cast<float>(rand())/RAND_MAX;
+ mStrike = true;
+ }
+ }
+ }
}
+
+ actor.getClass().getMovementSettings(actor) = mMovement;
+
return false;
}
+ void AiCombat::buildNewPath(const MWWorld::Ptr& actor)
+ {
+ //Construct path to target
+ ESM::Pathgrid::Point dest;
+ dest.mX = mTarget.getRefData().getPosition().pos[0];
+ dest.mY = mTarget.getRefData().getPosition().pos[1];
+ dest.mZ = mTarget.getRefData().getPosition().pos[2];
+ Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ);
+
+ float dist = -1; //hack to indicate first time, to construct a new path
+ if(!mPathFinder.getPath().empty())
+ {
+ ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
+ Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ);
+ dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length());
+ }
+
+ float targetPosThreshold;
+ bool isOutside = actor.getCell()->mCell->isExterior();
+ if (isOutside)
+ targetPosThreshold = 300;
+ else
+ targetPosThreshold = 100;
+
+ if((dist < 0) || (dist > targetPosThreshold))
+ {
+ //construct new path only if target has moved away more than on <targetPosThreshold>
+ ESM::Position pos = actor.getRefData().getPosition();
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ if(!mPathFinder.isPathConstructed())
+ mPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
+ else
+ {
+ PathFinder newPathFinder;
+ newPathFinder.buildPath(start, dest, actor.getCell(), isOutside);
+
+ //TO EXPLORE:
+ //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path,
+ //not the actual path length. Here we should know if the new path is actually more effective.
+ //if(pathFinder2.getPathSize() < mPathFinder.getPathSize())
+ if(!mPathFinder.getPath().empty())
+ {
+ newPathFinder.syncStart(mPathFinder.getPath());
+ mPathFinder = newPathFinder;
+ }
+ }
+ }
+ }
+
int AiCombat::getTypeId() const
{
- return 5;
+ return TypeIdCombat;
}
unsigned int AiCombat::getPriority() const
@@ -140,9 +359,60 @@ namespace MWMechanics
return 1;
}
+ const std::string &AiCombat::getTargetId() const
+ {
+ return mTarget.getRefData().getHandle();
+ }
+
+
AiCombat *MWMechanics::AiCombat::clone() const
{
return new AiCombat(*this);
}
}
+
+namespace
+{
+
+void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement)
+{
+ if (weapon == NULL)
+ {
+ //hand-to-hand deal equal damage for each type
+ float roll = static_cast<float>(rand())/RAND_MAX;
+ if(roll <= 0.333f) //side punch
+ {
+ movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
+ movement.mPosition[1] = 0;
+ }
+ else if(roll <= 0.666f) //forward punch
+ movement.mPosition[1] = 1;
+ else
+ {
+ movement.mPosition[1] = movement.mPosition[0] = 0;
+ }
+
+ return;
+ }
+
+ //the more damage attackType deals the more probability it has
+ 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;
+
+ float total = slash + chop + thrust;
+
+ float roll = static_cast<float>(rand())/RAND_MAX;
+ if(roll <= static_cast<float>(slash)/total)
+ {
+ movement.mPosition[0] = (static_cast<float>(rand())/RAND_MAX < 0.5f)? 1: -1;
+ movement.mPosition[1] = 0;
+ }
+ else if(roll <= (static_cast<float>(slash) + static_cast<float>(thrust))/total)
+ movement.mPosition[1] = 1;
+ else
+ movement.mPosition[1] = movement.mPosition[0] = 0;
+}
+
+}
diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp
index fa71e261fc..767a362924 100644
--- a/apps/openmw/mwmechanics/aicombat.hpp
+++ b/apps/openmw/mwmechanics/aicombat.hpp
@@ -7,12 +7,14 @@
#include "movement.hpp"
+#include "../mwbase/world.hpp"
+
namespace MWMechanics
{
class AiCombat : public AiPackage
{
public:
- AiCombat(const std::string &targetId);
+ AiCombat(const MWWorld::Ptr& actor);
virtual AiCombat *clone() const;
@@ -23,14 +25,31 @@ namespace MWMechanics
virtual unsigned int getPriority() const;
- private:
- std::string mTargetId;
+ const std::string &getTargetId() const;
+ private:
PathFinder mPathFinder;
- PathFinder mPathFinder2;
- float mTimer;
- float mTimer2;
+ // controls duration of the actual strike
+ float mTimerAttack;
+ float mTimerReact;
+ // controls duration of the sideway & forward moves
+ // when mCombatMove is true
+ float mTimerCombatMove;
+
+ // the z rotation angle (degrees) we want to reach
+ // used every frame when mRotate is true
+ float mTargetAngle;
+
+ bool mReadyToAttack, mStrike;
+ bool mFollowTarget;
+ bool mCombatMove;
+ bool mRotate;
+
+ MWMechanics::Movement mMovement;
+ MWWorld::Ptr mTarget;
+
+ void buildNewPath(const MWWorld::Ptr& actor);
};
}
-#endif \ No newline at end of file
+#endif
diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp
index 3615c8546e..bac2584254 100644
--- a/apps/openmw/mwmechanics/aiescort.cpp
+++ b/apps/openmw/mwmechanics/aiescort.cpp
@@ -3,12 +3,13 @@
#include "movement.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/timestamp.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "steering.hpp"
+
namespace
{
float sgn(float a)
@@ -29,12 +30,12 @@ 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())
+ , mCellX(std::numeric_limits<int>::max())
+ , mCellY(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
+ // The CS Help File states that if a duration is given, 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;
@@ -48,12 +49,12 @@ namespace MWMechanics
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())
+ , mCellX(std::numeric_limits<int>::max())
+ , mCellY(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
+ // The CS Help File states that if a duration is given, 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;
@@ -83,49 +84,38 @@ namespace MWMechanics
return true;
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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);
+ bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY;
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.
+ // Check if actor is near the border of an inactive cell. If so, pause walking.
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;
+ return false;
}
}
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.
+ // Check if actor is near the border of an inactive cell. If so, pause walking.
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;
+ return false;
}
}
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;
- }
+ mCellX = actor.getCell()->mCell->mData.mX;
+ mCellY = actor.getCell()->mCell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = mX;
@@ -137,7 +127,7 @@ namespace MWMechanics
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
- mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
@@ -161,7 +151,7 @@ namespace MWMechanics
if(distanceBetweenResult <= mMaxDist * mMaxDist)
{
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
- MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
+ zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
mMaxDist = 470;
}
@@ -178,7 +168,7 @@ namespace MWMechanics
int AiEscort::getTypeId() const
{
- return 2;
+ return TypeIdEscort;
}
}
diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp
index f3f6d2bd9b..53b57c0580 100644
--- a/apps/openmw/mwmechanics/aiescort.hpp
+++ b/apps/openmw/mwmechanics/aiescort.hpp
@@ -34,8 +34,8 @@ namespace MWMechanics
unsigned int mDuration;
PathFinder mPathFinder;
- int cellX;
- int cellY;
+ int mCellX;
+ int mCellY;
};
}
#endif
diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp
index 73bf9259af..cf5291fd35 100644
--- a/apps/openmw/mwmechanics/aifollow.cpp
+++ b/apps/openmw/mwmechanics/aifollow.cpp
@@ -1,27 +1,118 @@
-#include "aifollow.hpp"
+#include "aifollow.hpp"
#include <iostream>
-
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwworld/class.hpp"
+#include "movement.hpp"
+
+#include <OgreMath.h>
+
+#include "steering.hpp"
+
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
+: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0)
+{
+}
+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), mTimer(0), mStuckTimer(0)
{
- return new AiFollow(*this);
}
- bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
+bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
{
- std::cout << "AiFollow completed.\n";
- return true;
+ const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
+
+ mTimer = mTimer + duration;
+ mStuckTimer = mStuckTimer + duration;
+ mTotalTime = mTotalTime + duration;
+
+ ESM::Position pos = actor.getRefData().getPosition();
+
+ if(mTotalTime > mDuration && mDuration != 0)
+ return true;
+
+ if((pos.pos[0]-mX)*(pos.pos[0]-mX) +
+ (pos.pos[1]-mY)*(pos.pos[1]-mY) +
+ (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100)
+ {
+ if(actor.getCell()->isExterior())
+ {
+ if(mCellId == "")
+ return true;
+ }
+ else
+ {
+ if(mCellId == actor.getCell()->mCell->mName)
+ return true;
+ }
+ }
+
+ ESM::Pathgrid::Point dest;
+ dest.mX = target.getRefData().getPosition().pos[0];
+ dest.mY = target.getRefData().getPosition().pos[1];
+ dest.mZ = target.getRefData().getPosition().pos[2];
+
+ ESM::Pathgrid::Point start;
+ start.mX = pos.pos[0];
+ start.mY = pos.pos[1];
+ start.mZ = pos.pos[2];
+
+ if(mPathFinder.getPath().empty())
+ mPathFinder.buildPath(start, dest, actor.getCell(), true);
+
+
+ if(mTimer > 0.25)
+ {
+ if(!mPathFinder.getPath().empty())
+ {
+ ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back();
+
+ if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX)
+ +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY)
+ +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ)
+ > 100*100)
+ mPathFinder.addPointToPath(dest);
+ }
+
+ mTimer = 0;
+ }
+
+ if(mStuckTimer>0.5)
+ {
+ if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0])
+ +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1])
+ +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck
+ mPathFinder.buildPath(start, dest, actor.getCell(), true);
+
+ mStuckTimer = 0;
+ mStuckPos = pos;
+ }
+
+ if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
+ {
+ zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
+ }
+
+ if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2])
+ < 100*100)
+ actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
+ else
+ actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
+
+ return false;
+}
+
+std::string MWMechanics::AiFollow::getFollowedActor()
+{
+ return mActorId;
+}
+
+MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const
+{
+ return new AiFollow(*this);
}
int MWMechanics::AiFollow::getTypeId() const
{
- return 3;
+ return TypeIdFollow;
}
diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp
index 39df024e4b..9d77b903de 100644
--- a/apps/openmw/mwmechanics/aifollow.hpp
+++ b/apps/openmw/mwmechanics/aifollow.hpp
@@ -3,6 +3,8 @@
#include "aipackage.hpp"
#include <string>
+#include "pathfinding.hpp"
+#include "../../../components/esm/defs.hpp"
namespace MWMechanics
{
@@ -17,6 +19,8 @@ namespace MWMechanics
///< \return Package completed?
virtual int getTypeId() const;
+ std::string getFollowedActor();
+
private:
float mDuration;
float mX;
@@ -24,6 +28,14 @@ namespace MWMechanics
float mZ;
std::string mActorId;
std::string mCellId;
+
+ float mTimer;
+ float mStuckTimer;
+ float mTotalTime;
+
+ ESM::Position mStuckPos;
+
+ PathFinder mPathFinder;
};
}
#endif
diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp
index 5832198dad..74c77bf978 100644
--- a/apps/openmw/mwmechanics/aipackage.hpp
+++ b/apps/openmw/mwmechanics/aipackage.hpp
@@ -12,7 +12,16 @@ namespace MWMechanics
class AiPackage
{
public:
-
+ enum TypeId {
+ TypeIdNone = -1,
+ TypeIdWander = 0,
+ TypeIdTravel = 1,
+ TypeIdEscort = 2,
+ TypeIdFollow = 3,
+ TypeIdActivate = 4,
+ TypeIdCombat = 5
+ };
+
virtual ~AiPackage();
virtual AiPackage *clone() const = 0;
@@ -21,7 +30,7 @@ namespace MWMechanics
///< \return Package completed?
virtual int getTypeId() const = 0;
- ///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
+ ///< @see enum TypeId
virtual unsigned int getPriority() const {return 0;}
///< higher number is higher priority (0 beeing the lowest)
diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp
index 6d461e5f63..2110393fdc 100644
--- a/apps/openmw/mwmechanics/aisequence.cpp
+++ b/apps/openmw/mwmechanics/aisequence.cpp
@@ -15,7 +15,6 @@
#include "npcstats.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwworld/player.hpp"
void MWMechanics::AiSequence::copy (const AiSequence& sequence)
{
@@ -24,7 +23,7 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence)
mPackages.push_back ((*iter)->clone());
}
-MWMechanics::AiSequence::AiSequence() : mDone (false) {}
+MWMechanics::AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {}
MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false)
{
@@ -56,6 +55,24 @@ int MWMechanics::AiSequence::getTypeId() const
return mPackages.front()->getTypeId();
}
+bool MWMechanics::AiSequence::getCombatTarget(std::string &targetActorId) const
+{
+ if (getTypeId() != AiPackage::TypeIdCombat)
+ return false;
+ const AiCombat *combat = static_cast<const AiCombat *>(mPackages.front());
+ targetActorId = combat->getTargetId();
+ return true;
+}
+
+void MWMechanics::AiSequence::stopCombat()
+{
+ while (getTypeId() == AiPackage::TypeIdCombat)
+ {
+ delete *mPackages.begin();
+ mPackages.erase (mPackages.begin());
+ }
+}
+
bool MWMechanics::AiSequence::isPackageDone() const
{
return mDone;
@@ -63,17 +80,21 @@ bool MWMechanics::AiSequence::isPackageDone() const
void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
{
- if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
+ if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr())
{
if (!mPackages.empty())
{
+ mLastAiPackage = mPackages.front()->getTypeId();
if (mPackages.front()->execute (actor,duration))
{
+ delete *mPackages.begin();
mPackages.erase (mPackages.begin());
mDone = true;
}
else
+ {
mDone = false;
+ }
}
}
}
@@ -91,7 +112,10 @@ void MWMechanics::AiSequence::stack (const AiPackage& package)
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); it++)
{
if(mPackages.front()->getPriority() <= package.getPriority())
+ {
mPackages.insert(it,package.clone());
+ return;
+ }
}
if(mPackages.empty())
@@ -103,6 +127,14 @@ void MWMechanics::AiSequence::queue (const AiPackage& package)
mPackages.push_back (package.clone());
}
+MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage()
+{
+ if(mPackages.empty())
+ throw std::runtime_error(std::string("No AI Package!"));
+ else
+ return mPackages.front();
+}
+
void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list)
{
for (std::vector<ESM::AIPackage>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it)
@@ -114,7 +146,7 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list)
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);
+ package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat);
}
else if (it->mType == ESM::AI_Escort)
{
diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp
index 0976ef0995..62f48f981e 100644
--- a/apps/openmw/mwmechanics/aisequence.hpp
+++ b/apps/openmw/mwmechanics/aisequence.hpp
@@ -23,6 +23,9 @@ namespace MWMechanics
void copy (const AiSequence& sequence);
+ // The type of AI package that ran last
+ int mLastAiPackage;
+
public:
AiSequence();
@@ -34,7 +37,18 @@ namespace MWMechanics
virtual ~AiSequence();
int getTypeId() const;
- ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate, 5 Combat
+ ///< @see enum AiPackage::TypeId
+
+ int getLastRunTypeId() const { return mLastAiPackage; }
+ ///< Get the typeid of the Ai package that ran last, NOT the currently "active" Ai package that will be run in the next frame.
+ /// This difference is important when an Ai package has just finished and been removed.
+
+ bool getCombatTarget (std::string &targetActorId) const;
+ ///< Return true and assign target if combat package is currently
+ /// active, return false otherwise
+
+ void stopCombat();
+ ///< Removes all combat packages until first non-combat or stack empty.
bool isPackageDone() const;
///< Has a package been completed during the last update?
@@ -52,6 +66,9 @@ namespace MWMechanics
///< Add \a package to the end of the sequence (executed after all other packages have been
/// completed)
+ AiPackage* getActivePackage();
+ ///< return the current active package. If there is no active package, throw an exeption
+
void fill (const ESM::AIPackageList& list);
};
}
diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp
index 08d7586388..03052f327b 100644
--- a/apps/openmw/mwmechanics/aitravel.cpp
+++ b/apps/openmw/mwmechanics/aitravel.cpp
@@ -1,11 +1,11 @@
#include "aitravel.hpp"
-#include "movement.hpp"
-
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
+
+#include "steering.hpp"
+#include "movement.hpp"
namespace
{
@@ -21,8 +21,8 @@ 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())
+ , mCellX(std::numeric_limits<int>::max())
+ , mCellY(std::numeric_limits<int>::max())
{
}
@@ -38,7 +38,7 @@ namespace MWMechanics
Movement &movement = actor.getClass().getMovementSettings(actor);
const ESM::Cell *cell = actor.getCell()->mCell;
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->mCell->mData.mX)
{
int sideX = sgn(cell->mData.mX - player.getCell()->mCell->mData.mX);
@@ -62,20 +62,11 @@ namespace MWMechanics
}
}
- const ESM::Pathgrid *pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell);
- bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY;
+ bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
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;
- }
+ mCellX = cell->mData.mX;
+ mCellY = cell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = mX;
@@ -87,7 +78,7 @@ namespace MWMechanics
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
- mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
+ mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
@@ -96,8 +87,7 @@ namespace MWMechanics
return true;
}
- float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
- world->rotateObject(actor, 0, 0, zAngle, false);
+ zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
movement.mPosition[1] = 1;
return false;
@@ -105,7 +95,7 @@ namespace MWMechanics
int AiTravel::getTypeId() const
{
- return 1;
+ return TypeIdTravel;
}
}
diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp
index b479dfd431..72f3e02983 100644
--- a/apps/openmw/mwmechanics/aitravel.hpp
+++ b/apps/openmw/mwmechanics/aitravel.hpp
@@ -23,8 +23,8 @@ namespace MWMechanics
float mY;
float mZ;
- int cellX;
- int cellY;
+ int mCellX;
+ int mCellY;
PathFinder mPathFinder;
};
diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp
index f66f7ca620..77316fedf9 100644
--- a/apps/openmw/mwmechanics/aiwander.cpp
+++ b/apps/openmw/mwmechanics/aiwander.cpp
@@ -3,13 +3,16 @@
#include "movement.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "creaturestats.hpp"
#include <OgreVector3.h>
+#include "steering.hpp"
+
namespace
{
float sgn(float a)
@@ -31,6 +34,7 @@ namespace MWMechanics
, mX(0)
, mY(0)
, mZ(0)
+ , mSaidGreeting(false)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
{
@@ -65,6 +69,8 @@ namespace MWMechanics
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
{
+ actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
+ actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
MWBase::World *world = MWBase::Environment::get().getWorld();
if(mDuration)
{
@@ -183,11 +189,57 @@ namespace MWMechanics
playIdle(actor, mPlayedIdle);
mChooseAction = false;
mIdleNow = true;
+
+ // Play idle voiced dialogue entries randomly
+ int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
+ if (hello > 0)
+ {
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+ float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+
+ // Don't bother if the player is out of hearing range
+ if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500)
+ MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
+ }
}
}
if(mIdleNow)
{
+ // Play a random voice greeting if the player gets too close
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+
+ int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
+ float helloDistance = hello;
+ int iGreetDistanceMultiplier = store.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
+ helloDistance *= iGreetDistanceMultiplier;
+
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
+ Ogre::Vector3(actor.getRefData().getPosition().pos));
+
+ if (!mSaidGreeting)
+ {
+ // TODO: check if actor is aware / has line of sight
+ if (playerDist <= helloDistance
+ // Only play a greeting if the player is not moving
+ && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0)
+ {
+ mSaidGreeting = true;
+ MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
+ // TODO: turn to face player and interrupt the idle animation?
+ }
+ }
+ else
+ {
+ float fGreetDistanceReset = store.get<ESM::GameSetting>().find("fGreetDistanceReset")->getFloat();
+ if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier)
+ mSaidGreeting = false;
+ }
+
+ // Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle))
{
mPlayedIdle = 0;
@@ -214,7 +266,7 @@ namespace MWMechanics
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
- mPathFinder.buildPath(start, dest, mPathgrid, mXCell, mYCell, false);
+ mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(mPathFinder.isPathConstructed())
{
@@ -235,10 +287,6 @@ namespace MWMechanics
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);
@@ -246,6 +294,12 @@ namespace MWMechanics
mWalking = false;
mChooseAction = true;
}
+ else
+ {
+ zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
+
+ actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
+ }
}
return false;
@@ -253,7 +307,7 @@ namespace MWMechanics
int AiWander::getTypeId() const
{
- return 0;
+ return TypeIdWander;
}
void AiWander::stopWalking(const MWWorld::Ptr& actor)
diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp
index 48bc62c625..9a44aa0650 100644
--- a/apps/openmw/mwmechanics/aiwander.hpp
+++ b/apps/openmw/mwmechanics/aiwander.hpp
@@ -32,6 +32,8 @@ namespace MWMechanics
std::vector<int> mIdle;
bool mRepeat;
+ bool mSaidGreeting;
+
float mX;
float mY;
float mZ;
diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp
index f994c28b84..af58e9ee0b 100644
--- a/apps/openmw/mwmechanics/alchemy.cpp
+++ b/apps/openmw/mwmechanics/alchemy.cpp
@@ -62,7 +62,7 @@ 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);
+ bool negative = flags & (ESM::MagicEffect::Harmful);
int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic;
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
index 351e33f13e..c2a26ced39 100644
--- a/apps/openmw/mwmechanics/character.cpp
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -32,25 +32,25 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/statemanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
namespace
{
-int getBestAttack (const ESM::Weapon* weapon)
+std::string 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;
+ return "slash";
else if (chop >= slash && chop >= thrust)
- return MWMechanics::CreatureStats::AT_Chop;
+ return "chop";
else
- return MWMechanics::CreatureStats::AT_Thrust;
+ return "thrust";
}
}
@@ -63,16 +63,6 @@ struct StateInfo {
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" },
@@ -129,7 +119,7 @@ static const struct WeaponInfo {
{ WeapType_TwoWide, "2w", "weapontwowide" },
{ WeapType_BowAndArrow, "1h", "bowandarrow" },
{ WeapType_Crossbow, "crossbow", "crossbow" },
- { WeapType_ThowWeapon, "1h", "throwweapon" },
+ { WeapType_Thrown, "1h", "throwweapon" },
{ WeapType_PickProbe, "1h", "pickprobe" },
{ WeapType_Spell, "spell", "spellcast" },
};
@@ -145,9 +135,73 @@ public:
{ return weap.type == type; }
};
+std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num)
+{
+ int numAnims=0;
+ while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1)))
+ ++numAnims;
+
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * numAnims + 1; // [1, numAnims]
+ if (num)
+ *num = roll;
+ return prefix + Ogre::StringConverter::toString(roll);
+}
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force)
{
+ // hit recoils/knockdown animations handling
+ if(mPtr.getClass().isActor())
+ {
+ bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
+ bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
+ bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
+ if(mHitState == CharState_None)
+ {
+ if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0)
+ {
+ mHitState = CharState_KnockOut;
+ mCurrentHit = "knockout";
+ mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul);
+ mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
+ }
+ else if(knockdown)
+ {
+ mHitState = CharState_KnockDown;
+ mCurrentHit = "knockdown";
+ mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
+ }
+ else if (recovery)
+ {
+ mHitState = CharState_Hit;
+ mCurrentHit = chooseRandomGroup("hit");
+ mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
+ }
+ else if (block)
+ {
+ mHitState = CharState_Block;
+ mCurrentHit = "shield";
+ mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0);
+ }
+ }
+ else if(!mAnimation->isPlaying(mCurrentHit))
+ {
+ mCurrentHit.erase();
+ if (knockdown)
+ mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
+ if (recovery)
+ mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
+ if (block)
+ mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
+ mHitState = CharState_None;
+ }
+ else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0)
+ {
+ mHitState = CharState_KnockDown;
+ mAnimation->disable(mCurrentHit);
+ mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0);
+ }
+ }
+
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
if(force || idle != mIdleState)
@@ -203,14 +257,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentJump);
mCurrentJump = jump;
- mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
+ if (mAnimation->hasAnimation("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,
+ if (mAnimation->hasAnimation("jump"))
+ mAnimation->play(jump, Priority_Jump, jumpgroup, true,
1.0f, "loop stop", "stop", 0.0f, 0);
}
}
@@ -259,16 +315,29 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if(!mCurrentMovement.empty())
{
float vel, speedmult = 1.0f;
+
+ bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
+
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f)
speedmult = mMovementSpeed / vel;
+ else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
+ speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed
+ else if (mMovementSpeed > 0.0f)
+ // The first person anims don't have any velocity to calculate a speed multiplier from.
+ // We use the third person velocities instead.
+ // FIXME: should be pulled from the actual animation, but it is not presently loaded.
+ speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
+
+ mMovementAnimVelocity = vel;
}
+ else mMovementAnimVelocity = 0.0f;
}
}
-void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group)
+void getWeaponGroup(WeaponType weaptype, std::string &group)
{
const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype));
if(info != sWeaponTypeListEnd)
@@ -276,7 +345,7 @@ void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group
}
-MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
+MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
{
if(stats.getDrawState() == DrawState_Spell)
{
@@ -324,7 +393,7 @@ MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &s
*weaptype = WeapType_Crossbow;
break;
case ESM::Weapon::MarksmanThrown:
- *weaptype = WeapType_ThowWeapon;
+ *weaptype = WeapType_Thrown;
break;
}
}
@@ -336,6 +405,23 @@ MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &s
return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
}
+void CharacterController::playRandomDeath(float startpoint)
+{
+ if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
+ {
+ mDeathState = CharState_SwimDeath;
+ mCurrentDeath = "swimdeath";
+ }
+ else
+ {
+ int selected=0;
+ mCurrentDeath = chooseRandomGroup("death", &selected);
+ mDeathState = static_cast<CharacterState>(CharState_Death1 + (selected-1));
+ }
+
+ mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
+ false, 1.0f, "start", "stop", startpoint, 0);
+}
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
: mPtr(ptr)
@@ -343,7 +429,9 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mIdleState(CharState_None)
, mMovementState(CharState_None)
, mMovementSpeed(0.0f)
+ , mMovementAnimVelocity(0.0f)
, mDeathState(CharState_None)
+ , mHitState(CharState_None)
, mUpperBodyState(UpperCharState_Nothing)
, mJumpState(JumpState_None)
, mWeaponType(WeapType_None)
@@ -361,14 +449,16 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
* 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())
+ if (cls.hasInventoryStore(mPtr))
{
- getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
+ getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
if(mWeaponType != WeapType_None)
{
getWeaponGroup(mWeaponType, mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
+ mAnimation->showWeapons(true);
}
+ mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand);
}
if(!cls.getCreatureStats(mPtr).isDead())
@@ -388,15 +478,10 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
}
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);
+ playRandomDeath(1.0f);
}
}
@@ -410,18 +495,51 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
mPtr = ptr;
}
+bool CharacterController::updateCreatureState()
+{
+ const MWWorld::Class &cls = mPtr.getClass();
+ CreatureStats &stats = cls.getCreatureStats(mPtr);
+
+ if(stats.getAttackingOrSpell())
+ {
+ if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
+ {
+ MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
-bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak)
+ // These are unique animations and not linked to movement type. Just pick one randomly.
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 3; // [0, 2]
+ if (roll == 0)
+ mCurrentWeapon = "attack1";
+ else if (roll == 1)
+ mCurrentWeapon = "attack2";
+ else
+ mCurrentWeapon = "attack3";
+
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_All, true,
+ 1, "start", "stop",
+ 0.0f, 0);
+ mUpperBodyState = UpperCharState_StartToMinAttack;
+ }
+ }
+
+ bool animPlaying = mAnimation->getInfo(mCurrentWeapon);
+ if (!animPlaying)
+ mUpperBodyState = UpperCharState_Nothing;
+ return false;
+}
+
+bool CharacterController::updateWeaponState()
{
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
- NpcStats &stats = cls.getNpcStats(mPtr);
+ CreatureStats &stats = cls.getCreatureStats(mPtr);
WeaponType weaptype = WeapType_None;
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
- const bool isWerewolf = stats.isWerewolf();
+ const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
bool forcestateupdate = false;
- if(weaptype != mWeaponType)
+ if(weaptype != mWeaponType && mHitState != CharState_KnockDown)
{
forcestateupdate = true;
@@ -443,6 +561,8 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
{
getWeaponGroup(weaptype, weapgroup);
mAnimation->showWeapons(false);
+ mAnimation->setWeaponGroup(weapgroup);
+
mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
@@ -479,7 +599,9 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
if(isWerewolf)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
- if(isrunning && !inwater && mWeaponType == WeapType_None)
+ if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
+ && !MWBase::Environment::get().getWorld()->isSwimming(mPtr)
+ && mWeaponType == WeapType_None)
{
if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx,
@@ -494,11 +616,24 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
if(isWeapon)
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
+ // Cancel attack if we no longer have ammunition
+ bool ammunition = true;
+ MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
+ if (mWeaponType == WeapType_Crossbow)
+ ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt);
+ else if (mWeaponType == WeapType_BowAndArrow)
+ ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow);
+ if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
+ {
+ mAnimation->disable(mCurrentWeapon);
+ mUpperBodyState = UpperCharState_WeapEquiped;
+ }
+
float complete;
bool animPlaying;
if(stats.getAttackingOrSpell())
{
- if(mUpperBodyState == UpperCharState_WeapEquiped)
+ if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None)
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackType.clear();
@@ -506,7 +641,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
{
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
- mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
+ stats.setAttackingOrSpell(false);
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@@ -514,8 +649,10 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
// This has to be done at the start of the casting animation,
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
if (mPtr.getRefData().getHandle() == "player")
- stats.getSpells().setSelectedSpell(MWBase::Environment::get().getWindowManager()->getSelectedSpell());
-
+ {
+ std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
+ stats.getSpells().setSelectedSpell(selectedSpell);
+ }
std::string spellid = stats.getSpells().getSelectedSpell();
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr))
@@ -530,7 +667,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
const ESM::MagicEffect *effect;
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
- const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
+ const ESM::Static* castStatic;
+ if (!effect->mCasting.empty())
+ castStatic = store.get<ESM::Static>().find (effect->mCasting);
+ else
+ castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
+
mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
@@ -591,38 +733,34 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
if(item.getRefData().getCount())
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
}
- else
+ else if (ammunition)
{
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow ||
- mWeaponType == WeapType_ThowWeapon)
+ mWeaponType == WeapType_Thrown)
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";
+ if(isWeapon && mPtr.getRefData().getHandle() == "player" &&
+ Settings::Manager::getBool("best attack", "Game"))
+ mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
else
- mAttackType = "thrust";
+ determineAttackType();
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
weapSpeed, mAttackType+" start", mAttackType+" min attack",
0.0f, 0);
- mUpperBodyState = UpperCharState_StartToMinAttack;
+ mUpperBodyState = UpperCharState_StartToMinAttack;
}
}
+
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
}
else
{
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
- if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack)
+ if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown)
{
if(mAttackType != "shoot")
{
@@ -655,6 +793,55 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
1.0f-complete, 0);
mUpperBodyState = UpperCharState_MaxAttackToMinHit;
}
+ else if (mHitState == CharState_KnockDown)
+ {
+ mUpperBodyState = UpperCharState_WeapEquiped;
+ mAnimation->disable(mCurrentWeapon);
+ }
+ }
+
+ mAnimation->setPitchFactor(0.f);
+ if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
+ {
+ switch (mUpperBodyState)
+ {
+ case UpperCharState_StartToMinAttack:
+ mAnimation->setPitchFactor(complete);
+ break;
+ case UpperCharState_MinAttackToMaxAttack:
+ case UpperCharState_MaxAttackToMinHit:
+ case UpperCharState_MinHitToHit:
+ mAnimation->setPitchFactor(1.f);
+ break;
+ case UpperCharState_FollowStartToFollowStop:
+ if (animPlaying)
+ mAnimation->setPitchFactor(1.f-complete);
+ break;
+ default:
+ break;
+ }
+ }
+ else if (mWeaponType == WeapType_Crossbow)
+ {
+ switch (mUpperBodyState)
+ {
+ case UpperCharState_EquipingWeap:
+ mAnimation->setPitchFactor(complete);
+ break;
+ case UpperCharState_UnEquipingWeap:
+ mAnimation->setPitchFactor(1.f-complete);
+ break;
+ case UpperCharState_WeapEquiped:
+ case UpperCharState_StartToMinAttack:
+ case UpperCharState_MinAttackToMaxAttack:
+ case UpperCharState_MaxAttackToMinHit:
+ case UpperCharState_MinHitToHit:
+ case UpperCharState_FollowStartToFollowStop:
+ mAnimation->setPitchFactor(1.f);
+ break;
+ default:
+ break;
+ }
}
if(!animPlaying)
@@ -662,58 +849,104 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
if(mUpperBodyState == UpperCharState_EquipingWeap ||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
mUpperBodyState == UpperCharState_CastingSpell)
+ {
+ if (ammunition && mWeaponType == WeapType_Crossbow)
+ mAnimation->attachArrow();
+
mUpperBodyState = UpperCharState_WeapEquiped;
+ //don't allow to continue playing hit animation on UpperBody after actor had attacked during it
+ if(mHitState == CharState_Hit)
+ {
+ mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody);
+ //commenting out following 2 lines will give a bit different combat dynamics(slower)
+ mHitState = CharState_None;
+ mCurrentHit.clear();
+ mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
+ }
+ }
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
mUpperBodyState = UpperCharState_Nothing;
}
else if(complete >= 1.0f)
{
- if(mUpperBodyState == UpperCharState_StartToMinAttack)
+ std::string start, stop;
+ switch(mUpperBodyState)
{
- 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;
+ case UpperCharState_StartToMinAttack:
+ start = mAttackType+" min attack";
+ stop = mAttackType+" max attack";
+ mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
+ break;
+ case UpperCharState_MinAttackToMaxAttack:
+ //hack to avoid body pos desync when jumping/sneaking in 'max attack' state
+ if(!mAnimation->isPlaying(mCurrentWeapon))
+ mAnimation->play(mCurrentWeapon, Priority_Weapon,
+ MWRender::Animation::Group_UpperBody, false,
+ 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
+ break;
+ case UpperCharState_MaxAttackToMinHit:
+ if(mAttackType == "shoot")
+ {
+ start = mAttackType+" min hit";
+ stop = mAttackType+" release";
+ }
+ else
+ {
+ start = mAttackType+" min hit";
+ stop = mAttackType+" hit";
+ }
+ mUpperBodyState = UpperCharState_MinHitToHit;
+ break;
+ case UpperCharState_MinHitToHit:
+ if(mAttackType == "shoot")
+ {
+ start = mAttackType+" follow start";
+ stop = mAttackType+" follow stop";
+ }
+ else
+ {
+ float str = stats.getAttackStrength();
+ start = mAttackType+((str < 0.5f) ? " small follow start"
+ : (str < 1.0f) ? " medium follow start"
+ : " large follow start");
+ stop = mAttackType+((str < 0.5f) ? " small follow stop"
+ : (str < 1.0f) ? " medium follow stop"
+ : " large follow stop");
+ }
+ mUpperBodyState = UpperCharState_FollowStartToFollowStop;
+ break;
+ default:
+ break;
}
- else if(mUpperBodyState == UpperCharState_MaxAttackToMinHit)
+
+ if(!start.empty())
{
mAnimation->disable(mCurrentWeapon);
- if(mAttackType == "shoot")
+ if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, Priority_Weapon,
- MWRender::Animation::Group_UpperBody, false,
- weapSpeed, mAttackType+" min hit", mAttackType+" follow start",
- 0.0f, 0);
+ MWRender::Animation::Group_UpperBody, true,
+ weapSpeed, start, stop, 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;
+ weapSpeed, start, stop, 0.0f, 0);
}
- else if(mUpperBodyState == UpperCharState_MinHitToHit)
+ }
+
+ //if playing combat animation and lowerbody is not busy switch to whole body animation
+ if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying)
+ {
+ if( mMovementState != CharState_None ||
+ mJumpState != JumpState_None ||
+ mHitState != CharState_None ||
+ MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
+ cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
{
- 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;
+ mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
+ }
+ else
+ {
+ mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
}
}
@@ -760,10 +993,14 @@ void CharacterController::update(float duration)
{
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 isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
+ bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
bool flying = world->isFlying(mPtr);
- Ogre::Vector3 vec = cls.getMovementVector(mPtr);
+ //Ogre::Vector3 vec = cls.getMovementVector(mPtr);
+ Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition);
+ vec.normalise();
+ if(mHitState != CharState_None && mJumpState == JumpState_None)
+ vec = Ogre::Vector3(0.0f);
Ogre::Vector3 rot = cls.getRotationVector(mPtr);
mMovementSpeed = cls.getSpeed(mPtr);
@@ -776,6 +1013,7 @@ void CharacterController::update(float duration)
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")
{
@@ -799,6 +1037,41 @@ void CharacterController::update(float duration)
}
}
+ // reduce fatigue
+ const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
+ float fatigueLoss = 0;
+ static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat();
+ static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat();
+ static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat();
+ static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat();
+ static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat();
+ static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat();
+ static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat();
+ static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat();
+
+ const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr);
+ if (encumbrance < 1)
+ {
+ if (sneak)
+ fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult;
+ else
+ {
+ if (inwater)
+ {
+ if (!isrunning)
+ fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult;
+ else
+ fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult;
+ }
+ if (isrunning)
+ fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult;
+ }
+ }
+ fatigueLoss *= duration;
+ DynamicStat<float> fatigue = cls.getCreatureStats(mPtr).getFatigue();
+ fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0);
+ cls.getCreatureStats(mPtr).setFatigue(fatigue);
+
if(sneak || inwater || flying)
vec.z = 0.0f;
@@ -815,8 +1088,6 @@ void CharacterController::update(float duration)
cls.getCreatureStats(mPtr).land();
}
- const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
-
forcestateupdate = (mJumpState != JumpState_Falling);
mJumpState = JumpState_Falling;
@@ -879,21 +1150,24 @@ void CharacterController::update(float duration)
int realHealthLost = healthLost * (1.0f - 0.25 * fatigueTerm);
health.setCurrent(health.getCurrent() - realHealthLost);
cls.getCreatureStats(mPtr).setHealth(health);
+ cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), true);
- // report acrobatics progression
- if (mPtr.getRefData().getHandle() == "player")
- cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
-
- const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified();
+ const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
if (healthLost > (acrobaticsSkill * fatigueTerm))
{
- //TODO: actor falls over
+ cls.getCreatureStats(mPtr).setKnockedDown(true);
+ }
+ else
+ {
+ // report acrobatics progression
+ if (mPtr.getRefData().getHandle() == "player")
+ cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
}
}
}
else
{
- if(!(vec.z > 0.0f))
+ if(!(vec.z > 0.0f))
mJumpState = JumpState_None;
vec.z = 0.0f;
@@ -949,20 +1223,36 @@ void CharacterController::update(float duration)
}
}
- if(cls.isNpc())
- forcestateupdate = updateNpcState(onground, inwater, isrunning, sneak) || forcestateupdate;
+ if(cls.hasInventoryStore(mPtr))
+ forcestateupdate = updateWeaponState() || forcestateupdate;
+ else
+ forcestateupdate = updateCreatureState() || forcestateupdate;
refreshCurrentAnims(idlestate, movestate, forcestateupdate);
- rot *= duration * Ogre::Math::RadiansToDegrees(1.0f);
- world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
+ if (!mSkipAnim)
+ {
+ rot *= Ogre::Math::RadiansToDegrees(1.0f);
+ if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut)
+ {
+ world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
+ }
+ else //avoid z-rotating for knockdown
+ world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true);
+
+ // always control actual movement by animation unless this:
+ // FIXME: actor falling/landing should be controlled by physics engine
+ if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None))
+ {
+ world->queueMovement(mPtr, vec);
+ }
+ }
- world->queueMovement(mPtr, vec);
movement = vec;
+ cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
else if(cls.getCreatureStats(mPtr).isDead())
{
- MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false);
world->queueMovement(mPtr, Ogre::Vector3(0.0f));
}
@@ -974,9 +1264,11 @@ void CharacterController::update(float duration)
else
moved = Ogre::Vector3(0.0f);
- // Ensure we're moving in generally the right direction
+ // Ensure we're moving in generally the right direction...
if(mMovementSpeed > 0.f)
{
+ float l = moved.length();
+
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;
@@ -986,7 +1278,12 @@ void CharacterController::update(float duration)
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;
+ // but keep the original speed
+ float newLength = moved.length();
+ if (newLength > 0)
+ moved *= (l / newLength);
}
+
// Update movement
if(moved.squaredLength() > 1.0f)
world->queueMovement(mPtr, moved);
@@ -1053,65 +1350,33 @@ void CharacterController::forceStateUpdate()
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);
+ playRandomDeath();
}
}
-void CharacterController::kill()
+bool CharacterController::kill()
{
- if(mDeathState != CharState_None)
- return;
-
- if(mPtr.getTypeName() == typeid(ESM::NPC).name())
+ if( isDead() )
{
- 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)))
+ //player's death animation is over
+ if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) )
{
- 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));
+ MWBase::Environment::get().getStateManager()->askLoadRecent();
}
- mCurrentDeath = state->groupname;
- }
- else
- {
- mDeathState = CharState_Death1;
- mCurrentDeath = "death1";
+ return false;
}
+ playRandomDeath();
+
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();
+
+ return true;
}
void CharacterController::resurrect()
@@ -1163,4 +1428,19 @@ void CharacterController::updateVisibility()
mAnimation->setAlpha(alpha);
}
+void CharacterController::determineAttackType()
+{
+ float * move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
+
+ if(mPtr.getClass().hasInventoryStore(mPtr))
+ {
+ if (move[0] && !move[1]) //sideway
+ mAttackType = "slash";
+ else if (move[1]) //forward
+ mAttackType = "thrust";
+ else
+ mAttackType = "chop";
+ }
+}
+
}
diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp
index 817fa2fd51..4009744efb 100644
--- a/apps/openmw/mwmechanics/character.hpp
+++ b/apps/openmw/mwmechanics/character.hpp
@@ -22,13 +22,15 @@ namespace MWMechanics
{
class Movement;
-class NpcStats;
+class CreatureStats;
enum Priority {
Priority_Default,
Priority_Jump,
Priority_Movement,
+ Priority_Hit,
Priority_Weapon,
+ Priority_Knockdown,
Priority_Torch,
Priority_Death,
@@ -87,7 +89,12 @@ enum CharacterState {
CharState_Death3,
CharState_Death4,
CharState_Death5,
- CharState_SwimDeath
+ CharState_SwimDeath,
+
+ CharState_Hit,
+ CharState_KnockDown,
+ CharState_KnockOut,
+ CharState_Block
};
enum WeaponType {
@@ -99,7 +106,7 @@ enum WeaponType {
WeapType_TwoWide,
WeapType_BowAndArrow,
WeapType_Crossbow,
- WeapType_ThowWeapon,
+ WeapType_Thrown,
WeapType_PickProbe,
WeapType_Spell
@@ -138,10 +145,14 @@ class CharacterController
CharacterState mMovementState;
std::string mCurrentMovement;
float mMovementSpeed;
+ float mMovementAnimVelocity;
CharacterState mDeathState;
std::string mCurrentDeath;
+ CharacterState mHitState;
+ std::string mCurrentHit;
+
UpperBodyCharacterState mUpperBodyState;
JumpingState mJumpState;
@@ -157,21 +168,23 @@ class CharacterController
float mSecondsOfRunning;
std::string mAttackType; // slash, chop or thrust
+ void determineAttackType();
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);
+ bool updateWeaponState();
+ bool updateCreatureState();
void updateVisibility();
+ void playRandomDeath(float startpoint = 0.0f);
+
+ /// choose a random animation group with \a prefix and numeric suffix
+ /// @param num if non-NULL, the chosen animation number will be written here
+ std::string chooseRandomGroup (const std::string& prefix, int* num = NULL);
+
public:
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
virtual ~CharacterController();
@@ -187,7 +200,7 @@ public:
void skipAnim();
bool isAnimPlaying(const std::string &groupName);
- void kill();
+ bool kill();
void resurrect();
bool isDead() const
{ return mDeathState != CharState_None; }
@@ -195,6 +208,8 @@ public:
void forceStateUpdate();
};
+ void getWeaponGroup(WeaponType weaptype, std::string &group);
+ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
}
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp
new file mode 100644
index 0000000000..2042641065
--- /dev/null
+++ b/apps/openmw/mwmechanics/combat.cpp
@@ -0,0 +1,137 @@
+#include "combat.hpp"
+
+#include <OgreSceneNode.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+#include "../mwmechanics/movement.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/inventorystore.hpp"
+
+#include "../mwbase/windowmanager.hpp"
+
+namespace
+{
+
+Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n)
+{
+ return Ogre::Math::ATan2(
+ n.dotProduct( v1.crossProduct(v2) ),
+ v1.dotProduct(v2)
+ );
+}
+
+}
+
+namespace MWMechanics
+{
+
+ bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage)
+ {
+ if (!blocker.getClass().hasInventoryStore(blocker))
+ return false;
+
+ if (blocker.getClass().getCreatureStats(blocker).getKnockedDown()
+ || blocker.getClass().getCreatureStats(blocker).getHitRecovery())
+ return false;
+
+ MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker);
+ MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
+ if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name())
+ return false;
+
+ Ogre::Degree angle = signedAngle (Ogre::Vector3(attacker.getRefData().getPosition().pos) - Ogre::Vector3(blocker.getRefData().getPosition().pos),
+ blocker.getRefData().getBaseNode()->getOrientation().yAxis(), Ogre::Vector3(0,0,1));
+
+ const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ if (angle.valueDegrees() < gmst.find("fCombatBlockLeftAngle")->getFloat())
+ return false;
+ if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat())
+ return false;
+
+ MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker);
+ if (blockerStats.getDrawState() == DrawState_Spell)
+ return false;
+
+ MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
+
+ float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified()
+ + 0.1 * blockerStats.getAttribute(ESM::Attribute::Luck).getModified();
+ float enemySwing = attackerStats.getAttackStrength();
+ float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->getFloat() + gmst.find("fSwingBlockBase")->getFloat();
+
+ float blockerTerm = blockTerm * swingTerm;
+ if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0)
+ blockerTerm *= gmst.find("fBlockStillBonus")->getFloat();
+ blockerTerm *= blockerStats.getFatigueTerm();
+
+ float attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
+ float attackerTerm = attackerSkill + 0.2 * attackerStats.getAttribute(ESM::Attribute::Agility).getModified()
+ + 0.1 * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
+ attackerTerm *= attackerStats.getFatigueTerm();
+
+ int x = int(blockerTerm - attackerTerm);
+ int iBlockMaxChance = gmst.find("iBlockMaxChance")->getInt();
+ int iBlockMinChance = gmst.find("iBlockMinChance")->getInt();
+ x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
+
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < x)
+ {
+ // Reduce shield durability by incoming damage
+ if (shield->getCellRef().mCharge == -1)
+ shield->getCellRef().mCharge = shield->getClass().getItemMaxHealth(*shield);
+ shield->getCellRef().mCharge -= std::min(shield->getCellRef().mCharge, int(damage));
+ if (!shield->getCellRef().mCharge)
+ inv.unequipItem(*shield, blocker);
+
+ // Reduce blocker fatigue
+ const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat();
+ const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat();
+ const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->getFloat();
+ MWMechanics::DynamicStat<float> fatigue = blockerStats.getFatigue();
+ float normalizedEncumbrance = blocker.getClass().getEncumbrance(blocker) / blocker.getClass().getCapacity(blocker);
+ normalizedEncumbrance = std::min(1.f, normalizedEncumbrance);
+ float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult;
+ fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult;
+ fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
+ blockerStats.setFatigue(fatigue);
+
+ blockerStats.setBlock(true);
+
+ if (blocker.getClass().isNpc())
+ blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
+
+ return true;
+ }
+ return false;
+ }
+
+ void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage)
+ {
+ MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+ float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).mMagnitude
+ - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).mMagnitude);
+
+ float multiplier = 0;
+ if (resistance >= 0)
+ multiplier = 1 - resistance / 100.f;
+ else
+ multiplier = -(resistance-100) / 100.f;
+
+ if (!(weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
+ || weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Magical))
+ damage *= multiplier;
+
+ if (weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
+ & actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
+ damage *= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWereWolfSilverWeaponDamageMult")->getFloat();
+
+ if (damage == 0 && attacker.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
+ }
+
+}
diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp
new file mode 100644
index 0000000000..7f24156972
--- /dev/null
+++ b/apps/openmw/mwmechanics/combat.hpp
@@ -0,0 +1,16 @@
+#ifndef OPENMW_MECHANICS_COMBAT_H
+#define OPENMW_MECHANICS_COMBAT_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWMechanics
+{
+
+/// @return can we block the attack?
+bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage);
+
+void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage);
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp
index 95bd405b1d..d61b967390 100644
--- a/apps/openmw/mwmechanics/creaturestats.cpp
+++ b/apps/openmw/mwmechanics/creaturestats.cpp
@@ -2,6 +2,8 @@
#include <algorithm>
+#include <components/esm/creaturestats.hpp>
+
#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
@@ -10,46 +12,18 @@
namespace MWMechanics
{
CreatureStats::CreatureStats()
- : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mDied (false), mFriendlyHits (0),
+ : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0),
mTalkedTo (false), mAlarmed (false),
mAttacked (false), mHostile (false),
- mAttackingOrSpell(false), mAttackType(AT_Chop),
+ mAttackingOrSpell(false),
mIsWerewolf(false),
- mFallHeight(0), mRecalcDynamicStats(false)
+ mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false),
+ mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
{
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;
@@ -74,7 +48,7 @@ namespace MWMechanics
- gmst.find ("fFatigueMult")->getFloat() * (1-normalised);
}
- const Stat<int> &CreatureStats::getAttribute(int index) const
+ const AttributeValue &CreatureStats::getAttribute(int index) const
{
if (index < 0 || index > 7) {
throw std::runtime_error("attribute index is out of range");
@@ -122,7 +96,7 @@ namespace MWMechanics
return mLevel;
}
- int CreatureStats::getAiSetting (int index) const
+ Stat<int> CreatureStats::getAiSetting (AiSetting index) const
{
assert (index>=0 && index<4);
return mAiSettings[index];
@@ -158,20 +132,20 @@ namespace MWMechanics
void CreatureStats::setAttribute(int index, int base)
{
- MWMechanics::Stat<int> current = getAttribute(index);
+ AttributeValue current = getAttribute(index);
current.setBase(base);
setAttribute(index, current);
}
- void CreatureStats::setAttribute(int index, const Stat<int> &value)
+ void CreatureStats::setAttribute(int index, const AttributeValue &value)
{
if (index < 0 || index > 7) {
throw std::runtime_error("attribute index is out of range");
}
- const Stat<int>& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index];
+ const AttributeValue& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index];
- if (value.getModified() != currentValue.getModified())
+ if (value != currentValue)
{
if (index != ESM::Attribute::Luck
&& index != ESM::Attribute::Personality
@@ -228,8 +202,8 @@ namespace MWMechanics
void CreatureStats::setMagicEffects(const MagicEffects &effects)
{
- if (effects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude
- != mMagicEffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude)
+ if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude
+ != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude)
mRecalcDynamicStats = true;
mMagicEffects = effects;
@@ -240,12 +214,19 @@ namespace MWMechanics
mAttackingOrSpell = attackingOrSpell;
}
- void CreatureStats::setAiSetting (int index, int value)
+ void CreatureStats::setAiSetting (AiSetting index, Stat<int> value)
{
assert (index>=0 && index<4);
mAiSettings[index] = value;
}
+ void CreatureStats::setAiSetting (AiSetting index, int base)
+ {
+ Stat<int> stat = getAiSetting(index);
+ stat.setBase(base);
+ setAiSetting(index, stat);
+ }
+
bool CreatureStats::isDead() const
{
return mDead;
@@ -266,8 +247,10 @@ namespace MWMechanics
if (mDead)
{
if (mDynamic[0].getCurrent()<1)
- mDynamic[0].setCurrent (1);
-
+ {
+ mDynamic[0].setModified(mDynamic[0].getModified(), 1);
+ mDynamic[0].setCurrent(1);
+ }
if (mDynamic[0].getCurrent()>=1)
mDead = false;
}
@@ -335,6 +318,13 @@ namespace MWMechanics
bool CreatureStats::getCreatureTargetted() const
{
+ std::string target;
+ if (mAiSequence.getCombatTarget(target))
+ {
+ MWWorld::Ptr targetPtr;
+ targetPtr = MWBase::Environment::get().getWorld()->getPtr(target, true);
+ return targetPtr.getTypeName() == typeid(ESM::Creature).name();
+ }
return false;
}
@@ -343,7 +333,7 @@ namespace MWMechanics
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;
+ evasion += mMagicEffects.get(ESM::MagicEffect::Sanctuary).mMagnitude;
return evasion;
}
@@ -393,4 +383,97 @@ namespace MWMechanics
}
return false;
}
+
+ void CreatureStats::setKnockedDown(bool value)
+ {
+ mKnockdown = value;
+ }
+
+ bool CreatureStats::getKnockedDown() const
+ {
+ return mKnockdown;
+ }
+
+ void CreatureStats::setHitRecovery(bool value)
+ {
+ mHitRecovery = value;
+ }
+
+ bool CreatureStats::getHitRecovery() const
+ {
+ return mHitRecovery;
+ }
+
+ void CreatureStats::setBlock(bool value)
+ {
+ mBlock = value;
+ }
+
+ bool CreatureStats::getBlock() const
+ {
+ return mBlock;
+ }
+
+ bool CreatureStats::getMovementFlag (Flag flag) const
+ {
+ return mMovementFlags & flag;
+ }
+
+ void CreatureStats::setMovementFlag (Flag flag, bool state)
+ {
+ if (state)
+ mMovementFlags |= flag;
+ else
+ mMovementFlags &= ~flag;
+ }
+
+ bool CreatureStats::getStance(Stance flag) const
+ {
+ switch (flag)
+ {
+ case Stance_Run:
+ return getMovementFlag (Flag_Run) || getMovementFlag (Flag_ForceRun);
+ case Stance_Sneak:
+ return getMovementFlag (Flag_Sneak) || getMovementFlag (Flag_ForceSneak);
+ }
+ return false; // shut up, compiler
+ }
+
+ DrawState_ CreatureStats::getDrawState() const
+ {
+ return mDrawState;
+ }
+
+ void CreatureStats::setDrawState(DrawState_ state)
+ {
+ mDrawState = state;
+ }
+
+ float CreatureStats::getAttackStrength() const
+ {
+ return mAttackStrength;
+ }
+
+ void CreatureStats::setAttackStrength(float value)
+ {
+ mAttackStrength = value;
+ }
+
+ void CreatureStats::writeState (ESM::CreatureStats& state) const
+ {
+ for (int i=0; i<8; ++i)
+ mAttributes[i].writeState (state.mAttributes[i]);
+
+ for (int i=0; i<3; ++i)
+ mDynamic[i].writeState (state.mDynamic[i]);
+ }
+
+ void CreatureStats::readState (const ESM::CreatureStats& state)
+ {
+ for (int i=0; i<8; ++i)
+ mAttributes[i].readState (state.mAttributes[i]);
+
+ for (int i=0; i<3; ++i)
+ mDynamic[i].readState (state.mDynamic[i]);
+ }
}
diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp
index 4d9be01324..94e506fc4f 100644
--- a/apps/openmw/mwmechanics/creaturestats.hpp
+++ b/apps/openmw/mwmechanics/creaturestats.hpp
@@ -10,6 +10,12 @@
#include "spells.hpp"
#include "activespells.hpp"
#include "aisequence.hpp"
+#include "drawstate.hpp"
+
+namespace ESM
+{
+ struct CreatureStats;
+}
namespace MWMechanics
{
@@ -18,15 +24,14 @@ namespace MWMechanics
///
class CreatureStats
{
- Stat<int> mAttributes[8];
+ DrawState_ mDrawState;
+ AttributeValue mAttributes[8];
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
- int mLevel;
Spells mSpells;
ActiveSpells mActiveSpells;
MagicEffects mMagicEffects;
- int mAiSettings[4];
+ Stat<int> mAiSettings[4];
AiSequence mAiSequence;
- float mLevelHealthBonus;
bool mDead;
bool mDied;
int mFriendlyHits;
@@ -34,26 +39,36 @@ namespace MWMechanics
bool mAlarmed;
bool mAttacked;
bool mHostile;
- bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not.
+ bool mAttackingOrSpell;
+ bool mKnockdown;
+ bool mHitRecovery;
+ bool mBlock;
+ unsigned int mMovementFlags;
+ float mAttackStrength; // Note only some creatures attack with weapons
float mFallHeight;
- int mAttackType;
-
std::string mLastHitObject; // The last object to hit this actor
// Do we need to recalculate stats derived from attributes or other factors?
bool mRecalcDynamicStats;
std::map<std::string, MWWorld::TimeStamp> mUsedPowers;
-
protected:
bool mIsWerewolf;
- Stat<int> mWerewolfAttributes[8];
+ AttributeValue mWerewolfAttributes[8];
+ int mLevel;
public:
CreatureStats();
+ 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);
+
bool needToRecalcDynamicStats();
void addToFallHeight(float height);
@@ -65,7 +80,7 @@ namespace MWMechanics
bool canUsePower (const std::string& power) const;
void usePower (const std::string& power);
- const Stat<int> & getAttribute(int index) const;
+ const AttributeValue & getAttribute(int index) const;
const DynamicStat<float> & getHealth() const;
@@ -85,16 +100,13 @@ namespace MWMechanics
int getLevel() const;
- int getAiSetting (int index) const;
- ///< 0: hello, 1 fight, 2 flee, 3 alarm
-
Spells & getSpells();
ActiveSpells & getActiveSpells();
MagicEffects & getMagicEffects();
- void setAttribute(int index, const Stat<int> &value);
+ void setAttribute(int index, const AttributeValue &value);
// Shortcut to set only the base
void setAttribute(int index, int base);
@@ -114,19 +126,18 @@ namespace MWMechanics
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
+ enum AiSetting
+ {
+ AI_Hello = 0,
+ AI_Fight = 1,
+ AI_Flee = 2,
+ AI_Alarm = 3
+ };
+ void setAiSetting (AiSetting index, Stat<int> value);
+ void setAiSetting (AiSetting index, int base);
+ Stat<int> getAiSetting (AiSetting index) const;
const AiSequence& getAiSequence() const;
@@ -135,14 +146,6 @@ namespace MWMechanics
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;
@@ -182,6 +185,31 @@ namespace MWMechanics
float getEvasion() const;
+ void setKnockedDown(bool value);
+ bool getKnockedDown() const;
+ void setHitRecovery(bool value);
+ bool getHitRecovery() const;
+ void setBlock(bool value);
+ bool getBlock() const;
+
+ enum Flag
+ {
+ Flag_ForceRun = 1,
+ Flag_ForceSneak = 2,
+ Flag_Run = 4,
+ Flag_Sneak = 8
+ };
+ enum Stance
+ {
+ Stance_Run,
+ Stance_Sneak
+ };
+
+ bool getMovementFlag (Flag flag) const;
+ void setMovementFlag (Flag flag, bool state);
+ /// Like getMovementFlag, but also takes into account if the flag is Forced
+ bool getStance (Stance flag) const;
+
void setLastHitObject(const std::string &objectid);
const std::string &getLastHitObject() const;
@@ -189,6 +217,10 @@ namespace MWMechanics
std::set<int> mBoundItems;
// Same as above
std::map<int, std::string> mSummonedCreatures;
+
+ void writeState (ESM::CreatureStats& state) const;
+
+ void readState (const ESM::CreatureStats& state);
};
}
diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp
new file mode 100644
index 0000000000..d3ea825cf5
--- /dev/null
+++ b/apps/openmw/mwmechanics/disease.hpp
@@ -0,0 +1,53 @@
+#ifndef OPENMW_MECHANICS_DISEASE_H
+#define OPENMW_MECHANICS_DISEASE_H
+
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwmechanics/spells.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
+namespace MWMechanics
+{
+
+ /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him)
+ inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier)
+ {
+ if (!carrier.getClass().isActor())
+ return;
+
+ float fDiseaseXferChance =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ "fDiseaseXferChance")->getFloat();
+
+ Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells();
+ for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
+ {
+ const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(it->first);
+ if (spell->mData.mType == ESM::Spell::ST_Disease
+ || spell->mData.mType == ESM::Spell::ST_Blight)
+ {
+ float roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (roll < fDiseaseXferChance)
+ {
+ // Contracted disease!
+ actor.getClass().getCreatureStats(actor).getSpells().add(it->first);
+
+ if (actor.getRefData().getHandle() == "player")
+ {
+ std::string msg = "sMagicContractDisease";
+ msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(msg)->getString();
+ if (msg.find("%s") != std::string::npos)
+ msg.replace(msg.find("%s"), 2, spell->mName);
+ MWBase::Environment::get().getWindowManager()->messageBox(msg);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+#endif
diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp
index 7e11acdb0c..87337cdd70 100644
--- a/apps/openmw/mwmechanics/enchanting.cpp
+++ b/apps/openmw/mwmechanics/enchanting.cpp
@@ -1,5 +1,4 @@
#include "enchanting.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
@@ -7,7 +6,6 @@
#include "creaturestats.hpp"
#include "npcstats.hpp"
-#include <boost/algorithm/string.hpp>
namespace MWMechanics
{
@@ -53,7 +51,7 @@ namespace MWMechanics
bool Enchanting::create()
{
- const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
ESM::Enchantment enchantment;
enchantment.mData.mCharge = getGemCharge();
@@ -61,7 +59,7 @@ namespace MWMechanics
store.remove(mSoulGemPtr, 1, player);
//Exception for Azura Star, new one will be added after enchanting
- if(boost::iequals(mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
+ if(Misc::StringUtils::ciEqual(mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
store.add("Misc_SoulGem_Azura", 1, player);
if(mSelfEnchanting)
@@ -90,7 +88,7 @@ namespace MWMechanics
// Add the new item to player inventory and remove the old one
store.remove(mOldItemPtr, 1, player);
- store.add(newItemPtr, player);
+ store.add(newItemPtr, 1, player);
if(!mSelfEnchanting)
payForEnchantment();
@@ -213,7 +211,7 @@ namespace MWMechanics
return 0;
const float enchantCost = getEnchantPoints();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::NpcStats &stats = MWWorld::Class::get(player).getNpcStats(player);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
@@ -252,7 +250,10 @@ namespace MWMechanics
{
if (itemEmpty())
return 0;
- return MWWorld::Class::get(mOldItemPtr).getEnchantmentPoints(mOldItemPtr);
+
+ const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
+
+ return mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get<ESM::GameSetting>().find("fEnchantmentMult")->getFloat();
}
bool Enchanting::soulEmpty() const
{
@@ -276,30 +277,26 @@ namespace MWMechanics
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()));
+ (0.25 * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified())
+ + (0.125 * npcStats.getAttribute (ESM::Attribute::Luck).getModified()));
+
+ const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ float chance2 = 7.5 / (gmst.find("fEnchantmentChanceMult")->getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ?
+ gmst.find("fEnchantmentConstantChanceMult")->getFloat() : 1 ))
+ * getEnchantPoints();
- 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
{
- const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
- store.remove("gold_001", getEnchantPrice(), player);
+ store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player);
}
}
diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp
index 988ce41fc7..ae0b25a4a2 100644
--- a/apps/openmw/mwmechanics/enchanting.hpp
+++ b/apps/openmw/mwmechanics/enchanting.hpp
@@ -28,6 +28,8 @@ namespace MWMechanics
void setEnchanter(MWWorld::Ptr enchanter);
void setSelfEnchanting(bool selfEnchanting);
void setOldItem(MWWorld::Ptr oldItem);
+ MWWorld::Ptr getOldItem() { return mOldItemPtr; }
+ MWWorld::Ptr getGem() { return mSoulGemPtr; }
void setNewItemName(const std::string& s);
void setEffect(ESM::EffectList effectList);
void setSoulGem(MWWorld::Ptr soulGem);
diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp
new file mode 100644
index 0000000000..120616f9fe
--- /dev/null
+++ b/apps/openmw/mwmechanics/levelledlist.hpp
@@ -0,0 +1,83 @@
+#ifndef OPENMW_MECHANICS_LEVELLEDLIST_H
+#define OPENMW_MECHANICS_LEVELLEDLIST_H
+
+#include "../mwworld/ptr.hpp"
+#include "../mwworld/manualref.hpp"
+#include "../mwworld/class.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwmechanics/creaturestats.hpp"
+
+namespace MWMechanics
+{
+
+ /// @return ID of resulting item, or empty if none
+ inline std::string getLevelledItem (const ESM::LeveledListBase* levItem, bool creature, unsigned char failChance=0)
+ {
+ const std::vector<ESM::LeveledListBase::LevelItem>& items = levItem->mList;
+
+ const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int playerLevel = player.getClass().getCreatureStats(player).getLevel();
+
+ failChance += levItem->mChanceNone;
+
+ int random = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (random < failChance)
+ return std::string();
+
+ 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 && it->mLevel <= playerLevel)
+ highestLevel = it->mLevel;
+ }
+
+ // For levelled creatures, the flags are swapped. This file format just makes so much sense.
+ bool allLevels = levItem->mFlags & ESM::ItemLevList::AllLevels;
+ if (creature)
+ allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels;
+
+ 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
+ && (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();
+ std::string item = candidates[std::rand()%candidates.size()];
+
+ // Is this another levelled item or a real item?
+ try
+ {
+ MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1);
+ if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name()
+ && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name())
+ {
+ return item;
+ }
+ else
+ {
+ if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name())
+ return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, failChance);
+ else
+ return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, failChance);
+ }
+ }
+ catch (std::logic_error& e)
+ {
+ // Vanilla doesn't fail on nonexistent items in levelled lists
+ std::cerr << "Warning: ignoring nonexistent item '" << item << "'" << std::endl;
+ return std::string();
+ }
+ }
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp
index 2c1b363b7d..45abda21d9 100644
--- a/apps/openmw/mwmechanics/magiceffects.hpp
+++ b/apps/openmw/mwmechanics/magiceffects.hpp
@@ -56,7 +56,8 @@ namespace MWMechanics
struct EffectSourceVisitor
{
virtual void visit (MWMechanics::EffectKey key,
- const std::string& sourceName, float magnitude, float remainingTime = -1) = 0;
+ const std::string& sourceName, const std::string& casterHandle,
+ float magnitude, float remainingTime = -1) = 0;
};
/// \brief Effects currently affecting a NPC or creature
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
index 7740240b6b..4c8f35edb0 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
@@ -12,13 +12,39 @@
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
+#include <OgreSceneNode.h>
+
#include "spellcasting.hpp"
+namespace
+{
+ /// @return is \a ptr allowed to take/use \a item or is it a crime?
+ bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim)
+ {
+ const std::string& owner = item.getCellRef().mOwner;
+ bool isOwned = !owner.empty();
+
+ const std::string& faction = item.getCellRef().mFaction;
+ bool isFactionOwned = false;
+ if (!faction.empty())
+ {
+ const std::map<std::string, int>& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks();
+ if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end())
+ isFactionOwned = true;
+ }
+
+ if (!item.getCellRef().mOwner.empty())
+ victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().mOwner, true);
+
+ return (!isOwned && !isFactionOwned);
+ }
+}
+
namespace MWMechanics
{
void MechanicsManager::buildPlayer()
{
- MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
MWMechanics::NpcStats& npcStats = MWWorld::Class::get (ptr).getNpcStats (ptr);
@@ -170,8 +196,8 @@ namespace MWMechanics
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);
+ // auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable
+ MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
invStore.unequipAll(ptr);
invStore.autoEquip(ptr);
@@ -228,7 +254,7 @@ namespace MWMechanics
{
// Uses ingame time, but scaled to real time
duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor();
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
player.getClass().getInventoryStore(player).rechargeItems(duration);
}
@@ -309,7 +335,7 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager();
const ESM::NPC *player =
- world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ world->getPlayerPtr().get<ESM::NPC>()->mBase;
const ESM::Race *race =
world->getStore().get<ESM::Race>().find(player->mRace);
@@ -335,7 +361,7 @@ namespace MWMechanics
// 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();
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
mActors.removeActor(ptr);
mActors.addActor(ptr);
}
@@ -344,9 +370,14 @@ namespace MWMechanics
mObjects.update(duration, paused);
}
- void MechanicsManager::restoreDynamicStats()
+ void MechanicsManager::rest(bool sleep)
+ {
+ mActors.restoreDynamicStats (sleep);
+ }
+
+ int MechanicsManager::getHoursToRest() const
{
- mActors.restoreDynamicStats ();
+ return mActors.getHoursToRest(mWatched);
}
void MechanicsManager::setPlayerName (const std::string& name)
@@ -354,7 +385,7 @@ namespace MWMechanics
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::NPC player =
- *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ *world->getPlayerPtr().get<ESM::NPC>()->mBase;
player.mName = name;
world->createRecord(player);
@@ -367,7 +398,7 @@ namespace MWMechanics
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::NPC player =
- *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ *world->getPlayerPtr().get<ESM::NPC>()->mBase;
player.mRace = race;
player.mHead = head;
@@ -393,7 +424,7 @@ namespace MWMechanics
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::NPC player =
- *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ *world->getPlayerPtr().get<ESM::NPC>()->mBase;
player.mClass = id;
world->createRecord(player);
@@ -410,7 +441,7 @@ namespace MWMechanics
const ESM::Class *ptr = world->createRecord(cls);
ESM::NPC player =
- *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ *world->getPlayerPtr().get<ESM::NPC>()->mBase;
player.mClass = ptr->mId;
world->createRecord(player);
@@ -422,11 +453,11 @@ namespace MWMechanics
int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr)
{
- MWMechanics::NpcStats npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr);
+ const 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::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::LiveCellRef<ESM::NPC>* player = playerPtr.get<ESM::NPC>();
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
@@ -441,21 +472,23 @@ namespace MWMechanics
std::string npcFaction = "";
if(!npcSkill.getFactionRanks().empty()) npcFaction = npcSkill.getFactionRanks().begin()->first;
- if (playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction)) != playerStats.getFactionRanks().end())
+ Misc::StringUtils::toLower(npcFaction);
+
+ if (playerStats.getFactionRanks().find(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)
+ for(std::vector<ESM::Faction::Reaction>::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(npcFaction)->mReactions.begin();
+ it != MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(npcFaction)->mReactions.end(); ++it)
{
- if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)
- && playerStats.getExpelled().find(Misc::StringUtils::lowerCase(it->mFaction)) == playerStats.getExpelled().end())
+ if(Misc::StringUtils::ciEqual(it->mFaction, npcFaction)
+ && !playerStats.getExpelled(it->mFaction))
reaction = it->mReaction;
}
- rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second;
+ rank = playerStats.getFactionRanks().find(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)
+ for(std::vector<ESM::Faction::Reaction>::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(npcFaction)->mReactions.begin();
+ it != MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(npcFaction)->mReactions.end();++it)
{
if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() )
{
@@ -481,6 +514,8 @@ namespace MWMechanics
if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon)
x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispWeaponDrawn")->getFloat();
+ x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).mMagnitude;
+
int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used
return effective_disposition;
}
@@ -492,17 +527,17 @@ namespace MWMechanics
const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(ptr).getNpcStats(ptr);
- MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
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 a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100);
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 d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100);
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);
@@ -535,9 +570,9 @@ namespace MWMechanics
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
- MWMechanics::NpcStats npcStats = MWWorld::Class::get(npc).getNpcStats(npc);
+ MWMechanics::NpcStats& npcStats = MWWorld::Class::get(npc).getNpcStats(npc);
- MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
float persTerm = playerStats.getAttribute(ESM::Attribute::Personality).getModified()
@@ -605,8 +640,14 @@ namespace MWMechanics
{
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)))));
+ int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase();
+ int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase();
+ npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee,
+ std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s)))));
+ // TODO: initiate combat and quit dialogue if fight rating is too high
+ // or should setAiSetting handle this?
+ npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight,
+ std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s)))));
}
float c = -std::abs(int(r * fPerDieRollMult));
@@ -636,12 +677,15 @@ namespace MWMechanics
float c = std::abs(int(target1 - roll));
- if (roll <= target1)
+ if (success)
{
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)))));
+ int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase();
+ int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase();
+ npcStats.setAiSetting (CreatureStats::AI_Flee,
+ std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s)))));
+ npcStats.setAiSetting (CreatureStats::AI_Fight,
+ std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s)))));
}
x = int(-c * fPerDieRollMult);
@@ -715,4 +759,214 @@ namespace MWMechanics
{
return mAI;
}
+
+ void MechanicsManager::playerLoaded()
+ {
+ mUpdatePlayer = true;
+ mClassSelected = true;
+ mRaceSelected = true;
+ mAI = true;
+ }
+
+ bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed)
+ {
+ MWWorld::Ptr victim;
+ if (isAllowedToUse(ptr, bed, victim))
+ return false;
+
+ if(commitCrime(ptr, victim, OT_SleepingInOwnedBed))
+ {
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage64}");
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item)
+ {
+ MWWorld::Ptr victim;
+ if (isAllowedToUse(ptr, item, victim))
+ return;
+ commitCrime(ptr, victim, OT_Trespassing);
+ }
+
+ void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, int count)
+ {
+ MWWorld::Ptr victim;
+ if (isAllowedToUse(ptr, item, victim))
+ return;
+ commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count);
+ }
+
+ bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
+ {
+ if (ptr.getRefData().getHandle() != "player")
+ return false;
+
+ bool reported=false;
+ for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it)
+ {
+ if (it->first != ptr &&
+ MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) &&
+ awarenessCheck(ptr, it->first))
+ {
+ // NPCs will always curse you when they notice you steal their items, even if they don't report the crime
+ if (it->first == victim && type == OT_Theft)
+ {
+ MWBase::Environment::get().getDialogueManager()->say(victim, "Thief");
+ }
+
+ // Actor has witnessed a crime. Will he report it?
+ // (not sure, is > 0 correct?)
+ if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0)
+ {
+ // TODO: stats.setAlarmed(true) on NPCs within earshot
+ // fAlarmRadius ?
+ reported=true;
+ break;
+ }
+ }
+ }
+
+ if (reported)
+ reportCrime(ptr, victim, type, arg);
+ return reported;
+ }
+
+ void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
+ {
+ const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+ // Bounty for each type of crime
+ if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
+ arg = store.find("iCrimeTresspass")->getInt();
+ else if (type == OT_Pickpocket)
+ arg = store.find("iCrimePickPocket")->getInt();
+ else if (type == OT_Assault)
+ arg = store.find("iCrimeAttack")->getInt();
+ else if (type == OT_Murder)
+ arg = store.find("iCrimeKilling")->getInt();
+ else if (type == OT_Theft)
+ arg *= store.find("fCrimeStealing")->getFloat();
+
+ // TODO: In some cases (type == Assault), if no NPCs are within earshot, the report will have no effect.
+ // however other crime types seem to be always produce a bounty.
+
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}");
+ ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty()
+ + arg);
+
+ if (!victim.isEmpty())
+ {
+ int fight = 0;
+ // Increase in fight rating for each type of crime
+ if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
+ fight = store.find("iFightTrespass")->getFloat();
+ else if (type == OT_Pickpocket)
+ fight = store.find("iFightPickpocket")->getInt();
+ else if (type == OT_Assault)
+ fight = store.find("iFightAttack")->getInt();
+ else if (type == OT_Murder)
+ fight = store.find("iFightKilling")->getInt();
+ else if (type == OT_Theft)
+ fight = store.find("fFightStealing")->getFloat();
+ // Not sure if this should be permanent?
+ fight = victim.getClass().getCreatureStats(victim).getAiSetting(CreatureStats::AI_Fight).getBase() + fight;
+ victim.getClass().getCreatureStats(victim).setAiSetting(CreatureStats::AI_Fight, fight);
+ }
+
+ // If committing a crime against a faction member, expell from the faction
+ if (!victim.isEmpty() && victim.getClass().isNpc())
+ {
+ std::string factionID;
+ if(!victim.getClass().getNpcStats(victim).getFactionRanks().empty())
+ factionID = victim.getClass().getNpcStats(victim).getFactionRanks().begin()->first;
+ if (ptr.getClass().getNpcStats(ptr).isSameFaction(victim.getClass().getNpcStats(victim)))
+ {
+ ptr.getClass().getNpcStats(ptr).expell(factionID);
+ }
+ }
+
+ // TODO: make any guards in the area try to arrest the player
+ }
+
+ bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)
+ {
+ if (observer.getClass().getCreatureStats(observer).isDead())
+ return false;
+
+ const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
+
+ float invisibility = stats.getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude;
+ if (invisibility > 0)
+ return false;
+
+ float sneakTerm = 0;
+ if (ptr.getClass().getCreatureStats(ptr).getStance(CreatureStats::Stance_Sneak)
+ && !MWBase::Environment::get().getWorld()->isSwimming(ptr)
+ && MWBase::Environment::get().getWorld()->isOnGround(ptr))
+ {
+ static float fSneakSkillMult = store.find("fSneakSkillMult")->getFloat();
+ static float fSneakBootMult = store.find("fSneakBootMult")->getFloat();
+ float sneak = ptr.getClass().getSkill(ptr, ESM::Skill::Sneak);
+ int agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
+ int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+ float bootWeight = 0;
+ if (ptr.getClass().isNpc())
+ {
+ MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
+ MWWorld::ContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
+ if (it != inv.end())
+ bootWeight = it->getClass().getWeight(*it);
+ }
+ sneakTerm = fSneakSkillMult * sneak + 0.2 * agility + 0.1 * luck + bootWeight * fSneakBootMult;
+ }
+
+ static float fSneakDistBase = store.find("fSneakDistanceBase")->getFloat();
+ static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->getFloat();
+
+ Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos);
+ Ogre::Vector3 pos2 (observer.getRefData().getPosition().pos);
+ float distTerm = fSneakDistBase + fSneakDistMult * pos1.distance(pos2);
+
+ float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude;
+ float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility;
+
+ CreatureStats& observerStats = observer.getClass().getCreatureStats(observer);
+ int obsAgility = observerStats.getAttribute(ESM::Attribute::Agility).getModified();
+ int obsLuck = observerStats.getAttribute(ESM::Attribute::Luck).getModified();
+ float obsBlind = observerStats.getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude;
+ int obsSneak = observer.getClass().getSkill(observer, ESM::Skill::Sneak);
+
+ float obsTerm = obsSneak + 0.2 * obsAgility + 0.1 * obsLuck - obsBlind;
+
+ // is ptr behind the observer?
+ static float fSneakNoViewMult = store.find("fSneakNoViewMult")->getFloat();
+ static float fSneakViewMult = store.find("fSneakViewMult")->getFloat();
+ float y = 0;
+ Ogre::Vector3 vec = pos1 - pos2;
+ Ogre::Radian angle = observer.getRefData().getBaseNode()->getOrientation().yAxis().angleBetween(vec);
+ if (angle < Ogre::Degree(90))
+ y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult;
+ else
+ y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult;
+
+ float target = x - y;
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+
+ return (roll >= target);
+ }
+
+ void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector<MWWorld::Ptr> &objects)
+ {
+ mActors.getObjectsInRange(position, radius, objects);
+ mObjects.getObjectsInRange(position, radius, objects);
+ }
+
+ std::list<MWWorld::Ptr> MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor)
+ {
+ return mActors.getActorsFollowing(actor);
+ }
}
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
index ec03b457b2..761caf586c 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
@@ -81,8 +81,12 @@ namespace MWMechanics
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 void rest(bool sleep);
+ ///< If the player is sleeping or waiting, this should be called every hour.
+ /// @param sleep is the player sleeping or waiting?
+
+ virtual int getHoursToRest() const;
+ ///< Calculate how many hours the player needs to rest in order to be fully healed
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.
@@ -98,18 +102,45 @@ namespace MWMechanics
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);
+ /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
+ virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer);
+
+ /**
+ * @brief Commit a crime. If any actors witness the crime and report it,
+ * reportCrime will be called automatically.
+ * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen.
+ * @return was the crime reported?
+ */
+ virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
+ OffenseType type, int arg=0);
+ virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
+ OffenseType type, int arg=0);
+ /// Utility to check if taking this item is illegal and calling commitCrime if so
+ virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count);
+ /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
+ virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item);
+ /// Attempt sleeping in a bed. If this is illegal, call commitCrime.
+ /// @return was it illegal, and someone saw you doing it?
+ virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed);
+
+ 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);
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)
virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
- virtual void toggleAI();
- virtual bool isAIActive();
+ virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects);
+
+ virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
+
+ virtual void toggleAI();
+ virtual bool isAIActive();
+
+ virtual void playerLoaded();
};
}
diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp
index 1fdefc84f9..8918bfbe70 100644
--- a/apps/openmw/mwmechanics/npcstats.cpp
+++ b/apps/openmw/mwmechanics/npcstats.cpp
@@ -12,10 +12,10 @@
#include <components/esm/loadclas.hpp>
#include <components/esm/loadgmst.hpp>
#include <components/esm/loadfact.hpp>
+#include <components/esm/npcstats.hpp>
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
-#include "../mwworld/player.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -23,42 +23,17 @@
#include "../mwbase/soundmanager.hpp"
MWMechanics::NpcStats::NpcStats()
-: mMovementFlags (0)
-, mDrawState (DrawState_Nothing)
-, mBounty (0)
+ : mBounty (0)
, mLevelProgress(0)
, mDisposition(0)
-, mVampire (0)
, mReputation(0)
, mWerewolfKills (0)
, mProfit(0)
-, mAttackStrength(0.0f)
, mTimeToStartDrowning(20.0)
, mLastDrowningHit(0)
+, mLevelHealthBonus(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;
+ mSkillIncreases.resize (ESM::Attribute::Length, 0);
}
int MWMechanics::NpcStats::getBaseDisposition() const
@@ -71,20 +46,7 @@ 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
+const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const
{
if (index<0 || index>=ESM::Skill::Length)
throw std::runtime_error ("skill index out of range");
@@ -92,7 +54,7 @@ const MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index) cons
return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]);
}
-MWMechanics::Stat<float>& MWMechanics::NpcStats::getSkill (int index)
+MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index)
{
if (index<0 || index>=ESM::Skill::Length)
throw std::runtime_error ("skill index out of range");
@@ -110,14 +72,26 @@ std::map<std::string, int>& MWMechanics::NpcStats::getFactionRanks()
return mFactionRank;
}
-const std::set<std::string>& MWMechanics::NpcStats::getExpelled() const
+bool MWMechanics::NpcStats::getExpelled(const std::string& factionID) const
{
- return mExpelled;
+ return mExpelled.find(Misc::StringUtils::lowerCase(factionID)) != mExpelled.end();
}
-std::set<std::string>& MWMechanics::NpcStats::getExpelled()
+void MWMechanics::NpcStats::expell(const std::string& factionID)
{
- return mExpelled;
+ std::string lower = Misc::StringUtils::lowerCase(factionID);
+ if (mExpelled.find(lower) == mExpelled.end())
+ {
+ std::string message = "#{sExpelledMessage}";
+ message += MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(factionID)->mName;
+ MWBase::Environment::get().getWindowManager()->messageBox(message);
+ mExpelled.insert(lower);
+ }
+}
+
+void MWMechanics::NpcStats::clearExpelled(const std::string& factionID)
+{
+ mExpelled.erase(Misc::StringUtils::lowerCase(factionID));
}
bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const
@@ -197,69 +171,73 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_,
if(mIsWerewolf)
return;
- float base = getSkill (skillIndex).getBase();
+ MWMechanics::SkillValue& value = getSkill (skillIndex);
- int level = static_cast<int> (base);
+ value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType));
- base += getSkillGain (skillIndex, class_, usageType);
-
- if (static_cast<int> (base)!=level)
+ if (value.getProgress()>=1)
{
// 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);
+ int base = getSkill (skillIndex).getBase();
- if (level >= 100)
+ if (base >= 100)
return;
- if (preserveProgress)
- base += 1;
- else
- base = level+1;
+ base += 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)
+ const MWWorld::Store<ESM::GameSetting> &gmst =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
+
+ // is this a minor or major skill?
+ int increase = gmst.find("iLevelupMiscMultAttriubte")->getInt(); // Note: GMST has a typo
+ for (int k=0; k<5; ++k)
+ {
+ if (class_.mData.mSkills[k][0] == skillIndex)
{
- int skill = class_.mData.mSkills[j][i];
- if (skill == skillIndex)
- levelProgress = true;
+ mLevelProgress += gmst.find("iLevelUpMinorMult")->getInt();
+ increase = gmst.find("iLevelUpMajorMultAttribute")->getInt();
}
+ }
+ for (int k=0; k<5; ++k)
+ {
+ if (class_.mData.mSkills[k][1] == skillIndex)
+ {
+ mLevelProgress += gmst.find("iLevelUpMajorMult")->getInt();
+ increase = gmst.find("iLevelUpMinorMultAttribute")->getInt();
+ }
+ }
- 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];
+ mSkillIncreases[skill->mData.mAttribute] += increase;
// 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::vector <std::string> noButtons;
+
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());
+ MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), noButtons, MWGui::ShowInDialogueMode_Never);
- if (mLevelProgress >= 10)
+ if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt())
{
// levelup is possible now
- MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}");
+ MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", noButtons, MWGui::ShowInDialogueMode_Never);
}
getSkill (skillIndex).setBase (base);
+ if (!preserveProgress)
+ getSkill(skillIndex).setProgress(0);
}
int MWMechanics::NpcStats::getLevelProgress () const
@@ -272,22 +250,43 @@ void MWMechanics::NpcStats::levelUp()
mLevelProgress -= 10;
for (int i=0; i<ESM::Attribute::Length; ++i)
mSkillIncreases[i] = 0;
+
+ 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();
+
+ setLevel(getLevel()+1);
+}
+
+void MWMechanics::NpcStats::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);
}
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)
+
+ if (num == 0)
return 1;
- else if (num <= 4)
- return 2;
- else if (num <= 7)
- return 3;
- else if (num <= 9)
- return 4;
- else
- return 5;
+
+ num = std::min(10, num);
+
+ // iLevelUp01Mult - iLevelUp10Mult
+ std::stringstream gmst;
+ gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult";
+
+ return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(gmst.str())->getInt();
}
void MWMechanics::NpcStats::flagAsUsed (const std::string& id)
@@ -302,12 +301,16 @@ bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const
int MWMechanics::NpcStats::getBounty() const
{
- return mBounty;
+ if (mIsWerewolf)
+ return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iWereWolfBounty")->getInt();
+ else
+ return mBounty;
}
void MWMechanics::NpcStats::setBounty (int bounty)
{
- mBounty = bounty;
+ if (!mIsWerewolf)
+ mBounty = bounty;
}
int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const
@@ -325,16 +328,6 @@ void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, in
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;
@@ -387,7 +380,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set)
// 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);
+ mWerewolfAttributes[i].setBase(int(gmst.find(name)->getFloat()));
}
for(size_t i = 0;i < ESM::Skill::Length;i++)
@@ -401,7 +394,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set)
// "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);
+ mWerewolfSkill[i].setBase(int(gmst.find(name)->getFloat()));
}
}
mIsWerewolf = set;
@@ -431,3 +424,89 @@ void MWMechanics::NpcStats::setTimeToStartDrowning(float time)
assert(time>=0 && time<=20);
mTimeToStartDrowning=time;
}
+
+void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
+{
+ for (std::map<std::string, int>::const_iterator iter (mFactionRank.begin());
+ iter!=mFactionRank.end(); ++iter)
+ state.mFactions[iter->first].mRank = iter->second;
+
+ state.mDisposition = mDisposition;
+
+ for (int i=0; i<27; ++i)
+ {
+ mSkill[i].writeState (state.mSkills[i].mRegular);
+ mWerewolfSkill[i].writeState (state.mSkills[i].mWerewolf);
+ }
+
+ state.mBounty = mBounty;
+
+ for (std::set<std::string>::const_iterator iter (mExpelled.begin());
+ iter!=mExpelled.end(); ++iter)
+ state.mFactions[*iter].mExpelled = true;
+
+ for (std::map<std::string, int>::const_iterator iter (mFactionReputation.begin());
+ iter!=mFactionReputation.end(); ++iter)
+ state.mFactions[iter->first].mReputation = iter->second;
+
+ state.mReputation = mReputation;
+ state.mWerewolfKills = mWerewolfKills;
+ state.mProfit = mProfit;
+ state.mAttackStrength = mAttackStrength;
+ state.mLevelProgress = mLevelProgress;
+
+ for (int i=0; i<8; ++i)
+ state.mSkillIncrease[i] = mSkillIncreases[i];
+
+ std::copy (mUsedIds.begin(), mUsedIds.end(), std::back_inserter (state.mUsedIds));
+
+ state.mTimeToStartDrowning = mTimeToStartDrowning;
+ state.mLastDrowningHit = mLastDrowningHit;
+ state.mLevelHealthBonus = mLevelHealthBonus;
+}
+
+void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
+{
+ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
+
+ for (std::map<std::string, ESM::NpcStats::Faction>::const_iterator iter (state.mFactions.begin());
+ iter!=state.mFactions.end(); ++iter)
+ if (store.get<ESM::Faction>().search (iter->first))
+ {
+ if (iter->second.mExpelled)
+ mExpelled.insert (iter->first);
+
+ if (iter->second.mRank)
+ mFactionRank.insert (std::make_pair (iter->first, iter->second.mRank));
+
+ if (iter->second.mReputation)
+ mFactionReputation.insert (std::make_pair (iter->first, iter->second.mReputation));
+ }
+
+ mDisposition = state.mDisposition;
+
+ for (int i=0; i<27; ++i)
+ {
+ mSkill[i].readState (state.mSkills[i].mRegular);
+ mWerewolfSkill[i].readState (state.mSkills[i].mWerewolf);
+ }
+
+ mBounty = state.mBounty;
+ mReputation = state.mReputation;
+ mWerewolfKills = state.mWerewolfKills;
+ mProfit = state.mProfit;
+ mAttackStrength = state.mAttackStrength;
+ mLevelProgress = state.mLevelProgress;
+
+ for (int i=0; i<8; ++i)
+ mSkillIncreases[i] = state.mSkillIncrease[i];
+
+ for (std::vector<std::string>::const_iterator iter (state.mUsedIds.begin());
+ iter!=state.mUsedIds.end(); ++iter)
+ if (store.find (*iter))
+ mUsedIds.insert (*iter);
+
+ mTimeToStartDrowning = state.mTimeToStartDrowning;
+ mLastDrowningHit = state.mLastDrowningHit;
+ mLevelHealthBonus = state.mLevelHealthBonus;
+} \ No newline at end of file
diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp
index 6b7efa5b72..ad493be3c1 100644
--- a/apps/openmw/mwmechanics/npcstats.hpp
+++ b/apps/openmw/mwmechanics/npcstats.hpp
@@ -7,13 +7,13 @@
#include <vector>
#include "stat.hpp"
-#include "drawstate.hpp"
#include "creaturestats.hpp"
namespace ESM
{
struct Class;
+ struct NpcStats;
}
namespace MWMechanics
@@ -25,32 +25,17 @@ namespace MWMechanics
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];
+ SkillValue mSkill[27];
+ SkillValue mWerewolfSkill[27];
int mBounty;
std::set<std::string> mExpelled;
std::map<std::string, int> mFactionReputation;
- bool mVampire;
int mReputation;
int mWerewolfKills;
int mProfit;
@@ -67,6 +52,8 @@ namespace MWMechanics
/// time since last hit from drowning
float mLastDrowningHit;
+ float mLevelHealthBonus;
+
public:
NpcStats();
@@ -75,13 +62,6 @@ namespace MWMechanics
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);
@@ -90,18 +70,16 @@ namespace MWMechanics
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 SkillValue& getSkill (int index) const;
+ SkillValue& 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();
+ const std::set<std::string>& getExpelled() const { return mExpelled; }
+ bool getExpelled(const std::string& factionID) const;
+ void expell(const std::string& factionID);
+ void clearExpelled(const std::string& factionID);
bool isSameFaction (const NpcStats& npcStats) const;
///< Do *this and \a npcStats share a faction?
@@ -123,6 +101,10 @@ namespace MWMechanics
void levelUp();
+ void updateHealth();
+ ///< Calculate health based on endurance and strength.
+ /// Called at character creation and at level up.
+
void flagAsUsed (const std::string& id);
bool hasBeenUsed (const std::string& id) const;
@@ -135,10 +117,6 @@ namespace MWMechanics
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;
@@ -151,6 +129,10 @@ namespace MWMechanics
/// Sets time left for the creature to drown if it stays underwater.
/// @param time value from [0,20]
void setTimeToStartDrowning(float time);
+
+ void writeState (ESM::NpcStats& state) const;
+
+ void readState (const ESM::NpcStats& state);
};
}
diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp
index 41d6b4ffad..ba35af777d 100644
--- a/apps/openmw/mwmechanics/objects.cpp
+++ b/apps/openmw/mwmechanics/objects.cpp
@@ -55,7 +55,7 @@ void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
}
}
-void Objects::dropObjects (const MWWorld::Ptr::CellStore *cellStore)
+void Objects::dropObjects (const MWWorld::CellStore *cellStore)
{
PtrControllerMap::iterator iter = mObjects.begin();
while(iter != mObjects.end())
@@ -92,4 +92,13 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr)
iter->second->skipAnim();
}
+void Objects::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
+{
+ for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter)
+ {
+ if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
+ out.push_back(iter->first);
+ }
+}
+
}
diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp
index 32432c130a..373a2a1056 100644
--- a/apps/openmw/mwmechanics/objects.hpp
+++ b/apps/openmw/mwmechanics/objects.hpp
@@ -40,6 +40,8 @@ namespace MWMechanics
void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
void skipAnimation(const MWWorld::Ptr& ptr);
+
+ void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
};
}
diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp
index ff266f9ae7..8dbdd87707 100644
--- a/apps/openmw/mwmechanics/pathfinding.cpp
+++ b/apps/openmw/mwmechanics/pathfinding.cpp
@@ -4,31 +4,13 @@
#include "../mwbase/environment.hpp"
#include "OgreMath.h"
+#include "OgreVector3.h"
-#include <boost/graph/dijkstra_shortest_paths.hpp>
-#include <boost/graph/adjacency_list.hpp>
+
+#include <map>
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;
@@ -53,9 +35,9 @@ namespace
return sqrt(x * x + y * y + z * z);
}
- static float sgn(float a)
+ static float sgn(Ogre::Radian a)
{
- if(a > 0)
+ if(a.valueRadians() > 0)
return 1.0;
return -1.0;
}
@@ -80,64 +62,106 @@ namespace
return closestIndex;
}
- PathGridGraph buildGraph(const ESM::Pathgrid* pathgrid, float xCell = 0, float yCell = 0)
+ /*std::list<ESM::Pathgrid::Point> reconstructPath(const std::vector<MWMechanics::PathFinder::Node>& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell)
{
- PathGridGraph graph;
-
- for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
+ std::list<ESM::Pathgrid::Point> path;
+ while(graph[lastNode].parent != -1)
{
- 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;
+ //std::cout << "not empty" << xCell;
+ ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode];
+ pt.mX += xCell;
+ pt.mY += yCell;
+ path.push_front(pt);
+ lastNode = graph[lastNode].parent;
}
+ return path;
+ }*/
- 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::list<ESM::Pathgrid::Point> buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0)
{
- std::vector<PointID> p(boost::num_vertices(graph));
- std::vector<float> d(boost::num_vertices(graph));
- std::list<ESM::Pathgrid::Point> shortest_path;
-
- try
+ std::vector<Node> graph;
+ for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++)
{
- boost::dijkstra_shortest_paths(graph, start,
- boost::predecessor_map(&p[0]).distance_map(&d[0]).visitor(goalVisited(end)));
+ Node node;
+ node.label = i;
+ node.parent = -1;
+ graph.push_back(node);
}
+ for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++)
+ {
+ Edge edge;
+ edge.destination = pathgrid->mEdges[i].mV1;
+ edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]);
+ graph[pathgrid->mEdges[i].mV0].edges.push_back(edge);
+ edge.destination = pathgrid->mEdges[i].mV0;
+ graph[pathgrid->mEdges[i].mV1].edges.push_back(edge);
+ }
+
+ std::vector<float> g_score(pathgrid->mPoints.size(),-1.);
+ std::vector<float> f_score(pathgrid->mPoints.size(),-1.);
- catch(found_path& fg)
+ g_score[start] = 0;
+ f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]);
+
+ std::list<int> openset;
+ std::list<int> closedset;
+ openset.push_back(start);
+
+ int current = -1;
+
+ while(!openset.empty())
{
- for(PointID v = end; ; v = p[v])
+ current = openset.front();
+ openset.pop_front();
+
+ if(current == goal) break;
+
+ closedset.push_back(current);
+
+ for(int j = 0;j<graph[current].edges.size();j++)
{
- shortest_path.push_front(graph[v]);
- if(p[v] == v)
- break;
+ //int next = graph[current].edges[j].destination
+ if(std::find(closedset.begin(),closedset.end(),graph[current].edges[j].destination) == closedset.end())
+ {
+ int dest = graph[current].edges[j].destination;
+ float tentative_g = g_score[current] + graph[current].edges[j].cost;
+ bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end();
+ if(!isInOpenSet
+ || tentative_g < g_score[dest] )
+ {
+ graph[dest].parent = current;
+ g_score[dest] = tentative_g;
+ f_score[dest] = tentative_g + distance(pathgrid->mPoints[dest],pathgrid->mPoints[goal]);
+ if(!isInOpenSet)
+ {
+ std::list<int>::iterator it = openset.begin();
+ for(it = openset.begin();it!= openset.end();it++)
+ {
+ if(g_score[*it]>g_score[dest])
+ break;
+ }
+ openset.insert(it,dest);
+ }
+ }
+ }
}
+
}
+ return reconstructPath(graph,pathgrid,current,xCell,yCell);
+
+ }*/
- return shortest_path;
- }
}
namespace MWMechanics
{
PathFinder::PathFinder()
+ : mIsPathConstructed(false),
+ mIsGraphConstructed(false),
+ mCell(NULL)
{
- mIsPathConstructed = false;
}
void PathFinder::clearPath()
@@ -147,9 +171,124 @@ namespace MWMechanics
mIsPathConstructed = false;
}
+ void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid)
+ {
+ mGraph.clear();
+ mGScore.resize(pathGrid->mPoints.size(),-1);
+ mFScore.resize(pathGrid->mPoints.size(),-1);
+ Node defaultNode;
+ defaultNode.label = -1;
+ defaultNode.parent = -1;
+ mGraph.resize(pathGrid->mPoints.size(),defaultNode);
+ for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++)
+ {
+ Node node;
+ node.label = i;
+ node.parent = -1;
+ mGraph[i] = node;
+ }
+ for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++)
+ {
+ Edge edge;
+ edge.destination = pathGrid->mEdges[i].mV1;
+ edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]);
+ mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge);
+ edge.destination = pathGrid->mEdges[i].mV0;
+ mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge);
+ }
+ mIsGraphConstructed = true;
+ }
+
+ void PathFinder::cleanUpAStar()
+ {
+ for(int i=0;i<static_cast<int> (mGraph.size());i++)
+ {
+ mGraph[i].parent = -1;
+ mGScore[i] = -1;
+ mFScore[i] = -1;
+ }
+ }
+
+ std::list<ESM::Pathgrid::Point> PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell)
+ {
+ cleanUpAStar();
+ mGScore[start] = 0;
+ mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]);
+
+ std::list<int> openset;
+ std::list<int> closedset;
+ openset.push_back(start);
+
+ int current = -1;
+
+ while(!openset.empty())
+ {
+ current = openset.front();
+ openset.pop_front();
+
+ if(current == goal) break;
+
+ closedset.push_back(current);
+
+ for(int j = 0;j<static_cast<int> (mGraph[current].edges.size());j++)
+ {
+ //int next = mGraph[current].edges[j].destination
+ if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end())
+ {
+ int dest = mGraph[current].edges[j].destination;
+ float tentative_g = mGScore[current] + mGraph[current].edges[j].cost;
+ bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end();
+ if(!isInOpenSet
+ || tentative_g < mGScore[dest] )
+ {
+ mGraph[dest].parent = current;
+ mGScore[dest] = tentative_g;
+ mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]);
+ if(!isInOpenSet)
+ {
+ std::list<int>::iterator it = openset.begin();
+ for(it = openset.begin();it!= openset.end();it++)
+ {
+ if(mGScore[*it]>mGScore[dest])
+ break;
+ }
+ openset.insert(it,dest);
+ }
+ }
+ }
+ }
+
+ }
+
+ std::list<ESM::Pathgrid::Point> path;
+ while(mGraph[current].parent != -1)
+ {
+ //std::cout << "not empty" << xCell;
+ ESM::Pathgrid::Point pt = pathGrid->mPoints[current];
+ pt.mX += xCell;
+ pt.mY += yCell;
+ path.push_front(pt);
+ current = mGraph[current].parent;
+ }
+
+ if(path.empty())
+ {
+ ESM::Pathgrid::Point pt = pathGrid->mPoints[goal];
+ pt.mX += xCell;
+ pt.mY += yCell;
+ path.push_front(pt);
+ }
+
+ return path;
+ }
+
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
- const ESM::Pathgrid *pathGrid, float xCell, float yCell, bool allowShortcuts)
+ const MWWorld::CellStore* cell, bool allowShortcuts)
{
+ mPath.clear();
+ if(mCell != cell) mIsGraphConstructed = false;
+ mCell = cell;
+
if(allowShortcuts)
{
if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ,
@@ -159,13 +298,24 @@ namespace MWMechanics
if(!allowShortcuts)
{
+ const ESM::Pathgrid *pathGrid =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->mCell);
+ float xCell = 0;
+ float yCell = 0;
+
+ if (mCell->isExterior())
+ {
+ xCell = mCell->mCell->mData.mX * ESM::Land::REAL_SIZE;
+ yCell = mCell->mCell->mData.mY * ESM::Land::REAL_SIZE;
+ }
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(!mIsGraphConstructed) buildPathgridGraph(pathGrid);
+
+ mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph);
if(!mPath.empty())
{
@@ -196,7 +346,13 @@ namespace MWMechanics
float directionY = nextPoint.mY - y;
float directionResult = sqrt(directionX * directionX + directionY * directionY);
- return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees();
+ return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees();
+ }
+
+ float PathFinder::getDistToNext(float x, float y, float z)
+ {
+ ESM::Pathgrid::Point nextPoint = *mPath.begin();
+ return distance(nextPoint, x, y, z);
}
bool PathFinder::checkWaypoint(float x, float y, float z)
@@ -232,5 +388,24 @@ namespace MWMechanics
return false;
}
+
+ void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
+ {
+ if (mPath.size() < 2)
+ return; //nothing to pop
+ std::list<ESM::Pathgrid::Point>::const_iterator oldStart = path.begin();
+ std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
+
+ if( (*iter).mX == oldStart->mX
+ && (*iter).mY == oldStart->mY
+ && (*iter).mZ == oldStart->mZ
+ && (*iter).mAutogenerated == oldStart->mAutogenerated
+ && (*iter).mConnectionNum == oldStart->mConnectionNum )
+ {
+ mPath.pop_front();
+ }
+
+ }
+
}
diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp
index 916df850b5..8771ef0cad 100644
--- a/apps/openmw/mwmechanics/pathfinding.hpp
+++ b/apps/openmw/mwmechanics/pathfinding.hpp
@@ -4,6 +4,11 @@
#include <components/esm/loadpgrd.hpp>
#include <list>
+namespace MWWorld
+{
+ class CellStore;
+}
+
namespace MWMechanics
{
class PathFinder
@@ -12,16 +17,22 @@ namespace MWMechanics
PathFinder();
void clearPath();
+
+ void buildPathgridGraph(const ESM::Pathgrid* pathGrid);
+
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);
+ const MWWorld::CellStore* cell, bool allowShortcuts = true);
bool checkPathCompleted(float x, float y, float z);
///< \Returns true if the last point of the path has been reached.
+
bool checkWaypoint(float x, float y, float z);
///< \Returns true if a way point was reached
+
float getZAngleToNext(float x, float y) const;
+ float getDistToNext(float x, float y, float z);
+
bool isPathConstructed() const
{
return mIsPathConstructed;
@@ -37,9 +48,43 @@ namespace MWMechanics
return mPath;
}
+ //When first point of newly created path is the nearest to actor point, then
+ //the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path)
+ //This functions deletes that point.
+ void syncStart(const std::list<ESM::Pathgrid::Point> &path);
+
+ void addPointToPath(ESM::Pathgrid::Point &point)
+ {
+ mPath.push_back(point);
+ }
+
private:
- std::list<ESM::Pathgrid::Point> mPath;
+
+ struct Edge
+ {
+ int destination;
+ float cost;
+ };
+ struct Node
+ {
+ int label;
+ std::vector<Edge> edges;
+ int parent;//used in pathfinding
+ };
+
+ std::vector<float> mGScore;
+ std::vector<float> mFScore;
+
+ std::list<ESM::Pathgrid::Point> aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0);
+ void cleanUpAStar();
+
+ std::vector<Node> mGraph;
bool mIsPathConstructed;
+
+
+ std::list<ESM::Pathgrid::Point> mPath;
+ bool mIsGraphConstructed;
+ const MWWorld::CellStore* mCell;
};
}
diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp
new file mode 100644
index 0000000000..53681caf8b
--- /dev/null
+++ b/apps/openmw/mwmechanics/pickpocket.cpp
@@ -0,0 +1,66 @@
+#include "pickpocket.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/environment.hpp"
+#include "npcstats.hpp"
+
+namespace MWMechanics
+{
+
+ Pickpocket::Pickpocket(const MWWorld::Ptr &thief, const MWWorld::Ptr &victim)
+ : mThief(thief)
+ , mVictim(victim)
+ {
+ }
+
+ float Pickpocket::getChanceModifier(const MWWorld::Ptr &ptr, float add)
+ {
+ NpcStats& stats = ptr.getClass().getNpcStats(ptr);
+ float agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
+ float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+ float sneak = ptr.getClass().getSkill(ptr, ESM::Skill::Sneak);
+ return (add + 0.2 * agility + 0.1 * luck + sneak) * stats.getFatigueTerm();
+ }
+
+ bool Pickpocket::getDetected(float valueTerm)
+ {
+ float x = getChanceModifier(mThief);
+ float y = getChanceModifier(mVictim, valueTerm);
+
+ float t = 2*x - y;
+
+ float pcSneak = mThief.getClass().getSkill(mThief, ESM::Skill::Sneak);
+ int iPickMinChance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("iPickMinChance")->getInt();
+ int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("iPickMaxChance")->getInt();
+
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
+ if (t < pcSneak / iPickMinChance)
+ {
+ return (roll > int(pcSneak / iPickMinChance));
+ }
+ else
+ {
+ t = std::min(float(iPickMaxChance), t);
+ return (roll > int(t));
+ }
+ }
+
+ bool Pickpocket::pick(MWWorld::Ptr item, int count)
+ {
+ float stackValue = item.getClass().getValue(item) * count;
+ float fPickPocketMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("fPickPocketMod")->getFloat();
+ float valueTerm = 10 * fPickPocketMod * stackValue;
+
+ return getDetected(valueTerm);
+ }
+
+ bool Pickpocket::finish()
+ {
+ return getDetected(0.f);
+ }
+
+}
diff --git a/apps/openmw/mwmechanics/pickpocket.hpp b/apps/openmw/mwmechanics/pickpocket.hpp
new file mode 100644
index 0000000000..4de1e37f84
--- /dev/null
+++ b/apps/openmw/mwmechanics/pickpocket.hpp
@@ -0,0 +1,30 @@
+#ifndef OPENMW_MECHANICS_PICKPOCKET_H
+#define OPENMW_MECHANICS_PICKPOCKET_H
+
+#include "../mwworld/ptr.hpp"
+
+namespace MWMechanics
+{
+
+ class Pickpocket
+ {
+ public:
+ Pickpocket (const MWWorld::Ptr& thief, const MWWorld::Ptr& victim);
+
+ /// Steal some items
+ /// @return Was the thief detected?
+ bool pick (MWWorld::Ptr item, int count);
+ /// End the pickpocketing process
+ /// @return Was the thief detected?
+ bool finish ();
+
+ private:
+ bool getDetected(float valueTerm);
+ float getChanceModifier(const MWWorld::Ptr& ptr, float add=0);
+ MWWorld::Ptr mThief;
+ MWWorld::Ptr mVictim;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp
index 38b2a48d7b..1b17f83056 100644
--- a/apps/openmw/mwmechanics/repair.cpp
+++ b/apps/openmw/mwmechanics/repair.cpp
@@ -8,7 +8,6 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
@@ -20,25 +19,17 @@ namespace MWMechanics
void Repair::repair(const MWWorld::Ptr &itemToRepair)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::LiveCellRef<ESM::Repair> *ref =
mTool.get<ESM::Repair>();
+ // unstack tool if required
+ player.getClass().getContainerStore(player).unstack(mTool, player);
+
// 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);
@@ -85,7 +76,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
// tool used up?
if (mTool.getCellRef().mCharge == 0)
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
store.remove(mTool, 1, player);
diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp
index c373e83f51..2e5eaecfde 100644
--- a/apps/openmw/mwmechanics/security.cpp
+++ b/apps/openmw/mwmechanics/security.cpp
@@ -1,12 +1,12 @@
#include "security.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "npcstats.hpp"
#include "creaturestats.hpp"
@@ -46,6 +46,7 @@ namespace MWMechanics
resultMessage = "#{sLockImpossible}";
else
{
+ MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock);
int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
if (roll <= x)
{
@@ -87,6 +88,7 @@ namespace MWMechanics
resultMessage = "#{sTrapImpossible}";
else
{
+ MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap);
int roll = static_cast<float> (std::rand()) / RAND_MAX * 100;
if (roll <= x)
{
diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp
index 735652163e..fe395e566c 100644
--- a/apps/openmw/mwmechanics/spellcasting.cpp
+++ b/apps/openmw/mwmechanics/spellcasting.cpp
@@ -1,29 +1,192 @@
#include "spellcasting.hpp"
+#include <cfloat>
+
#include <boost/format.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
-
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
#include "../mwworld/containerstore.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/actionteleport.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
#include "../mwrender/animation.hpp"
+#include "magiceffects.hpp"
+#include "npcstats.hpp"
+
namespace MWMechanics
{
+ ESM::Skill::SkillEnum spellSchoolToSkill(int school)
+ {
+ std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
+ schoolSkillMap[0] = ESM::Skill::Alteration;
+ schoolSkillMap[1] = ESM::Skill::Conjuration;
+ schoolSkillMap[3] = ESM::Skill::Illusion;
+ schoolSkillMap[2] = ESM::Skill::Destruction;
+ schoolSkillMap[4] = ESM::Skill::Mysticism;
+ schoolSkillMap[5] = ESM::Skill::Restoration;
+ assert(schoolSkillMap.find(school) != schoolSkillMap.end());
+ return schoolSkillMap[school];
+ }
+
+ float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
+ {
+ CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+
+ if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
+ return 0;
+
+ float y = FLT_MAX;
+ float lowestSkill = 0;
+
+ for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
+ {
+ float x = it->mDuration;
+ const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
+ it->mEffectID);
+ if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
+ x = std::max(1.f, x);
+ x *= 0.1 * magicEffect->mData.mBaseCost;
+ x *= 0.5 * (it->mMagnMin + it->mMagnMax);
+ x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
+ x *= 1.5;
+ static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
+ "fEffectCostMult")->getFloat();
+ x *= fEffectCostMult;
+
+ float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
+ if (s - x < y)
+ {
+ y = s - x;
+ if (effectiveSchool)
+ *effectiveSchool = magicEffect->mData.mSchool;
+ lowestSkill = s;
+ }
+ }
+
+ if (spell->mData.mType != ESM::Spell::ST_Spell)
+ return 100;
+
+ if (spell->mData.mFlags & ESM::Spell::F_Always)
+ return 100;
+
+ int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
+
+ int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
+ int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+
+ float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
+ if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
+ castChance = 100;
+
+ return std::max(0.f, std::min(100.f, castChance));
+ }
+
+ float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool)
+ {
+ const ESM::Spell* spell =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
+ return getSpellSuccessChance(spell, actor, effectiveSchool);
+ }
+
+
+ int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
+ {
+ int school = 0;
+ getSpellSuccessChance(spellId, actor, &school);
+ return school;
+ }
+
+ int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
+ {
+ int school = 0;
+ getSpellSuccessChance(spell, actor, &school);
+ return school;
+ }
+
+ float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
+ {
+ const ESM::MagicEffect *magicEffect =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
+ effectId);
+
+ const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
+
+ float resisted = 0;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
+ {
+
+ short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
+ short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
+
+ float resistance = 0;
+ if (resistanceEffect != -1)
+ resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
+ if (weaknessEffect != -1)
+ resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
+
+
+ float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
+ float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
+ float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
+
+ // This makes spells that are easy to cast harder to resist and vice versa
+ if (spell != NULL && caster.getClass().isActor())
+ {
+ float castChance = getSpellSuccessChance(spell, caster);
+ if (castChance > 0)
+ x *= 50 / castChance;
+ }
+
+ float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
+ roll -= resistance;
+
+ if (x <= roll)
+ x = 0;
+ else
+ {
+ if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
+ x = 100;
+ else
+ x = roll / std::min(x, 100.f);
+ }
+
+ x = std::min(x + resistance, 100.f);
+
+ resisted = x;
+ }
+
+ return resisted;
+ }
+
+ float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
+ {
+ float resistance = getEffectResistance(effectId, actor, caster, spell);
+ if (resistance >= 0)
+ return 1 - resistance / 100.f;
+ else
+ return -(resistance-100) / 100.f;
+ }
+
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
: mCaster(caster)
, mTarget(target)
, mStack(false)
+ , mHitPosition(0,0,0)
{
}
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
- const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
+ const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
// If none of the effects need to apply, we can early-out
bool found = false;
@@ -57,6 +220,7 @@ namespace MWMechanics
ESM::EffectList reflectedEffects;
std::vector<ActiveSpells::Effect> appliedLastingEffects;
bool firstAppliedEffect = true;
+ bool anyHarmfulEffect = false;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
effectIt!=effects.mList.end(); ++effectIt)
@@ -68,9 +232,35 @@ namespace MWMechanics
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectIt->mEffectID);
+ if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate)
+ {
+ if (caster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
+ continue;
+ }
+
+ if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() &&
+ (effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention ||
+ effectIt->mEffectID == ESM::MagicEffect::DivineIntervention ||
+ effectIt->mEffectID == ESM::MagicEffect::Mark ||
+ effectIt->mEffectID == ESM::MagicEffect::Recall))
+ {
+ if (caster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
+ continue;
+ }
+
+ // If player is healing someone, show the target's HP bar
+ if (caster.getRefData().getHandle() == "player" && target != caster
+ && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
+ && target.getClass().isActor())
+ MWBase::Environment::get().getWindowManager()->setEnemy(target);
+
float magnitudeMult = 1;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
{
+ anyHarmfulEffect = true;
+
// If player is attempting to cast a harmful spell, show the target's HP bar
if (caster.getRefData().getHandle() == "player" && target != caster)
MWBase::Environment::get().getWindowManager()->setEnemy(target);
@@ -135,7 +325,8 @@ namespace MWMechanics
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
magnitude *= magnitudeMult;
- if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
+ bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
+ if (target.getClass().isActor() && hasDuration)
{
ActiveSpells::Effect effect;
effect.mKey = MWMechanics::EffectKey(*effectIt);
@@ -154,12 +345,24 @@ namespace MWMechanics
ActiveSpells::Effect effect_ = effect;
effect_.mMagnitude *= -1;
effects.push_back(effect_);
- caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName);
+ // Also make sure to set casterHandle = target, so that the effect on the caster gets purged when the target dies
+ caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
+ effects, mSourceName, target.getRefData().getHandle());
}
}
}
else
- applyInstantEffect(target, effectIt->mEffectID, magnitude);
+ applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude);
+
+ // HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent.
+ // This was probably just done to have the effect visible in the magic menu for a while
+ // to notify the player they've been damaged?
+ if (effectIt->mEffectID == ESM::MagicEffect::DamageAttribute
+ || effectIt->mEffectID == ESM::MagicEffect::DamageSkill
+ || effectIt->mEffectID == ESM::MagicEffect::RestoreAttribute
+ || effectIt->mEffectID == ESM::MagicEffect::RestoreSkill
+ )
+ applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude);
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{
@@ -179,30 +382,39 @@ namespace MWMechanics
}
// Add VFX
+ const ESM::Static* castStatic;
if (!magicEffect->mHit.empty())
- {
- const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
- bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
- // Note: in case of non actor, a free effect should be fine as well
- MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
- if (anim)
- anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
- }
+ castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
+ else
+ castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
+
+ bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
+ // Note: in case of non actor, a free effect should be fine as well
+ MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
+ if (anim)
+ anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
}
-
- // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
}
}
+ if (!exploded)
+ MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName);
+
if (reflectedEffects.mList.size())
inflict(caster, target, reflectedEffects, range, true);
if (appliedLastingEffects.size())
- target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName);
+ target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
+ mSourceName, caster.getRefData().getHandle());
+
+ if (anyHarmfulEffect && target.getClass().isActor() && target != caster
+ && target.getClass().getCreatureStats(target).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30)
+ MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault);
}
- void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude)
+ void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
{
+ short effectId = effect.mId;
if (!target.getClass().isActor())
{
if (effectId == ESM::MagicEffect::Lock)
@@ -212,11 +424,13 @@ namespace MWMechanics
}
else if (effectId == ESM::MagicEffect::Open)
{
- // TODO: This is a crime
if (target.getCellRef().mLockLevel <= magnitude)
{
if (target.getCellRef().mLockLevel > 0)
+ {
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
+ MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target);
+ }
target.getCellRef().mLockLevel = 0;
}
else
@@ -225,6 +439,28 @@ namespace MWMechanics
}
else
{
+ if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute)
+ {
+ int attribute = effect.mArg;
+ AttributeValue value = target.getClass().getCreatureStats(target).getAttribute(attribute);
+ if (effectId == ESM::MagicEffect::DamageAttribute)
+ value.damage(magnitude);
+ else
+ value.restore(magnitude);
+ target.getClass().getCreatureStats(target).setAttribute(attribute, value);
+ }
+ else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill)
+ {
+ if (target.getTypeName() != typeid(ESM::NPC).name())
+ return;
+ int skill = effect.mArg;
+ SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill);
+ if (effectId == ESM::MagicEffect::DamageSkill)
+ value.damage(magnitude);
+ else
+ value.restore(magnitude);
+ }
+
if (effectId == ESM::MagicEffect::CurePoison)
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
else if (effectId == ESM::MagicEffect::CureParalyzation)
@@ -245,17 +481,13 @@ namespace MWMechanics
if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled())
return;
- Ogre::Vector3 worldPos;
- if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(target.getCell(), worldPos))
- worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition();
-
if (effectId == ESM::MagicEffect::DivineIntervention)
{
- MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker", worldPos);
+ MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker");
}
else if (effectId == ESM::MagicEffect::AlmsiviIntervention)
{
- MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker", worldPos);
+ MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker");
}
else if (effectId == ESM::MagicEffect::Mark)
@@ -309,12 +541,11 @@ namespace MWMechanics
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
- if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
+ // Check if there's enough charge left
+ if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{
- // Check if there's enough charge left
const float enchantCost = enchantment->mData.mCost;
- MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
- int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
+ int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
if (item.getCellRef().mEnchantmentCharge == -1)
@@ -327,21 +558,26 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
return false;
}
-
// Reduce charge
item.getCellRef().mEnchantmentCharge -= castCost;
}
+
+ if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
+ {
+ if (mCaster.getRefData().getHandle() == "player")
+ mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
+ }
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
item.getContainerStore()->remove(item, 1, mCaster);
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
{
if (mCaster.getRefData().getHandle() == "player")
+ {
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
+ mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
+ }
}
- if (mCaster.getRefData().getHandle() == "player")
- mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
-
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty())
@@ -388,8 +624,7 @@ namespace MWMechanics
DynamicStat<float> fatigue = stats.getFatigue();
const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster);
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
- fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
- stats.setFatigue(fatigue);
+ fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue);
bool fail = false;
@@ -398,7 +633,8 @@ namespace MWMechanics
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (!fail && roll >= successChance)
{
- MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
+ if (mCaster.getRefData().getHandle() == "player")
+ MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
fail = true;
}
diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp
index e2efaa140b..74dc490ea9 100644
--- a/apps/openmw/mwmechanics/spellcasting.hpp
+++ b/apps/openmw/mwmechanics/spellcasting.hpp
@@ -1,33 +1,15 @@
#ifndef MWMECHANICS_SPELLSUCCESS_H
#define MWMECHANICS_SPELLSUCCESS_H
-#include <cfloat>
-
-#include "../mwbase/world.hpp"
-#include "../mwbase/environment.hpp"
-
#include "../mwworld/ptr.hpp"
-#include "../mwworld/class.hpp"
-#include "../mwmechanics/creaturestats.hpp"
-#include "../mwworld/esmstore.hpp"
-
-#include "npcstats.hpp"
+#include <OgreVector3.h>
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];
- }
+ class EffectKey;
+
+ ESM::Skill::SkillEnum spellSchoolToSkill(int school);
/**
* @param spell spell to cast
@@ -36,148 +18,16 @@ namespace MWMechanics
* @attention actor has to be an NPC and not a creature!
* @return success chance from 0 to 100 (in percent)
*/
- inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
- {
- NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
- CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
-
- if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
- return 0;
-
- float y = FLT_MAX;
- float lowestSkill = 0;
-
- for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
- {
- float x = it->mDuration;
- const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
- it->mEffectID);
- if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
- x = std::max(1.f, x);
- x *= 0.1 * magicEffect->mData.mBaseCost;
- x *= 0.5 * (it->mMagnMin + it->mMagnMax);
- x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
- if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
- x *= 1.5;
- static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
- "fEffectCostMult")->getFloat();
- x *= fEffectCostMult;
-
- float s = 2 * stats.getSkill(spellSchoolToSkill(magicEffect->mData.mSchool)).getModified();
- if (s - x < y)
- {
- y = s - x;
- if (effectiveSchool)
- *effectiveSchool = magicEffect->mData.mSchool;
- lowestSkill = s;
- }
- }
-
- if (spell->mData.mType != ESM::Spell::ST_Spell)
- return 100;
-
- if (spell->mData.mFlags & ESM::Spell::F_Always)
- return 100;
-
- int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
-
- int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
- int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
-
- float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
- if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
- castChance = 100;
-
- return std::max(0.f, std::min(100.f, castChance));
- }
-
- inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
- {
- const ESM::Spell* spell =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
- return getSpellSuccessChance(spell, actor, effectiveSchool);
- }
+ float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
+ float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
- inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
- {
- int school = 0;
- getSpellSuccessChance(spellId, actor, &school);
- return school;
- }
-
- inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
- {
- int school = 0;
- getSpellSuccessChance(spell, actor, &school);
- return school;
- }
+ int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
+ int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
- inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
- {
- const ESM::MagicEffect *magicEffect =
- MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
- effectId);
-
- const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
-
- float resisted = 0;
- if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
- {
-
- short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
- short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
-
- float resistance = 0;
- if (resistanceEffect != -1)
- resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
- if (weaknessEffect != -1)
- resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
-
-
- float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
- float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
- float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
-
- // This makes spells that are easy to cast harder to resist and vice versa
- if (spell != NULL && caster.getClass().isActor())
- {
- float castChance = getSpellSuccessChance(spell, caster);
- if (castChance > 0)
- x *= 50 / castChance;
- }
-
- float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
- if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
- roll -= resistance;
-
- if (x <= roll)
- x = 0;
- else
- {
- if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
- x = 100;
- else
- x = roll / std::min(x, 100.f);
- }
-
- x = std::min(x + resistance, 100.f);
-
- resisted = x;
- }
-
- return resisted;
- }
-
- inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
- {
- float resistance = getEffectResistance(effectId, actor, caster, spell);
- if (resistance >= 0)
- return 1 - resistance / 100.f;
- else
- return -(resistance-100) / 100.f;
- }
+ float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
+ float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
class CastSpell
{
@@ -188,6 +38,7 @@ namespace MWMechanics
bool mStack;
std::string mId; // ID of spell, potion, item etc
std::string mSourceName; // Display name for spell, potion, etc
+ Ogre::Vector3 mHitPosition; // Used for spawning area orb
public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
@@ -201,9 +52,9 @@ namespace MWMechanics
bool cast (const std::string& id);
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
- const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false);
+ const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
- void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude);
+ void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
};
}
diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp
index 0088bcb603..21781c530c 100644
--- a/apps/openmw/mwmechanics/spells.cpp
+++ b/apps/openmw/mwmechanics/spells.cpp
@@ -34,13 +34,14 @@ namespace MWMechanics
random.resize(spell->mEffects.mList.size());
for (unsigned int i=0; i<random.size();++i)
random[i] = static_cast<float> (std::rand()) / RAND_MAX;
- mSpells.insert (std::make_pair (spellId, random));
+ mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random));
}
}
void Spells::remove (const std::string& spellId)
{
- TContainer::iterator iter = mSpells.find (spellId);
+ std::string lower = Misc::StringUtils::lowerCase(spellId);
+ TContainer::iterator iter = mSpells.find (lower);
if (iter!=mSpells.end())
mSpells.erase (iter);
@@ -192,7 +193,7 @@ namespace MWMechanics
effectIt != list.mList.end(); ++effectIt, ++i)
{
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i];
- visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude);
+ visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, "", magnitude);
}
}
}
diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp
index cf9b660915..cc239a650f 100644
--- a/apps/openmw/mwmechanics/spells.hpp
+++ b/apps/openmw/mwmechanics/spells.hpp
@@ -20,7 +20,7 @@ namespace MWMechanics
/// \brief Spell list
///
/// This class manages known spells as well as abilities, powers and permanent negative effects like
- /// diseaes.
+ /// diseases.
class Spells
{
public:
@@ -44,6 +44,8 @@ namespace MWMechanics
TIterator end() const;
+ bool hasSpell(const std::string& spell) { return mSpells.find(Misc::StringUtils::lowerCase(spell)) != mSpells.end(); }
+
void add (const std::string& spell);
///< Adding a spell that is already listed in *this is a no-op.
diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp
new file mode 100644
index 0000000000..61b6d60ad4
--- /dev/null
+++ b/apps/openmw/mwmechanics/stat.cpp
@@ -0,0 +1,29 @@
+
+#include "stat.hpp"
+
+void MWMechanics::AttributeValue::writeState (ESM::StatState<int>& state) const
+{
+ state.mBase = mBase;
+ state.mMod = mModifier;
+ state.mDamage = mDamage;
+}
+
+void MWMechanics::AttributeValue::readState (const ESM::StatState<int>& state)
+{
+ mBase = state.mBase;
+ mModifier = state.mMod;
+ mDamage = state.mDamage;
+}
+
+
+void MWMechanics::SkillValue::writeState (ESM::StatState<int>& state) const
+{
+ AttributeValue::writeState (state);
+ state.mProgress = mProgress;
+}
+
+void MWMechanics::SkillValue::readState (const ESM::StatState<int>& state)
+{
+ AttributeValue::readState (state);
+ mProgress = state.mProgress;
+} \ No newline at end of file
diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp
index cb6c09014b..0fb4c57328 100644
--- a/apps/openmw/mwmechanics/stat.hpp
+++ b/apps/openmw/mwmechanics/stat.hpp
@@ -6,6 +6,8 @@
#include <limits>
+#include <components/esm/statstate.hpp>
+
namespace MWMechanics
{
template<typename T>
@@ -86,6 +88,18 @@ namespace MWMechanics
{
mModified = mBase + modifier;
}
+
+ void writeState (ESM::StatState<T>& state) const
+ {
+ state.mBase = mBase;
+ state.mMod = mModified;
+ }
+
+ void readState (const ESM::StatState<T>& state)
+ {
+ mBase = state.mBase;
+ mModified = state.mMod;
+ }
};
template<typename T>
@@ -190,6 +204,18 @@ namespace MWMechanics
mStatic.setModifier (modifier);
setCurrent (getCurrent()+diff);
}
+
+ void writeState (ESM::StatState<T>& state) const
+ {
+ mStatic.writeState (state);
+ state.mCurrent = mCurrent;
+ }
+
+ void readState (const ESM::StatState<T>& state)
+ {
+ mStatic.readState (state);
+ mCurrent = state.mCurrent;
+ }
};
template<typename T>
@@ -205,6 +231,67 @@ namespace MWMechanics
{
return !(left==right);
}
+
+ class AttributeValue
+ {
+ int mBase;
+ int mModifier;
+ int mDamage;
+
+ public:
+ AttributeValue() : mBase(0), mModifier(0), mDamage(0) {}
+
+ int getModified() const { return std::max(0, mBase - mDamage + mModifier); }
+ int getBase() const { return mBase; }
+ int getModifier() const { return mModifier; }
+
+ void setBase(int base) { mBase = std::max(0, base); }
+ void setModifier(int mod) { mModifier = mod; }
+
+ void damage(int damage) { mDamage += damage; }
+ void restore(int amount) { mDamage -= std::min(mDamage, amount); }
+ int getDamage() const { return mDamage; }
+
+ void writeState (ESM::StatState<int>& state) const;
+
+ void readState (const ESM::StatState<int>& state);
+ };
+
+ class SkillValue : public AttributeValue
+ {
+ float mProgress;
+ public:
+ SkillValue() : mProgress(0) {}
+ float getProgress() const { return mProgress; }
+ void setProgress(float progress) { mProgress = progress; }
+
+ void writeState (ESM::StatState<int>& state) const;
+
+ void readState (const ESM::StatState<int>& state);
+ };
+
+ inline bool operator== (const AttributeValue& left, const AttributeValue& right)
+ {
+ return left.getBase() == right.getBase()
+ && left.getModifier() == right.getModifier()
+ && left.getDamage() == right.getDamage();
+ }
+ inline bool operator!= (const AttributeValue& left, const AttributeValue& right)
+ {
+ return !(left == right);
+ }
+
+ inline bool operator== (const SkillValue& left, const SkillValue& right)
+ {
+ return left.getBase() == right.getBase()
+ && left.getModifier() == right.getModifier()
+ && left.getDamage() == right.getDamage()
+ && left.getProgress() == right.getProgress();
+ }
+ inline bool operator!= (const SkillValue& left, const SkillValue& right)
+ {
+ return !(left == right);
+ }
}
#endif
diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp
new file mode 100644
index 0000000000..d911fd81b8
--- /dev/null
+++ b/apps/openmw/mwmechanics/steering.cpp
@@ -0,0 +1,43 @@
+#include "steering.hpp"
+
+#include "../mwworld/class.hpp"
+#include "../mwworld/ptr.hpp"
+
+#include "../mwbase/environment.hpp"
+
+#include "movement.hpp"
+
+namespace MWMechanics
+{
+
+bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle)
+{
+ Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]);
+ Ogre::Radian diff (targetAngle - currentAngle);
+ if (diff >= Ogre::Degree(180))
+ {
+ // Turning the other way would be a better idea
+ diff = diff-Ogre::Degree(360);
+ }
+ else if (diff <= Ogre::Degree(-180))
+ {
+ diff = Ogre::Degree(360)-diff;
+ }
+ Ogre::Radian absDiff = Ogre::Math::Abs(diff);
+
+ // The turning animation actually moves you slightly, so the angle will be wrong again.
+ // Use epsilon to prevent jerkiness.
+ const Ogre::Degree epsilon (0.5);
+ if (absDiff < epsilon)
+ return true;
+
+ // Max. speed of 10 radian per sec
+ Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
+ if (absDiff > limit)
+ diff = Ogre::Math::Sign(diff) * limit;
+
+ actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians();
+ return false;
+}
+
+}
diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp
new file mode 100644
index 0000000000..504dc3ac33
--- /dev/null
+++ b/apps/openmw/mwmechanics/steering.hpp
@@ -0,0 +1,19 @@
+#ifndef OPENMW_MECHANICS_STEERING_H
+
+#include <OgreMath.h>
+
+namespace MWWorld
+{
+class Ptr;
+}
+
+namespace MWMechanics
+{
+
+/// configure rotation settings for an actor to reach this target angle (eventually)
+/// @return have we reached the target angle?
+bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle);
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp
index f3ea38f447..eb3e5815e7 100644
--- a/apps/openmw/mwrender/activatoranimation.hpp
+++ b/apps/openmw/mwrender/activatoranimation.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_ACTIVATORANIMATION_H
-#define _GAME_RENDER_ACTIVATORANIMATION_H
+#ifndef GAME_RENDER_ACTIVATORANIMATION_H
+#define GAME_RENDER_ACTIVATORANIMATION_H
#include "animation.hpp"
diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp
index 639045bbe9..820ba8acca 100644
--- a/apps/openmw/mwrender/actors.cpp
+++ b/apps/openmw/mwrender/actors.cpp
@@ -75,14 +75,15 @@ void Actors::insertNPC(const MWWorld::Ptr& ptr)
delete mAllActors[ptr];
mAllActors[ptr] = anim;
mRendering->addWaterRippleEmitter (ptr);
-
- // Create CustomData, will do autoEquip and trigger animation parts update
- ptr.getClass().getInventoryStore(ptr);
}
-void Actors::insertCreature (const MWWorld::Ptr& ptr)
+void Actors::insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields)
{
insertBegin(ptr);
- CreatureAnimation* anim = new CreatureAnimation(ptr);
+ Animation* anim = NULL;
+ if (weaponsShields)
+ anim = new CreatureWeaponAnimation(ptr);
+ else
+ anim = new CreatureAnimation(ptr);
delete mAllActors[ptr];
mAllActors[ptr] = anim;
mRendering->addWaterRippleEmitter (ptr);
@@ -126,7 +127,7 @@ bool Actors::deleteObject (const MWWorld::Ptr& ptr)
return true;
}
-void Actors::removeCell(MWWorld::Ptr::CellStore* store)
+void Actors::removeCell(MWWorld::CellStore* store)
{
for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();)
{
diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp
index d91321843b..d5d6c52bb0 100644
--- a/apps/openmw/mwrender/actors.hpp
+++ b/apps/openmw/mwrender/actors.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_ACTORS_H
-#define _GAME_RENDER_ACTORS_H
+#ifndef GAME_RENDER_ACTORS_H
+#define GAME_RENDER_ACTORS_H
#include <openengine/ogre/renderer.hpp>
@@ -40,7 +40,7 @@ namespace MWRender
void setRootNode(Ogre::SceneNode* root);
void insertNPC(const MWWorld::Ptr& ptr);
- void insertCreature (const MWWorld::Ptr& ptr);
+ void insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields);
void insertActivator (const MWWorld::Ptr& ptr);
bool deleteObject (const MWWorld::Ptr& ptr);
///< \return found?
diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp
index faf9d979ae..b3aa0cd858 100644
--- a/apps/openmw/mwrender/animation.cpp
+++ b/apps/openmw/mwrender/animation.cpp
@@ -30,7 +30,7 @@
namespace MWRender
{
-Ogre::Real Animation::AnimationValue::getValue() const
+Ogre::Real Animation::AnimationTime::getValue() const
{
AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName);
if(iter != mAnimation->mStates.end())
@@ -38,32 +38,31 @@ Ogre::Real Animation::AnimationValue::getValue() const
return 0.0f;
}
-void Animation::AnimationValue::setValue(Ogre::Real)
+void Animation::AnimationTime::setValue(Ogre::Real)
{
}
-Ogre::Real Animation::EffectAnimationValue::getValue() const
+Ogre::Real Animation::EffectAnimationTime::getValue() const
{
return mTime;
}
-void Animation::EffectAnimationValue::setValue(Ogre::Real)
+void Animation::EffectAnimationTime::setValue(Ogre::Real)
{
}
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)
+ , mNullAnimationTimePtr(OGRE_NEW NullAnimationTime)
{
for(size_t i = 0;i < sNumGroups;i++)
- mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this));
+ mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this));
}
Animation::~Animation()
@@ -139,7 +138,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
{
if(mObjectRoot->mControllers[i].getSource().isNull())
- mObjectRoot->mControllers[i].setSource(mAnimationValuePtr[0]);
+ mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]);
}
}
@@ -185,6 +184,7 @@ public:
for(unsigned int i = 0;i < numsubs;++i)
{
Ogre::SubEntity* subEnt = entity->getSubEntity(i);
+ sh::Factory::getInstance()._ensureMaterial(subEnt->getMaterial()->getName(), "Default");
subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue);
}
}
@@ -285,7 +285,18 @@ void Animation::addAnimSource(const std::string &model)
}
}
- ctrls[i].setSource(mAnimationValuePtr[grp]);
+ if (grp == 0 && dstval->getNode()->getName() == "Bip01")
+ {
+ 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(mAnimationTimePtr[grp]);
grpctrls[grp].push_back(ctrls[i]);
}
}
@@ -295,7 +306,7 @@ void Animation::clearAnimSources()
mStates.clear();
for(size_t i = 0;i < sNumGroups;i++)
- mAnimationValuePtr[i]->setAnimName(std::string());
+ mAnimationTimePtr[i]->setAnimName(std::string());
mNonAccumCtrl = NULL;
@@ -387,7 +398,6 @@ Ogre::Node *Animation::getNode(const std::string &name)
return NULL;
}
-
NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname)
{
NifOgre::TextKeyMap::const_iterator iter(keys.begin());
@@ -532,12 +542,6 @@ static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bo
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())
@@ -584,7 +588,11 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
const std::string stoptag = groupname+": "+stop;
NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
- while(stopkey != keys.end() && stopkey->second != stoptag)
+ while(stopkey != keys.end()
+ // We have to ignore extra garbage at the end.
+ // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
+ // Why, just why? :(
+ && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
stopkey++;
if(stopkey == keys.end())
return false;
@@ -616,6 +624,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
return true;
}
+void split(const std::string &s, char delim, std::vector<std::string> &elems) {
+ std::stringstream ss(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+}
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
{
@@ -630,14 +645,29 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
}
if(evt.compare(0, 10, "soundgen: ") == 0)
{
- std::string sound = MWWorld::Class::get(mPtr).getSoundIdFromSndGen(mPtr, evt.substr(10));
+ std::string soundgen = evt.substr(10);
+
+ // The event can optionally contain volume and pitch modifiers
+ float volume=1.f, pitch=1.f;
+ if (soundgen.find(" ") != std::string::npos)
+ {
+ std::vector<std::string> tokens;
+ split(soundgen, ' ', tokens);
+ soundgen = tokens[0];
+ if (tokens.size() >= 2)
+ volume = Ogre::StringConverter::parseReal(tokens[1]);
+ if (tokens.size() >= 3)
+ pitch = Ogre::StringConverter::parseReal(tokens[2]);
+ }
+
+ std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
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);
+ sndMgr->playSound3D(mPtr, sound, volume, pitch, type);
}
return;
}
@@ -660,19 +690,50 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
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);
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop);
else if(evt.compare(off, len, "slash hit") == 0)
- MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Slash);
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash);
else if(evt.compare(off, len, "thrust hit") == 0)
- MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust);
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust);
else if(evt.compare(off, len, "hit") == 0)
- MWWorld::Class::get(mPtr).hit(mPtr);
+ {
+ if (groupname == "attack1")
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop);
+ else if (groupname == "attack2")
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash);
+ else if (groupname == "attack3")
+ mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust);
+ else
+ mPtr.getClass().hit(mPtr);
+ }
+ else if (evt.compare(off, len, "shoot attach") == 0)
+ attachArrow();
+ else if (evt.compare(off, len, "shoot release") == 0)
+ releaseArrow();
+ else if (evt.compare(off, len, "shoot follow attach") == 0)
+ attachArrow();
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
MWBase::Environment::get().getWorld()->castSpell(mPtr);
-}
+ else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
+ mPtr.getClass().block(mPtr);
+}
+void Animation::changeGroups(const std::string &groupname, int groups)
+{
+ AnimStateMap::iterator stateiter = mStates.begin();
+ stateiter = mStates.find(groupname);
+ if(stateiter != mStates.end())
+ {
+ if(stateiter->second.mGroups != groups)
+ {
+ stateiter->second.mGroups = groups;
+ resetActiveGroups();
+ }
+ return;
+ }
+}
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())
@@ -776,7 +837,7 @@ void Animation::resetActiveGroups()
active = state;
}
- mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ?
+ mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ?
std::string() : active->first);
}
mNonAccumCtrl = NULL;
@@ -784,7 +845,7 @@ void Animation::resetActiveGroups()
if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f))
return;
- AnimStateMap::const_iterator state = mStates.find(mAnimationValuePtr[0]->getAnimName());
+ AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName());
if(state == mStates.end())
return;
@@ -825,6 +886,27 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp
return true;
}
+float Animation::getStartTime(const std::string &groupname) const
+{
+ AnimSourceList::const_iterator iter(mAnimSources.begin());
+ for(;iter != mAnimSources.end();iter++)
+ {
+ const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
+ NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
+ if(found != keys.end())
+ return found->first;
+ }
+ return -1.f;
+}
+
+float Animation::getCurrentTime(const std::string &groupname) const
+{
+ AnimStateMap::const_iterator iter = mStates.find(groupname);
+ if(iter == mStates.end())
+ return -1.f;
+
+ return iter->second.mTime;
+}
void Animation::disable(const std::string &groupname)
{
@@ -838,7 +920,6 @@ void Animation::disable(const std::string &groupname)
Ogre::Vector3 Animation::runAnimation(float duration)
{
Ogre::Vector3 movement(0.0f);
-
AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end())
{
@@ -857,13 +938,13 @@ Ogre::Vector3 Animation::runAnimation(float duration)
targetTime = state.mTime + timepassed;
if(textkey == textkeys.end() || textkey->first > targetTime)
{
- if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
+ if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
updatePosition(state.mTime, targetTime, movement);
state.mTime = std::min(targetTime, state.mStopTime);
}
else
{
- if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName())
+ if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName())
updatePosition(state.mTime, textkey->first, movement);
state.mTime = textkey->first;
}
@@ -914,7 +995,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
// Apply group controllers
for(size_t grp = 0;grp < sNumGroups;grp++)
{
- const std::string &name = mAnimationValuePtr[grp]->getAnimName();
+ const std::string &name = mAnimationTimePtr[grp]->getAnimName();
if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end())
{
const Ogre::SharedPtr<AnimSource> &src = stateiter->second.mSource;
@@ -996,14 +1077,15 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj)
mSkelBase->detachObjectFromBone(obj);
}
-bool Animation::isPlaying(Group group) const
+bool Animation::allowSwitchViewMode() const
{
for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
{
- if(stateiter->second.mGroups == group)
- return true;
+ if(stateiter->second.mPriority > MWMechanics::Priority_Movement
+ && stateiter->second.mPriority < MWMechanics::Priority_Torch)
+ return false;
}
- return false;
+ return true;
}
void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture)
@@ -1028,6 +1110,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
else
params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
+ // TODO: turn off shadow casting
setRenderProperties(params.mObjects, RV_Misc,
RQG_Main, RQG_Alpha, 0.f, false, NULL);
@@ -1038,7 +1121,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
for(size_t i = 0;i < params.mObjects->mControllers.size();i++)
{
if(params.mObjects->mControllers[i].getSource().isNull())
- params.mObjects->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
+ params.mObjects->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationTime> (new EffectAnimationTime()));
}
if (!texture.empty())
@@ -1096,7 +1179,7 @@ void Animation::updateEffects(float duration)
NifOgre::ObjectScenePtr objects = it->mObjects;
for(size_t i = 0; i < objects->mControllers.size() ;i++)
{
- EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
+ EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
if (value)
value->addTime(duration);
@@ -1111,7 +1194,7 @@ void Animation::updateEffects(float duration)
float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength;
for(size_t i = 0; i < objects->mControllers.size() ;i++)
{
- EffectAnimationValue* value = dynamic_cast<EffectAnimationValue*>(objects->mControllers[i].getSource().get());
+ EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
if (value)
value->resetTime(remainder);
}
@@ -1188,6 +1271,7 @@ public:
unsigned int numsubs = ent->getNumSubEntities();
for(unsigned int i = 0;i < numsubs;++i)
{
+ sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(i)->getMaterial()->getName(), "Default");
if(ent->getSubEntity(i)->getMaterial()->isTransparent())
return true;
}
diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp
index 72d1c100ef..c0cb18010c 100644
--- a/apps/openmw/mwrender/animation.hpp
+++ b/apps/openmw/mwrender/animation.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_ANIMATION_H
-#define _GAME_RENDER_ANIMATION_H
+#ifndef GAME_RENDER_ANIMATION_H
+#define GAME_RENDER_ANIMATION_H
#include <OgreController.h>
#include <OgreVector3.h>
@@ -32,14 +32,14 @@ protected:
/* This is the number of *discrete* groups. */
static const size_t sNumGroups = 4;
- class AnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ class AnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
Animation *mAnimation;
std::string mAnimationName;
public:
- AnimationValue(Animation *anim)
+ AnimationTime(Animation *anim)
: mAnimation(anim)
{ }
@@ -52,12 +52,12 @@ protected:
virtual void setValue(Ogre::Real value);
};
- class EffectAnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
float mTime;
public:
- EffectAnimationValue() : mTime(0) { }
+ EffectAnimationTime() : mTime(0) { }
void addTime(float time) { mTime += time; }
void resetTime(float value) { mTime = value; }
@@ -67,7 +67,7 @@ protected:
- class NullAnimationValue : public Ogre::ControllerValue<Ogre::Real>
+ class NullAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
public:
virtual Ogre::Real getValue() const
@@ -121,7 +121,6 @@ protected:
std::vector<EffectParams> mEffects;
MWWorld::Ptr mPtr;
- Camera *mCamera;
Ogre::SceneNode *mInsert;
Ogre::Entity *mSkelBase;
@@ -134,8 +133,8 @@ protected:
AnimStateMap mStates;
- Ogre::SharedPtr<AnimationValue> mAnimationValuePtr[sNumGroups];
- Ogre::SharedPtr<NullAnimationValue> mNullAnimationValuePtr;
+ Ogre::SharedPtr<AnimationTime> mAnimationTimePtr[sNumGroups];
+ Ogre::SharedPtr<NullAnimationTime> mNullAnimationTimePtr;
ObjectAttachMap mAttachedObjects;
@@ -189,16 +188,18 @@ protected:
/** Adds an additional light to the given object list using the specified ESM record. */
void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light);
- static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue,
- Ogre::uint8 transqueue, Ogre::Real dist=0.0f,
- bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
-
void clearAnimSources();
// TODO: Should not be here
Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item);
public:
+ // FIXME: Move outside of this class
+ static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue,
+ Ogre::uint8 transqueue, Ogre::Real dist=0.0f,
+ bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL);
+
+
Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node);
virtual ~Animation();
@@ -258,7 +259,8 @@ public:
/** Returns true if the named animation group is playing. */
bool isPlaying(const std::string &groupname) const;
- bool isPlaying(Group group) const;
+ //Checks if playing any animation which shouldn't be stopped when switching camera view modes
+ bool allowSwitchViewMode() const;
/** Gets info about the given animation group.
* \param groupname Animation group to check.
@@ -268,26 +270,37 @@ public:
*/
bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
+ /// Get the absolute position in the animation track of the first text key with the given group.
+ float getStartTime(const std::string &groupname) const;
+
+ /// Get the current absolute position in the animation track for the animation that is currently playing from the given group.
+ float getCurrentTime(const std::string& groupname) const;
+
/** Disables the specified animation group;
* \param groupname Animation group to disable.
*/
void disable(const std::string &groupname);
+ void changeGroups(const std::string &groupname, int group);
+
+ virtual void setWeaponGroup(const std::string& group) {}
/** Retrieves the velocity (in units per second) that the animation will move. */
float getVelocity(const std::string &groupname) const;
+ /// A relative factor (0-1) that decides if and how much the skeleton should be pitched
+ /// to indicate the facing orientation of the character.
+ virtual void setPitchFactor(float factor) {}
+
virtual Ogre::Vector3 runAnimation(float duration);
virtual void showWeapons(bool showWeapon);
virtual void showCarriedLeft(bool show) {}
-
+ virtual void attachArrow() {}
+ virtual void releaseArrow() {}
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
diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp
index 9a35725ee3..9ae9c58788 100644
--- a/apps/openmw/mwrender/camera.cpp
+++ b/apps/openmw/mwrender/camera.cpp
@@ -107,7 +107,7 @@ namespace MWRender
void Camera::update(float duration, bool paused)
{
- if (!mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ if (mAnimation->allowSwitchViewMode())
{
// Now process the view changes we queued earlier
if (mVanityToggleQueued)
@@ -144,7 +144,7 @@ namespace MWRender
{
// Changing the view will stop all playing animations, so if we are playing
// anything important, queue the view change for later
- if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ if (!mAnimation->allowSwitchViewMode())
{
mViewModeToggleQueued = true;
return;
@@ -171,7 +171,7 @@ namespace MWRender
{
// Changing the view will stop all playing animations, so if we are playing
// anything important, queue the view change for later
- if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ if (!mPreviewMode)
{
mVanityToggleQueued = true;
return false;
@@ -205,7 +205,7 @@ namespace MWRender
void Camera::togglePreviewMode(bool enable)
{
- if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody))
+ if (mFirstPersonView && !mAnimation->allowSwitchViewMode())
return;
if(mPreviewMode == enable)
@@ -226,10 +226,10 @@ namespace MWRender
mCamera->setPosition(0.f, 0.f, offset);
}
- void Camera::setSneakOffset()
+ void Camera::setSneakOffset(float offset)
{
if(mAnimation)
- mAnimation->addFirstPersonOffset(Ogre::Vector3(0.f, 0.f, -9.8f));
+ mAnimation->addFirstPersonOffset(Ogre::Vector3(0.f, 0.f, -offset));
}
float Camera::getYaw()
@@ -338,11 +338,9 @@ namespace MWRender
if(mAnimation && mAnimation != anim)
{
mAnimation->setViewMode(NpcAnimation::VM_Normal);
- mAnimation->setCamera(NULL);
mAnimation->detachObjectFromBone(mCamera);
}
mAnimation = anim;
- mAnimation->setCamera(this);
processViewChange();
}
@@ -358,7 +356,7 @@ namespace MWRender
Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera);
tag->setInheritOrientation(false);
}
- else
+ else
{
mAnimation->setViewMode(NpcAnimation::VM_Normal);
mCameraNode->attachObject(mCamera);
diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp
index d31d9e56c0..808f817cf5 100644
--- a/apps/openmw/mwrender/camera.hpp
+++ b/apps/openmw/mwrender/camera.hpp
@@ -87,7 +87,7 @@ namespace MWRender
/// As animation is tied to the camera, this needs
/// to be set each frame after the animation is
/// applied.
- void setSneakOffset();
+ void setSneakOffset(float offset);
bool isFirstPerson() const
{ return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); }
diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp
index 5e659ca1d7..2808286520 100644
--- a/apps/openmw/mwrender/characterpreview.cpp
+++ b/apps/openmw/mwrender/characterpreview.cpp
@@ -4,13 +4,13 @@
#include <OgreSceneManager.h>
#include <OgreRoot.h>
#include <OgreHardwarePixelBuffer.h>
+#include <OgreCamera.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"
@@ -140,10 +140,9 @@ namespace MWRender
void InventoryPreview::update(int sizeX, int sizeY)
{
- // TODO: can we avoid this. Vampire state needs to be updated.
- mAnimation->rebuild();
+ mAnimation->updateParts();
- MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter);
+ MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
std::string groupname;
if(iter == inv.end())
@@ -161,7 +160,10 @@ namespace MWRender
if(type == ESM::Weapon::ShortBladeOneHand ||
type == ESM::Weapon::LongBladeOneHand ||
type == ESM::Weapon::BluntOneHand ||
- type == ESM::Weapon::AxeOneHand)
+ type == ESM::Weapon::AxeOneHand ||
+ type == ESM::Weapon::MarksmanThrown ||
+ type == ESM::Weapon::MarksmanCrossbow ||
+ type == ESM::Weapon::MarksmanBow)
groupname = "inventoryweapononehand";
else if(type == ESM::Weapon::LongBladeTwoHand ||
type == ESM::Weapon::BluntTwoClose ||
@@ -177,12 +179,8 @@ namespace MWRender
groupname = "inventoryhandtohand";
}
- // TODO see above
- //if(groupname != mCurrentAnimGroup)
- //{
- mCurrentAnimGroup = groupname;
- mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
- //}
+ 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())
@@ -194,7 +192,6 @@ namespace MWRender
else if(mAnimation->getInfo("torch"))
mAnimation->disable("torch");
- mAnimation->updateParts();
mAnimation->runAnimation(0.0f);
mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024)));
@@ -225,7 +222,7 @@ namespace MWRender
// --------------------------------------------------------------------------------------------------
RaceSelectionPreview::RaceSelectionPreview()
- : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayer().getPlayer(),
+ : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayerPtr(),
512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0))
, mRef(&mBase)
{
diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp
index b2dfc96795..cd30cdf466 100644
--- a/apps/openmw/mwrender/characterpreview.hpp
+++ b/apps/openmw/mwrender/characterpreview.hpp
@@ -6,8 +6,6 @@
#include <components/esm/loadnpc.hpp>
-#include "externalrendering.hpp"
-
#include "../mwworld/ptr.hpp"
namespace OEngine
diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp
index c3ad512ddd..e2aa9a2b83 100644
--- a/apps/openmw/mwrender/creatureanimation.cpp
+++ b/apps/openmw/mwrender/creatureanimation.cpp
@@ -1,32 +1,144 @@
#include "creatureanimation.hpp"
+#include <OgreEntity.h>
+#include <OgreSkeletonInstance.h>
+#include <OgreBone.h>
+
#include "renderconst.hpp"
#include "../mwbase/world.hpp"
+#include "../mwworld/class.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 = ptr.getClass().getModel(ptr);
+ if(!model.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::Bipedal))
+ addAnimSource("meshes\\base_anim.nif");
+ addAnimSource(model);
+ }
+}
+
+
+CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr)
+ : Animation(ptr, ptr.getRefData().getBaseNode())
+ , mShowWeapons(false)
+ , mShowCarriedLeft(false)
+{
+ MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
+
+ std::string model = ptr.getClass().getModel(ptr);
+ if(!model.empty())
+ {
setObjectRoot(model, false);
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
- if((ref->mBase->mFlags&ESM::Creature::Biped))
+ if((ref->mBase->mFlags&ESM::Creature::Bipedal))
addAnimSource("meshes\\base_anim.nif");
addAnimSource(model);
+
+ mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
+
+ updateParts();
+ }
+}
+
+void CreatureWeaponAnimation::showWeapons(bool showWeapon)
+{
+ if (showWeapon != mShowWeapons)
+ {
+ mShowWeapons = showWeapon;
+ updateParts();
+ }
+}
+
+void CreatureWeaponAnimation::showCarriedLeft(bool show)
+{
+ if (show != mShowCarriedLeft)
+ {
+ mShowCarriedLeft = show;
+ updateParts();
+ }
+}
+
+void CreatureWeaponAnimation::updateParts()
+{
+ mWeapon.setNull();
+ mShield.setNull();
+
+ if (mShowWeapons)
+ updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
+ if (mShowCarriedLeft)
+ updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft);
+}
+
+void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slot)
+{
+ MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
+ MWWorld::ContainerStoreIterator it = inv.getSlot(slot);
+
+ if (it == inv.end())
+ {
+ scene.setNull();
+ return;
+ }
+ MWWorld::Ptr item = *it;
+
+ std::string bonename;
+ if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
+ bonename = "Weapon Bone";
+ else
+ bonename = "Shield Bone";
+
+ scene = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, item.getClass().getModel(item));
+ Ogre::Vector3 glowColor = getEnchantmentColor(item);
+
+ setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0,
+ !item.getClass().getEnchantment(item).empty(), &glowColor);
+
+ if(scene->mSkelBase)
+ {
+ Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton();
+ if(scene->mSkelBase->isParentTagPoint())
+ {
+ Ogre::Node *root = scene->mSkelBase->getParentNode();
+ if(skel->hasBone("BoneOffset"))
+ {
+ Ogre::Bone *offset = skel->getBone("BoneOffset");
+
+ root->translate(offset->getPosition());
+
+ // It appears that the BoneOffset rotation is completely bogus, at least for light models.
+ //root->rotate(offset->getOrientation());
+ root->pitch(Ogre::Degree(-90.0f));
+
+ root->scale(offset->getScale());
+ root->setInitialState();
+ }
+ }
+ updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
+ }
+
+ // TODO:
+ // 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(scene->mControllers.begin());
+ for(;ctrl != scene->mControllers.end();ctrl++)
+ {
+ if(ctrl->getSource().isNull())
+ ctrl->setSource(Ogre::SharedPtr<NullAnimationTime>(new NullAnimationTime()));
}
}
diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp
index 0c277d1985..37826673d8 100644
--- a/apps/openmw/mwrender/creatureanimation.hpp
+++ b/apps/openmw/mwrender/creatureanimation.hpp
@@ -1,7 +1,8 @@
-#ifndef _GAME_RENDER_CREATUREANIMATION_H
-#define _GAME_RENDER_CREATUREANIMATION_H
+#ifndef GAME_RENDER_CREATUREANIMATION_H
+#define GAME_RENDER_CREATUREANIMATION_H
#include "animation.hpp"
+#include "../mwworld/inventorystore.hpp"
namespace MWWorld
{
@@ -14,7 +15,32 @@ namespace MWRender
{
public:
CreatureAnimation(const MWWorld::Ptr& ptr);
- virtual ~CreatureAnimation();
+ virtual ~CreatureAnimation() {}
+ };
+
+ // For creatures with weapons and shields
+ // Animation is already virtual anyway, so might as well make a separate class.
+ // Most creatures don't need weapons/shields, so this will save some memory.
+ class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener
+ {
+ public:
+ CreatureWeaponAnimation(const MWWorld::Ptr& ptr);
+ virtual ~CreatureWeaponAnimation() {}
+
+ virtual void equipmentChanged() { updateParts(); }
+
+ virtual void showWeapons(bool showWeapon);
+ virtual void showCarriedLeft(bool show);
+
+ void updateParts();
+
+ void updatePart(NifOgre::ObjectScenePtr& scene, int slot);
+
+ private:
+ NifOgre::ObjectScenePtr mWeapon;
+ NifOgre::ObjectScenePtr mShield;
+ bool mShowWeapons;
+ bool mShowCarriedLeft;
};
}
diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp
index b318c2d569..2b61e109b6 100644
--- a/apps/openmw/mwrender/debugging.cpp
+++ b/apps/openmw/mwrender/debugging.cpp
@@ -185,14 +185,14 @@ bool Debugging::toggleRenderMode (int mode){
return false;
}
-void Debugging::cellAdded(MWWorld::Ptr::CellStore *store)
+void Debugging::cellAdded(MWWorld::CellStore *store)
{
mActiveCells.push_back(store);
if (mPathgridEnabled)
enableCellPathgrid(store);
}
-void Debugging::cellRemoved(MWWorld::Ptr::CellStore *store)
+void Debugging::cellRemoved(MWWorld::CellStore *store)
{
mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end());
if (mPathgridEnabled)
@@ -227,7 +227,7 @@ void Debugging::togglePathgrid()
}
}
-void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store)
+void Debugging::enableCellPathgrid(MWWorld::CellStore *store)
{
const ESM::Pathgrid *pathgrid =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*store->mCell);
@@ -254,7 +254,7 @@ void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store)
}
}
-void Debugging::disableCellPathgrid(MWWorld::Ptr::CellStore *store)
+void Debugging::disableCellPathgrid(MWWorld::CellStore *store)
{
if (store->mCell->isExterior())
{
diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp
index 4a574017ce..39be34cb02 100644
--- a/apps/openmw/mwrender/debugging.hpp
+++ b/apps/openmw/mwrender/debugging.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_MWSCENE_H
-#define _GAME_RENDER_MWSCENE_H
+#ifndef GAME_RENDER_MWSCENE_H
+#define GAME_RENDER_MWSCENE_H
#include <utility>
#include <openengine/ogre/renderer.hpp>
diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp
new file mode 100644
index 0000000000..7d41525b74
--- /dev/null
+++ b/apps/openmw/mwrender/effectmanager.cpp
@@ -0,0 +1,119 @@
+#include "effectmanager.hpp"
+
+#include <OgreSceneManager.h>
+#include <OgreParticleSystem.h>
+
+#include "animation.hpp"
+#include "renderconst.hpp"
+
+namespace MWRender
+{
+
+class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
+{
+private:
+ float mTime;
+public:
+ EffectAnimationTime() : mTime(0) { }
+ void addTime(float time) { mTime += time; }
+
+ virtual Ogre::Real getValue() const { return mTime; }
+ virtual void setValue(Ogre::Real value) {}
+};
+
+EffectManager::EffectManager(Ogre::SceneManager *sceneMgr)
+ : mSceneMgr(sceneMgr)
+{
+}
+
+void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition, float scale)
+{
+ Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition);
+ sceneNode->setScale(scale,scale,scale);
+
+ // fix texture extension to .dds
+ if (textureOverride.size() > 4)
+ {
+ textureOverride[textureOverride.size()-3] = 'd';
+ textureOverride[textureOverride.size()-2] = 'd';
+ textureOverride[textureOverride.size()-1] = 's';
+ }
+
+
+ NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model);
+
+ // TODO: turn off shadow casting
+ MWRender::Animation::setRenderProperties(scene, RV_Misc,
+ RQG_Main, RQG_Alpha, 0.f, false, NULL);
+
+ for(size_t i = 0;i < scene->mControllers.size();i++)
+ {
+ if(scene->mControllers[i].getSource().isNull())
+ scene->mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationTime> (new EffectAnimationTime()));
+ }
+
+ if (!textureOverride.empty())
+ {
+ for(size_t i = 0;i < scene->mParticles.size(); ++i)
+ {
+ Ogre::ParticleSystem* partSys = scene->mParticles[i];
+
+ Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(partSys);
+
+ for (int t=0; t<mat->getNumTechniques(); ++t)
+ {
+ Ogre::Technique* tech = mat->getTechnique(t);
+ for (int p=0; p<tech->getNumPasses(); ++p)
+ {
+ Ogre::Pass* pass = tech->getPass(p);
+ for (int tex=0; tex<pass->getNumTextureUnitStates(); ++tex)
+ {
+ Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex);
+ tus->setTextureName("textures\\" + textureOverride);
+ }
+ }
+ }
+ }
+ }
+
+ mEffects.push_back(std::make_pair(sceneNode, scene));
+}
+
+void EffectManager::update(float dt, Ogre::Camera* camera)
+{
+ for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
+ {
+ NifOgre::ObjectScenePtr objects = it->second;
+ for(size_t i = 0; i < objects->mControllers.size() ;i++)
+ {
+ EffectAnimationTime* value = dynamic_cast<EffectAnimationTime*>(objects->mControllers[i].getSource().get());
+ if (value)
+ value->addTime(dt);
+
+ objects->mControllers[i].update();
+ }
+ objects->rotateBillboardNodes(camera);
+
+ // Finished playing?
+ if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
+ {
+ Ogre::SceneNode* node = it->first;
+ it = mEffects.erase(it);
+ mSceneMgr->destroySceneNode(node);
+ continue;
+ }
+ ++it;
+ }
+}
+
+void EffectManager::clear()
+{
+ for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
+ {
+ Ogre::SceneNode* node = it->first;
+ it = mEffects.erase(it);
+ mSceneMgr->destroySceneNode(node);
+ }
+}
+
+}
diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp
new file mode 100644
index 0000000000..bc9e68d269
--- /dev/null
+++ b/apps/openmw/mwrender/effectmanager.hpp
@@ -0,0 +1,31 @@
+#ifndef OPENMW_MWRENDER_EFFECTMANAGER_H
+#define OPENMW_MWRENDER_EFFECTMANAGER_H
+
+#include <components/nifogre/ogrenifloader.hpp>
+
+namespace MWRender
+{
+ // Note: effects attached to another object should be managed by MWRender::Animation::addEffect.
+ // This class manages "free" effects, i.e. attached to a dedicated scene node in the world.
+ class EffectManager
+ {
+ public:
+ EffectManager(Ogre::SceneManager* sceneMgr);
+ ~EffectManager() { clear(); }
+
+ /// Add an effect. When it's finished playing, it will be removed automatically.
+ void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition, float scale);
+
+ void update(float dt, Ogre::Camera* camera);
+
+ /// Remove all effects
+ void clear();
+
+ private:
+ std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> > mEffects;
+ Ogre::SceneManager* mSceneMgr;
+ };
+
+}
+
+#endif
diff --git a/apps/openmw/mwrender/externalrendering.hpp b/apps/openmw/mwrender/externalrendering.hpp
deleted file mode 100644
index 33c9afd875..0000000000
--- a/apps/openmw/mwrender/externalrendering.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#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
index 120a83fae7..018dc082a3 100644
--- a/apps/openmw/mwrender/globalmap.cpp
+++ b/apps/openmw/mwrender/globalmap.cpp
@@ -12,6 +12,8 @@
#include <components/loadinglistener/loadinglistener.hpp>
+#include <components/esm/globalmap.hpp>
+
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -60,8 +62,6 @@ namespace MWRender
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)
{
@@ -170,21 +170,10 @@ namespace MWRender
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();
+ clear();
loadingListener->loadingOff();
}
@@ -227,9 +216,124 @@ namespace MWRender
if (!localMapTexture.isNull())
{
-
mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512),
Ogre::Image::Box(originX,originY,originX+24,originY+24));
}
}
+
+ void GlobalMap::clear()
+ {
+ std::vector<Ogre::uint32> buffer;
+ // initialize to (0,0,0,0)
+ buffer.resize(mWidth * mHeight, 0);
+
+ Ogre::PixelBox pb(mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, &buffer[0]);
+
+ mOverlayTexture->getBuffer()->blitFromMemory(pb);
+ }
+
+ void GlobalMap::write(ESM::ESMWriter &writer)
+ {
+ ESM::GlobalMap map;
+ map.mBounds.mMinX = mMinX;
+ map.mBounds.mMaxX = mMaxX;
+ map.mBounds.mMinY = mMinY;
+ map.mBounds.mMaxY = mMaxY;
+
+ Ogre::Image image;
+ mOverlayTexture->convertToImage(image);
+ Ogre::DataStreamPtr encoded = image.encode("png");
+ map.mImageData.resize(encoded->size());
+ encoded->read(&map.mImageData[0], encoded->size());
+
+ writer.startRecord(ESM::REC_GMAP);
+ map.save(writer);
+ writer.endRecord(ESM::REC_GMAP);
+ }
+
+ void GlobalMap::readRecord(ESM::ESMReader &reader, int32_t type, std::vector<std::pair<int, int> >& exploredCells)
+ {
+ if (type == ESM::REC_GMAP)
+ {
+ ESM::GlobalMap map;
+ map.load(reader);
+
+ const ESM::GlobalMap::Bounds& bounds = map.mBounds;
+
+ if (bounds.mMaxX-bounds.mMinX <= 0)
+ return;
+ if (bounds.mMaxY-bounds.mMinY <= 0)
+ return;
+
+ if (bounds.mMinX > bounds.mMaxX
+ || bounds.mMinY > bounds.mMaxY)
+ throw std::runtime_error("invalid map bounds");
+
+ Ogre::Image image;
+ Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size()));
+ image.load(stream, "png");
+
+ int xLength = (bounds.mMaxX-bounds.mMinX+1);
+ int yLength = (bounds.mMaxY-bounds.mMinY+1);
+
+ // Size of one cell in image space
+ int cellImageSizeSrc = image.getWidth() / xLength;
+ if (int(image.getHeight() / yLength) != cellImageSizeSrc)
+ throw std::runtime_error("cell size must be quadratic");
+
+ // Determine which cells were explored by reading the image data
+ for (int x=0; x < xLength; ++x)
+ {
+ for (int y=0; y < yLength; ++y)
+ {
+ unsigned int imageX = (x) * cellImageSizeSrc;
+ // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is
+ unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc;
+
+ assert(imageX < image.getWidth());
+ assert(imageY < image.getHeight());
+
+ if (image.getColourAt(imageX, imageY, 0).a > 0)
+ exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY));
+ }
+ }
+
+ // If cell bounds of the currently loaded content and the loaded savegame do not match,
+ // we need to resize source/dest boxes to accommodate
+ // This means nonexisting cells will be dropped silently
+ int cellImageSizeDst = 24;
+
+ // Completely off-screen? -> no need to blit anything
+ if (bounds.mMaxX < mMinX
+ || bounds.mMaxY < mMinY
+ || bounds.mMinX > mMaxX
+ || bounds.mMinY > mMaxY)
+ return;
+
+ int leftDiff = (mMinX - bounds.mMinX);
+ int topDiff = (bounds.mMaxY - mMaxY);
+ int rightDiff = (bounds.mMaxX - mMaxX);
+ int bottomDiff = (mMinY - bounds.mMinY);
+ Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc),
+ std::max(0, topDiff * cellImageSizeSrc),
+ std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc),
+ std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc));
+
+ Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst),
+ std::max(0, -topDiff * cellImageSizeDst),
+ std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst),
+ std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst));
+
+ // Looks like there is no interface for blitting from memory with src/dst boxes.
+ // So we create a temporary texture for blitting.
+ Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp",
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(),
+ image.getHeight(), 0, Ogre::PF_A8B8G8R8);
+ tex->loadImage(image);
+
+ mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox);
+
+ Ogre::TextureManager::getSingleton().remove("@temp");
+ }
+ }
}
diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp
index dd3787b62b..5fe878cd45 100644
--- a/apps/openmw/mwrender/globalmap.hpp
+++ b/apps/openmw/mwrender/globalmap.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_GLOBALMAP_H
-#define _GAME_RENDER_GLOBALMAP_H
+#ifndef GAME_RENDER_GLOBALMAP_H
+#define GAME_RENDER_GLOBALMAP_H
#include <string>
@@ -10,6 +10,12 @@ namespace Loading
class Listener;
}
+namespace ESM
+{
+ class ESMWriter;
+ class ESMReader;
+}
+
namespace MWRender
{
@@ -31,13 +37,18 @@ namespace MWRender
void exploreCell (int cellX, int cellY);
+ /// Clears the overlay
+ void clear();
+
+ void write (ESM::ESMWriter& writer);
+ void readRecord (ESM::ESMReader& reader, int32_t type, std::vector<std::pair<int, int> >& exploredCells);
+
private:
std::string mCacheDir;
std::vector< std::pair<int,int> > mExploredCells;
Ogre::TexturePtr mOverlayTexture;
- std::vector<Ogre::uchar> mExploredBuffer;
int mWidth;
int mHeight;
diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp
index 3ea3380e85..f147ae7b70 100644
--- a/apps/openmw/mwrender/localmap.cpp
+++ b/apps/openmw/mwrender/localmap.cpp
@@ -79,7 +79,7 @@ std::string LocalMap::coordStr(const int x, const int y)
return StringConverter::toString(x) + "_" + StringConverter::toString(y);
}
-void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
+void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
{
if (!mInterior)
{
@@ -108,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
}
}
-void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax)
+void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax)
{
mInterior = false;
@@ -125,7 +125,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax)
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
}
-void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,
+void LocalMap::requestMap(MWWorld::CellStore* cell,
AxisAlignedBox bounds)
{
// if we're in an empty cell, don't bother rendering anything
diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp
index 5384896407..638469d2d7 100644
--- a/apps/openmw/mwrender/localmap.hpp
+++ b/apps/openmw/mwrender/localmap.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_LOCALMAP_H
-#define _GAME_RENDER_LOCALMAP_H
+#ifndef GAME_RENDER_LOCALMAP_H
+#define GAME_RENDER_LOCALMAP_H
#include <openengine/ogre/renderer.hpp>
diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp
index b1455f0dc6..d07aad31d4 100644
--- a/apps/openmw/mwrender/npcanimation.cpp
+++ b/apps/openmw/mwrender/npcanimation.cpp
@@ -61,8 +61,9 @@ std::string getVampireHead(const std::string& race, bool female)
namespace MWRender
{
-float SayAnimationValue::getValue() const
+float HeadAnimationTime::getValue() const
{
+ // TODO: Handle eye blinking (time is in the text keys)
if (MWBase::Environment::get().getSoundManager()->sayDone(mReference))
return 0;
else
@@ -70,6 +71,27 @@ float SayAnimationValue::getValue() const
return 1;
}
+float WeaponAnimationTime::getValue() const
+{
+ if (mWeaponGroup.empty())
+ return 0;
+ float current = mAnimation->getCurrentTime(mWeaponGroup);
+ if (current == -1)
+ return 0;
+ return current - mStartTime;
+}
+
+void WeaponAnimationTime::setGroup(const std::string &group)
+{
+ mWeaponGroup = group;
+ mStartTime = mAnimation->getStartTime(mWeaponGroup);
+}
+
+void WeaponAnimationTime::updateStartTime()
+{
+ setGroup(mWeaponGroup);
+}
+
static NpcAnimation::PartBoneMap createPartListMap()
{
NpcAnimation::PartBoneMap result;
@@ -119,11 +141,14 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
mShowWeapons(false),
mShowCarriedLeft(true),
mFirstPersonOffset(0.f, 0.f, 0.f),
- mAlpha(1.f)
+ mAlpha(1.f),
+ mNpcType(Type_Normal),
+ mPitchFactor(0)
{
mNpc = mPtr.get<ESM::NPC>()->mBase;
- mSayAnimationValue = Ogre::SharedPtr<SayAnimationValue>(new SayAnimationValue(mPtr));
+ mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr));
+ mWeaponAnimationTime = Ogre::SharedPtr<WeaponAnimationTime>(new WeaponAnimationTime(this));
for(size_t i = 0;i < ESM::PRT_Count;i++)
{
@@ -140,6 +165,9 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
{
assert(viewMode != VM_HeadOnly);
+ if(mViewMode == viewMode)
+ return;
+
mViewMode = viewMode;
rebuild();
}
@@ -157,8 +185,8 @@ void NpcAnimation::updateNpcBase()
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
- bool isWerewolf = mPtr.getClass().getNpcStats(mPtr).isWerewolf();
- bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude;
+ bool isWerewolf = (mNpcType == Type_Werewolf);
+ bool isVampire = (mNpcType == Type_Vampire);
if (isWerewolf)
{
@@ -167,7 +195,7 @@ void NpcAnimation::updateNpcBase()
}
else
{
- if (vampire)
+ if (isVampire)
mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female);
else
mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find(mNpc->mHead)->mModel;
@@ -218,14 +246,29 @@ void NpcAnimation::updateNpcBase()
for(size_t i = 0;i < ESM::PRT_Count;i++)
removeIndividualPart((ESM::PartReferenceType)i);
updateParts();
+
+ mWeaponAnimationTime->updateStartTime();
}
void NpcAnimation::updateParts()
-{
+{
mAlpha = 1.f;
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
+ NpcType curType = Type_Normal;
+ if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude > 0)
+ curType = Type_Vampire;
+ if (cls.getNpcStats(mPtr).isWerewolf())
+ curType = Type_Werewolf;
+
+ if (curType != mNpcType)
+ {
+ mNpcType = curType;
+ rebuild();
+ return;
+ }
+
static const struct {
int mSlot;
int mBasePriority;
@@ -329,7 +372,7 @@ void NpcAnimation::updateParts()
static const int Flag_Female = 1<<0;
static const int Flag_FirstPerson = 1<<1;
- bool isWerewolf = cls.getNpcStats(mPtr).isWerewolf();
+ bool isWerewolf = (mNpcType == Type_Werewolf);
int flags = (isWerewolf ? -1 : 0);
if(!mNpc->isMale())
flags |= Flag_Female;
@@ -480,16 +523,25 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
Ogre::Vector3 ret = Animation::runAnimation(timepassed);
Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton();
- if(mViewMode == VM_FirstPerson && mCamera)
+ if(mViewMode == VM_FirstPerson)
{
- float pitch = mCamera->getPitch();
+ float pitch = mPtr.getRefData().getPosition().rot[0];
Ogre::Node *node = baseinst->getBone("Bip01 Neck");
- node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD);
+ node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD);
// This has to be done before this function ends;
// updateSkeletonInstance, below, touches the hands.
node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD);
}
+ else if (mPitchFactor > 0)
+ {
+ // In third person mode we may still need pitch for ranged weapon targeting
+ float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor;
+ Ogre::Node *node = baseinst->getBone("Bip01 Spine2");
+ node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
+ node = baseinst->getBone("Bip01 Spine1");
+ node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
+ }
mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame.
for(size_t i = 0;i < ESM::PRT_Count;i++)
@@ -570,18 +622,17 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
}
- // TODO:
- // 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);
+ ctrl->setSource(mNullAnimationTimePtr);
if (type == ESM::PRT_Head)
- ctrl->setSource(mSayAnimationValue);
+ ctrl->setSource(mHeadAnimationTime);
+ else if (type == ESM::PRT_Weapon)
+ ctrl->setSource(mWeaponAnimationTime);
}
}
@@ -645,6 +696,18 @@ void NpcAnimation::showWeapons(bool showWeapon)
std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon);
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
+
+ if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
+ weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
+ {
+ MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
+ if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
+ attachArrow();
+ else
+ mAmmunition.setNull();
+ }
+ else
+ mAmmunition.setNull();
}
}
else
@@ -675,6 +738,52 @@ void NpcAnimation::showCarriedLeft(bool show)
removeIndividualPart(ESM::PRT_Shield);
}
+void NpcAnimation::attachArrow()
+{
+ MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
+ MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ if (weaponSlot != inv.end() && weaponSlot->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
+ showWeapons(true);
+ else
+ {
+ NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon];
+
+ MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
+ if (ammo == inv.end())
+ return;
+ std::string model = ammo->getClass().getModel(*ammo);
+
+ mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model);
+ Ogre::Vector3 glowColor = getEnchantmentColor(*ammo);
+ setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0,
+ !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor);
+
+ std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
+ std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
+ }
+}
+
+void NpcAnimation::releaseArrow()
+{
+ // Thrown weapons get detached now
+ MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
+ MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
+ if (weapon != inv.end() && weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
+ {
+ showWeapons(false);
+ inv.remove(*weapon, 1, mPtr);
+ }
+ else
+ {
+ // With bows and crossbows only the used arrow/bolt gets detached
+ MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
+ if (ammo == inv.end())
+ return;
+ inv.remove(*ammo, 1, mPtr);
+ mAmmunition.setNull();
+ }
+}
+
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
{
// During first auto equip, we don't play any sounds.
@@ -736,6 +845,7 @@ void NpcAnimation::preRender(Ogre::Camera *camera)
void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene)
{
+ sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(0)->getMaterial()->getName(), "Default");
ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent()
? RQG_Alpha : RQG_Main);
diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp
index 28bb81dddf..725fde01d8 100644
--- a/apps/openmw/mwrender/npcanimation.hpp
+++ b/apps/openmw/mwrender/npcanimation.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_NPCANIMATION_H
-#define _GAME_RENDER_NPCANIMATION_H
+#ifndef GAME_RENDER_NPCANIMATION_H
+#define GAME_RENDER_NPCANIMATION_H
#include "animation.hpp"
@@ -13,18 +13,35 @@ namespace ESM
namespace MWRender
{
-class SayAnimationValue : public Ogre::ControllerValue<Ogre::Real>
+class HeadAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
MWWorld::Ptr mReference;
public:
- SayAnimationValue(MWWorld::Ptr reference) : mReference(reference) {}
+ HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference) {}
virtual Ogre::Real getValue() const;
virtual void setValue(Ogre::Real value)
{ }
};
+class WeaponAnimationTime : public Ogre::ControllerValue<Ogre::Real>
+{
+private:
+ Animation* mAnimation;
+ std::string mWeaponGroup;
+ float mStartTime;
+public:
+ WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {}
+ void setGroup(const std::string& group);
+ void updateStartTime();
+
+ virtual Ogre::Real getValue() const;
+ virtual void setValue(Ogre::Real value)
+ { }
+};
+
+
class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener
{
public:
@@ -55,6 +72,14 @@ private:
bool mShowWeapons;
bool mShowCarriedLeft;
+ enum NpcType
+ {
+ Type_Normal,
+ Type_Werewolf,
+ Type_Vampire
+ };
+ NpcType mNpcType;
+
int mVisibilityFlags;
int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty
@@ -62,9 +87,11 @@ private:
Ogre::Vector3 mFirstPersonOffset;
- Ogre::SharedPtr<SayAnimationValue> mSayAnimationValue;
+ Ogre::SharedPtr<HeadAnimationTime> mHeadAnimationTime;
+ Ogre::SharedPtr<WeaponAnimationTime> mWeaponAnimationTime;
float mAlpha;
+ float mPitchFactor;
void updateNpcBase();
@@ -97,11 +124,22 @@ public:
ViewMode viewMode=VM_Normal);
virtual ~NpcAnimation();
+ virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); }
+
virtual Ogre::Vector3 runAnimation(float timepassed);
+ /// A relative factor (0-1) that decides if and how much the skeleton should be pitched
+ /// to indicate the facing orientation of the character.
+ virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
+
virtual void showWeapons(bool showWeapon);
virtual void showCarriedLeft(bool showa);
+ virtual void attachArrow();
+ virtual void releaseArrow();
+
+ NifOgre::ObjectScenePtr mAmmunition;
+
void setViewMode(ViewMode viewMode);
void updateParts();
diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp
index 2673207137..e721477ee0 100644
--- a/apps/openmw/mwrender/objects.cpp
+++ b/apps/openmw/mwrender/objects.cpp
@@ -172,7 +172,7 @@ bool Objects::deleteObject (const MWWorld::Ptr& ptr)
}
-void Objects::removeCell(MWWorld::Ptr::CellStore* store)
+void Objects::removeCell(MWWorld::CellStore* store)
{
for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();)
{
@@ -212,7 +212,7 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store)
}
}
-void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell)
+void Objects::buildStaticGeometry(MWWorld::CellStore& cell)
{
if(mStaticGeometry.find(&cell) != mStaticGeometry.end())
{
@@ -226,7 +226,7 @@ void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell)
}
}
-Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell)
+Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell)
{
return mBounds[cell];
}
diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp
index 8a50745032..665a1e6570 100644
--- a/apps/openmw/mwrender/objects.hpp
+++ b/apps/openmw/mwrender/objects.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDER_OBJECTS_H
-#define _GAME_RENDER_OBJECTS_H
+#ifndef GAME_RENDER_OBJECTS_H
+#define GAME_RENDER_OBJECTS_H
#include <OgreColourValue.h>
#include <OgreAxisAlignedBox.h>
diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp
index a69511acd7..2461034717 100644
--- a/apps/openmw/mwrender/occlusionquery.cpp
+++ b/apps/openmw/mwrender/occlusionquery.cpp
@@ -8,6 +8,7 @@
#include <OgreSubEntity.h>
#include <OgreMeshManager.h>
#include <OgreMaterialManager.h>
+#include <OgreCamera.h>
#include "renderconst.hpp"
diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp
index 983361c187..6974f37b96 100644
--- a/apps/openmw/mwrender/occlusionquery.hpp
+++ b/apps/openmw/mwrender/occlusionquery.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_OCCLUSION_QUERY_H
-#define _GAME_OCCLUSION_QUERY_H
+#ifndef GAME_OCCLUSION_QUERY_H
+#define GAME_OCCLUSION_QUERY_H
#include <OgreRenderObjectListener.h>
#include <OgreRenderQueueListener.h>
diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp
index 8ae2c0f8f9..02f3c804a0 100644
--- a/apps/openmw/mwrender/renderinginterface.hpp
+++ b/apps/openmw/mwrender/renderinginterface.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDERING_INTERFACE_H
-#define _GAME_RENDERING_INTERFACE_H
+#ifndef GAME_RENDERING_INTERFACE_H
+#define GAME_RENDERING_INTERFACE_H
namespace MWRender
{
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
index f6e69b7260..a4ce92347a 100644
--- a/apps/openmw/mwrender/renderingmanager.cpp
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -19,7 +19,6 @@
#include <openengine/bullet/physic.hpp>
-#include <components/esm/loadstat.hpp>
#include <components/settings/settings.hpp>
#include <components/terrain/world.hpp>
@@ -30,20 +29,20 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp" // FIXME
#include "../mwbase/windowmanager.hpp" // FIXME
+#include "../mwbase/statemanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwworld/ptr.hpp"
-#include "../mwworld/player.hpp"
#include "shadows.hpp"
#include "localmap.hpp"
#include "water.hpp"
#include "npcanimation.hpp"
-#include "externalrendering.hpp"
#include "globalmap.hpp"
#include "videoplayer.hpp"
#include "terrainstorage.hpp"
+#include "effectmanager.hpp"
using namespace MWRender;
using namespace Ogre;
@@ -60,9 +59,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
, mSunEnabled(0)
, mPhysicsEngine(engine)
, mTerrain(NULL)
+ , mEffectManager(NULL)
{
mActors = new MWRender::Actors(mRendering, this);
mObjects = new MWRender::Objects(mRendering);
+ mEffectManager = new EffectManager(mRendering.getScene());
// 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);
@@ -196,6 +197,7 @@ RenderingManager::~RenderingManager ()
delete mVideoPlayer;
delete mActors;
delete mObjects;
+ delete mEffectManager;
delete mFactory;
}
@@ -216,7 +218,12 @@ OEngine::Render::Fader* RenderingManager::getFader()
return mRendering.getFader();
}
-void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store)
+ MWRender::Camera* RenderingManager::getCamera() const
+{
+ return mCamera;
+}
+
+void RenderingManager::removeCell (MWWorld::CellStore *store)
{
mObjects->removeCell(store);
mActors->removeCell(store);
@@ -233,7 +240,7 @@ void RenderingManager::toggleWater()
mWater->toggle();
}
-void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
+void RenderingManager::cellAdded (MWWorld::CellStore *store)
{
mObjects->buildStaticGeometry (*store);
sh::Factory::getInstance().unloadUnreferencedMaterials();
@@ -324,11 +331,17 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr)
void RenderingManager::update (float duration, bool paused)
{
+ mVideoPlayer->update ();
+
+ if (MWBase::Environment::get().getStateManager()->getState()==
+ MWBase::StateManager::State_NoGame)
+ return;
+
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
- int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude;
+ int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude;
mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f)));
setAmbientMode();
@@ -350,18 +363,18 @@ void RenderingManager::update (float duration, bool paused)
}
// Sink the camera while sneaking
- bool isSneaking = MWWorld::Class::get(player).getStance(player, MWWorld::Class::Sneak);
+ bool isSneaking = player.getClass().getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak);
bool isInAir = !world->isOnGround(player);
bool isSwimming = world->isSwimming(player);
+ static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
+ .find("i1stPersonSneakDelta")->getInt();
if(isSneaking && !(isSwimming || isInAir))
- mCamera->setSneakOffset();
+ mCamera->setSneakOffset(i1stPersonSneakDelta);
mOcclusionQuery->update(duration);
- mVideoPlayer->update ();
-
mRendering.update(duration);
Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f);
@@ -375,6 +388,8 @@ void RenderingManager::update (float duration, bool paused)
if(paused)
return;
+ mEffectManager->update(duration, mRendering.getCamera());
+
mActors->update (mRendering.getCamera());
mPlayerAnimation->preRender(mRendering.getCamera());
mObjects->update (duration, mRendering.getCamera());
@@ -405,14 +420,9 @@ void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt)
mOcclusionQuery->setActive(false);
}
-void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store)
+void RenderingManager::waterAdded (MWWorld::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.
+ if(store->mCell->mData.mFlags & ESM::Cell::HasWater)
{
mWater->changeCell(store->mCell);
mWater->setActive(true);
@@ -488,7 +498,7 @@ bool RenderingManager::toggleRenderMode(int mode)
}
}
-void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell)
+void RenderingManager::configureFog(MWWorld::CellStore &mCell)
{
Ogre::ColourValue color;
color.setAsABGR (mCell.mCell->mAmbi.mFog);
@@ -541,7 +551,7 @@ void RenderingManager::setAmbientMode()
}
}
-void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell)
+void RenderingManager::configureAmbient(MWWorld::CellStore &mCell)
{
if (mCell.mCell->mData.mFlags & ESM::Cell::Interior)
mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient);
@@ -592,8 +602,8 @@ 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;
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(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));
@@ -638,7 +648,7 @@ void RenderingManager::setGlare(bool glare)
mSkyManager->setGlare(glare);
}
-void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
+void RenderingManager::requestMap(MWWorld::CellStore* cell)
{
if (cell->mCell->isExterior())
{
@@ -657,7 +667,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
mLocalMap->requestMap(cell, mObjects->getDimensions(cell));
}
-void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell)
+void RenderingManager::preCellChange(MWWorld::CellStore* cell)
{
mLocalMap->saveFogOfWar(cell);
}
@@ -681,14 +691,14 @@ Shadows* RenderingManager::getShadows()
void RenderingManager::switchToInterior()
{
- // causes light flicker in opengl when moving..
- //mRendering.getScene()->setCameraRelativeRendering(false);
+ // TODO: also do this when switching worldspace
+ mEffectManager->clear();
}
void RenderingManager::switchToExterior()
{
- // causes light flicker in opengl when moving..
- //mRendering.getScene()->setCameraRelativeRendering(true);
+ // TODO: also do this when switching worldspace
+ mEffectManager->clear();
}
Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds)
@@ -744,7 +754,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec
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());
+ configureFog(*MWBase::Environment::get().getWorld()->getPlayerPtr().getCell());
}
else if (it->first == "Video" && (
it->second == "resolution x"
@@ -892,8 +902,6 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
mPlayerAnimation->~NpcAnimation();
new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
}
- // Ensure CustomData -> autoEquip -> animation update
- ptr.getClass().getInventoryStore(ptr);
mCamera->setAnimation(mPlayerAnimation);
mWater->removeEmitter(ptr);
@@ -938,11 +946,6 @@ bool RenderingManager::isPositionExplored (float nX, float nY, int x, int y, boo
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);
@@ -956,6 +959,36 @@ Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
return anim;
}
+void RenderingManager::screenshot(Image &image, int w, int h)
+{
+ // Create a temporary render target. We do not use the RenderWindow since we want a specific size.
+ // Also, the GUI should not be visible (and it is only rendered on the RenderWindow's primary viewport)
+ const std::string tempName = "@temp";
+ Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(tempName,
+ Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, w, h, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
+
+ float oldAspect = mRendering.getCamera()->getAspectRatio();
+
+ mRendering.getCamera()->setAspectRatio(w / static_cast<float>(h));
+
+ Ogre::RenderTarget* rt = texture->getBuffer()->getRenderTarget();
+ Ogre::Viewport* vp = rt->addViewport(mRendering.getCamera());
+ vp->setBackgroundColour(mRendering.getViewport()->getBackgroundColour());
+ vp->setOverlaysEnabled(false);
+ vp->setVisibilityMask(mRendering.getViewport()->getVisibilityMask());
+ rt->update();
+
+ Ogre::PixelFormat pf = rt->suggestPixelFormat();
+
+ image.loadDynamicImage(
+ OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL),
+ w, h, 1, pf, true // autoDelete=true, frees memory we allocate
+ );
+ rt->copyContentsToMemory(image.getPixelBox()); // getPixelBox returns a box sharing the same memory as the image
+
+ Ogre::TextureManager::getSingleton().remove(tempName);
+ mRendering.getCamera()->setAspectRatio(oldAspect);
+}
void RenderingManager::playVideo(const std::string& name, bool allowSkipping)
{
@@ -1031,4 +1064,9 @@ float RenderingManager::getCameraDistance() const
return mCamera->getCameraDistance();
}
+void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale)
+{
+ mEffectManager->addEffect(model, "", worldPosition, scale);
+}
+
} // namespace
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index b13e546e83..64ec029ce8 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -1,5 +1,5 @@
-#ifndef _GAME_RENDERING_MANAGER_H
-#define _GAME_RENDERING_MANAGER_H
+#ifndef GAME_RENDERING_MANAGER_H
+#define GAME_RENDERING_MANAGER_H
#include "sky.hpp"
#include "debugging.hpp"
@@ -21,10 +21,7 @@
namespace Ogre
{
- class SceneManager;
class SceneNode;
- class Quaternion;
- class Vector3;
}
namespace MWWorld
@@ -48,10 +45,10 @@ namespace MWRender
class Shadows;
class LocalMap;
class Water;
- class ExternalRendering;
class GlobalMap;
class VideoPlayer;
class Animation;
+ class EffectManager;
class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener
{
@@ -97,6 +94,8 @@ public:
SkyManager* getSkyManager();
+ MWRender::Camera* getCamera() const;
+
void toggleLight();
bool toggleRenderMode(int mode);
@@ -204,13 +203,14 @@ public:
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);
+ void screenshot(Ogre::Image& image, int w, int h);
+
+ void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f);
protected:
virtual void windowResized(int x, int y);
@@ -242,6 +242,8 @@ private:
MWRender::Objects* mObjects;
MWRender::Actors* mActors;
+ MWRender::EffectManager* mEffectManager;
+
MWRender::NpcAnimation *mPlayerAnimation;
// 0 normal, 1 more bright, 2 max
diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp
index 47fbc8ddf6..e5db8346f7 100644
--- a/apps/openmw/mwrender/ripplesimulation.cpp
+++ b/apps/openmw/mwrender/ripplesimulation.cpp
@@ -10,8 +10,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwworld/player.hpp"
-
namespace MWRender
{
@@ -154,11 +152,11 @@ void RippleSimulation::addImpulses()
/// \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 ())
+ if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr())
{
// 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 ();
+ it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
}
float* _currentPos = it->mPtr.getRefData().getPosition().pos;
Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]);
diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp
index 21bbe51b63..9ebb0ab087 100644
--- a/apps/openmw/mwrender/shadows.cpp
+++ b/apps/openmw/mwrender/shadows.cpp
@@ -8,6 +8,7 @@
#include <OgreShadowCameraSetupLiSPSM.h>
#include <OgreShadowCameraSetupPSSM.h>
#include <OgreHardwarePixelBuffer.h>
+#include <OgreCamera.h>
#include <extern/shiny/Main/Factory.hpp>
diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp
index 318627fc70..750441f6a7 100644
--- a/apps/openmw/mwrender/terrainstorage.cpp
+++ b/apps/openmw/mwrender/terrainstorage.cpp
@@ -1,5 +1,14 @@
#include "terrainstorage.hpp"
+#include <OgreVector2.h>
+#include <OgreTextureManager.h>
+#include <OgreStringConverter.h>
+#include <OgreRenderSystem.h>
+#include <OgreResourceGroupManager.h>
+#include <OgreRoot.h>
+
+#include <boost/algorithm/string.hpp>
+
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
@@ -7,9 +16,9 @@
namespace MWRender
{
- Ogre::AxisAlignedBox TerrainStorage::getBounds()
+ void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
{
- int minX = 0, minY = 0, maxX = 0, maxY = 0;
+ minX = 0, minY = 0, maxX = 0, maxY = 0;
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
@@ -30,8 +39,6 @@ namespace MWRender
// 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)
@@ -53,4 +60,509 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().find(index, plugin);
}
+ bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
+ {
+ assert (size <= 1 && "TerrainStorage::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 TerrainStorage::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 TerrainStorage::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 TerrainStorage::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 TerrainStorage::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);
+ }
+
+ TerrainStorage::UniqueTextureId TerrainStorage::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 TerrainStorage::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 TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
+ bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
+ {
+ // TODO - blending isn't completely right yet; the blending radius appears to be
+ // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
+ // 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(getLayerInfo(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 TerrainStorage::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 TerrainStorage::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];
+ }
+
+ Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture)
+ {
+ // Already have this cached?
+ if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
+ return mLayerInfoMap[texture];
+
+ Terrain::LayerInfo info;
+ info.mParallax = false;
+ info.mSpecular = false;
+ info.mDiffuseMap = "textures\\" + texture;
+ std::string texture_ = texture;
+ boost::replace_last(texture_, ".", "_nh.");
+ if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
+ {
+ info.mNormalMap = "textures\\" + texture_;
+ info.mParallax = true;
+ }
+ else
+ {
+ texture_ = texture;
+ boost::replace_last(texture_, ".", "_n.");
+ if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
+ info.mNormalMap = "textures\\" + texture_;
+ }
+
+ texture_ = texture;
+ boost::replace_last(texture_, ".", "_diffusespec.");
+ if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
+ {
+ info.mDiffuseMap = "textures\\" + texture_;
+ info.mSpecular = true;
+ }
+
+ mLayerInfoMap[texture] = info;
+
+ return info;
+ }
+
+ Terrain::LayerInfo TerrainStorage::getDefaultLayer()
+ {
+ Terrain::LayerInfo info;
+ info.mDiffuseMap = "textures\\_land_default.dds";
+ info.mParallax = false;
+ info.mSpecular = false;
+ return info;
+ }
+
+ float TerrainStorage::getCellWorldSize()
+ {
+ return ESM::Land::REAL_SIZE;
+ }
+
+ int TerrainStorage::getCellVertices()
+ {
+ return ESM::Land::LAND_SIZE;
+ }
+
}
diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp
index ebf5e26ab7..2ef014aaf1 100644
--- a/apps/openmw/mwrender/terrainstorage.hpp
+++ b/apps/openmw/mwrender/terrainstorage.hpp
@@ -1,6 +1,9 @@
#ifndef MWRENDER_TERRAINSTORAGE_H
#define MWRENDER_TERRAINSTORAGE_H
+#include <components/esm/loadland.hpp>
+#include <components/esm/loadltex.hpp>
+
#include <components/terrain/storage.hpp>
namespace MWRender
@@ -12,8 +15,75 @@ namespace MWRender
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
+
+ /// Get bounds of the whole terrain in cell units
+ virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
+
+ /// 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
+ virtual 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
+ virtual 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
+ virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
+ std::vector<Ogre::TexturePtr>& blendmaps,
+ std::vector<Terrain::LayerInfo>& layerList);
+
+ virtual float getHeightAt (const Ogre::Vector3& worldPos);
+
+ virtual Terrain::LayerInfo getDefaultLayer();
+
+ /// Get the transformation factor for mapping cell units to world units.
+ virtual float getCellWorldSize();
+
+ /// Get the number of vertices on one side for each cell. Should be (power of two)+1
+ virtual int getCellVertices();
+
+ 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);
+
+ std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
+
+ Terrain::LayerInfo getLayerInfo(const std::string& texture);
};
}
diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp
index 0a4db30e9c..9e3105168b 100644
--- a/apps/openmw/mwrender/water.cpp
+++ b/apps/openmw/mwrender/water.cpp
@@ -5,6 +5,7 @@
#include <OgreMeshManager.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h>
+#include <OgreCamera.h>
#include "sky.hpp"
#include "renderingmanager.hpp"
diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp
index 1cdbaa008d..8314d011a9 100644
--- a/apps/openmw/mwscript/aiextensions.cpp
+++ b/apps/openmw/mwscript/aiextensions.cpp
@@ -16,6 +16,7 @@
#include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/aitravel.hpp"
#include "../mwmechanics/aiwander.hpp"
+#include "../mwmechanics/aicombat.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -225,7 +226,8 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex));
+ runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (
+ (MWMechanics::CreatureStats::AiSetting)mIndex).getModified());
}
};
template<class R>
@@ -241,8 +243,11 @@ namespace MWScript
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);
+ MWMechanics::CreatureStats::AiSetting setting
+ = MWMechanics::CreatureStats::AiSetting(mIndex);
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (setting,
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (setting).getBase() + value);
}
};
template<class R>
@@ -258,8 +263,11 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
- MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex,
- value);
+ MWMechanics::CreatureStats::AiSetting setting = (MWMechanics::CreatureStats::AiSetting)mIndex;
+
+ MWMechanics::Stat<int> stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(setting);
+ stat.setModified(value, 0);
+ ptr.getClass().getCreatureStats(ptr).setAiSetting(setting, stat);
}
};
@@ -344,26 +352,28 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getTypeId ();
+ Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getLastRunTypeId();
runtime.push (value);
}
};
template<class R>
- class OpGetDetected : public Interpreter::Opcode1
+ class OpGetDetected : public Interpreter::Opcode0
{
public:
- virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
+ virtual void execute (Interpreter::Runtime& runtime)
{
-
+ MWWorld::Ptr observer = R()(runtime);
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
- Interpreter::Type_Integer value = false; // TODO replace with implementation
+ MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true);
- std::cout << "AiGetDetected: " << actorID << ", " << value << std::endl;
+ Interpreter::Type_Integer value =
+ MWBase::Environment::get().getWorld()->getLOS(observer, actor) &&
+ MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer);
runtime.push (value);
}
@@ -393,6 +403,61 @@ namespace MWScript
};
template<class R>
+ class OpGetTarget : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr actor = R()(runtime);
+ std::string testedTargetId = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
+ std::string currentTargetId;
+
+ bool targetsAreEqual = false;
+ if (creatureStats.getAiSequence().getCombatTarget (currentTargetId))
+ {
+ if (currentTargetId == testedTargetId)
+ targetsAreEqual = true;
+ }
+ runtime.push(int(targetsAreEqual));
+ }
+ };
+
+ template<class R>
+ class OpStartCombat : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr actor = R()(runtime);
+ std::string targetID = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
+
+
+ creatureStats.setHostile(true);
+ creatureStats.getAiSequence().stack(
+ MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));
+ }
+ };
+
+ template<class R>
+ class OpStopCombat : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr actor = R()(runtime);
+ MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
+ creatureStats.getAiSequence().stopCombat();
+ creatureStats.setHostile(false);
+ }
+ };
+
+ template<class R>
class OpToggleAI : public Interpreter::Opcode0
{
public:
@@ -425,10 +490,16 @@ namespace MWScript
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::opcodeGetDetected, new OpGetDetected<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected<ExplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight<ImplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetTarget, new OpGetTarget<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeGetTargetExplicit, new OpGetTarget<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeStartCombat, new OpStartCombat<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat<ExplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI<ImplicitRef>);
interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI<ExplicitRef>);
diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp
index f26602f7a7..a0acfa4daa 100644
--- a/apps/openmw/mwscript/cellextensions.cpp
+++ b/apps/openmw/mwscript/cellextensions.cpp
@@ -12,7 +12,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-
#include "../mwworld/player.hpp"
#include "interpretercontext.hpp"
@@ -89,7 +88,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
bool interior =
- !MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->isExterior();
+ !MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell->isExterior();
runtime.push (interior ? 1 : 0);
}
@@ -104,17 +103,19 @@ namespace MWScript
std::string name = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
- const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell;
+ const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell;
std::string current = cell->mName;
- if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty())
+ if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty()
+ && !cell->mRegion.empty())
{
const ESM::Region *region =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().find (cell->mRegion);
current = region->mName;
}
+ Misc::StringUtils::toLower(current);
bool match = current.length()>=name.length() &&
current.substr (0, name.length())==name;
@@ -129,8 +130,11 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
- MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
- runtime.push (cell->mWaterLevel);
+ MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell();
+ if (cell->mCell->hasWater())
+ runtime.push (cell->mWaterLevel);
+ else
+ runtime.push (-std::numeric_limits<float>().max());
}
};
@@ -142,7 +146,7 @@ namespace MWScript
{
Interpreter::Type_Float level = runtime[0].mFloat;
- MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+ MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell();
if (cell->mCell->isExterior())
throw std::runtime_error("Can't set water level in exterior cell");
@@ -160,7 +164,7 @@ namespace MWScript
{
Interpreter::Type_Float level = runtime[0].mFloat;
- MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell();
+ MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell();
if (cell->mCell->isExterior())
throw std::runtime_error("Can't set water level in exterior cell");
diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp
index 7e63a33b25..1b3e769bf1 100644
--- a/apps/openmw/mwscript/compilercontext.cpp
+++ b/apps/openmw/mwscript/compilercontext.cpp
@@ -3,6 +3,8 @@
#include "../mwworld/esmstore.hpp"
+#include <components/esm/loaddial.hpp>
+
#include <components/compiler/locals.hpp>
#include "../mwbase/environment.hpp"
@@ -28,16 +30,32 @@ namespace MWScript
return MWBase::Environment::get().getWorld()->getGlobalVariableType (name);
}
- char CompilerContext::getMemberType (const std::string& name, const std::string& id) const
+ std::pair<char, bool> CompilerContext::getMemberType (const std::string& name,
+ const std::string& id) const
{
- MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false);
+ std::string script;
+ bool reference = false;
+
+ if (const ESM::Script *scriptRecord =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().search (id))
+ {
+ script = scriptRecord->mId;
+ }
+ else
+ {
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false);
+
+ script = MWWorld::Class::get (ptr).getScript (ptr);
+ reference = true;
+ }
- std::string script = MWWorld::Class::get (ptr).getScript (ptr);
+ char type = ' ';
- if (script.empty())
- return ' ';
+ if (!script.empty())
+ type = MWBase::Environment::get().getScriptManager()->getLocals (script).getType (
+ Misc::StringUtils::lowerCase (name));
- return MWBase::Environment::get().getScriptManager()->getLocals (script).getType (name);
+ return std::make_pair (type, reference);
}
bool CompilerContext::isId (const std::string& name) const
@@ -67,4 +85,14 @@ namespace MWScript
store.get<ESM::Static>().search (name) ||
store.get<ESM::Weapon>().search (name);
}
+
+ bool CompilerContext::isJournalId (const std::string& name) const
+ {
+ const MWWorld::ESMStore &store =
+ MWBase::Environment::get().getWorld()->getStore();
+
+ const ESM::Dialogue *topic = store.get<ESM::Dialogue>().search (name);
+
+ return topic && topic->mType==ESM::Dialogue::Journal;
+ }
}
diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp
index 5ec98e09a7..95719ab692 100644
--- a/apps/openmw/mwscript/compilercontext.hpp
+++ b/apps/openmw/mwscript/compilercontext.hpp
@@ -30,11 +30,18 @@ namespace MWScript
/// '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 std::pair<char, bool> getMemberType (const std::string& name,
+ const std::string& id) const;
+ ///< Return type of member variable \a name in script \a id or in script of reference of
+ /// \a id
+ /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist.
+ /// second: true: script of reference
virtual bool isId (const std::string& name) const;
///< Does \a name match an ID, that can be referenced?
+
+ virtual bool isJournalId (const std::string& name) const;
+ ///< Does \a name match a journal ID?
};
}
diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp
index 53f4c23c97..fc21c57122 100644
--- a/apps/openmw/mwscript/containerextensions.cpp
+++ b/apps/openmw/mwscript/containerextensions.cpp
@@ -21,7 +21,6 @@
#include "../mwworld/containerstore.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/inventorystore.hpp"
-#include "../mwworld/player.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp"
@@ -55,7 +54,7 @@ namespace MWScript
MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr);
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
- if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() )
+ if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() )
{
// 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;
@@ -71,7 +70,7 @@ namespace MWScript
msgBox = boost::str(boost::format(msgBox) % count % itemName);
}
std::vector <std::string> noButtons;
- MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
+ MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
}
}
};
@@ -88,15 +87,9 @@ namespace MWScript
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();
+ MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
- runtime.push (sum);
+ runtime.push (store.count(item));
}
};
@@ -133,7 +126,7 @@ namespace MWScript
// Spawn a messagebox (only for items removed from player's inventory)
if ((numRemoved > 0)
- && (ptr == MWBase::Environment::get().getWorld()->getPlayer().getPlayer()))
+ && (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()))
{
// 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;
@@ -149,7 +142,7 @@ namespace MWScript
msgBox = boost::str (boost::format(msgBox) % itemName);
}
std::vector <std::string> noButtons;
- MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
+ MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
}
}
};
@@ -166,7 +159,7 @@ namespace MWScript
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
+ MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
MWWorld::ContainerStoreIterator it = invStore.begin();
for (; it != invStore.end(); ++it)
{
@@ -178,6 +171,9 @@ namespace MWScript
MWWorld::ActionEquip action (*it);
action.execute(ptr);
+
+ if (ptr.getRefData().getHandle() == "player" && !ptr.getClass().getScript(ptr).empty())
+ ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1);
}
};
@@ -289,7 +285,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- const std::string &name = runtime.getStringLiteral (runtime[0].mInteger);
+ const std::string &name = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp
index e46302b183..d2e7748598 100644
--- a/apps/openmw/mwscript/controlextensions.cpp
+++ b/apps/openmw/mwscript/controlextensions.cpp
@@ -11,7 +11,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
@@ -77,34 +76,34 @@ namespace MWScript
template<class R>
class OpClearMovementFlag : public Interpreter::Opcode0
{
- MWMechanics::NpcStats::Flag mFlag;
+ MWMechanics::CreatureStats::Flag mFlag;
public:
- OpClearMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {}
+ OpClearMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr ptr = R()(runtime);
- MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, false);
+ ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, false);
}
};
template<class R>
class OpSetMovementFlag : public Interpreter::Opcode0
{
- MWMechanics::NpcStats::Flag mFlag;
+ MWMechanics::CreatureStats::Flag mFlag;
public:
- OpSetMovementFlag (MWMechanics::NpcStats::Flag flag) : mFlag (flag) {}
+ OpSetMovementFlag (MWMechanics::CreatureStats::Flag flag) : mFlag (flag) {}
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr ptr = R()(runtime);
- MWWorld::Class::get (ptr).getNpcStats (ptr).setMovementFlag (mFlag, true);
+ ptr.getClass().getCreatureStats(ptr).setMovementFlag (mFlag, true);
}
};
@@ -117,9 +116,8 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr);
-
- runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun));
+ MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
+ runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun));
}
};
@@ -132,9 +130,8 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- MWMechanics::NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats (ptr);
-
- runtime.push (npcStats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak));
+ MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
+ runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak));
}
};
@@ -144,8 +141,8 @@ namespace MWScript
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));
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
+ runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run));
}
};
@@ -155,8 +152,8 @@ namespace MWScript
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));
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
+ runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak));
}
};
@@ -173,22 +170,22 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision);
interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun,
- new OpClearMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ new OpClearMovementFlag<ImplicitRef> (MWMechanics::CreatureStats::Flag_ForceRun));
interpreter.installSegment5 (Compiler::Control::opcodeForceRun,
- new OpSetMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ new OpSetMovementFlag<ImplicitRef> (MWMechanics::CreatureStats::Flag_ForceRun));
interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak,
- new OpClearMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ new OpClearMovementFlag<ImplicitRef> (MWMechanics::CreatureStats::Flag_ForceSneak));
interpreter.installSegment5 (Compiler::Control::opcodeForceSneak,
- new OpSetMovementFlag<ImplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ new OpSetMovementFlag<ImplicitRef> (MWMechanics::CreatureStats::Flag_ForceSneak));
interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit,
- new OpClearMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ new OpClearMovementFlag<ExplicitRef> (MWMechanics::CreatureStats::Flag_ForceRun));
interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit,
- new OpSetMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceRun));
+ new OpSetMovementFlag<ExplicitRef> (MWMechanics::CreatureStats::Flag_ForceRun));
interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit,
- new OpClearMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ new OpClearMovementFlag<ExplicitRef> (MWMechanics::CreatureStats::Flag_ForceSneak));
interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit,
- new OpSetMovementFlag<ExplicitRef> (MWMechanics::NpcStats::Flag_ForceSneak));
+ new OpSetMovementFlag<ExplicitRef> (MWMechanics::CreatureStats::Flag_ForceSneak));
interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning);
interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking);
interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun<ImplicitRef>);
diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp
index 5e797ee589..a882ae05e7 100644
--- a/apps/openmw/mwscript/dialogueextensions.cpp
+++ b/apps/openmw/mwscript/dialogueextensions.cpp
@@ -13,7 +13,6 @@
#include "../mwbase/journal.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "interpretercontext.hpp"
@@ -183,7 +182,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).isSameFaction (MWWorld::Class::get(player).getNpcStats (player)));
}
diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt
index cf533451c6..70186a089b 100644
--- a/apps/openmw/mwscript/docs/vmformat.txt
+++ b/apps/openmw/mwscript/docs/vmformat.txt
@@ -52,7 +52,9 @@ op 0x20023: AiFollow, explicit reference
op 0x20024: AiFollowCell
op 0x20025: AiFollowCell, explicit reference
op 0x20026: ModRegion
-opcodes 0x20027-0x3ffff unused
+op 0x20027: RemoveSoulGem
+op 0x20028: RemoveSoulGem, explicit reference
+opcodes 0x20029-0x3ffff unused
Segment 4:
(not implemented yet)
@@ -308,8 +310,8 @@ 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 0x20001f5: unused
+op 0x20001f6: unused
op 0x20001f7: PlayBink
op 0x20001f8: Drop
op 0x20001f9: Drop, explicit reference
@@ -358,5 +360,30 @@ op 0x2000223: GetLineOfSightExplicit
op 0x2000224: ToggleAI
op 0x2000225: ToggleAIExplicit
op 0x2000226: COE
+op 0x2000227: Cast
+op 0x2000228: Cast, explicit
+op 0x2000229: ExplodeSpell
+op 0x200022a: ExplodeSpell, explicit
+op 0x200022b: RemoveSpellEffects
+op 0x200022c: RemoveSpellEffects, explicit
+op 0x200022d: RemoveEffects
+op 0x200022e: RemoveEffects, explicit
+op 0x200022f: Resurrect
+op 0x2000230: Resurrect, explicit
+op 0x2000231: GetSpellReadied
+op 0x2000232: GetSpellReadied, explicit
+op 0x2000233: GetPcJumping
+op 0x2000234: ShowRestMenu, explicit
+op 0x2000235: GoToJail
+op 0x2000236: PayFine
+op 0x2000237: PayFineThief
+op 0x2000238: GetTarget
+op 0x2000239: GetTargetExplicit
+op 0x200023a: StartCombat
+op 0x200023b: StartCombatExplicit
+op 0x200023c: StopCombat
+op 0x200023d: StopCombatExplicit
+op 0x200023e: GetPcInJail
+op 0x200023f: GetPcTraveling
-opcodes 0x2000227-0x3ffffff unused
+opcodes 0x2000240-0x3ffffff unused
diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp
index 608725ae6f..179e2bb0bb 100644
--- a/apps/openmw/mwscript/globalscripts.cpp
+++ b/apps/openmw/mwscript/globalscripts.cpp
@@ -3,6 +3,9 @@
#include <cassert>
+#include <components/misc/stringops.hpp>
+#include <components/esm/globalscript.hpp>
+
#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
@@ -15,25 +18,16 @@ 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);
- }
+ addStartup();
}
void GlobalScripts::addScript (const std::string& name)
{
- if (mScripts.find (name)==mScripts.end())
+ std::map<std::string, std::pair<bool, Locals> >::iterator iter =
+ mScripts.find (Misc::StringUtils::lowerCase (name));
+
+ if (iter==mScripts.end())
+ {
if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
{
Locals locals;
@@ -42,11 +36,15 @@ namespace MWScript
mScripts.insert (std::make_pair (name, std::make_pair (true, locals)));
}
+ }
+ else
+ iter->second.first = true;
}
void GlobalScripts::removeScript (const std::string& name)
{
- std::map<std::string, std::pair<bool, Locals> >::iterator iter = mScripts.find (name);
+ std::map<std::string, std::pair<bool, Locals> >::iterator iter =
+ mScripts.find (Misc::StringUtils::lowerCase (name));
if (iter!=mScripts.end())
iter->second.first = false;
@@ -55,7 +53,7 @@ namespace MWScript
bool GlobalScripts::isRunning (const std::string& name) const
{
std::map<std::string, std::pair<bool, Locals> >::const_iterator iter =
- mScripts.find (name);
+ mScripts.find (Misc::StringUtils::lowerCase (name));
if (iter==mScripts.end())
return false;
@@ -76,4 +74,99 @@ namespace MWScript
}
}
}
+
+ void GlobalScripts::clear()
+ {
+ mScripts.clear();
+ }
+
+ void GlobalScripts::addStartup()
+ {
+ addScript ("main");
+
+ for (MWWorld::Store<ESM::StartScript>::iterator iter =
+ mStore.get<ESM::StartScript>().begin();
+ iter != mStore.get<ESM::StartScript>().end(); ++iter)
+ {
+ addScript (iter->mScript);
+ }
+ }
+
+ int GlobalScripts::countSavedGameRecords() const
+ {
+ return mScripts.size();
+ }
+
+ void GlobalScripts::write (ESM::ESMWriter& writer) const
+ {
+ for (std::map<std::string, std::pair<bool, Locals> >::const_iterator iter (mScripts.begin());
+ iter!=mScripts.end(); ++iter)
+ {
+ ESM::GlobalScript script;
+
+ script.mId = iter->first;
+
+ iter->second.second.write (script.mLocals, iter->first);
+
+ script.mRunning = iter->second.first ? 1 : 0;
+
+ writer.startRecord (ESM::REC_GSCR);
+ script.save (writer);
+ writer.endRecord (ESM::REC_GSCR);
+ }
+ }
+
+ bool GlobalScripts::readRecord (ESM::ESMReader& reader, int32_t type)
+ {
+ if (type==ESM::REC_GSCR)
+ {
+ ESM::GlobalScript script;
+ script.load (reader);
+
+ std::map<std::string, std::pair<bool, Locals> >::iterator iter =
+ mScripts.find (script.mId);
+
+ if (iter==mScripts.end())
+ {
+ if (const ESM::Script *scriptRecord = mStore.get<ESM::Script>().search (script.mId))
+ {
+ std::pair<bool, Locals> data (false, Locals());
+
+ data.second.configure (*scriptRecord);
+
+ iter = mScripts.insert (std::make_pair (script.mId, data)).first;
+ }
+ else // script does not exist anymore
+ return true;
+ }
+
+ iter->second.first = script.mRunning!=0;
+ iter->second.second.read (script.mLocals, script.mId);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ Locals& GlobalScripts::getLocals (const std::string& name)
+ {
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+ std::map<std::string, std::pair<bool, Locals> >::iterator iter =
+ mScripts.find (name2);
+
+ if (iter==mScripts.end())
+ {
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
+ {
+ Locals locals;
+
+ locals.configure (*script);
+
+ iter = mScripts.insert (std::make_pair (name, std::make_pair (false, locals))).first;
+ }
+ }
+
+ return iter->second.second;
+ }
}
diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp
index 628919d1d2..a4a7662263 100644
--- a/apps/openmw/mwscript/globalscripts.hpp
+++ b/apps/openmw/mwscript/globalscripts.hpp
@@ -4,9 +4,17 @@
#include <string>
#include <map>
+#include <libs/platform/stdint.h>
+
#include "locals.hpp"
-namespace MWWorld
+namespace ESM
+{
+ class ESMWriter;
+ class ESMReader;
+}
+
+namespace MWWorld
{
struct ESMStore;
}
@@ -22,8 +30,6 @@ namespace MWScript
GlobalScripts (const MWWorld::ESMStore& store);
- void reset();
-
void addScript (const std::string& name);
void removeScript (const std::string& name);
@@ -32,6 +38,24 @@ namespace MWScript
void run();
///< run all active global scripts
+
+ void clear();
+
+ void addStartup();
+ ///< Add startup script
+
+ int countSavedGameRecords() const;
+
+ void write (ESM::ESMWriter& writer) const;
+
+ bool readRecord (ESM::ESMReader& reader, int32_t type);
+ ///< Records for variables that do not exist are dropped silently.
+ ///
+ /// \return Known type?
+
+ Locals& getLocals (const std::string& name);
+ ///< If the script \a name has not been added as a global script yet, it is added
+ /// automatically, but is not set to running state.
};
}
diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp
index 6c89a0d1ce..ab8901881b 100644
--- a/apps/openmw/mwscript/guiextensions.cpp
+++ b/apps/openmw/mwscript/guiextensions.cpp
@@ -1,8 +1,6 @@
#include "guiextensions.hpp"
-#include <boost/algorithm/string.hpp>
-
#include <components/compiler/extensions.hpp>
#include <components/compiler/opcodes.hpp>
@@ -15,7 +13,10 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+
#include "interpretercontext.hpp"
+#include "ref.hpp"
namespace MWScript
{
@@ -45,6 +46,20 @@ namespace MWScript
}
};
+ template <class R>
+ class OpShowRestMenu : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr bed = R()(runtime, false);
+
+ if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWBase::Environment::get().getWorld()->getPlayerPtr(),
+ bed))
+ MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_RestBed);
+ }
+ };
+
class OpShowDialogue : public Interpreter::Opcode0
{
MWGui::GuiMode mDialogue;
@@ -172,7 +187,8 @@ namespace MWScript
new OpEnableRest ());
interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu,
- new OpShowDialogue (MWGui::GM_RestBed));
+ new OpShowRestMenu<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenuExplicit, new OpShowRestMenu<ExplicitRef>);
interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed);
diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp
index b8fc9ed477..b79808d08b 100644
--- a/apps/openmw/mwscript/interpretercontext.cpp
+++ b/apps/openmw/mwscript/interpretercontext.cpp
@@ -14,7 +14,6 @@
#include "../mwbase/inputmanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwmechanics/npcstats.hpp"
@@ -24,7 +23,7 @@
namespace MWScript
{
MWWorld::Ptr InterpreterContext::getReference (
- const std::string& id, bool activeOnly)
+ const std::string& id, bool activeOnly, bool doThrow)
{
if (!id.empty())
{
@@ -32,7 +31,7 @@ namespace MWScript
}
else
{
- if (mReference.isEmpty())
+ if (mReference.isEmpty() && doThrow)
throw std::runtime_error ("no implicit reference");
return mReference;
@@ -40,7 +39,7 @@ namespace MWScript
}
const MWWorld::Ptr InterpreterContext::getReference (
- const std::string& id, bool activeOnly) const
+ const std::string& id, bool activeOnly, bool doThrow) const
{
if (!id.empty())
{
@@ -48,13 +47,54 @@ namespace MWScript
}
else
{
- if (mReference.isEmpty())
+ if (mReference.isEmpty() && doThrow)
throw std::runtime_error ("no implicit reference");
return mReference;
}
}
+ const Locals& InterpreterContext::getMemberLocals (std::string& id, bool global)
+ const
+ {
+ if (global)
+ {
+ return MWBase::Environment::get().getScriptManager()->getGlobalScripts().
+ getLocals (id);
+ }
+ else
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ id = MWWorld::Class::get (ptr).getScript (ptr);
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (id));
+
+ return ptr.getRefData().getLocals();
+ }
+ }
+
+ Locals& InterpreterContext::getMemberLocals (std::string& id, bool global)
+ {
+ if (global)
+ {
+ return MWBase::Environment::get().getScriptManager()->getGlobalScripts().
+ getLocals (id);
+ }
+ else
+ {
+ const MWWorld::Ptr ptr = getReference (id, false);
+
+ id = MWWorld::Class::get (ptr).getScript (ptr);
+
+ ptr.getRefData().setLocals (
+ *MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (id));
+
+ return ptr.getRefData().getLocals();
+ }
+ }
+
InterpreterContext::InterpreterContext (
MWScript::Locals *locals, MWWorld::Ptr reference)
: mLocals (locals), mReference (reference),
@@ -127,61 +167,49 @@ namespace MWScript
int InterpreterContext::getGlobalShort (const std::string& name) const
{
- return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort;
+ return MWBase::Environment::get().getWorld()->getGlobalInt (name);
}
int InterpreterContext::getGlobalLong (const std::string& name) const
{
// a global long is internally a float.
- return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong;
+ return MWBase::Environment::get().getWorld()->getGlobalInt (name);
}
float InterpreterContext::getGlobalFloat (const std::string& name) const
{
- return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat;
+ return MWBase::Environment::get().getWorld()->getGlobalFloat (name);
}
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;
+ MWBase::Environment::get().getWorld()->setGlobalInt (name, 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;
+ MWBase::Environment::get().getWorld()->setGlobalInt (name, 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;
+ MWBase::Environment::get().getWorld()->setGlobalFloat (name, value);
}
- std::vector<std::string> InterpreterContext::getGlobals () const
+ std::vector<std::string> InterpreterContext::getGlobals() const
{
- MWBase::World *world = MWBase::Environment::get().getWorld();
- return world->getGlobals();
+ std::vector<std::string> ids;
+ const MWWorld::Store<ESM::Global>& globals =
+ MWBase::Environment::get().getWorld()->getStore().get<ESM::Global>();
+
+ for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end();
+ ++iter)
+ {
+ ids.push_back (iter->mId);
+ }
+
+ return ids;
}
char InterpreterContext::getGlobalType (const std::string& name) const
@@ -248,28 +276,28 @@ namespace MWScript
std::string InterpreterContext::getPCName() const
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- ESM::NPC player = *world->getPlayer().getPlayer().get<ESM::NPC>()->mBase;
+ ESM::NPC player = *world->getPlayerPtr().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;
+ std::string race = world->getPlayerPtr().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;
+ std::string class_ = world->getPlayerPtr().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();
+ MWWorld::Ptr player = world->getPlayerPtr();
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
@@ -288,7 +316,7 @@ namespace MWScript
std::string InterpreterContext::getPCNextRank() const
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first;
@@ -316,14 +344,13 @@ namespace MWScript
int InterpreterContext::getPCBounty() const
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
return MWWorld::Class::get (player).getNpcStats (player).getBounty();
}
std::string InterpreterContext::getCurrentCellName() const
{
- MWBase::World *world = MWBase::Environment::get().getWorld();
- return world->getCurrentCellName();
+ return MWBase::Environment::get().getWorld()->getCellName();
}
bool InterpreterContext::isScriptRunning (const std::string& name) const
@@ -387,7 +414,7 @@ namespace MWScript
if (!mAction.get())
throw std::runtime_error ("activation failed, because no action to perform");
- mAction->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
+ mAction->execute (MWBase::Environment::get().getWorld()->getPlayerPtr());
mActivationHandled = true;
}
@@ -421,86 +448,84 @@ namespace MWScript
MWBase::Environment::get().getWorld()->disable (ref);
}
- int InterpreterContext::getMemberShort (const std::string& id, const std::string& name) const
+ int InterpreterContext::getMemberShort (const std::string& id, const std::string& name,
+ bool global) const
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ const Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's');
+ 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];
+ return locals.mShorts[index];
}
- int InterpreterContext::getMemberLong (const std::string& id, const std::string& name) const
+ int InterpreterContext::getMemberLong (const std::string& id, const std::string& name,
+ bool global) const
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ const Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l');
+ 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];
+ return locals.mLongs[index];
}
- float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name) const
+ float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name,
+ bool global) const
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ const Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f');
+ 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];
+ return locals.mFloats[index];
}
- void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, int value)
+ void InterpreterContext::setMemberShort (const std::string& id, const std::string& name,
+ int value, bool global)
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's');
+ 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;
+ locals.mShorts[index] = value;
}
- void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value)
+ void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value, bool global)
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l');
+ 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;
+ locals.mLongs[index] = value;
}
- void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value)
+ void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value, bool global)
{
- const MWWorld::Ptr ptr = getReference (id, false);
+ std::string scriptId (id);
- std::string scriptId = MWWorld::Class::get (ptr).getScript (ptr);
+ Locals& locals = getMemberLocals (scriptId, global);
- int index = MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f');
+ 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;
+ locals.mFloats[index] = value;
}
- MWWorld::Ptr InterpreterContext::getReference()
+ MWWorld::Ptr InterpreterContext::getReference(bool required)
{
- return getReference ("", true);
+ return getReference ("", true, required);
}
}
diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp
index 9c7b443ae1..9fb7fa2bf8 100644
--- a/apps/openmw/mwscript/interpretercontext.hpp
+++ b/apps/openmw/mwscript/interpretercontext.hpp
@@ -33,9 +33,15 @@ namespace MWScript
bool mActivationHandled;
boost::shared_ptr<MWWorld::Action> mAction;
- MWWorld::Ptr getReference (const std::string& id, bool activeOnly);
+ MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true);
- const MWWorld::Ptr getReference (const std::string& id, bool activeOnly) const;
+ const MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true) const;
+
+ const Locals& getMemberLocals (std::string& id, bool global) const;
+ ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before
+
+ Locals& getMemberLocals (std::string& id, bool global);
+ ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before
public:
@@ -75,35 +81,35 @@ namespace MWScript
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;
@@ -138,19 +144,19 @@ namespace MWScript
virtual void disable (const std::string& id = "");
- virtual int getMemberShort (const std::string& id, const std::string& name) const;
+ virtual int getMemberShort (const std::string& id, const std::string& name, bool global) const;
- virtual int getMemberLong (const std::string& id, const std::string& name) const;
+ virtual int getMemberLong (const std::string& id, const std::string& name, bool global) const;
- virtual float getMemberFloat (const std::string& id, const std::string& name) const;
+ virtual float getMemberFloat (const std::string& id, const std::string& name, bool global) const;
- virtual void setMemberShort (const std::string& id, const std::string& name, int value);
+ virtual void setMemberShort (const std::string& id, const std::string& name, int value, bool global);
- virtual void setMemberLong (const std::string& id, const std::string& name, int value);
+ virtual void setMemberLong (const std::string& id, const std::string& name, int value, bool global);
- virtual void setMemberFloat (const std::string& id, const std::string& name, float value);
+ virtual void setMemberFloat (const std::string& id, const std::string& name, float value, bool global);
- MWWorld::Ptr getReference();
+ MWWorld::Ptr getReference(bool required=true);
///< Reference, that the script is running from (can be empty)
};
}
diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp
index 180a2791bc..094fe085a2 100644
--- a/apps/openmw/mwscript/locals.cpp
+++ b/apps/openmw/mwscript/locals.cpp
@@ -1,6 +1,8 @@
#include "locals.hpp"
#include <components/esm/loadscpt.hpp>
+#include <components/esm/variant.hpp>
+#include <components/esm/locals.hpp>
#include <components/compiler/locals.hpp>
@@ -65,4 +67,68 @@ namespace MWScript
}
return false;
}
+
+ void Locals::write (ESM::Locals& locals, const std::string& script) const
+ {
+ const Compiler::Locals& declarations =
+ MWBase::Environment::get().getScriptManager()->getLocals(script);
+
+ for (int i=0; i<3; ++i)
+ {
+ char type = 0;
+
+ switch (i)
+ {
+ case 0: type = 's'; break;
+ case 1: type = 'l'; break;
+ case 2: type = 'f'; break;
+ }
+
+ const std::vector<std::string>& names = declarations.get (type);
+
+ for (int i2=0; i2<static_cast<int> (names.size()); ++i2)
+ {
+ ESM::Variant value;
+
+ switch (i)
+ {
+ case 0: value.setType (ESM::VT_Int); value.setInteger (mShorts.at (i2)); break;
+ case 1: value.setType (ESM::VT_Int); value.setInteger (mLongs.at (i2)); break;
+ case 2: value.setType (ESM::VT_Float); value.setFloat (mFloats.at (i2)); break;
+ }
+
+ locals.mVariables.push_back (std::make_pair (names[i2], value));
+ }
+ }
+ }
+
+ void Locals::read (const ESM::Locals& locals, const std::string& script)
+ {
+ const Compiler::Locals& declarations =
+ MWBase::Environment::get().getScriptManager()->getLocals(script);
+
+ for (std::vector<std::pair<std::string, ESM::Variant> >::const_iterator iter
+ = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter)
+ {
+ char type = declarations.getType (iter->first);
+ char index = declarations.getIndex (iter->first);
+
+ try
+ {
+ switch (type)
+ {
+ case 's': mShorts.at (index) = iter->second.getInteger(); break;
+ case 'l': mLongs.at (index) = iter->second.getInteger(); break;
+ case 'f': mFloats.at (index) = iter->second.getFloat(); break;
+
+ // silently ignore locals that don't exist anymore
+ }
+ }
+ catch (...)
+ {
+ // ignore type changes
+ /// \todo write to log
+ }
+ }
+ }
}
diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp
index deae0d44ea..d17d1be2dc 100644
--- a/apps/openmw/mwscript/locals.hpp
+++ b/apps/openmw/mwscript/locals.hpp
@@ -8,6 +8,7 @@
namespace ESM
{
struct Script;
+ struct Locals;
}
namespace MWScript
@@ -23,6 +24,9 @@ namespace MWScript
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
+ void write (ESM::Locals& locals, const std::string& script) const;
+
+ void read (const ESM::Locals& locals, const std::string& script);
};
}
diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp
index 8e2a8af8c4..0c60e1c940 100644
--- a/apps/openmw/mwscript/miscextensions.cpp
+++ b/apps/openmw/mwscript/miscextensions.cpp
@@ -18,11 +18,11 @@
#include "../mwbase/scriptmanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/spellcasting.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp"
@@ -57,6 +57,18 @@ namespace MWScript
}
};
+ class OpGetPcJumping : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ MWWorld::Ptr player = world->getPlayerPtr();
+ runtime.push (!world->isOnGround(player) && !world->isFlying(player));
+ }
+ };
+
class OpWakeUpPc : public Interpreter::Opcode0
{
public:
@@ -353,21 +365,30 @@ namespace MWScript
};
template<class R>
- class OpRemoveSoulGem : public Interpreter::Opcode0
+ class OpRemoveSoulGem : public Interpreter::Opcode1
{
public:
- virtual void execute (Interpreter::Runtime& runtime)
+ virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
{
-
MWWorld::Ptr ptr = R()(runtime);
std::string soul = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
- MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
+ // throw away additional arguments
+ for (unsigned int i=0; i<arg0; ++i)
+ runtime.pop();
- store.remove(soul, 1, ptr);
+ MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr);
+ for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ if (::Misc::StringUtils::ciEqual(it->getCellRef().mSoul, soul))
+ {
+ store.remove(*it, 1, ptr);
+ return;
+ }
+ }
}
};
@@ -464,7 +485,20 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- runtime.push(MWWorld::Class::get(ptr).getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon);
+ runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon);
+ }
+ };
+
+ template <class R>
+ class OpGetSpellReadied : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Spell);
}
};
@@ -652,35 +686,53 @@ namespace MWScript
void printGlobalVars(Interpreter::Runtime &runtime)
{
+ InterpreterContext& context =
+ static_cast<InterpreterContext&> (runtime.getContext());
+
std::stringstream str;
str<< "Global variables:";
MWBase::World *world = MWBase::Environment::get().getWorld();
- std::vector<std::string> names = world->getGlobals();
+ std::vector<std::string> names = context.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)";
+ char type = world->getGlobalVariableType (names[i]);
+ str << std::endl << " " << names[i] << " = ";
+
+ switch (type)
+ {
+ case 's':
+
+ str << context.getGlobalShort (names[i]) << " (short)";
+ break;
+
+ case 'l':
+
+ str << context.getGlobalLong (names[i]) << " (long)";
+ break;
+
+ case 'f':
+
+ str << context.getGlobalFloat (names[i]) << " (float)";
+ break;
+
+ default:
+
+ str << "<unknown type>";
+ }
}
- runtime.getContext().report(str.str());
+ context.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);
+ MWWorld::Ptr ptr = R()(runtime, false);
+ if (!ptr.isEmpty())
printLocalVars(runtime, ptr);
- }
- catch(std::runtime_error&) {
+ else
+ {
// No reference, no problem.
printGlobalVars(runtime);
}
@@ -700,6 +752,97 @@ namespace MWScript
}
};
+ template <class R>
+ class OpCast : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger));
+ runtime.pop();
+
+ MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
+
+ MWMechanics::CastSpell cast(ptr, target);
+ cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
+ cast.cast(spell);
+ }
+ };
+
+ template <class R>
+ class OpExplodeSpell : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWMechanics::CastSpell cast(ptr, ptr);
+ cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos);
+ cast.cast(spell);
+ }
+ };
+
+ class OpGoToJail : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWBase::World* world = MWBase::Environment::get().getWorld();
+ world->goToJail();
+ }
+ };
+
+ class OpPayFine : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ player.getClass().getNpcStats(player).setBounty(0);
+ MWBase::Environment::get().getWorld()->confiscateStolenItems(player);
+ }
+ };
+
+ class OpPayFineThief : public Interpreter::Opcode0
+ {
+ public:
+ virtual void execute(Interpreter::Runtime &runtime)
+ {
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
+ player.getClass().getNpcStats(player).setBounty(0);
+ }
+ };
+
+ class OpGetPcInJail : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime &runtime)
+ {
+ /// \todo implement jail check
+ runtime.push (0);
+ }
+ };
+
+ class OpGetPcTraveling : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime &runtime)
+ {
+ /// \todo implement traveling check
+ runtime.push (0);
+ }
+ };
void installOpcodes (Interpreter::Interpreter& interpreter)
{
@@ -721,16 +864,20 @@ namespace MWScript
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::opcodeGetPcJumping, new OpGetPcJumping);
interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc);
interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink);
+ interpreter.installSegment5 (Compiler::Misc::opcodePayFine, new OpPayFine);
+ interpreter.installSegment5 (Compiler::Misc::opcodePayFineThief, new OpPayFineThief);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGoToJail, new OpGoToJail);
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.installSegment3 (Compiler::Misc::opcodeRemoveSoulGem, new OpRemoveSoulGem<ImplicitRef>);
+ interpreter.installSegment3 (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>);
@@ -739,6 +886,8 @@ namespace MWScript
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::opcodeGetSpellReadied, new OpGetSpellReadied<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied<ExplicitRef>);
interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects<ImplicitRef>);
interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects<ExplicitRef>);
interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime);
@@ -761,6 +910,12 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode);
interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation<false>);
interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation<true>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail);
+ interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling);
}
}
}
diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp
index 81b1d5ef99..7958391395 100644
--- a/apps/openmw/mwscript/ref.hpp
+++ b/apps/openmw/mwscript/ref.hpp
@@ -16,7 +16,7 @@ namespace MWScript
{
struct ExplicitRef
{
- MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const
+ MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true) const
{
std::string id = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
@@ -27,12 +27,12 @@ namespace MWScript
struct ImplicitRef
{
- MWWorld::Ptr operator() (Interpreter::Runtime& runtime) const
+ MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true) const
{
MWScript::InterpreterContext& context
= static_cast<MWScript::InterpreterContext&> (runtime.getContext());
- return context.getReference();
+ return context.getReference(required);
}
};
}
diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp
index 14fe5b7fd6..74c85dbbf7 100644
--- a/apps/openmw/mwscript/scriptmanagerimp.cpp
+++ b/apps/openmw/mwscript/scriptmanagerimp.cpp
@@ -7,22 +7,28 @@
#include <exception>
#include <components/esm/loadscpt.hpp>
-#include "../mwworld/esmstore.hpp"
+
+#include <components/misc/stringops.hpp>
#include <components/compiler/scanner.hpp>
#include <components/compiler/context.hpp>
#include <components/compiler/exception.hpp>
+#include <components/compiler/quickfileparser.hpp>
+
+#include "../mwworld/esmstore.hpp"
#include "extensions.hpp"
namespace MWScript
{
ScriptManager::ScriptManager (const MWWorld::ESMStore& store, bool verbose,
- Compiler::Context& compilerContext)
+ Compiler::Context& compilerContext, int warningsMode)
: mErrorHandler (std::cerr), mStore (store), mVerbose (verbose),
mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext),
mOpcodesInstalled (false), mGlobalScripts (store)
- {}
+ {
+ mErrorHandler.setWarningsMode (warningsMode);
+ }
bool ScriptManager::compile (const std::string& name)
{
@@ -114,10 +120,8 @@ namespace MWScript
}
catch (const std::exception& e)
{
- std::cerr << "execution of script " << name << " failed." << std::endl;
-
- if (mVerbose)
- std::cerr << "(" << e.what() << ")" << std::endl;
+ std::cerr << "Execution of script " << name << " failed:" << std::endl;
+ std::cerr << e.what() << std::endl;
iter->second.first.clear(); // don't execute again.
}
@@ -140,37 +144,33 @@ namespace MWScript
Compiler::Locals& ScriptManager::getLocals (const std::string& name)
{
+ std::string name2 = Misc::StringUtils::lowerCase (name);
+
{
- ScriptCollection::iterator iter = mScripts.find (name);
+ ScriptCollection::iterator iter = mScripts.find (name2);
if (iter!=mScripts.end())
return iter->second.second;
}
{
- std::map<std::string, Compiler::Locals>::iterator iter = mOtherLocals.find (name);
+ std::map<std::string, Compiler::Locals>::iterator iter = mOtherLocals.find (name2);
if (iter!=mOtherLocals.end())
return iter->second;
}
- Compiler::Locals locals;
-
- if (const ESM::Script *script = mStore.get<ESM::Script>().find (name))
+ if (const ESM::Script *script = mStore.get<ESM::Script>().find (name2))
{
- 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++]);
+ Compiler::Locals locals;
- for (int i=0; i<script->mData.mNumFloats; ++i)
- locals.declare ('f', script->mVarNames[index++]);
+ std::istringstream stream (script->mScriptText);
+ Compiler::QuickFileParser parser (mErrorHandler, mCompilerContext, locals);
+ Compiler::Scanner scanner (mErrorHandler, stream, mCompilerContext.getExtensions());
+ scanner.scan (parser);
std::map<std::string, Compiler::Locals>::iterator iter =
- mOtherLocals.insert (std::make_pair (name, locals)).first;
+ mOtherLocals.insert (std::make_pair (name2, locals)).first;
return iter->second;
}
@@ -216,15 +216,12 @@ namespace MWScript
throw std::runtime_error ("invalid variable type");
}
+ std::string variable2 = Misc::StringUtils::lowerCase (variable);
+
for (int i=0; i<size; ++i)
- if (script->mVarNames.at (i+offset)==variable)
+ if (Misc::StringUtils::lowerCase (script->mVarNames.at (i+offset))==variable2)
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
index 7bb98ffbd3..da3abc60b5 100644
--- a/apps/openmw/mwscript/scriptmanagerimp.hpp
+++ b/apps/openmw/mwscript/scriptmanagerimp.hpp
@@ -52,7 +52,7 @@ namespace MWScript
public:
ScriptManager (const MWWorld::ESMStore& store, bool verbose,
- Compiler::Context& compilerContext);
+ Compiler::Context& compilerContext, int warningsMode);
virtual void run (const std::string& name, Interpreter::Context& interpreterContext);
///< Run the script with the given name (compile first, if not compiled yet)
@@ -61,8 +61,6 @@ namespace MWScript
///< Compile script with the given namen
/// \return Success?
- virtual void resetGlobalScripts();
-
virtual std::pair<int, int> compileAll();
///< Compile all scripts
/// \return count, success
diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp
index 45e5b0f187..c4f672dc76 100644
--- a/apps/openmw/mwscript/statsextensions.cpp
+++ b/apps/openmw/mwscript/statsextensions.cpp
@@ -3,8 +3,6 @@
#include <cmath>
-#include <boost/algorithm/string.hpp>
-
#include <components/esm/loadnpc.hpp>
#include "../mwworld/esmstore.hpp"
@@ -19,9 +17,9 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
@@ -124,8 +122,8 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
- MWMechanics::Stat<int> attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex);
- attribute.setModified (value, 0);
+ MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex);
+ attribute.setBase (value - (attribute.getModified() - attribute.getBase()));
ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute);
}
};
@@ -146,15 +144,11 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
- MWMechanics::Stat<int> attribute = MWWorld::Class::get(ptr)
+ MWMechanics::AttributeValue attribute = MWWorld::Class::get(ptr)
.getCreatureStats(ptr)
.getAttribute(mIndex);
- value +=
- attribute.getModified();
-
- attribute
- .setModified (value, 0, 100);
+ attribute.setBase (std::min(100, attribute.getBase() + value));
ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute);
}
};
@@ -313,9 +307,7 @@ namespace MWScript
{
MWWorld::Ptr ptr = R()(runtime);
- Interpreter::Type_Integer value =
- MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
- getModified();
+ Interpreter::Type_Integer value = ptr.getClass().getSkill(ptr, mIndex);
runtime.push (value);
}
@@ -346,12 +338,10 @@ namespace MWScript
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();
+ float level = stats.getSkill(mIndex).getBase();
+ float progress = stats.getSkill(mIndex).getProgress();
- int newLevel = static_cast<int> (value-modifier);
+ int newLevel = value - (stats.getSkill(mIndex).getModified() - stats.getSkill(mIndex).getBase());
if (newLevel<0)
newLevel = 0;
@@ -362,8 +352,8 @@ namespace MWScript
if (progress>=1)
progress = 0.999999999;
- stats.getSkill (mIndex).set (newLevel + progress);
- stats.getSkill (mIndex).setModifier (modifier);
+ stats.getSkill (mIndex).setBase (newLevel);
+ stats.getSkill (mIndex).setProgress(progress);
}
};
@@ -383,11 +373,10 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
- value += MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
- getModified();
+ MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr);
- MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex).
- setModified (value, 0, 100);
+ stats.getSkill(mIndex).
+ setBase (std::min(100, stats.getSkill(mIndex).getBase() + value));
}
};
@@ -398,7 +387,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
runtime.push (static_cast <Interpreter::Type_Float> (MWWorld::Class::get (player).getNpcStats (player).getBounty()));
}
};
@@ -410,7 +399,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat);
runtime.pop();
@@ -424,7 +413,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty());
runtime.pop();
@@ -463,6 +452,46 @@ namespace MWScript
runtime.pop();
MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id);
+
+ MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
+
+ if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
+ id == wm->getSelectedSpell())
+ {
+ wm->unsetSelectedSpell();
+ }
+ }
+ };
+
+ template<class R>
+ class OpRemoveSpellEffects : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ std::string spellid = runtime.getStringLiteral (runtime[0].mInteger);
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().removeEffects(spellid);
+ }
+ };
+
+ template<class R>
+ class OpRemoveEffects : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+
+ Interpreter::Type_Integer effectId = runtime[0].mInteger;
+ runtime.pop();
+
+ MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId);
}
};
@@ -514,7 +543,7 @@ namespace MWScript
Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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;
@@ -543,7 +572,7 @@ namespace MWScript
Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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;
@@ -576,7 +605,7 @@ namespace MWScript
Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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;
@@ -612,7 +641,7 @@ namespace MWScript
}
}
Misc::StringUtils::toLower(factionID);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if(factionID!="")
{
if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end())
@@ -715,7 +744,7 @@ namespace MWScript
Misc::StringUtils::toLower (factionId);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
runtime.push (
MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId));
}
@@ -751,7 +780,7 @@ namespace MWScript
Misc::StringUtils::toLower (factionId);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value);
}
};
@@ -786,7 +815,7 @@ namespace MWScript
Misc::StringUtils::toLower (factionId);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId,
MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)+
value);
@@ -845,7 +874,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
- MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ();
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getWerewolfKills ());
}
@@ -878,18 +907,10 @@ namespace MWScript
}
}
Misc::StringUtils::toLower(factionID);
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
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);
- }
+ runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID));
}
else
{
@@ -905,8 +926,6 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
{
- MWWorld::Ptr ptr = R()(runtime);
-
std::string factionID = "";
if(arg0 >0 )
{
@@ -915,6 +934,7 @@ namespace MWScript
}
else
{
+ MWWorld::Ptr ptr = R()(runtime);
if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
{
factionID = "";
@@ -924,12 +944,10 @@ namespace MWScript
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if(factionID!="")
{
- std::set<std::string>& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled ();
- Misc::StringUtils::toLower(factionID);
- expelled.insert(factionID);
+ player.getClass().getNpcStats(player).expell(factionID);
}
}
};
@@ -941,8 +959,6 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0)
{
- MWWorld::Ptr ptr = R()(runtime);
-
std::string factionID = "";
if(arg0 >0 )
{
@@ -951,6 +967,7 @@ namespace MWScript
}
else
{
+ MWWorld::Ptr ptr = R()(runtime);
if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty())
{
factionID = "";
@@ -960,13 +977,9 @@ namespace MWScript
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if(factionID!="")
- {
- std::set<std::string>& expelled = MWWorld::Class::get(player).getNpcStats(player).getExpelled ();
- Misc::StringUtils::toLower(factionID);
- expelled.erase (factionID);
- }
+ player.getClass().getNpcStats(player).clearExpelled(factionID);
}
};
@@ -986,7 +999,7 @@ namespace MWScript
{
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// no-op when executed on the player
if (ptr == player)
@@ -1013,7 +1026,7 @@ namespace MWScript
{
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// no-op when executed on the player
if (ptr == player)
@@ -1079,6 +1092,18 @@ namespace MWScript
}
};
+ template <class R>
+ class OpResurrect : public Interpreter::Opcode0
+ {
+ public:
+
+ virtual void execute (Interpreter::Runtime& runtime)
+ {
+ MWWorld::Ptr ptr = R()(runtime);
+ ptr.getClass().getCreatureStats(ptr).resurrect();
+ }
+ };
+
void installOpcodes (Interpreter::Interpreter& interpreter)
{
for (int i=0; i<Compiler::Stats::numberOfAttributes; ++i)
@@ -1142,6 +1167,15 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpell, new OpRemoveSpell<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit,
new OpRemoveSpell<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit,
+ new OpRemoveSpellEffects<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit,
+ new OpResurrect<ExplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects<ImplicitRef>);
+ interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit,
+ new OpRemoveEffects<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell<ExplicitRef>);
diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp
index e441809778..43a0fedce9 100644
--- a/apps/openmw/mwscript/transformationextensions.cpp
+++ b/apps/openmw/mwscript/transformationextensions.cpp
@@ -1,9 +1,5 @@
-#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>
@@ -16,8 +12,9 @@
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
-#include "../mwworld/player.hpp"
#include "../mwworld/manualref.hpp"
+#include "../mwworld/player.hpp"
+#include "../mwworld/esmstore.hpp"
#include "interpretercontext.hpp"
#include "ref.hpp"
@@ -482,7 +479,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr actor = pc
- ? MWBase::Environment::get().getWorld()->getPlayer().getPlayer()
+ ? MWBase::Environment::get().getWorld()->getPlayerPtr()
: R()(runtime);
std::string itemID = runtime.getStringLiteral (runtime[0].mInteger);
diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp
index 4ee754b35d..7563ad0150 100644
--- a/apps/openmw/mwsound/openal_output.cpp
+++ b/apps/openmw/mwsound/openal_output.cpp
@@ -172,6 +172,7 @@ class OpenAL_SoundStream : public Sound
DecoderPtr mDecoder;
volatile bool mIsFinished;
+ volatile bool mIsInitialBatchEnqueued;
void updateAll(bool local);
@@ -264,7 +265,7 @@ private:
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)
+ , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true), mIsInitialBatchEnqueued(false)
{
throwALerror();
@@ -315,16 +316,8 @@ void OpenAL_SoundStream::play()
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;
+ mIsInitialBatchEnqueued = false;
mOutput.mStreamThread->add(this);
}
@@ -332,6 +325,7 @@ void OpenAL_SoundStream::stop()
{
mOutput.mStreamThread->remove(this);
mIsFinished = true;
+ mIsInitialBatchEnqueued = false;
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
@@ -403,7 +397,7 @@ void OpenAL_SoundStream::update()
alSourcef(mSource, AL_GAIN, gain);
alSourcef(mSource, AL_PITCH, pitch);
- alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]);
+ 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();
@@ -444,6 +438,24 @@ bool OpenAL_SoundStream::process()
} while(processed > 0);
throwALerror();
}
+ else if (!mIsInitialBatchEnqueued) { // nothing enqueued yet
+ std::vector<char> data(mBufferSize);
+
+ for(ALuint i = 0;i < sNumBuffers && !finished;i++)
+ {
+ size_t got = mDecoder->read(&data[0], data.size());
+ finished = (got < data.size());
+ if(got > 0)
+ {
+ ALuint bufid = mBuffers[i];
+ alBufferData(bufid, mFormat, &data[0], got, mSampleRate);
+ alSourceQueueBuffers(mSource, 1, &bufid);
+ throwALerror();
+ mSamplesQueued += getBufferSampleCount(bufid);
+ }
+ }
+ mIsInitialBatchEnqueued = true;
+ }
if(state != AL_PLAYING && state != AL_PAUSED)
{
@@ -461,6 +473,7 @@ bool OpenAL_SoundStream::process()
std::cout<< "Error updating stream \""<<mDecoder->getName()<<"\"" <<std::endl;
mSamplesQueued = 0;
mIsFinished = true;
+ mIsInitialBatchEnqueued = false;
}
return !mIsFinished;
}
diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp
index 2e52739ac5..fd3fe4c0ad 100644
--- a/apps/openmw/mwsound/soundmanagerimp.cpp
+++ b/apps/openmw/mwsound/soundmanagerimp.cpp
@@ -6,9 +6,9 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
+#include "../mwbase/statemanager.hpp"
#include "../mwworld/esmstore.hpp"
-#include "../mwworld/player.hpp"
#include "sound_output.hpp"
#include "sound_decoder.hpp"
@@ -157,11 +157,12 @@ namespace MWSound
volume *= mFootstepsVolume;
break;
case Play_TypeMusic:
- case Play_TypeMovie:
volume *= mMusicVolume;
break;
case Play_TypeMask:
break;
+ default:
+ break;
}
return volume;
}
@@ -248,14 +249,13 @@ namespace MWSound
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);
MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f,
- 20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0);
+ 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0);
mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound"));
}
catch(std::exception &e)
@@ -399,7 +399,7 @@ namespace MWSound
}
}
- void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell)
+ void SoundManager::stopSound(const MWWorld::CellStore *cell)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
@@ -479,7 +479,7 @@ namespace MWSound
static std::string regionName = "";
static float sTimePassed = 0.0;
MWBase::World *world = MWBase::Environment::get().getWorld();
- const MWWorld::Ptr player = world->getPlayer().getPlayer();
+ const MWWorld::Ptr player = world->getPlayerPtr();
const ESM::Cell *cell = player.getCell()->mCell;
sTimePassed += duration;
@@ -547,7 +547,7 @@ namespace MWSound
startRandomTitle();
MWWorld::Ptr player =
- MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+ MWBase::Environment::get().getWorld()->getPlayerPtr();
const ESM::Cell *cell = player.getCell()->mCell;
Environment env = Env_Normal;
@@ -595,7 +595,7 @@ namespace MWSound
soundDuration=snditer->first->mFadeOutTime;
snditer->first->setVolume(snditer->first->mVolume
- soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume);
- snditer->first->mFadeOutTime -= soundDuration;
+ snditer->first->mFadeOutTime -= soundDuration;
}
snditer->first->update();
++snditer;
@@ -607,8 +607,13 @@ namespace MWSound
{
if(!mOutput->isInitialized())
return;
- updateSounds(duration);
- updateRegionSound(duration);
+
+ if (MWBase::Environment::get().getStateManager()->getState()!=
+ MWBase::StateManager::State_NoGame)
+ {
+ updateSounds(duration);
+ updateRegionSound(duration);
+ }
}
@@ -641,6 +646,15 @@ namespace MWSound
mListenerUp = up;
}
+ void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
+ {
+ for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer)
+ {
+ if (snditer->second.first == old)
+ snditer->second.first = updated;
+ }
+ }
+
// Default readAll implementation, for decoders that can't do anything
// better
void Sound_Decoder::readAll(std::vector<char> &output)
@@ -705,4 +719,13 @@ namespace MWSound
{
return bytes / framesToBytes(1, config, type);
}
+
+ void SoundManager::clear()
+ {
+ for (SoundMap::iterator iter (mActiveSounds.begin()); iter!=mActiveSounds.end(); ++iter)
+ iter->first->stop();
+
+ mActiveSounds.clear();
+ stopMusic();
+ }
}
diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp
index f62e62d503..1454240b4a 100644
--- a/apps/openmw/mwsound/soundmanagerimp.hpp
+++ b/apps/openmw/mwsound/soundmanagerimp.hpp
@@ -148,6 +148,10 @@ namespace MWSound
virtual void update(float duration);
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up);
+
+ virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
+
+ virtual void clear();
};
}
diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp
new file mode 100644
index 0000000000..304eaddd35
--- /dev/null
+++ b/apps/openmw/mwstate/character.cpp
@@ -0,0 +1,153 @@
+
+#include "character.hpp"
+
+#include <ctime>
+
+#include <sstream>
+#include <algorithm>
+#include <stdexcept>
+
+#include <boost/filesystem.hpp>
+
+#include <components/esm/esmreader.hpp>
+#include <components/esm/defs.hpp>
+
+#include <components/misc/stringops.hpp>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+
+bool MWState::operator< (const Slot& left, const Slot& right)
+{
+ return left.mTimeStamp<right.mTimeStamp;
+}
+
+
+void MWState::Character::addSlot (const boost::filesystem::path& path, const std::string& game)
+{
+ Slot slot;
+ slot.mPath = path;
+ slot.mTimeStamp = boost::filesystem::last_write_time (path);
+
+ ESM::ESMReader reader;
+ reader.open (slot.mPath.string());
+
+ if (reader.getFormat()>ESM::Header::CurrentFormat)
+ return; // format is too new -> ignore
+
+ if (reader.getRecName()!=ESM::REC_SAVE)
+ return; // invalid save file -> ignore
+
+ reader.getRecHeader();
+
+ slot.mProfile.load (reader);
+
+ if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!=
+ Misc::StringUtils::lowerCase (game))
+ return; // this file is for a different game -> ignore
+
+ mSlots.push_back (slot);
+}
+
+void MWState::Character::addSlot (const ESM::SavedGame& profile)
+{
+ Slot slot;
+
+ std::ostringstream stream;
+ stream << mNext++;
+
+ slot.mPath = mPath / stream.str();
+ slot.mProfile = profile;
+ slot.mTimeStamp = std::time (0);
+
+ mSlots.push_back (slot);
+}
+
+MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game)
+: mPath (saves), mNext (0)
+{
+ if (!boost::filesystem::is_directory (mPath))
+ {
+ boost::filesystem::create_directories (mPath);
+ }
+ else
+ {
+ for (boost::filesystem::directory_iterator iter (mPath);
+ iter!=boost::filesystem::directory_iterator(); ++iter)
+ {
+ boost::filesystem::path slotPath = *iter;
+
+ try
+ {
+ addSlot (slotPath, game);
+ }
+ catch (...) {} // ignoring bad saved game files for now
+
+ std::istringstream stream (slotPath.filename().string());
+
+ int index = 0;
+
+ if ((stream >> index) && index>=mNext)
+ mNext = index+1;
+ }
+
+ std::sort (mSlots.begin(), mSlots.end());
+ }
+}
+
+const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile)
+{
+ addSlot (profile);
+
+ return &mSlots.back();
+}
+
+const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile)
+{
+ int index = slot - &mSlots[0];
+
+ if (index<0 || index>=static_cast<int> (mSlots.size()))
+ {
+ // sanity check; not entirely reliable
+ throw std::logic_error ("slot not found");
+ }
+
+ Slot newSlot = *slot;
+ newSlot.mProfile = profile;
+ newSlot.mTimeStamp = std::time (0);
+
+ mSlots.erase (mSlots.begin()+index);
+
+ mSlots.push_back (newSlot);
+
+ return &mSlots.back();
+}
+
+MWState::Character::SlotIterator MWState::Character::begin() const
+{
+ return mSlots.rbegin();
+}
+
+MWState::Character::SlotIterator MWState::Character::end() const
+{
+ return mSlots.rend();
+}
+
+ESM::SavedGame MWState::Character::getSignature() const
+{
+ if (mSlots.empty())
+ throw std::logic_error ("character signature not available");
+
+ std::vector<Slot>::const_iterator iter (mSlots.begin());
+
+ Slot slot = *iter;
+
+ for (++iter; iter!=mSlots.end(); ++iter)
+ if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel)
+ slot = *iter;
+ else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel &&
+ iter->mTimeStamp>slot.mTimeStamp)
+ slot = *iter;
+
+ return slot.mProfile;
+} \ No newline at end of file
diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp
new file mode 100644
index 0000000000..61e4e5b25f
--- /dev/null
+++ b/apps/openmw/mwstate/character.hpp
@@ -0,0 +1,63 @@
+#ifndef GAME_STATE_CHARACTER_H
+#define GAME_STATE_CHARACTER_H
+
+#include <boost/filesystem/path.hpp>
+
+#include <components/esm/savedgame.hpp>
+
+namespace MWState
+{
+ struct Slot
+ {
+ boost::filesystem::path mPath;
+ ESM::SavedGame mProfile;
+ std::time_t mTimeStamp;
+ };
+
+ bool operator< (const Slot& left, const Slot& right);
+
+ class Character
+ {
+ public:
+
+ typedef std::vector<Slot>::const_reverse_iterator SlotIterator;
+
+ private:
+
+ boost::filesystem::path mPath;
+ std::vector<Slot> mSlots;
+ int mNext;
+
+ void addSlot (const boost::filesystem::path& path, const std::string& game);
+
+ void addSlot (const ESM::SavedGame& profile);
+
+ public:
+
+ Character (const boost::filesystem::path& saves, const std::string& game);
+
+ const Slot *createSlot (const ESM::SavedGame& profile);
+ ///< Create new slot.
+ ///
+ /// \attention The ownership of the slot is not transferred.
+
+ const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile);
+ /// \note Slot must belong to this character.
+ ///
+ /// \attention The \a slot pointer will be invalidated by this call.
+
+ SlotIterator begin() const;
+ ///< First slot is the most recent. Other slots follow in descending order of save date.
+ ///
+ /// Any call to createSlot and updateSlot can invalidate the returned iterator.
+
+ SlotIterator end() const;
+
+ ESM::SavedGame getSignature() const;
+ ///< Return signature information for this character.
+ ///
+ /// \attention This function must not be called if there are no slots.
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp
new file mode 100644
index 0000000000..2a40fb1cc5
--- /dev/null
+++ b/apps/openmw/mwstate/charactermanager.cpp
@@ -0,0 +1,85 @@
+
+#include "charactermanager.hpp"
+
+#include <sstream>
+#include <stdexcept>
+
+#include <boost/filesystem.hpp>
+
+MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves,
+ const std::string& game)
+: mPath (saves), mNext (0), mCurrent (0), mGame (game)
+{
+ if (!boost::filesystem::is_directory (mPath))
+ {
+ boost::filesystem::create_directories (mPath);
+ }
+ else
+ {
+ for (boost::filesystem::directory_iterator iter (mPath);
+ iter!=boost::filesystem::directory_iterator(); ++iter)
+ {
+ boost::filesystem::path characterDir = *iter;
+
+ if (boost::filesystem::is_directory (characterDir))
+ {
+ Character character (characterDir, mGame);
+
+ if (character.begin()!=character.end())
+ mCharacters.push_back (character);
+ }
+
+ std::istringstream stream (characterDir.filename().string());
+
+ int index = 0;
+
+ if ((stream >> index) && index>=mNext)
+ mNext = index+1;
+ }
+ }
+}
+
+MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create)
+{
+ if (!mCurrent && create)
+ createCharacter();
+
+ return mCurrent;
+}
+
+void MWState::CharacterManager::createCharacter()
+{
+ std::ostringstream stream;
+ stream << mNext++;
+
+ boost::filesystem::path path = mPath / stream.str();
+
+ mCharacters.push_back (Character (path, mGame));
+
+ mCurrent = &mCharacters.back();
+}
+
+void MWState::CharacterManager::setCurrentCharacter (const Character *character)
+{
+ int index = character - &mCharacters[0];
+
+ if (index<0 || index>=static_cast<int> (mCharacters.size()))
+ throw std::logic_error ("invalid character");
+
+ mCurrent = &mCharacters[index];
+}
+
+void MWState::CharacterManager::clearCurrentCharacter()
+{
+ mCurrent = 0;
+}
+
+std::vector<MWState::Character>::const_iterator MWState::CharacterManager::begin() const
+{
+ return mCharacters.begin();
+}
+
+std::vector<MWState::Character>::const_iterator MWState::CharacterManager::end() const
+{
+ return mCharacters.end();
+}
diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp
new file mode 100644
index 0000000000..bc2e23f89a
--- /dev/null
+++ b/apps/openmw/mwstate/charactermanager.hpp
@@ -0,0 +1,46 @@
+#ifndef GAME_STATE_CHARACTERMANAGER_H
+#define GAME_STATE_CHARACTERMANAGER_H
+
+#include <boost/filesystem/path.hpp>
+
+#include "character.hpp"
+
+namespace MWState
+{
+ class CharacterManager
+ {
+ boost::filesystem::path mPath;
+ int mNext;
+ std::vector<Character> mCharacters;
+ Character *mCurrent;
+ std::string mGame;
+
+ private:
+
+ CharacterManager (const CharacterManager&);
+ ///< Not implemented
+
+ CharacterManager& operator= (const CharacterManager&);
+ ///< Not implemented
+
+ public:
+
+ CharacterManager (const boost::filesystem::path& saves, const std::string& game);
+
+ Character *getCurrentCharacter (bool create = true);
+ ///< \param create Create a new character, if there is no current character.
+
+ void createCharacter();
+ ///< Create new character within saved game management
+
+ void setCurrentCharacter (const Character *character);
+
+ void clearCurrentCharacter();
+
+ std::vector<Character>::const_iterator begin() const;
+
+ std::vector<Character>::const_iterator end() const;
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp
new file mode 100644
index 0000000000..265069dc46
--- /dev/null
+++ b/apps/openmw/mwstate/statemanagerimp.cpp
@@ -0,0 +1,352 @@
+
+#include "statemanagerimp.hpp"
+
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/esmreader.hpp>
+#include <components/esm/cellid.hpp>
+#include <components/esm/loadcell.hpp>
+
+#include <components/misc/stringops.hpp>
+
+#include <components/settings/settings.hpp>
+
+#include <OgreImage.h>
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/world.hpp"
+#include "../mwbase/journal.hpp"
+#include "../mwbase/dialoguemanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
+#include "../mwbase/scriptmanager.hpp"
+#include "../mwbase/soundmanager.hpp"
+
+#include "../mwworld/player.hpp"
+#include "../mwworld/class.hpp"
+
+#include "../mwmechanics/npcstats.hpp"
+
+#include "../mwscript/globalscripts.hpp"
+
+void MWState::StateManager::cleanup (bool force)
+{
+ if (mState!=State_NoGame || force)
+ {
+ MWBase::Environment::get().getSoundManager()->clear();
+ MWBase::Environment::get().getDialogueManager()->clear();
+ MWBase::Environment::get().getJournal()->clear();
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear();
+ MWBase::Environment::get().getWorld()->clear();
+ MWBase::Environment::get().getWindowManager()->clear();
+
+ mState = State_NoGame;
+ mCharacterManager.clearCurrentCharacter();
+ mTimePlayed = 0;
+ }
+}
+
+std::map<int, int> MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader)
+ const
+{
+ const std::vector<std::string>& current =
+ MWBase::Environment::get().getWorld()->getContentFiles();
+
+ const std::vector<ESM::Header::MasterData>& prev = reader.getGameFiles();
+
+ std::map<int, int> map;
+
+ for (int iPrev = 0; iPrev<static_cast<int> (prev.size()); ++iPrev)
+ {
+ std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name);
+
+ for (int iCurrent = 0; iCurrent<static_cast<int> (current.size()); ++iCurrent)
+ if (id==Misc::StringUtils::lowerCase (current[iCurrent]))
+ {
+ map.insert (std::make_pair (iPrev, iCurrent));
+ break;
+ }
+ }
+
+ return map;
+}
+
+MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game)
+: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0)
+{
+
+}
+
+void MWState::StateManager::requestQuit()
+{
+ mQuitRequest = true;
+}
+
+bool MWState::StateManager::hasQuitRequest() const
+{
+ return mQuitRequest;
+}
+
+void MWState::StateManager::askLoadRecent()
+{
+ if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)
+ return;
+
+ if( !mAskLoadRecent )
+ {
+ if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves
+ {
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
+ }
+ else
+ {
+ MWState::Slot lastSave = *getCurrentCharacter()->begin();
+ std::vector<std::string> buttons;
+ buttons.push_back("#{sYes}");
+ buttons.push_back("#{sNo}");
+ std::string tag("%s");
+ std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag);
+ size_t pos = message.find(tag);
+ message.replace(pos, tag.length(), lastSave.mProfile.mDescription);
+ MWBase::Environment::get().getWindowManager()->messageBox(message, buttons);
+ mAskLoadRecent = true;
+ }
+ }
+}
+
+MWState::StateManager::State MWState::StateManager::getState() const
+{
+ return mState;
+}
+
+void MWState::StateManager::newGame (bool bypass)
+{
+ cleanup();
+
+ if (!bypass)
+ {
+ MWBase::Environment::get().getWorld()->startNewGame();
+ MWBase::Environment::get().getWindowManager()->setNewGame (true);
+ }
+ else
+ MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1);
+
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
+
+ mState = State_Running;
+}
+
+void MWState::StateManager::endGame()
+{
+ mState = State_Ended;
+ MWBase::Environment::get().getWorld()->useDeathCamera();
+}
+
+void MWState::StateManager::saveGame (const std::string& description, const Slot *slot)
+{
+ ESM::SavedGame profile;
+
+ MWBase::World& world = *MWBase::Environment::get().getWorld();
+
+ MWWorld::Ptr player = world.getPlayer().getPlayer();
+
+ profile.mContentFiles = world.getContentFiles();
+
+ profile.mPlayerName = player.getClass().getName (player);
+ profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel();
+ profile.mPlayerClass = player.get<ESM::NPC>()->mBase->mClass;
+
+ profile.mPlayerCell = world.getCellName();
+
+ profile.mInGameTime.mGameHour = world.getTimeStamp().getHour();
+ profile.mInGameTime.mDay = world.getDay();
+ profile.mInGameTime.mMonth = world.getMonth();
+ profile.mInGameTime.mYear = world.getYear();
+ profile.mTimePlayed = mTimePlayed;
+ profile.mDescription = description;
+
+ int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing
+ Ogre::Image screenshot;
+ world.screenshot(screenshot, screenshotW, screenshotH);
+ Ogre::DataStreamPtr encoded = screenshot.encode("jpg");
+ profile.mScreenshot.resize(encoded->size());
+ encoded->read(&profile.mScreenshot[0], encoded->size());
+
+ if (!slot)
+ slot = mCharacterManager.getCurrentCharacter()->createSlot (profile);
+ else
+ slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile);
+
+ std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary);
+
+ ESM::ESMWriter writer;
+
+ const std::vector<std::string>& current =
+ MWBase::Environment::get().getWorld()->getContentFiles();
+
+ for (std::vector<std::string>::const_iterator iter (current.begin()); iter!=current.end();
+ ++iter)
+ writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0
+
+ writer.setFormat (ESM::Header::CurrentFormat);
+ writer.setRecordCount (
+ 1 // saved game header
+ +MWBase::Environment::get().getJournal()->countSavedGameRecords()
+ +MWBase::Environment::get().getWorld()->countSavedGameRecords()
+ +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
+ +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
+ +1 // global map
+ );
+
+ writer.save (stream);
+
+ writer.startRecord (ESM::REC_SAVE);
+ slot->mProfile.save (writer);
+ writer.endRecord (ESM::REC_SAVE);
+
+ MWBase::Environment::get().getJournal()->write (writer);
+ MWBase::Environment::get().getDialogueManager()->write (writer);
+ MWBase::Environment::get().getWorld()->write (writer);
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer);
+ MWBase::Environment::get().getWindowManager()->write(writer);
+
+ writer.close();
+
+ Settings::Manager::setString ("character", "Saves",
+ slot->mPath.parent_path().filename().string());
+}
+
+void MWState::StateManager::loadGame (const Character *character, const Slot *slot)
+{
+ try
+ {
+ cleanup();
+
+ mTimePlayed = slot->mProfile.mTimePlayed;
+
+ ESM::ESMReader reader;
+ reader.open (slot->mPath.string());
+
+ std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
+
+ while (reader.hasMoreRecs())
+ {
+ ESM::NAME n = reader.getRecName();
+ reader.getRecHeader();
+
+ switch (n.val)
+ {
+ case ESM::REC_SAVE:
+
+ // don't need to read that here
+ reader.skipRecord();
+ break;
+
+ case ESM::REC_JOUR:
+ case ESM::REC_QUES:
+
+ MWBase::Environment::get().getJournal()->readRecord (reader, n.val);
+ break;
+
+ case ESM::REC_DIAS:
+
+ MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val);
+ break;
+
+ case ESM::REC_ALCH:
+ case ESM::REC_ARMO:
+ case ESM::REC_BOOK:
+ case ESM::REC_CLAS:
+ case ESM::REC_CLOT:
+ case ESM::REC_ENCH:
+ case ESM::REC_NPC_:
+ case ESM::REC_SPEL:
+ case ESM::REC_WEAP:
+ case ESM::REC_GLOB:
+ case ESM::REC_PLAY:
+ case ESM::REC_CSTA:
+
+ MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap);
+ break;
+
+ case ESM::REC_GSCR:
+
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val);
+ break;
+
+ case ESM::REC_GMAP:
+
+ MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val);
+ break;
+
+ default:
+
+ // ignore invalid records
+ /// \todo log error
+ reader.skipRecord();
+ }
+ }
+
+ mCharacterManager.setCurrentCharacter(character);
+
+ mState = State_Running;
+
+ Settings::Manager::setString ("character", "Saves",
+ slot->mPath.parent_path().filename().string());
+
+ MWBase::Environment::get().getWindowManager()->setNewGame(false);
+ MWBase::Environment::get().getWorld()->setupPlayer();
+ MWBase::Environment::get().getWorld()->renderPlayer();
+ MWBase::Environment::get().getWindowManager()->updatePlayer();
+ MWBase::Environment::get().getMechanicsManager()->playerLoaded();
+
+ MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
+
+ ESM::CellId cellId = ptr.getCell()->mCell->getCellId();
+
+ MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition());
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "failed to load saved game: " << e.what() << std::endl;
+ cleanup (true);
+ }
+}
+
+MWState::Character *MWState::StateManager::getCurrentCharacter (bool create)
+{
+ return mCharacterManager.getCurrentCharacter (create);
+}
+
+MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin()
+{
+ return mCharacterManager.begin();
+}
+
+MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd()
+{
+ return mCharacterManager.end();
+}
+
+void MWState::StateManager::update (float duration)
+{
+ mTimePlayed += duration;
+
+ // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update.
+ if (mAskLoadRecent)
+ {
+ int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
+ if(iButton==0)
+ {
+ mAskLoadRecent = false;
+ //Load last saved game for current character
+ MWState::Character *curCharacter = getCurrentCharacter();
+ MWState::Slot lastSave = *curCharacter->begin();
+ loadGame(curCharacter, &lastSave);
+ }
+ else if(iButton==1)
+ {
+ mAskLoadRecent = false;
+ MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
+ }
+ }
+}
diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp
new file mode 100644
index 0000000000..46ade236b6
--- /dev/null
+++ b/apps/openmw/mwstate/statemanagerimp.hpp
@@ -0,0 +1,70 @@
+#ifndef GAME_STATE_STATEMANAGER_H
+#define GAME_STATE_STATEMANAGER_H
+
+#include <map>
+
+#include "../mwbase/statemanager.hpp"
+
+#include <boost/filesystem/path.hpp>
+
+#include "charactermanager.hpp"
+
+namespace MWState
+{
+ class StateManager : public MWBase::StateManager
+ {
+ bool mQuitRequest;
+ bool mAskLoadRecent;
+ State mState;
+ CharacterManager mCharacterManager;
+ double mTimePlayed;
+
+ private:
+
+ void cleanup (bool force = false);
+
+ std::map<int, int> buildContentFileIndexMap (const ESM::ESMReader& reader) const;
+
+ public:
+
+ StateManager (const boost::filesystem::path& saves, const std::string& game);
+
+ virtual void requestQuit();
+
+ virtual bool hasQuitRequest() const;
+
+ virtual void askLoadRecent();
+
+ virtual State getState() const;
+
+ virtual void newGame (bool bypass = false);
+ ///< Start a new game.
+ ///
+ /// \param bypass Skip new game mechanics.
+
+ virtual void endGame();
+
+ virtual void saveGame (const std::string& description, const Slot *slot = 0);
+ ///< Write a saved game to \a slot or create a new slot if \a slot == 0.
+ ///
+ /// \note Slot must belong to the current character.
+
+ virtual void loadGame (const Character *character, const Slot *slot);
+ ///< Load a saved game file from \a slot.
+ ///
+ /// \note \a slot must belong to \a character.
+
+ virtual Character *getCurrentCharacter (bool create = true);
+ ///< \param create Create a new character, if there is no current character.
+
+ virtual CharacterIterator characterBegin();
+ ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned
+ /// iterator.
+
+ virtual CharacterIterator characterEnd();
+
+ virtual void update (float duration);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp
index 0d091e7425..2a1b7a3aa4 100644
--- a/apps/openmw/mwworld/actionequip.cpp
+++ b/apps/openmw/mwworld/actionequip.cpp
@@ -19,12 +19,12 @@ namespace MWWorld
void ActionEquip::executeImp (const Ptr& actor)
{
MWWorld::Ptr object = getTarget();
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
+ MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
- std::pair <int, std::string> result = MWWorld::Class::get (object).canBeEquipped (object, actor);
+ std::pair <int, std::string> result = object.getClass().canBeEquipped (object, actor);
// display error message if the player tried to equip something
- if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
+ if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->messageBox(result.second);
switch(result.first)
@@ -54,8 +54,6 @@ namespace MWWorld
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)
@@ -68,7 +66,6 @@ namespace MWWorld
if (slot == --slots_.first.end())
{
invStore.equip(*slot, it, actor);
- equipped = true;
break;
}
@@ -76,15 +73,8 @@ namespace MWWorld
{
// slot is not occupied
invStore.equip(*slot, it, actor);
- 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/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp
index 728b6e32bf..e9d8b47161 100644
--- a/apps/openmw/mwworld/actionopen.cpp
+++ b/apps/openmw/mwworld/actionopen.cpp
@@ -5,6 +5,8 @@
#include "../mwgui/container.hpp"
+#include "../mwmechanics/disease.hpp"
+
#include "class.hpp"
#include "containerstore.hpp"
@@ -21,6 +23,8 @@ namespace MWWorld
if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
return;
+ MWMechanics::diseaseContact(actor, getTarget());
+
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container);
MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget(), mLoot);
}
diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp
index 6d5d9d8fde..67755259e0 100644
--- a/apps/openmw/mwworld/actionread.cpp
+++ b/apps/openmw/mwworld/actionread.cpp
@@ -34,7 +34,7 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->getBookWindow()->open(getTarget());
}
- MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
// Skill gain from books
diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp
index d3c4aa2f63..548a949819 100644
--- a/apps/openmw/mwworld/actiontake.cpp
+++ b/apps/openmw/mwworld/actiontake.cpp
@@ -4,6 +4,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "class.hpp"
#include "containerstore.hpp"
@@ -14,11 +15,9 @@ namespace MWWorld
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().getMechanicsManager()->itemTaken(
+ actor, getTarget(), getTarget().getRefData().getCount());
+ actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor);
MWBase::Environment::get().getWorld()->deleteObject (getTarget());
}
}
diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp
index 773fde81e3..b4c572ba94 100644
--- a/apps/openmw/mwworld/actionteleport.cpp
+++ b/apps/openmw/mwworld/actionteleport.cpp
@@ -3,6 +3,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
+#include "../mwbase/mechanicsmanager.hpp"
#include "player.hpp"
namespace MWWorld
@@ -16,11 +17,35 @@ namespace MWWorld
void ActionTeleport::executeImp (const Ptr& actor)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
- world->getPlayer().setTeleported(true);
- if (mCellName.empty())
- world->changeToExteriorCell (mPosition);
+ //find any NPC that is following the actor and teleport him too
+ std::list<MWWorld::Ptr> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor);
+ for(std::list<MWWorld::Ptr>::iterator it = followers.begin();it != followers.end();it++)
+ {
+ std::cout << "teleporting someone!" << (*it).getCellRef().mRefID;
+ executeImp(*it);
+ }
+
+ if(actor == world->getPlayerPtr())
+ {
+ world->getPlayer().setTeleported(true);
+ if (mCellName.empty())
+ world->changeToExteriorCell (mPosition);
+ else
+ world->changeToInteriorCell (mCellName, mPosition);
+ }
else
- world->changeToInteriorCell (mCellName, mPosition);
+ {
+ if (mCellName.empty())
+ {
+ int cellX;
+ int cellY;
+ world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY);
+ world->moveObject(actor,*world->getExterior(cellX,cellY),
+ mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
+ }
+ else
+ world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
+ }
}
}
diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp
index d723b98239..bcefb0181b 100644
--- a/apps/openmw/mwworld/actiontrap.cpp
+++ b/apps/openmw/mwworld/actiontrap.cpp
@@ -8,6 +8,7 @@ namespace MWWorld
void ActionTrap::executeImp(const Ptr &actor)
{
MWMechanics::CastSpell cast(mTrapSource, actor);
+ cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos);
cast.cast(mSpellId);
mTrapSource.getCellRef().mTrap = "";
diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp
index 865c0d01fc..965c9fc5de 100644
--- a/apps/openmw/mwworld/cells.cpp
+++ b/apps/openmw/mwworld/cells.cpp
@@ -1,5 +1,10 @@
#include "cells.hpp"
+#include <components/esm/esmreader.hpp>
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/defs.hpp>
+#include <components/esm/cellstate.hpp>
+
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -7,28 +12,29 @@
#include "esmstore.hpp"
#include "containerstore.hpp"
-MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell)
+MWWorld::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));
+ std::string lowerName(Misc::StringUtils::lowerCase(cell->mName));
+ std::map<std::string, CellStore>::iterator result = mInteriors.find (lowerName);
if (result==mInteriors.end())
{
- result = mInteriors.insert (std::make_pair (Misc::StringUtils::lowerCase(cell->mName), Ptr::CellStore (cell))).first;
+ result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first;
}
return &result->second;
}
else
{
- std::map<std::pair<int, int>, Ptr::CellStore>::iterator result =
+ std::map<std::pair<int, int>, 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;
+ std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell))).first;
}
@@ -40,11 +46,11 @@ void MWWorld::Cells::clear()
{
mInteriors.clear();
mExteriors.clear();
- std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::Ptr::CellStore*)0));
+ std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)0));
mIdCacheIndex = 0;
}
-MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellStore& cellStore)
+MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& cellStore)
{
Ptr ptr = getPtr (name, cellStore);
@@ -59,15 +65,39 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS
return ptr;
}
+void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, const CellStore& cell) const
+{
+ ESM::CellState cellState;
+
+ cell.saveState (cellState);
+
+ writer.startRecord (ESM::REC_CSTA);
+ cellState.mId.save (writer);
+ cellState.save (writer);
+ cell.writeReferences (writer);
+ writer.endRecord (ESM::REC_CSTA);
+}
+
+bool MWWorld::Cells::hasState (const CellStore& cellStore) const
+{
+ if (cellStore.mState==CellStore::State_Loaded)
+ return true;
+
+ if (cellStore.mCell->mData.mFlags & ESM::Cell::Interior)
+ return cellStore.mCell->mData.mFlags & ESM::Cell::HasWater;
+ else
+ return false;
+}
+
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
+ mIdCache (40, std::pair<std::string, CellStore *> ("", (CellStore*)0)), /// \todo make cache size configurable
mIdCacheIndex (0)
{}
-MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
+MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y)
{
- std::map<std::pair<int, int>, Ptr::CellStore>::iterator result =
+ std::map<std::pair<int, int>, CellStore>::iterator result =
mExteriors.find (std::make_pair (x, y));
if (result==mExteriors.end())
@@ -79,7 +109,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
// Cell isn't predefined. Make one on the fly.
ESM::Cell record;
- record.mData.mFlags = 0;
+ record.mData.mFlags = ESM::Cell::HasWater;
record.mData.mX = x;
record.mData.mY = y;
record.mWater = 0;
@@ -92,7 +122,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
std::make_pair (x, y), CellStore (cell))).first;
}
- if (result->second.mState!=Ptr::CellStore::State_Loaded)
+ if (result->second.mState!=CellStore::State_Loaded)
{
// Multiple plugin support for landscape data is much easier than for references. The last plugin wins.
result->second.load (mStore, mReader);
@@ -101,19 +131,19 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
return &result->second;
}
-MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name)
+MWWorld::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);
+ std::map<std::string, 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;
+ result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first;
}
- if (result->second.mState!=Ptr::CellStore::State_Loaded)
+ if (result->second.mState!=CellStore::State_Loaded)
{
result->second.load (mStore, mReader);
}
@@ -121,13 +151,21 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name)
return &result->second;
}
-MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& cell,
+MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id)
+{
+ if (id.mPaged)
+ return getExterior (id.mIndex.mX, id.mIndex.mY);
+
+ return getInterior (id.mWorldspace);
+}
+
+MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell,
bool searchInContainers)
{
- if (cell.mState==Ptr::CellStore::State_Unloaded)
+ if (cell.mState==CellStore::State_Unloaded)
cell.preload (mStore, mReader);
- if (cell.mState==Ptr::CellStore::State_Preloaded)
+ if (cell.mState==CellStore::State_Preloaded)
{
if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), name))
{
@@ -206,7 +244,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& ce
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());
+ for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin());
iter!=mIdCache.end(); ++iter)
if (iter->first==name && iter->second)
{
@@ -216,7 +254,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
}
// Then check cells that are already listed
- for (std::map<std::pair<int, int>, Ptr::CellStore>::iterator iter = mExteriors.begin();
+ for (std::map<std::pair<int, int>, CellStore>::iterator iter = mExteriors.begin();
iter!=mExteriors.end(); ++iter)
{
Ptr ptr = getPtrAndCache (name, iter->second);
@@ -224,7 +262,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
return ptr;
}
- for (std::map<std::string, Ptr::CellStore>::iterator iter = mInteriors.begin();
+ for (std::map<std::string, CellStore>::iterator iter = mInteriors.begin();
iter!=mInteriors.end(); ++iter)
{
Ptr ptr = getPtrAndCache (name, iter->second);
@@ -238,7 +276,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
for (iter = cells.extBegin(); iter != cells.extEnd(); ++iter)
{
- Ptr::CellStore *cellStore = getCellStore (&(*iter));
+ CellStore *cellStore = getCellStore (&(*iter));
Ptr ptr = getPtrAndCache (name, *cellStore);
@@ -248,7 +286,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
for (iter = cells.intBegin(); iter != cells.intEnd(); ++iter)
{
- Ptr::CellStore *cellStore = getCellStore (&(*iter));
+ CellStore *cellStore = getCellStore (&(*iter));
Ptr ptr = getPtrAndCache (name, *cellStore);
@@ -262,7 +300,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector<MWWorld::Ptr> &out)
{
- for (std::map<std::pair<int, int>, Ptr::CellStore>::iterator iter = mExteriors.begin();
+ for (std::map<std::pair<int, int>, CellStore>::iterator iter = mExteriors.begin();
iter!=mExteriors.end(); ++iter)
{
Ptr ptr = getPtrAndCache (name, iter->second);
@@ -271,3 +309,79 @@ void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector<MWWorl
}
}
+
+void MWWorld::Cells::getInteriorPtrs(const std::string &name, std::vector<MWWorld::Ptr> &out)
+{
+ for (std::map<std::string, CellStore>::iterator iter = mInteriors.begin();
+ iter!=mInteriors.end(); ++iter)
+ {
+ Ptr ptr = getPtrAndCache (name, iter->second);
+ if (!ptr.isEmpty())
+ out.push_back(ptr);
+ }
+
+}
+
+int MWWorld::Cells::countSavedGameRecords() const
+{
+ int count = 0;
+
+ for (std::map<std::string, CellStore>::const_iterator iter (mInteriors.begin());
+ iter!=mInteriors.end(); ++iter)
+ if (hasState (iter->second))
+ ++count;
+
+ for (std::map<std::pair<int, int>, CellStore>::const_iterator iter (mExteriors.begin());
+ iter!=mExteriors.end(); ++iter)
+ if (hasState (iter->second))
+ ++count;
+
+ return count;
+}
+
+void MWWorld::Cells::write (ESM::ESMWriter& writer) const
+{
+ for (std::map<std::pair<int, int>, CellStore>::const_iterator iter (mExteriors.begin());
+ iter!=mExteriors.end(); ++iter)
+ if (hasState (iter->second))
+ writeCell (writer, iter->second);
+
+ for (std::map<std::string, CellStore>::const_iterator iter (mInteriors.begin());
+ iter!=mInteriors.end(); ++iter)
+ if (hasState (iter->second))
+ writeCell (writer, iter->second);
+}
+
+bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type,
+ const std::map<int, int>& contentFileMap)
+{
+ if (type==ESM::REC_CSTA)
+ {
+ ESM::CellState state;
+ state.mId.load (reader);
+
+ CellStore *cellStore = 0;
+
+ try
+ {
+ cellStore = getCell (state.mId);
+ }
+ catch (...)
+ {
+ // silently drop cells that don't exist anymore
+ /// \todo log
+ }
+
+ state.load (reader);
+ cellStore->loadState (state);
+
+ if (cellStore->mState!=CellStore::State_Loaded)
+ cellStore->load (mStore, mReader);
+
+ cellStore->readReferences (reader, contentFileMap);
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp
index 31de2f60e8..7ee8a3f6c2 100644
--- a/apps/openmw/mwworld/cells.hpp
+++ b/apps/openmw/mwworld/cells.hpp
@@ -10,6 +10,8 @@
namespace ESM
{
class ESMReader;
+ class ESMWriter;
+ struct CellId;
}
namespace MWWorld
@@ -33,18 +35,23 @@ namespace MWWorld
Ptr getPtrAndCache (const std::string& name, CellStore& cellStore);
+ void writeCell (ESM::ESMWriter& writer, const CellStore& cell) const;
+
+ bool hasState (const CellStore& cellStore) const;
+ ///< Check if cell has state that needs to be included in a saved game file.
+
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);
+ CellStore *getCell (const ESM::CellId& id);
+
Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false);
///< \param searchInContainers Only affect loaded cells.
/// @note name must be lower case
@@ -56,6 +63,18 @@ namespace MWWorld
/// @note Due to the current implementation of getPtr this only supports one Ptr per cell.
/// @note name must be lower case
void getExteriorPtrs (const std::string& name, std::vector<MWWorld::Ptr>& out);
+
+ /// Get all Ptrs referencing \a name in interior cells
+ /// @note Due to the current implementation of getPtr this only supports one Ptr per cell.
+ /// @note name must be lower case
+ void getInteriorPtrs (const std::string& name, std::vector<MWWorld::Ptr>& out);
+
+ int countSavedGameRecords() const;
+
+ void write (ESM::ESMWriter& writer) const;
+
+ bool readRecord (ESM::ESMReader& reader, int32_t type,
+ const std::map<int, int>& contentFileMap);
};
}
diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp
index 0c145ab600..42c954afb3 100644
--- a/apps/openmw/mwworld/cellstore.cpp
+++ b/apps/openmw/mwworld/cellstore.cpp
@@ -2,6 +2,15 @@
#include <iostream>
+#include <components/esm/cellstate.hpp>
+#include <components/esm/cellid.hpp>
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/objectstate.hpp>
+#include <components/esm/lightstate.hpp>
+#include <components/esm/containerstate.hpp>
+#include <components/esm/npcstate.hpp>
+#include <components/esm/creaturestate.hpp>
+
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@@ -29,38 +38,99 @@ namespace
return MWWorld::Ptr();
}
+
+ template<typename RecordType, typename T>
+ void writeReferenceCollection (ESM::ESMWriter& writer,
+ const MWWorld::CellRefList<T>& collection)
+ {
+ if (!collection.mList.empty())
+ {
+ // references
+ for (typename MWWorld::CellRefList<T>::List::const_iterator
+ iter (collection.mList.begin());
+ iter!=collection.mList.end(); ++iter)
+ {
+ if (iter->mData.getCount()==0 && iter->mRef.mRefNum.mContentFile==-1)
+ continue; // deleted file that did not came from a content file -> ignore
+
+ RecordType state;
+ iter->save (state);
+
+ writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId);
+ state.save (writer);
+ }
+ }
+ }
+
+ template<typename RecordType, typename T>
+ void readReferenceCollection (ESM::ESMReader& reader,
+ MWWorld::CellRefList<T>& collection, const std::map<int, int>& contentFileMap)
+ {
+ const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
+
+ RecordType state;
+ state.load (reader);
+
+ std::map<int, int>::const_iterator iter =
+ contentFileMap.find (state.mRef.mRefNum.mContentFile);
+
+ if (iter==contentFileMap.end())
+ return; // content file has been removed -> skip
+
+ state.mRef.mRefNum.mContentFile = iter->second;
+
+ if (!MWWorld::LiveCellRef<T>::checkState (state))
+ return; // not valid anymore with current content files -> skip
+
+ const T *record = esmStore.get<T>().search (state.mRef.mRefID);
+
+ if (!record)
+ return;
+
+ for (typename MWWorld::CellRefList<T>::List::iterator iter (collection.mList.begin());
+ iter!=collection.mList.end(); ++iter)
+ if (iter->mRef.mRefNum==state.mRef.mRefNum)
+ {
+ // overwrite existing reference
+ iter->load (state);
+ return;
+ }
+
+ // new reference
+ MWWorld::LiveCellRef<T> ref (record);
+ ref.load (state);
+ collection.mList.push_back (ref);
+ }
}
namespace MWWorld
{
template <typename X>
- void CellRefList<X>::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
+ void CellRefList<X>::load(ESM::CellRef &ref, bool deleted, 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);
+ const MWWorld::Store<X> &store = esmStore.get<X>();
+
+ if (const X *ptr = store.search (ref.mRefID))
+ {
+ typename std::list<LiveRef>::iterator iter =
+ std::find(mList.begin(), mList.end(), ref.mRefNum);
+
+ LiveRef liveCellRef (ref, ptr);
+
+ if (deleted)
+ liveCellRef.mData.setCount (0);
- // 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;
+ *iter = liveCellRef;
+ else
+ mList.push_back (liveCellRef);
}
-
- // 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));
+ else
+ {
+ std::cerr
+ << "Error: could not resolve cell reference " << ref.mRefID
+ << " (dropping reference)" << std::endl;
}
}
@@ -117,16 +187,13 @@ namespace MWWorld
ESM::CellRef ref;
// Get each reference in turn
- while (mCell->getNextRef (esm[index], ref))
+ bool deleted = false;
+ while (mCell->getNextRef (esm[index], ref, deleted))
{
- 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...
+ if (deleted)
continue;
- }
- mIds.push_back (lowerCase);
+ mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID));
}
}
@@ -135,7 +202,7 @@ namespace MWWorld
void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{
- assert (mCell);
+ assert (mCell);
if (mCell->mContextList.empty())
return; // this is a dynamically generated cell -> skipping.
@@ -148,104 +215,30 @@ namespace MWWorld
mCell->restore (esm[index], i);
ESM::CellRef ref;
+ ref.mRefNum.mContentFile = -1;
// Get each reference in turn
- while(mCell->getNextRef(esm[index], ref))
+ bool deleted = false;
+ while(mCell->getNextRef(esm[index], ref, deleted))
{
// 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);
+ 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";
- }
+
+ loadRef (ref, deleted, store);
}
}
// 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";
- }
+ loadRef (ref, false, store);
}
}
@@ -274,4 +267,198 @@ namespace MWWorld
return Ptr();
}
+
+ void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store)
+ {
+ Misc::StringUtils::toLower (ref.mRefID);
+
+ switch (store.find (ref.mRefID))
+ {
+ case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break;
+ case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break;
+ case ESM::REC_APPA: mAppas.load(ref, deleted, store); break;
+ case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break;
+ case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break;
+ case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break;
+ case ESM::REC_CONT: mContainers.load(ref, deleted, store); break;
+ case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break;
+ case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break;
+ case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break;
+ case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break;
+ case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break;
+ case ESM::REC_LIGH: mLights.load(ref, deleted, store); break;
+ case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break;
+ case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break;
+ case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break;
+ case ESM::REC_PROB: mProbes.load(ref, deleted, store); break;
+ case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break;
+ case ESM::REC_STAT: mStatics.load(ref, deleted, store); break;
+ case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break;
+
+ case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break;
+
+ default:
+ std::cerr
+ << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
+ }
+ }
+
+ void CellStore::loadState (const ESM::CellState& state)
+ {
+ if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
+ mWaterLevel = state.mWaterLevel;
+
+ mWaterLevel = state.mWaterLevel;
+ }
+
+ void CellStore::saveState (ESM::CellState& state) const
+ {
+ state.mId = mCell->getCellId();
+
+ if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
+ state.mWaterLevel = mWaterLevel;
+
+ state.mWaterLevel = mWaterLevel;
+ }
+
+ void CellStore::writeReferences (ESM::ESMWriter& writer) const
+ {
+ writeReferenceCollection<ESM::ObjectState> (writer, mActivators);
+ writeReferenceCollection<ESM::ObjectState> (writer, mPotions);
+ writeReferenceCollection<ESM::ObjectState> (writer, mAppas);
+ writeReferenceCollection<ESM::ObjectState> (writer, mArmors);
+ writeReferenceCollection<ESM::ObjectState> (writer, mBooks);
+ writeReferenceCollection<ESM::ObjectState> (writer, mClothes);
+ writeReferenceCollection<ESM::ContainerState> (writer, mContainers);
+ writeReferenceCollection<ESM::CreatureState> (writer, mCreatures);
+ writeReferenceCollection<ESM::ObjectState> (writer, mDoors);
+ writeReferenceCollection<ESM::ObjectState> (writer, mIngreds);
+ writeReferenceCollection<ESM::ObjectState> (writer, mCreatureLists);
+ writeReferenceCollection<ESM::ObjectState> (writer, mItemLists);
+ writeReferenceCollection<ESM::LightState> (writer, mLights);
+ writeReferenceCollection<ESM::ObjectState> (writer, mLockpicks);
+ writeReferenceCollection<ESM::ObjectState> (writer, mMiscItems);
+ writeReferenceCollection<ESM::NpcState> (writer, mNpcs);
+ writeReferenceCollection<ESM::ObjectState> (writer, mProbes);
+ writeReferenceCollection<ESM::ObjectState> (writer, mRepairs);
+ writeReferenceCollection<ESM::ObjectState> (writer, mStatics);
+ writeReferenceCollection<ESM::ObjectState> (writer, mWeapons);
+ }
+
+ void CellStore::readReferences (ESM::ESMReader& reader,
+ const std::map<int, int>& contentFileMap)
+ {
+ while (reader.isNextSub ("OBJE"))
+ {
+ unsigned int id = 0;
+ reader.getHT (id);
+
+ switch (id)
+ {
+ case ESM::REC_ACTI:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mActivators, contentFileMap);
+ break;
+
+ case ESM::REC_ALCH:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mPotions, contentFileMap);
+ break;
+
+ case ESM::REC_APPA:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mAppas, contentFileMap);
+ break;
+
+ case ESM::REC_ARMO:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mArmors, contentFileMap);
+ break;
+
+ case ESM::REC_BOOK:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mBooks, contentFileMap);
+ break;
+
+ case ESM::REC_CLOT:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mClothes, contentFileMap);
+ break;
+
+ case ESM::REC_CONT:
+
+ readReferenceCollection<ESM::ContainerState> (reader, mContainers, contentFileMap);
+ break;
+
+ case ESM::REC_CREA:
+
+ readReferenceCollection<ESM::CreatureState> (reader, mCreatures, contentFileMap);
+ break;
+
+ case ESM::REC_DOOR:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mDoors, contentFileMap);
+ break;
+
+ case ESM::REC_INGR:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mIngreds, contentFileMap);
+ break;
+
+ case ESM::REC_LEVC:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mCreatureLists, contentFileMap);
+ break;
+
+ case ESM::REC_LEVI:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mItemLists, contentFileMap);
+ break;
+
+ case ESM::REC_LIGH:
+
+ readReferenceCollection<ESM::LightState> (reader, mLights, contentFileMap);
+ break;
+
+ case ESM::REC_LOCK:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mLockpicks, contentFileMap);
+ break;
+
+ case ESM::REC_MISC:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mMiscItems, contentFileMap);
+ break;
+
+ case ESM::REC_NPC_:
+
+ readReferenceCollection<ESM::NpcState> (reader, mNpcs, contentFileMap);
+ break;
+
+ case ESM::REC_PROB:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mProbes, contentFileMap);
+ break;
+
+ case ESM::REC_REPA:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mRepairs, contentFileMap);
+ break;
+
+ case ESM::REC_STAT:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mStatics, contentFileMap);
+ break;
+
+ case ESM::REC_WEAP:
+
+ readReferenceCollection<ESM::ObjectState> (reader, mWeapons, contentFileMap);
+ break;
+
+ default:
+
+ throw std::runtime_error ("unknown type in cell reference section");
+ }
+ }
+ }
}
diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp
index 8731c42dc6..a4f2190135 100644
--- a/apps/openmw/mwworld/cellstore.hpp
+++ b/apps/openmw/mwworld/cellstore.hpp
@@ -7,6 +7,11 @@
#include "livecellref.hpp"
#include "esmstore.hpp"
+namespace ESM
+{
+ struct CellState;
+}
+
namespace MWWorld
{
@@ -25,7 +30,7 @@ namespace MWWorld
// 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);
+ void load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore);
LiveRef *find (const std::string& name)
{
@@ -133,6 +138,14 @@ namespace MWWorld
Ptr searchInContainer (const std::string& id);
+ void loadState (const ESM::CellState& state);
+
+ void saveState (ESM::CellState& state) const;
+
+ void writeReferences (ESM::ESMWriter& writer) const;
+
+ void readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap);
+
private:
template<class Functor, class List>
@@ -154,6 +167,10 @@ namespace MWWorld
void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
+ void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store);
+ ///< Make case-adjustments to \a ref and insert it into the respective container.
+ ///
+ /// Invalid \a ref objects are silently dropped.
};
}
diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp
index ffe81a4ac8..2110086d37 100644
--- a/apps/openmw/mwworld/class.cpp
+++ b/apps/openmw/mwworld/class.cpp
@@ -92,6 +92,11 @@ namespace MWWorld
throw std::runtime_error("class cannot hit");
}
+ void Class::block(const Ptr &ptr) const
+ {
+ throw std::runtime_error("class cannot block");
+ }
+
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");
@@ -122,6 +127,11 @@ namespace MWWorld
throw std::runtime_error ("class does not have an inventory store");
}
+ bool Class::hasInventoryStore(const Ptr &ptr) const
+ {
+ return false;
+ }
+
void Class::lock (const Ptr& ptr, int lockLevel) const
{
throw std::runtime_error ("class does not support locking");
@@ -147,21 +157,6 @@ namespace MWWorld
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;
@@ -172,7 +167,7 @@ namespace MWWorld
return 0;
}
- float Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
+ int Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const
{
throw std::runtime_error ("class does not support enchanting");
}
@@ -232,11 +227,6 @@ namespace MWWorld
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");
@@ -372,4 +362,23 @@ namespace MWWorld
return newPtr;
}
+
+ bool Class::isFlying(const Ptr &ptr) const
+ {
+ return false;
+ }
+
+ int Class::getSkill(const MWWorld::Ptr& ptr, int skill) const
+ {
+ throw std::runtime_error("class does not support skills");
+ }
+
+ int Class::getBloodTexture (const MWWorld::Ptr& ptr) const
+ {
+ throw std::runtime_error("class does not support gore");
+ }
+
+ void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {}
+
+ void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {}
}
diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp
index d737c18a22..ad2cc3af47 100644
--- a/apps/openmw/mwworld/class.hpp
+++ b/apps/openmw/mwworld/class.hpp
@@ -9,6 +9,11 @@
#include "ptr.hpp"
+namespace ESM
+{
+ struct ObjectState;
+}
+
namespace Ogre
{
class Vector3;
@@ -70,7 +75,7 @@ namespace MWWorld
/// NPC-stances.
enum Stance
{
- Run, Sneak, Combat
+ Run, Sneak
};
virtual ~Class();
@@ -128,6 +133,10 @@ namespace MWWorld
/// actor responsible for the attack, and \a successful specifies if the hit is
/// successful or not.
+ virtual void block (const Ptr& ptr) const;
+ ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield
+ /// (default implementation: throw an exception)
+
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
@@ -150,6 +159,9 @@ namespace MWWorld
///< Return inventory store or throw an exception, if class does not have a
/// inventory store (default implementation: throw an exceoption)
+ virtual bool hasInventoryStore (const Ptr& ptr) const;
+ ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false)
+
virtual void lock (const Ptr& ptr, int lockLevel) const;
///< Lock object (default implementation: throw an exception)
@@ -168,15 +180,6 @@ namespace MWWorld
///< 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.
@@ -240,11 +243,6 @@ namespace MWWorld
///
/// (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)
@@ -266,7 +264,7 @@ namespace MWWorld
///< @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;
+ virtual int 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;
@@ -292,6 +290,9 @@ namespace MWWorld
virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; }
+ /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
+ virtual int getBloodTexture (const MWWorld::Ptr& ptr) const;
+
virtual Ptr
copyToCell(const Ptr &ptr, CellStore &cell) const;
@@ -306,6 +307,18 @@ namespace MWWorld
return false;
}
+ virtual bool isFlying(const MWWorld::Ptr& ptr) const;
+
+ virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;
+
+ virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
+ const;
+ ///< Read additional state from \a state into \a ptr.
+
+ virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
+ const;
+ ///< Write additional state from \a ptr into \a state.
+
static const Class& get (const std::string& key);
///< If there is no class for this \a key, an exception is thrown.
diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp
index 686e790a38..bd0704724f 100644
--- a/apps/openmw/mwworld/containerstore.cpp
+++ b/apps/openmw/mwworld/containerstore.cpp
@@ -5,17 +5,13 @@
#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 <components/esm/inventorystate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
-#include "../mwbase/scriptmanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
+#include "../mwmechanics/levelledlist.hpp"
#include "manualref.hpp"
#include "refdata.hpp"
@@ -51,7 +47,7 @@ namespace
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
iter!=list.mList.end(); ++iter)
{
- if (Misc::StringUtils::lowerCase (iter->mBase->mId)==id2)
+ if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2))
{
MWWorld::Ptr ptr (&*iter, 0);
ptr.setContainerStore (store);
@@ -63,6 +59,56 @@ namespace
}
}
+template<typename T>
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection,
+ const ESM::ObjectState& state)
+{
+ if (!LiveCellRef<T>::checkState (state))
+ return ContainerStoreIterator (this); // not valid anymore with current content files -> skip
+
+ const T *record = MWBase::Environment::get().getWorld()->getStore().
+ get<T>().search (state.mRef.mRefID);
+
+ if (!record)
+ return ContainerStoreIterator (this);
+
+ LiveCellRef<T> ref (record);
+ ref.load (state);
+ ref.mRef.mRefNum.mContentFile = -1;
+ collection.mList.push_back (ref);
+
+ return ContainerStoreIterator (this, --collection.mList.end());
+}
+
+template<typename T>
+void MWWorld::ContainerStore::storeState (const LiveCellRef<T>& ref, ESM::ObjectState& state) const
+{
+ ref.save (state);
+}
+
+template<typename T>
+void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection,
+ std::vector<std::pair<ESM::ObjectState, std::pair<unsigned int, int> > >& states, bool equipable) const
+{
+ for (typename CellRefList<T>::List::const_iterator iter (collection.mList.begin());
+ iter!=collection.mList.end(); ++iter)
+ {
+ ESM::ObjectState state;
+ storeState (*iter, state);
+ int slot = equipable ? getSlot (*iter) : -1;
+ states.push_back (std::make_pair (state, std::make_pair (T::sRecordId, slot)));
+ }
+}
+
+int MWWorld::ContainerStore::getSlot (const MWWorld::LiveCellRefBase& ref) const
+{
+ return -1;
+}
+
+void MWWorld::ContainerStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) {}
+
+const std::string MWWorld::ContainerStore::sGoldId = "gold_001";
+
MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {}
MWWorld::ContainerStore::~ContainerStore() {}
@@ -77,11 +123,20 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
return ContainerStoreIterator (this);
}
+int MWWorld::ContainerStore::count(const std::string &id)
+{
+ int total=0;
+ for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
+ if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, id))
+ total += iter->getRefData().getCount();
+ return total;
+}
+
void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container)
{
if (ptr.getRefData().getCount() <= 1)
return;
- addNewStack(ptr)->getRefData().setCount(ptr.getRefData().getCount()-1);
+ addNewStack(ptr, ptr.getRefData().getCount()-1);
remove(ptr, ptr.getRefData().getCount()-1, container);
}
@@ -123,12 +178,16 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr)
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count);
- return add(ref.getPtr(), actorPtr);
+ // a bit pointless to set owner for the player
+ if (actorPtr.getRefData().getHandle() != "player")
+ return add(ref.getPtr(), count, actorPtr, true);
+ else
+ return add(ref.getPtr(), count, actorPtr, false);
}
-MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr)
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner)
{
- MWWorld::ContainerStoreIterator it = addImp(itemPtr);
+ MWWorld::ContainerStoreIterator it = addImp(itemPtr, count);
MWWorld::Ptr item = *it;
// we may have copied an item from the world, so reset a few things first
@@ -140,12 +199,15 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
item.getCellRef().mPos.pos[1] = 0;
item.getCellRef().mPos.pos[2] = 0;
+ if (setOwner && actorPtr.getClass().isActor())
+ item.getCellRef().mOwner = actorPtr.getCellRef().mRefID;
+
std::string script = MWWorld::Class::get(item).getScript(item);
if(script != "")
{
CellStore *cell;
- Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
+ Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
if(&(MWWorld::Class::get (player).getContainerStore (player)) == this)
{
@@ -165,7 +227,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
return it;
}
-MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count)
{
int type = getType(ptr);
@@ -180,20 +242,20 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
|| Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025")
|| Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100"))
{
- int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount();
+ int realCount = count * ptr.getClass().getValue(ptr);
for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter)
{
- if (Misc::StringUtils::ciEqual((*iter).get<ESM::Miscellaneous>()->mRef.mRefID, "gold_001"))
+ if (Misc::StringUtils::ciEqual((*iter).getCellRef().mRefID, MWWorld::ContainerStore::sGoldId))
{
- iter->getRefData().setCount(iter->getRefData().getCount() + count);
+ iter->getRefData().setCount(iter->getRefData().getCount() + realCount);
flagAsModified();
return iter;
}
}
- MWWorld::ManualRef ref(esmStore, "Gold_001", count);
- return addNewStack(ref.getPtr());
+ MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount);
+ return addNewStack(ref.getPtr(), realCount);
}
// determine whether to stack or not
@@ -202,17 +264,17 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr)
if (stacks(*iter, ptr))
{
// stack
- iter->getRefData().setCount( iter->getRefData().getCount() + ptr.getRefData().getCount() );
+ iter->getRefData().setCount( iter->getRefData().getCount() + count );
flagAsModified();
return iter;
}
}
// if we got here, this means no stacking
- return addNewStack(ptr);
+ return addNewStack(ptr, count);
}
-MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr)
+MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr, int count)
{
ContainerStoreIterator it = begin();
@@ -232,6 +294,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr&
case Type_Weapon: weapons.mList.push_back (*ptr.get<ESM::Weapon>()); it = ContainerStoreIterator(this, --weapons.mList.end()); break;
}
+ it->getRefData().setCount(count);
+
flagAsModified();
return it;
}
@@ -274,83 +338,48 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
return count - toRemove;
}
-void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store)
+void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, 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);
+ addInitialItem(id, owner, faction, iter->mCount);
}
flagAsModified();
}
-void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance, bool topLevel)
+void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, const std::string& faction,
+ int count, bool topLevel)
{
count = std::abs(count); /// \todo implement item restocking (indicated by negative count)
- try
+ ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
+
+ if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name())
{
- ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
+ const ESM::ItemLevList* levItem = ref.getPtr().get<ESM::ItemLevList>()->mBase;
- if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name())
+ if (topLevel && count > 1 && levItem->mFlags & ESM::ItemLevList::Each)
{
- 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);
- }
+ for (int i=0; i<count; ++i)
+ addInitialItem(id, owner, faction, 1);
+ return;
}
else
{
- ref.getPtr().getCellRef().mOwner = owner;
- addImp (ref.getPtr());
+ std::string id = MWMechanics::getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false);
+ if (id.empty())
+ return;
+ addInitialItem(id, owner, faction, count, false);
}
}
- catch (std::logic_error& e)
+ else
{
- // Vanilla doesn't fail on nonexistent items in levelled lists
- std::cerr << "Warning: ignoring nonexistent item '" << id << "'" << std::endl;
- return;
+ ref.getPtr().getCellRef().mOwner = owner;
+ ref.getPtr().getCellRef().mFaction = faction;
+ addImp (ref.getPtr(), count);
}
}
@@ -514,6 +543,69 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
return Ptr();
}
+void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
+{
+ state.mItems.clear();
+
+ storeStates (potions, state.mItems);
+ storeStates (appas, state.mItems);
+ storeStates (armors, state.mItems, true);
+ storeStates (books, state.mItems);
+ storeStates (clothes, state.mItems, true);
+ storeStates (ingreds, state.mItems);
+ storeStates (lockpicks, state.mItems, true);
+ storeStates (miscItems, state.mItems);
+ storeStates (probes, state.mItems, true);
+ storeStates (repairs, state.mItems);
+ storeStates (weapons, state.mItems, true);
+
+ state.mLights.clear();
+
+ for (MWWorld::CellRefList<ESM::Light>::List::const_iterator iter (lights.mList.begin());
+ iter!=lights.mList.end(); ++iter)
+ {
+ ESM::LightState objectState;
+ storeState (*iter, objectState);
+ state.mLights.push_back (std::make_pair (objectState, getSlot (*iter)));
+ }
+}
+
+void MWWorld::ContainerStore::readState (const ESM::InventoryState& state)
+{
+ clear();
+
+ for (std::vector<std::pair<ESM::ObjectState, std::pair<unsigned int, int> > >::const_iterator
+ iter (state.mItems.begin()); iter!=state.mItems.end(); ++iter)
+ {
+ int slot = iter->second.second;
+
+ switch (iter->second.first)
+ {
+ case ESM::REC_ALCH: getState (potions, iter->first); break;
+ case ESM::REC_APPA: getState (appas, iter->first); break;
+ case ESM::REC_ARMO: setSlot (getState (armors, iter->first), slot); break;
+ case ESM::REC_BOOK: getState (books, iter->first); break;
+ case ESM::REC_CLOT: setSlot (getState (clothes, iter->first), slot); break;
+ case ESM::REC_INGR: getState (ingreds, iter->first); break;
+ case ESM::REC_LOCK: setSlot (getState (lockpicks, iter->first), slot); break;
+ case ESM::REC_MISC: getState (miscItems, iter->first); break;
+ case ESM::REC_PROB: setSlot (getState (probes, iter->first), slot); break;
+ case ESM::REC_REPA: getState (repairs, iter->first); break;
+ case ESM::REC_WEAP: setSlot (getState (weapons, iter->first), slot); break;
+
+ default:
+
+ std::cerr << "invalid item type in inventory state" << std::endl;
+ }
+ }
+
+ for (std::vector<std::pair<ESM::LightState, int> >::const_iterator iter (state.mLights.begin());
+ iter!=state.mLights.end(); ++iter)
+ {
+ getState (lights, iter->first);
+ }
+}
+
MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container)
: mType (-1), mMask (0), mContainer (container)
@@ -555,6 +647,11 @@ MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *contain
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){}
+MWWorld::ContainerStoreIterator::ContainerStoreIterator( const ContainerStoreIterator& src )
+{
+ copy(src);
+}
+
void MWWorld::ContainerStoreIterator::incType()
{
if (mType==0)
@@ -807,6 +904,41 @@ const MWWorld::ContainerStore *MWWorld::ContainerStoreIterator::getContainerStor
return mContainer;
}
+void MWWorld::ContainerStoreIterator::copy(const ContainerStoreIterator& src)
+{
+ mType = src.mType;
+ mMask = src.mMask;
+ mContainer = src.mContainer;
+ mPtr = src.mPtr;
+
+ switch (mType)
+ {
+ case MWWorld::ContainerStore::Type_Potion: mPotion = src.mPotion; break;
+ case MWWorld::ContainerStore::Type_Apparatus: mApparatus = src.mApparatus; break;
+ case MWWorld::ContainerStore::Type_Armor: mArmor = src.mArmor; break;
+ case MWWorld::ContainerStore::Type_Book: mBook = src.mBook; break;
+ case MWWorld::ContainerStore::Type_Clothing: mClothing = src.mClothing; break;
+ case MWWorld::ContainerStore::Type_Ingredient: mIngredient = src.mIngredient; break;
+ case MWWorld::ContainerStore::Type_Light: mLight = src.mLight; break;
+ case MWWorld::ContainerStore::Type_Lockpick: mLockpick = src.mLockpick; break;
+ case MWWorld::ContainerStore::Type_Miscellaneous: mMiscellaneous = src.mMiscellaneous; break;
+ case MWWorld::ContainerStore::Type_Probe: mProbe = src.mProbe; break;
+ case MWWorld::ContainerStore::Type_Repair: mRepair = src.mRepair; break;
+ case MWWorld::ContainerStore::Type_Weapon: mWeapon = src.mWeapon; break;
+ case -1: break;
+ default: assert(0);
+ }
+}
+
+MWWorld::ContainerStoreIterator& MWWorld::ContainerStoreIterator::operator=( const ContainerStoreIterator& rhs )
+{
+ if (this!=&rhs)
+ {
+ copy(rhs);
+ }
+ return *this;
+}
+
bool MWWorld::operator== (const ContainerStoreIterator& left, const ContainerStoreIterator& right)
{
return left.isEqual (right);
diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp
index b34c710063..acf4298918 100644
--- a/apps/openmw/mwworld/containerstore.hpp
+++ b/apps/openmw/mwworld/containerstore.hpp
@@ -8,6 +8,7 @@
namespace ESM
{
struct InventoryList;
+ struct InventoryState;
}
namespace MWWorld
@@ -35,6 +36,8 @@ namespace MWWorld
static const int Type_All = 0xffff;
+ static const std::string sGoldId;
+
private:
MWWorld::CellRefList<ESM::Potion> potions;
@@ -51,8 +54,26 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Weapon> weapons;
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);
+ ContainerStoreIterator addImp (const Ptr& ptr, int count);
+ void addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, int count, bool topLevel=true);
+
+ template<typename T>
+ ContainerStoreIterator getState (CellRefList<T>& collection,
+ const ESM::ObjectState& state);
+
+ template<typename T>
+ void storeState (const LiveCellRef<T>& ref, ESM::ObjectState& state) const;
+
+ template<typename T>
+ void storeStates (const CellRefList<T>& collection,
+ std::vector<std::pair<ESM::ObjectState, std::pair<unsigned int, int> > >& states,
+ bool equipable = false) const;
+
+ virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const;
+ ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot).
+
+ virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot);
+ ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1.
public:
@@ -60,11 +81,13 @@ namespace MWWorld
virtual ~ContainerStore();
+ virtual ContainerStore* clone() { return new ContainerStore(*this); }
+
ContainerStoreIterator begin (int mask = Type_All);
ContainerStoreIterator end();
- virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr);
+ virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
///< 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.
@@ -72,10 +95,12 @@ namespace MWWorld
/// \attention Do not add items to an existing stack by increasing the count instead of
/// calling this function!
///
+ /// @param setOwner Set the owner of the added item to \a actorPtr?
+ ///
/// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item.
ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr);
- ///< Utility to construct a ManualRef and call add(ptr, actorPtr)
+ ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true)
int remove(const std::string& itemId, int count, const Ptr& actor);
///< Remove \a count item(s) designated by \a itemId from this container.
@@ -90,8 +115,11 @@ namespace MWWorld
void unstack (const Ptr& ptr, const Ptr& container);
///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1).
+ /// @return How many items with refID \a id are in this container?
+ int count (const std::string& id);
+
protected:
- ContainerStoreIterator addNewStack (const Ptr& ptr);
+ ContainerStoreIterator addNewStack (const Ptr& ptr, int count);
///< Add the item to this container (do not try to stack it onto existing items)
virtual void flagAsModified();
@@ -101,10 +129,10 @@ namespace MWWorld
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);
+ void fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, const MWWorld::ESMStore& store);
///< Insert items into *this.
- void clear();
+ virtual void clear();
///< Empty container.
float getWeight() const;
@@ -116,6 +144,10 @@ namespace MWWorld
Ptr search (const std::string& id);
+ void writeState (ESM::InventoryState& state) const;
+
+ void readState (const ESM::InventoryState& state);
+
friend class ContainerStoreIterator;
};
@@ -165,6 +197,8 @@ namespace MWWorld
ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Repair>::List::iterator);
ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList<ESM::Weapon>::List::iterator);
+ void copy (const ContainerStoreIterator& src);
+
void incType();
void nextType();
@@ -181,6 +215,8 @@ namespace MWWorld
public:
+ ContainerStoreIterator(const ContainerStoreIterator& src);
+
Ptr *operator->() const;
Ptr operator*() const;
@@ -189,6 +225,8 @@ namespace MWWorld
ContainerStoreIterator operator++ (int);
+ ContainerStoreIterator& operator= (const ContainerStoreIterator& rhs);
+
bool isEqual (const ContainerStoreIterator& iter) const;
int getType() const;
diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp
index f1bff11a28..c5c826d471 100644
--- a/apps/openmw/mwworld/esmstore.cpp
+++ b/apps/openmw/mwworld/esmstore.cpp
@@ -52,7 +52,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
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
+ std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name
+ ", but it has not been loaded yet. Please check your load order.";
esm.fail(fstring);
}
@@ -108,7 +108,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
}
// Insert the reference into the global lookup
if (!id.empty() && isCacheableRecord(n.val)) {
- mIds[id] = n.val;
+ mIds[Misc::StringUtils::lowerCase (id)] = n.val;
}
}
listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000);
@@ -139,4 +139,68 @@ void ESMStore::setUp()
mAttributes.setUp();
}
+ int ESMStore::countSavedGameRecords() const
+ {
+ return
+ mPotions.getDynamicSize()
+ +mArmors.getDynamicSize()
+ +mBooks.getDynamicSize()
+ +mClasses.getDynamicSize()
+ +mClothes.getDynamicSize()
+ +mEnchants.getDynamicSize()
+ +mNpcs.getDynamicSize()
+ +mSpells.getDynamicSize()
+ +mWeapons.getDynamicSize();
+ }
+
+ void ESMStore::write (ESM::ESMWriter& writer) const
+ {
+ mPotions.write (writer);
+ mArmors.write (writer);
+ mBooks.write (writer);
+ mClasses.write (writer);
+ mClothes.write (writer);
+ mEnchants.write (writer);
+ mSpells.write (writer);
+ mWeapons.write (writer);
+ mNpcs.write (writer);
+ }
+
+ bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type)
+ {
+ switch (type)
+ {
+ case ESM::REC_ALCH:
+ case ESM::REC_ARMO:
+ case ESM::REC_BOOK:
+ case ESM::REC_CLAS:
+ case ESM::REC_CLOT:
+ case ESM::REC_ENCH:
+ case ESM::REC_SPEL:
+ case ESM::REC_WEAP:
+ case ESM::REC_NPC_:
+
+ mStores[type]->read (reader);
+
+ if (type==ESM::REC_NPC_)
+ {
+ // NPC record will always be last and we know that there can be only one
+ // dynamic NPC record (player) -> We are done here with dynamic record laoding
+ setUp();
+
+ const ESM::NPC *player = mNpcs.find ("player");
+
+ if (!mRaces.find (player->mRace) ||
+ !mClasses.find (player->mClass))
+ throw std::runtime_error ("Invalid player record (race or class unavilable");
+ }
+
+ return true;
+
+ default:
+
+ return false;
+ }
+ }
+
} // end namespace
diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp
index ebb086cee5..e6730c3074 100644
--- a/apps/openmw/mwworld/esmstore.hpp
+++ b/apps/openmw/mwworld/esmstore.hpp
@@ -24,10 +24,8 @@ namespace MWWorld
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;
@@ -40,7 +38,6 @@ namespace MWWorld
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;
@@ -103,7 +100,7 @@ namespace MWWorld
{
// 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;
@@ -114,10 +111,8 @@ namespace MWWorld
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;
@@ -133,7 +128,6 @@ namespace MWWorld
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;
@@ -215,6 +209,13 @@ namespace MWWorld
// 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();
+
+ int countSavedGameRecords() const;
+
+ void write (ESM::ESMWriter& writer) const;
+
+ bool readRecord (ESM::ESMReader& reader, int32_t type);
+ ///< \return Known type?
};
template <>
@@ -288,11 +289,6 @@ namespace MWWorld
}
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;
}
@@ -303,11 +299,6 @@ namespace MWWorld
}
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;
}
@@ -368,11 +359,6 @@ namespace MWWorld
}
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;
}
diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp
index a905f8aaed..879ffa8e34 100644
--- a/apps/openmw/mwworld/globals.cpp
+++ b/apps/openmw/mwworld/globals.cpp
@@ -3,21 +3,15 @@
#include <stdexcept>
+#include <components/misc/stringops.hpp>
+
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/esmreader.hpp>
+
#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);
@@ -38,122 +32,78 @@ namespace MWWorld
return iter;
}
- Globals::Globals (const MWWorld::ESMStore& store)
+ void Globals::fill (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;
+ mVariables.clear();
- case ESM::VT_Long:
+ const MWWorld::Store<ESM::Global>& globals = store.get<ESM::Global>();
- 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)));
+ for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end();
+ ++iter)
+ {
+ mVariables.insert (std::make_pair (iter->mId, iter->mValue));
}
}
- const Globals::Data& Globals::operator[] (const std::string& name) const
+ const ESM::Variant& Globals::operator[] (const std::string& name) const
{
- Collection::const_iterator iter = find (name);
-
- return iter->second.second;
+ return find (name)->second;
}
- Globals::Data& Globals::operator[] (const std::string& name)
+ ESM::Variant& Globals::operator[] (const std::string& name)
{
- Collection::iterator iter = find (name);
-
- return iter->second.second;
+ return find (name)->second;
}
- void Globals::setInt (const std::string& name, int value)
+ char Globals::getType (const std::string& name) const
{
- Collection::iterator iter = find (name);
+ Collection::const_iterator iter = mVariables.find (name);
- switch (iter->second.first)
+ if (iter==mVariables.end())
+ return ' ';
+
+ switch (iter->second.getType())
{
- case 's': iter->second.second.mShort = value; break;
- case 'l': iter->second.second.mLong = value; break;
- case 'f': iter->second.second.mFloat = value; break;
+ case ESM::VT_Short: return 's';
+ case ESM::VT_Long: return 'l';
+ case ESM::VT_Float: return 'f';
- default: throw std::runtime_error ("unsupported global variable type");
+ default: return ' ';
}
}
- void Globals::setFloat (const std::string& name, float value)
+ int Globals::countSavedGameRecords() const
{
- 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");
- }
+ return mVariables.size();
}
- int Globals::getInt (const std::string& name) const
+ void Globals::write (ESM::ESMWriter& writer) const
{
- Collection::const_iterator iter = find (name);
-
- switch (iter->second.first)
+ for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter)
{
- 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");
+ writer.startRecord (ESM::REC_GLOB);
+ writer.writeHNString ("NAME", iter->first);
+ iter->second.write (writer, ESM::Variant::Format_Global);
+ writer.endRecord (ESM::REC_GLOB);
}
}
- float Globals::getFloat (const std::string& name) const
+ bool Globals::readRecord (ESM::ESMReader& reader, int32_t type)
{
- Collection::const_iterator iter = find (name);
-
- switch (iter->second.first)
+ if (type==ESM::REC_GLOB)
{
- case 's': return iter->second.second.mShort;
- case 'l': return iter->second.second.mLong;
- case 'f': return iter->second.second.mFloat;
+ std::string id = reader.getHNString ("NAME");
- default: throw std::runtime_error ("unsupported global variable type");
- }
- }
+ Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id));
- char Globals::getType (const std::string& name) const
- {
- Collection::const_iterator iter = mVariables.find (name);
+ if (iter!=mVariables.end())
+ iter->second.read (reader, ESM::Variant::Format_Global);
+ else
+ reader.skipHRecord();
- if (iter==mVariables.end())
- return ' ';
+ return true;
+ }
- return iter->second.first;
+ return false;
}
}
-
diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp
index 681bd560e5..8f521c8a68 100644
--- a/apps/openmw/mwworld/globals.hpp
+++ b/apps/openmw/mwworld/globals.hpp
@@ -5,7 +5,16 @@
#include <string>
#include <map>
+#include <libs/platform/stdint.h>
+
#include <components/interpreter/types.hpp>
+#include <components/esm/variant.hpp>
+
+namespace ESM
+{
+ class ESMWriter;
+ class ESMReader;
+}
namespace MWWorld
{
@@ -13,49 +22,37 @@ namespace MWWorld
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:
-
+
+ typedef std::map<std::string, ESM::Variant> Collection;
+
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.
-
+
+ const ESM::Variant& operator[] (const std::string& name) const;
+
+ ESM::Variant& operator[] (const std::string& name);
+
char getType (const std::string& name) const;
///< If there is no global variable with this name, ' ' is returned.
- std::vector<std::string> getGlobals () const;
+ void fill (const MWWorld::ESMStore& store);
+ ///< Replace variables with variables from \a store with default values.
+
+ int countSavedGameRecords() const;
+
+ void write (ESM::ESMWriter& writer) const;
+
+ bool readRecord (ESM::ESMReader& reader, int32_t type);
+ ///< Records for variables that do not exist are dropped silently.
+ ///
+ /// \return Known type?
+
};
}
diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp
index 2c7c05317d..e002762939 100644
--- a/apps/openmw/mwworld/inventorystore.cpp
+++ b/apps/openmw/mwworld/inventorystore.cpp
@@ -34,6 +34,13 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store)
mSlots.push_back (slot);
}
+
+ // some const-trickery, required because of a flaw in the handling of MW-references and the
+ // resulting workarounds
+ std::size_t distance = std::distance (const_cast<InventoryStore&> (store).begin(), const_cast<InventoryStore&> (store).mSelectedEnchantItem);
+ ContainerStoreIterator slot = begin();
+ std::advance (slot, distance);
+ mSelectedEnchantItem = slot;
}
void MWWorld::InventoryStore::initSlots (TSlots& slots_)
@@ -42,6 +49,21 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_)
slots_.push_back (end());
}
+int MWWorld::InventoryStore::getSlot (const MWWorld::LiveCellRefBase& ref) const
+{
+ for (int i = 0; i<static_cast<int> (mSlots.size()); ++i)
+ if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref)
+ return i;
+
+ return -1;
+}
+
+void MWWorld::InventoryStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot)
+{
+ if (iter!=end() && slot>=0 && slot<Slots)
+ mSlots[slot] = iter;
+}
+
MWWorld::InventoryStore::InventoryStore()
: mSelectedEnchantItem(end())
, mUpdatesEnabled (true)
@@ -54,18 +76,19 @@ MWWorld::InventoryStore::InventoryStore()
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
: ContainerStore (store)
, mSelectedEnchantItem(end())
- , mListener(NULL)
- , mUpdatesEnabled(true)
{
mMagicEffects = store.mMagicEffects;
mFirstAutoEquip = store.mFirstAutoEquip;
- mSelectedEnchantItem = store.mSelectedEnchantItem;
+ mListener = store.mListener;
+ mUpdatesEnabled = store.mUpdatesEnabled;
+
mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
copySlots (store);
}
MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
{
+ mListener = store.mListener;
mMagicEffects = store.mMagicEffects;
mFirstAutoEquip = store.mFirstAutoEquip;
mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
@@ -75,17 +98,17 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor
return *this;
}
-MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const Ptr& actorPtr)
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner)
{
- const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, actorPtr);
+ const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner);
- // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
+ // Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves
if ((actorPtr.getRefData().getHandle() != "player")
- && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())
+ && !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
&& !actorPtr.getClass().getCreatureStats(actorPtr).isDead())
{
std::string type = itemPtr.getTypeName();
- if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
+ if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()) || (type == typeid(ESM::Weapon).name()))
autoEquip(actorPtr);
}
@@ -118,11 +141,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite
// 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);
- addNewStack(*iterator);
- iterator->getRefData().setCount(1);
+ unstack(*iterator, actor);
}
mSlots[slot] = iterator;
@@ -139,8 +158,13 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
{
+ // Only *one* change event should be fired
+ mUpdatesEnabled = false;
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
unequipSlot(slot, actor);
+ mUpdatesEnabled = true;
+ fireEquipmentChangedEvent();
+ updateMagicEffects(actor);
}
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
@@ -163,9 +187,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
{
- const MWMechanics::NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
- MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
-
TSlots slots_;
initSlots (slots_);
@@ -182,33 +203,18 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
continue;
}
- int testSkill = MWWorld::Class::get (test).getEquipmentSkill (test);
+ // Only autoEquip if we are the original owner of the item.
+ // This stops merchants from auto equipping anything you sell to them.
+ // ...unless this is a companion, he should always equip items given to him.
+ if (!Misc::StringUtils::ciEqual(test.getCellRef().mOwner, actor.getCellRef().mRefID) &&
+ (actor.getClass().getScript(actor).empty() ||
+ !actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion")))
+ continue;
- std::pair<std::vector<int>, bool> itemsSlots =
- MWWorld::Class::get (*iter).getEquipmentSlots (*iter);
+ int testSkill = test.getClass().getEquipmentSkill (test);
- // Skip items that have *only* harmful permanent effects
- if (!test.getClass().getEnchantment(test).empty())
- {
- const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
- const ESM::Enchantment* enchantment = store.get<ESM::Enchantment>().find(test.getClass().getEnchantment(test));
- bool harmfulEffect = false;
- bool usefulEffect = false;
- if (enchantment->mData.mType == ESM::Enchantment::ConstantEffect)
- {
- for (std::vector<ESM::ENAMstruct>::const_iterator it = enchantment->mEffects.mList.begin();
- it != enchantment->mEffects.mList.end(); ++it)
- {
- const ESM::MagicEffect* effect = store.get<ESM::MagicEffect>().find(it->mEffectID);
- if (effect->mData.mFlags & ESM::MagicEffect::Harmful)
- harmfulEffect = true;
- else
- usefulEffect = true;
- }
- }
- if (harmfulEffect && !usefulEffect)
- continue;
- }
+ std::pair<std::vector<int>, bool> itemsSlots =
+ iter->getClass().getEquipmentSlots (*iter);
for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
iter2!=itemsSlots.first.end(); ++iter2)
@@ -225,16 +231,16 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
{
// check skill
int oldSkill =
- MWWorld::Class::get (old).getEquipmentSkill (old);
+ old.getClass().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())
+ if (actor.getClass().getSkill(actor, oldSkill) > actor.getClass().getSkill (actor, testSkill))
continue; // rejected, because old item better matched the NPC's skills.
- if (stats.getSkill (oldSkill).getModified()<stats.getSkill (testSkill).getModified())
+ if (actor.getClass().getSkill(actor, oldSkill) < actor.getClass().getSkill (actor, testSkill))
use = true;
}
}
@@ -242,8 +248,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
if (!use)
{
// check value
- if (MWWorld::Class::get (old).getValue (old)>=
- MWWorld::Class::get (test).getValue (test))
+ if (old.getClass().getValue (old)>=
+ test.getClass().getValue (test))
{
continue;
}
@@ -257,10 +263,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
case 0:
continue;
case 2:
- invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor);
+ slots_[MWWorld::InventoryStore::Slot_CarriedLeft] = end();
break;
case 3:
- invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor);
+ // Prefer keeping twohanded weapon
break;
}
@@ -269,11 +275,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
// 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);
- addNewStack(*iter);
- iter->getRefData().setCount(1);
+ unstack(*iter, actor);
}
}
@@ -387,7 +389,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
// Apply instant effects
MWMechanics::CastSpell cast(actor, actor);
if (magnitude)
- cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude);
+ cast.applyInstantEffect(actor, actor, effectIt->mEffectID, magnitude);
}
if (magnitude)
@@ -460,25 +462,27 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem(
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor)
{
- for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
- {
- if (mSlots[slot] == end())
- continue;
+ int retCount = ContainerStore::remove(item, count, actor);
- if (*mSlots[slot] == item)
+ if (!item.getRefData().getCount())
+ {
+ for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
- // restacking is disabled cause it may break removal
- unequipSlot(slot, actor, false);
- break;
+ if (mSlots[slot] == end())
+ continue;
+
+ if (*mSlots[slot] == item)
+ {
+ unequipSlot(slot, actor);
+ break;
+ }
}
}
- int retCount = ContainerStore::remove(item, count, actor);
-
// If an armor/clothing item is removed, try to find a replacement,
// but not for the player nor werewolves.
if ((actor.getRefData().getHandle() != "player")
- && !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf()))
+ && !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()))
{
std::string type = item.getTypeName();
if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
@@ -498,9 +502,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
return retCount;
}
-MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack)
+MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor)
{
- ContainerStoreIterator it = getSlot(slot);
+ ContainerStoreIterator it = mSlots[slot];
if (it != end())
{
@@ -509,17 +513,15 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
// empty this slot
mSlots[slot] = end();
- if (restack) {
- // restack item previously in this slot
- for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
+ // restack the previously equipped item with other (non-equipped) items
+ for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
+ {
+ if (stacks(*iter, *it))
{
- if (stacks(*iter, *it))
- {
- iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount());
- it->getRefData().setCount(0);
- retval = iter;
- break;
- }
+ iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount());
+ it->getRefData().setCount(0);
+ retval = iter;
+ break;
}
}
@@ -607,7 +609,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i];
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
magnitude *= params.mMultiplier;
- visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude);
+ visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), "", magnitude);
++i;
}
@@ -650,3 +652,10 @@ void MWWorld::InventoryStore::purgeEffect(short effectId)
{
mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude);
}
+
+void MWWorld::InventoryStore::clear()
+{
+ mSlots.clear();
+ initSlots (mSlots);
+ ContainerStore::clear();
+}
diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp
index e764f64fb9..714ba47daa 100644
--- a/apps/openmw/mwworld/inventorystore.hpp
+++ b/apps/openmw/mwworld/inventorystore.hpp
@@ -105,6 +105,12 @@ namespace MWWorld
void fireEquipmentChangedEvent();
+ virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const;
+ ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot).
+
+ virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot);
+ ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1.
+
public:
InventoryStore();
@@ -113,7 +119,9 @@ namespace MWWorld
InventoryStore& operator= (const InventoryStore& store);
- virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr);
+ virtual InventoryStore* clone() { return new InventoryStore(*this); }
+
+ virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
///< 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).
///
@@ -122,6 +130,8 @@ namespace MWWorld
/// \attention Do not add items to an existing stack by increasing the count instead of
/// calling this function!
///
+ /// @param setOwner Set the owner of the added item to \a actorPtr?
+ ///
/// @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, const Ptr& actor);
@@ -158,12 +168,10 @@ namespace MWWorld
///
/// @return the number of items actually removed
- ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true);
+ ContainerStoreIterator unequipSlot(int slot, const Ptr& actor);
///< Unequip \a slot.
///
/// @return an iterator to the item that was previously in the slot
- /// (if \a restack is true, the item can be re-stacked so its count
- /// may differ from when it was equipped).
ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor);
///< Unequip an item identified by its Ptr. An exception is thrown
@@ -183,6 +191,9 @@ namespace MWWorld
void purgeEffect (short effectId);
///< Remove a magic effect
+
+ virtual void clear();
+ ///< Empty container.
};
}
diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp
new file mode 100644
index 0000000000..d71704fd70
--- /dev/null
+++ b/apps/openmw/mwworld/livecellref.cpp
@@ -0,0 +1,29 @@
+
+#include "livecellref.hpp"
+
+#include <components/esm/objectstate.hpp>
+
+#include "ptr.hpp"
+#include "class.hpp"
+
+void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state)
+{
+ mRef = state.mRef;
+ mData = RefData (state);
+ Ptr ptr (this);
+ mClass->readAdditionalState (ptr, state);
+}
+
+void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const
+{
+ state.mRef = mRef;
+ mData.write (state);
+ /// \todo get rid of this cast once const-correct Ptr are available
+ Ptr ptr (const_cast<LiveCellRefBase *> (this));
+ mClass->writeAdditionalState (ptr, state);
+}
+
+bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state)
+{
+ return true;
+} \ No newline at end of file
diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp
index 415351e783..b2e4d6d567 100644
--- a/apps/openmw/mwworld/livecellref.hpp
+++ b/apps/openmw/mwworld/livecellref.hpp
@@ -7,6 +7,11 @@
#include "refdata.hpp"
+namespace ESM
+{
+ struct ObjectState;
+}
+
namespace MWWorld
{
class Ptr;
@@ -29,8 +34,39 @@ namespace MWWorld
LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef());
/* Need this for the class to be recognized as polymorphic */
virtual ~LiveCellRefBase() { }
+
+ virtual void load (const ESM::ObjectState& state) = 0;
+ ///< Load state into a LiveCellRef, that has already been initialised with base and class.
+ ///
+ /// \attention Must not be called with an invalid \a state.
+
+ virtual void save (ESM::ObjectState& state) const = 0;
+ ///< Save LiveCellRef state into \a state.
+
+ protected:
+
+ void loadImp (const ESM::ObjectState& state);
+ ///< Load state into a LiveCellRef, that has already been initialised with base and
+ /// class.
+ ///
+ /// \attention Must not be called with an invalid \a state.
+
+ void saveImp (ESM::ObjectState& state) const;
+ ///< Save LiveCellRef state into \a state.
+
+ static bool checkStateImp (const ESM::ObjectState& state);
+ ///< Check if state is valid and report errors.
+ ///
+ /// \return Valid?
+ ///
+ /// \note Does not check if the RefId exists.
};
+ inline bool operator== (const LiveCellRefBase& cellRef, const ESM::CellRef::RefNum refNum)
+ {
+ return cellRef.mRef.mRefNum==refNum;
+ }
+
/// A reference to one object (of any type) in a cell.
///
/// Constructing this with a CellRef instance in the constructor means that
@@ -50,9 +86,41 @@ namespace MWWorld
// The object that this instance is based on.
const X* mBase;
+
+ virtual void load (const ESM::ObjectState& state);
+ ///< Load state into a LiveCellRef, that has already been initialised with base and class.
+ ///
+ /// \attention Must not be called with an invalid \a state.
+
+ virtual void save (ESM::ObjectState& state) const;
+ ///< Save LiveCellRef state into \a state.
+
+ static bool checkState (const ESM::ObjectState& state);
+ ///< Check if state is valid and report errors.
+ ///
+ /// \return Valid?
+ ///
+ /// \note Does not check if the RefId exists.
};
-// template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum);
+ template <typename X>
+ void LiveCellRef<X>::load (const ESM::ObjectState& state)
+ {
+ loadImp (state);
+ }
+
+ template <typename X>
+ void LiveCellRef<X>::save (ESM::ObjectState& state) const
+ {
+ saveImp (state);
+ }
+
+ template <typename X>
+ bool LiveCellRef<X>::checkState (const ESM::ObjectState& state)
+ {
+ return checkStateImp (state);
+ }
+
}
#endif
diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp
index 5ec5ca9b56..997e9e32c1 100644
--- a/apps/openmw/mwworld/localscripts.cpp
+++ b/apps/openmw/mwworld/localscripts.cpp
@@ -11,7 +11,7 @@ namespace
{
template<typename T>
void listCellScripts (MWWorld::LocalScripts& localScripts,
- MWWorld::CellRefList<T>& cellRefList, MWWorld::Ptr::CellStore *cell)
+ MWWorld::CellRefList<T>& cellRefList, MWWorld::CellStore *cell)
{
for (typename MWWorld::CellRefList<T>::List::iterator iter (
cellRefList.mList.begin());
@@ -27,15 +27,15 @@ namespace
// 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)
+ MWWorld::CellRefList<T>& cellRefList, MWWorld::CellStore *cell)
{
for (typename MWWorld::CellRefList<T>::List::iterator iter (
cellRefList.mList.begin());
iter!=cellRefList.mList.end(); ++iter)
{
-
- MWWorld::Ptr containerPtr (&*iter, cell);
-
+
+ MWWorld::Ptr containerPtr (&*iter, cell);
+
MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr);
for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3)
{
@@ -99,7 +99,7 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr)
}
}
-void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell)
+void MWWorld::LocalScripts::addCell (CellStore *cell)
{
listCellScripts (*this, cell->mActivators, cell);
listCellScripts (*this, cell->mPotions, cell);
@@ -128,7 +128,7 @@ void MWWorld::LocalScripts::clear()
mScripts.clear();
}
-void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell)
+void MWWorld::LocalScripts::clearCell (CellStore *cell)
{
std::list<std::pair<std::string, Ptr> >::iterator iter = mScripts.begin();
diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp
index 1cdcd8484e..0e21c55acd 100644
--- a/apps/openmw/mwworld/manualref.hpp
+++ b/apps/openmw/mwworld/manualref.hpp
@@ -25,6 +25,8 @@ namespace MWWorld
{
LiveCellRef<T> ref;
ref.mBase = instance;
+ ref.mRef.mRefNum.mIndex = 0;
+ ref.mRef.mRefNum.mContentFile = -1;
mRef = ref;
mPtr = Ptr (&boost::any_cast<LiveCellRef<T>&> (mRef), 0);
@@ -64,8 +66,9 @@ namespace MWWorld
// initialise
ESM::CellRef& cellRef = mPtr.getCellRef();
- cellRef.mRefID = name;
- cellRef.mRefnum = -1;
+ cellRef.mRefID = Misc::StringUtils::lowerCase (name);
+ cellRef.mRefNum.mIndex = 0;
+ cellRef.mRefNum.mContentFile = -1;
cellRef.mScale = 1;
cellRef.mFactIndex = 0;
cellRef.mCharge = -1;
diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp
index 2a7f5948e4..a7103b9911 100644
--- a/apps/openmw/mwworld/physicssystem.cpp
+++ b/apps/openmw/mwworld/physicssystem.cpp
@@ -108,7 +108,7 @@ namespace MWWorld
}
static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
- bool isFlying, float waterlevel, OEngine::Physic::PhysicEngine *engine)
+ bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine)
{
const ESM::Position &refpos = ptr.getRefData().getPosition();
Ogre::Vector3 position(refpos.pos);
@@ -229,7 +229,10 @@ namespace MWWorld
physicActor->setInertialForce(Ogre::Vector3(0.0f));
else
{
- inertia.z += time*-627.2f;
+ float diff = time*-627.2f;
+ if (inertia.z < 0)
+ diff *= slowFall;
+ inertia.z += diff;
physicActor->setInertialForce(inertia);
}
physicActor->setOnGround(isOnGround);
@@ -577,9 +580,10 @@ namespace MWWorld
float oldHeight = iter->first.getRefData().getPosition().pos[2];
+ const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects();
+
bool waterCollision = false;
- if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects()
- .get(ESM::MagicEffect::WaterWalking).mMagnitude
+ if (effects.get(ESM::MagicEffect::WaterWalking).mMagnitude
&& cell->hasWater()
&& !world->isUnderwater(iter->first.getCell(),
Ogre::Vector3(iter->first.getRefData().getPosition().pos)))
@@ -592,9 +596,12 @@ namespace MWWorld
if (waterCollision)
mEngine->dynamicsWorld->addCollisionObject(&object);
+ // 100 points of slowfall reduce gravity by 90% (this is just a guess)
+ float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f);
+
Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum,
world->isFlying(iter->first),
- waterlevel, mEngine);
+ waterlevel, slowFall, mEngine);
if (waterCollision)
mEngine->dynamicsWorld->removeCollisionObject(&object);
diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp
index c594454028..c1cce84fc1 100644
--- a/apps/openmw/mwworld/player.cpp
+++ b/apps/openmw/mwworld/player.cpp
@@ -1,6 +1,13 @@
#include "player.hpp"
+#include <stdexcept>
+
+#include <components/esm/esmreader.hpp>
+#include <components/esm/esmwriter.hpp>
+#include <components/esm/player.hpp>
+#include <components/esm/defs.hpp>
+
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -34,9 +41,6 @@ namespace MWWorld
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)
@@ -114,14 +118,14 @@ namespace MWWorld
void Player::setRunState(bool run)
{
MWWorld::Ptr ptr = getPlayer();
- MWWorld::Class::get(ptr).setStance(ptr, MWWorld::Class::Run, run);
+ ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, run);
}
void Player::setSneak(bool sneak)
{
MWWorld::Ptr ptr = getPlayer();
- MWWorld::Class::get (ptr).setStance (ptr, MWWorld::Class::Sneak, sneak);
+ ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak);
// TODO show sneak indicator only when the player is not detected by any actor
MWBase::Environment::get().getWindowManager()->setSneakVisibility(sneak);
@@ -171,4 +175,102 @@ namespace MWWorld
if (mMarkedCell)
markedPosition = mMarkedPosition;
}
+
+ void Player::clear()
+ {
+ mCellStore = 0;
+ mSign.clear();
+ mMarkedCell = 0;
+ mAutoMove = false;
+ mForwardBackward = 0;
+ mTeleported = false;
+ }
+
+ void Player::write (ESM::ESMWriter& writer) const
+ {
+ ESM::Player player;
+
+ mPlayer.save (player.mObject);
+ player.mCellId = mCellStore->mCell->getCellId();
+
+ player.mBirthsign = mSign;
+
+ player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x;
+ player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y;
+ player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z;
+
+ if (mMarkedCell)
+ {
+ player.mHasMark = true;
+ player.mMarkedPosition = mMarkedPosition;
+ player.mMarkedCell = mMarkedCell->mCell->getCellId();
+ }
+ else
+ player.mHasMark = false;
+
+ player.mAutoMove = mAutoMove ? 1 : 0;
+
+ writer.startRecord (ESM::REC_PLAY);
+ player.save (writer);
+ writer.endRecord (ESM::REC_PLAY);
+ }
+
+ bool Player::readRecord (ESM::ESMReader& reader, int32_t type)
+ {
+ if (type==ESM::REC_PLAY)
+ {
+ ESM::Player player;
+ player.load (reader);
+
+ if (!mPlayer.checkState (player.mObject))
+ {
+ // this is the one object we can not silently drop.
+ throw std::runtime_error ("invalid player state record (object state)");
+ }
+
+ mPlayer.load (player.mObject);
+
+ MWBase::World& world = *MWBase::Environment::get().getWorld();
+
+ mCellStore = world.getCell (player.mCellId);
+
+ if (!player.mBirthsign.empty() &&
+ !world.getStore().get<ESM::BirthSign>().search (player.mBirthsign))
+ throw std::runtime_error ("invalid player state record (birthsign)");
+
+ mSign = player.mBirthsign;
+
+ mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0];
+ mLastKnownExteriorPosition.y = player.mLastKnownExteriorPosition[1];
+ mLastKnownExteriorPosition.z = player.mLastKnownExteriorPosition[2];
+
+ if (player.mHasMark && !player.mMarkedCell.mPaged)
+ {
+ // interior cell -> need to check if it exists (exterior cell will be
+ // generated on the fly)
+
+ if (!world.getStore().get<ESM::Cell>().search (player.mMarkedCell.mWorldspace))
+ player.mHasMark = false; // drop mark silently
+ }
+
+ if (player.mHasMark)
+ {
+ mMarkedPosition = player.mMarkedPosition;
+ mMarkedCell = world.getCell (player.mMarkedCell);
+ }
+ else
+ {
+ mMarkedCell = 0;
+ }
+
+ mAutoMove = player.mAutoMove!=0;
+
+ mForwardBackward = 0;
+ mTeleported = false;
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp
index 1df848111b..7eb023a2b1 100644
--- a/apps/openmw/mwworld/player.hpp
+++ b/apps/openmw/mwworld/player.hpp
@@ -11,6 +11,8 @@
namespace ESM
{
struct NPC;
+ class ESMWriter;
+ class ESMReader;
}
namespace MWBase
@@ -86,6 +88,12 @@ namespace MWWorld
bool wasTeleported() const;
void setTeleported(bool teleported);
+
+ void clear();
+
+ void write (ESM::ESMWriter& writer) const;
+
+ bool readRecord (ESM::ESMReader& reader, int32_t type);
};
}
#endif
diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp
index 384bd71b11..67bfe49007 100644
--- a/apps/openmw/mwworld/ptr.cpp
+++ b/apps/openmw/mwworld/ptr.cpp
@@ -21,6 +21,14 @@ const std::string& MWWorld::Ptr::getTypeName() const
throw std::runtime_error("Can't get type name from an empty object.");
}
+MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const
+{
+ if (!mRef)
+ throw std::runtime_error ("Can't access cell ref pointed to by null Ptr");
+
+ return mRef;
+}
+
ESM::CellRef& MWWorld::Ptr::getCellRef() const
{
assert(mRef);
diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp
index e5352da280..1212619d03 100644
--- a/apps/openmw/mwworld/ptr.hpp
+++ b/apps/openmw/mwworld/ptr.hpp
@@ -14,9 +14,6 @@ namespace MWWorld
{
public:
- typedef MWWorld::CellStore CellStore;
- ///< \deprecated
-
MWWorld::LiveCellRefBase *mRef;
CellStore *mCell;
ContainerStore *mContainerStore;
@@ -55,11 +52,13 @@ namespace MWWorld
throw std::runtime_error(str.str());
}
+ MWWorld::LiveCellRefBase *getBase() const;
+
ESM::CellRef& getCellRef() const;
RefData& getRefData() const;
- Ptr::CellStore *getCell() const
+ CellStore *getCell() const
{
assert(mCell);
return mCell;
diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp
index c1a3ae7859..8d48078b1d 100644
--- a/apps/openmw/mwworld/refdata.cpp
+++ b/apps/openmw/mwworld/refdata.cpp
@@ -3,6 +3,8 @@
#include <OgreSceneNode.h>
+#include <components/esm/objectstate.hpp>
+
#include "customdata.hpp"
#include "cellstore.hpp"
@@ -32,6 +34,17 @@ namespace MWWorld
mCustomData = 0;
}
+ RefData::RefData()
+ : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0)
+ {
+ for (int i=0; i<3; ++i)
+ {
+ mLocalRotation.rot[i] = 0;
+ mPosition.pos[i] = 0;
+ mPosition.rot[i] = 0;
+ }
+ }
+
RefData::RefData (const ESM::CellRef& cellRef)
: mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos),
mCustomData (0)
@@ -41,6 +54,14 @@ namespace MWWorld
mLocalRotation.rot[2]=0;
}
+ RefData::RefData (const ESM::ObjectState& objectState)
+ : mBaseNode (0), mHasLocals (false), mEnabled (objectState.mEnabled),
+ mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0)
+ {
+ for (int i=0; i<3; ++i)
+ mLocalRotation.rot[i] = objectState.mLocalRotation[i];
+ }
+
RefData::RefData (const RefData& refData)
: mBaseNode(0), mCustomData (0)
{
@@ -55,6 +76,17 @@ namespace MWWorld
}
}
+ void RefData::write (ESM::ObjectState& objectState) const
+ {
+ objectState.mHasLocals = false;
+ objectState.mEnabled = mEnabled;
+ objectState.mCount = mCount;
+ objectState.mPosition = mPosition;
+
+ for (int i=0; i<3; ++i)
+ objectState.mLocalRotation[i] = mLocalRotation.rot[i];
+ }
+
RefData& RefData::operator= (const RefData& refData)
{
try
@@ -88,7 +120,7 @@ namespace MWWorld
static const std::string empty;
return empty;
}
-
+
return mBaseNode->getName();
}
@@ -120,7 +152,7 @@ namespace MWWorld
{
if(count == 0)
MWBase::Environment::get().getWorld()->removeRefScript(this);
-
+
mCount = count;
}
diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp
index d5701efc51..19e3d48822 100644
--- a/apps/openmw/mwworld/refdata.hpp
+++ b/apps/openmw/mwworld/refdata.hpp
@@ -14,6 +14,7 @@ namespace ESM
{
class Script;
class CellRef;
+ struct ObjectState;
}
namespace MWWorld
@@ -48,15 +49,25 @@ namespace MWWorld
public:
+ RefData();
+
/// @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 ESM::ObjectState& objectState);
+ ///< Ignores local variables and custom data (not enough context available here to
+ /// perform these operations).
+
RefData (const RefData& refData);
~RefData();
+ void write (ESM::ObjectState& objectState) const;
+ ///< Ignores local variables and custom data (not enough context available here to
+ /// perform these operations).
+
RefData& operator= (const RefData& refData);
/// Return OGRE handle (may be empty).
diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp
index dab272f7c0..167adf3014 100644
--- a/apps/openmw/mwworld/scene.cpp
+++ b/apps/openmw/mwworld/scene.cpp
@@ -118,7 +118,7 @@ namespace MWWorld
mActiveCells.erase(*iter);
}
- void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener)
+ void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener)
{
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
@@ -163,13 +163,13 @@ namespace MWWorld
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
}
- void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos)
+ void Scene::playerCellChange(CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- MWWorld::Ptr old = world->getPlayer().getPlayer();
+ MWWorld::Ptr old = world->getPlayerPtr();
world->getPlayer().setCell(cell);
- MWWorld::Ptr player = world->getPlayer().getPlayer();
+ MWWorld::Ptr player = world->getPlayerPtr();
mRendering.updatePlayerPtr(player);
if (adjustPlayerPos) {
@@ -350,6 +350,7 @@ namespace MWWorld
void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
{
+ Nif::NIFFile::CacheLock lock;
MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5);
mRendering.enableTerrain(false);
@@ -368,14 +369,14 @@ namespace MWWorld
if(!loadcell)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
- world->moveObject(world->getPlayer().getPlayer(), position.pos[0], position.pos[1], position.pos[2]);
+ world->moveObject(world->getPlayerPtr(), 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);
+ world->rotateObject(world->getPlayerPtr(), x, y, z);
- MWWorld::Class::get(world->getPlayer().getPlayer()).adjustPosition(world->getPlayer().getPlayer());
+ MWWorld::Class::get(world->getPlayerPtr()).adjustPosition(world->getPlayerPtr());
world->getFader()->fadeIn(0.5f);
return;
}
@@ -440,7 +441,7 @@ namespace MWWorld
changeCell (x, y, position, true);
}
- Ptr::CellStore* Scene::getCurrentCell ()
+ CellStore* Scene::getCurrentCell ()
{
return mCurrentCell;
}
@@ -450,7 +451,7 @@ namespace MWWorld
mCellChanged = false;
}
- int Scene::countRefs (const Ptr::CellStore& cell)
+ int Scene::countRefs (const CellStore& cell)
{
return cell.mActivators.mList.size()
+ cell.mPotions.mList.size()
@@ -474,7 +475,7 @@ namespace MWWorld
+ cell.mNpcs.mList.size();
}
- void Scene::insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener)
+ void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener)
{
// Loop through all references in the cell
insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale, loadingListener);
@@ -486,7 +487,6 @@ namespace MWWorld
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);
@@ -498,6 +498,8 @@ namespace MWWorld
// 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);
+ // Since this adds additional creatures, load afterwards, or they would be loaded twice
+ insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
}
void Scene::addObjectToScene (const Ptr& ptr)
diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp
index 73c3c4b126..6652748318 100644
--- a/apps/openmw/mwworld/scene.hpp
+++ b/apps/openmw/mwworld/scene.hpp
@@ -56,9 +56,9 @@ namespace MWWorld
void playerCellChange (CellStore *cell, const ESM::Position& position,
bool adjustPlayerPos = true);
- void insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener);
+ void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener);
- int countRefs (const Ptr::CellStore& cell);
+ int countRefs (const CellStore& cell);
public:
diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp
index 512883f1a3..1156cbc152 100644
--- a/apps/openmw/mwworld/store.cpp
+++ b/apps/openmw/mwworld/store.cpp
@@ -10,7 +10,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
// 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;
@@ -30,17 +30,14 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
// 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);
+ bool deleted = false;
+ cell->getNextRef(esm, ref, deleted);
// 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);
+ 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
@@ -60,7 +57,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
// copy list into new cell
cell->mContextList = oldcell->mContextList;
// have new cell replace old cell
- *oldcell = *cell;
+ ESM::Cell::merge(oldcell, cell);
} else
mInt[idLower] = *cell;
}
@@ -76,18 +73,18 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
// 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);
+ 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);
+ 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;
+ ESM::Cell::merge(oldcell, cell);
} else
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
}
diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp
index c25197319e..7bd00d6bfd 100644
--- a/apps/openmw/mwworld/store.hpp
+++ b/apps/openmw/mwworld/store.hpp
@@ -6,6 +6,8 @@
#include <map>
#include <stdexcept>
+#include <components/esm/esmwriter.hpp>
+
#include "recordcmp.hpp"
namespace MWWorld
@@ -18,10 +20,16 @@ namespace MWWorld
virtual void listIdentifier(std::vector<std::string> &list) const {}
virtual size_t getSize() const = 0;
+ virtual int getDynamicSize() const { return 0; }
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
virtual bool eraseStatic(const std::string &id) {return false;}
virtual void clearDynamic() {}
+
+ virtual void write (ESM::ESMWriter& writer) const {}
+
+ virtual void read (ESM::ESMReader& reader) {}
+ ///< Read into dynamic storage
};
template <class T>
@@ -193,6 +201,7 @@ namespace MWWorld
void setUp() {
//std::sort(mStatic.begin(), mStatic.end(), RecordCmp());
+ mShared.clear();
mShared.reserve(mStatic.size());
typename std::map<std::string, T>::iterator it = mStatic.begin();
for (; it != mStatic.end(); ++it) {
@@ -212,6 +221,11 @@ namespace MWWorld
return mShared.size();
}
+ int getDynamicSize() const
+ {
+ return mDynamic.size();
+ }
+
void listIdentifier(std::vector<std::string> &list) const {
list.reserve(list.size() + getSize());
typename std::vector<T *>::const_iterator it = mShared.begin();
@@ -290,9 +304,43 @@ namespace MWWorld
bool erase(const T &item) {
return erase(item.mId);
}
+
+ void write (ESM::ESMWriter& writer) const
+ {
+ for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end();
+ ++iter)
+ {
+ writer.startRecord (T::sRecordId);
+ writer.writeHNString ("NAME", iter->second.mId);
+ iter->second.save (writer);
+ writer.endRecord (T::sRecordId);
+ }
+ }
+
+ void read (ESM::ESMReader& reader)
+ {
+ T record;
+ record.mId = reader.getHNString ("NAME");
+ record.load (reader);
+ insert (record);
+ }
};
template <>
+ inline void Store<ESM::NPC>::clearDynamic()
+ {
+ std::map<std::string, ESM::NPC>::iterator iter = mDynamic.begin();
+
+ while (iter!=mDynamic.end())
+ if (iter->first=="player")
+ ++iter;
+ else
+ mDynamic.erase (iter++);
+
+ mShared.clear();
+ }
+
+ template <>
inline void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) {
std::string idLower = Misc::StringUtils::lowerCase(id);
diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp
index 513dcf6c72..b00ad15ca3 100644
--- a/apps/openmw/mwworld/weather.cpp
+++ b/apps/openmw/mwworld/weather.cpp
@@ -1,6 +1,5 @@
#include "weather.hpp"
-#include <boost/algorithm/string.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
@@ -665,7 +664,7 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int
mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather;
- std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion;
+ std::string playerRegion = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->mCell->mRegion;
if (Misc::StringUtils::ciEqual(region, playerRegion))
setWeather(weather);
}
@@ -696,7 +695,7 @@ void WeatherManager::switchToNextWeather(bool instantly)
MWBase::World* world = MWBase::Environment::get().getWorld();
if (world->isCellExterior() || world->isCellQuasiExterior())
{
- std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayer().getPlayer().getCell()->mCell->mRegion);
+ std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->mCell->mRegion);
if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion)
{
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
index 148c8f3016..be6c0b338f 100644
--- a/apps/openmw/mwworld/worldimp.cpp
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -14,6 +14,7 @@
#include <components/bsa/bsa_archive.hpp>
#include <components/files/collections.hpp>
#include <components/compiler/locals.hpp>
+#include <components/esm/cellid.hpp>
#include <boost/math/special_functions/sign.hpp>
@@ -21,12 +22,12 @@
#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 "../mwmechanics/spellcasting.hpp"
+#include "../mwmechanics/levelledlist.hpp"
#include "../mwrender/sky.hpp"
@@ -49,30 +50,6 @@ 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)
@@ -126,7 +103,7 @@ namespace MWWorld
LoadersContainer mLoaders;
};
- Ptr World::getPtrViaHandle (const std::string& handle, Ptr::CellStore& cell)
+ Ptr World::getPtrViaHandle (const std::string& handle, CellStore& cell)
{
if (MWWorld::LiveCellRef<ESM::Activator> *ref =
searchViaHandle (handle, cell.mActivators))
@@ -197,9 +174,9 @@ namespace MWWorld
{
if (mSky && (isCellExterior() || isCellQuasiExterior()))
{
- mRendering->skySetHour (mGlobalVariables->getFloat ("gamehour"));
- mRendering->skySetDate (mGlobalVariables->getInt ("day"),
- mGlobalVariables->getInt ("month"));
+ mRendering->skySetHour (mGlobalVariables["gamehour"].getFloat());
+ mRendering->skySetDate (mGlobalVariables["day"].getInteger(),
+ mGlobalVariables["month"].getInteger());
mRendering->skyEnable();
}
@@ -212,11 +189,12 @@ namespace MWWorld
const std::vector<std::string>& contentFiles,
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),
+ : mPlayer (0), mLocalScripts (mStore),
mSky (true), mCells (mStore, mEsm),
mActivationDistanceOverride (mActivationDistanceOverride),
- mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(false),
- mFacedDistance(FLT_MAX), mGodMode(false)
+ mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true),
+ mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles),
+ mGoToJail(false)
{
mPhysics = new PhysicsSystem(renderer);
mPhysEngine = mPhysics->getEngine();
@@ -252,36 +230,25 @@ namespace MWWorld
mStore.setUp();
mStore.movePlayerRecord();
- mGlobalVariables = new Globals (mStore);
+ mGlobalVariables.fill (mStore);
mWorldScene = new Scene(*mRendering, mPhysics);
}
void World::startNewGame()
{
- mWorldScene->changeToVoid();
-
- mStore.clearDynamic();
- mStore.setUp();
-
- mCells.clear();
+ mGoToJail = false;
+ mLevitationEnabled = true;
+ mTeleportEnabled = true;
// Rebuild player
setupPlayer();
- mPlayer->setCell(NULL);
- 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();
+ // FIXME: this will add cell 0,0 as visible on the global map
ESM::Position pos;
const int cellSize = 8192;
pos.pos[0] = cellSize/2;
@@ -292,29 +259,82 @@ namespace MWWorld
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);
+ mGlobalVariables["chargenstate"].setInteger (1);
+ mGlobalVariables["pcrace"].setInteger (3);
// we don't want old weather to persist on a new game
delete mWeatherManager;
+ mWeatherManager = 0;
mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback);
+ }
+
+ void World::clear()
+ {
+ mLocalScripts.clear();
+ mPlayer->clear();
+
+ // enable collision
+ if (!mPhysics->toggleCollisionMode())
+ mPhysics->toggleCollisionMode();
+
+ mWorldScene->changeToVoid();
+
+ mStore.clearDynamic();
+ mStore.setUp();
+
+ if (mPlayer)
+ {
+ mPlayer->setCell (0);
+ mPlayer->getPlayer().getRefData() = RefData();
+ mPlayer->set (mStore.get<ESM::NPC>().find ("player"));
+ }
+
+ mCells.clear();
- MWBase::Environment::get().getScriptManager()->resetGlobalScripts();
+ mProjectiles.clear();
+ mDoorStates.clear();
+
+ mGodMode = false;
+ mSky = true;
+ mTeleportEnabled = true;
+ mPlayIntro = 0;
+ mFacedDistance = FLT_MAX;
+
+ mGlobalVariables.fill (mStore);
}
+ int World::countSavedGameRecords() const
+ {
+ return
+ mStore.countSavedGameRecords()
+ +mGlobalVariables.countSavedGameRecords()
+ +1 // player record
+ +mCells.countSavedGameRecords();
+ }
+
+ void World::write (ESM::ESMWriter& writer) const
+ {
+ mStore.write (writer);
+ mGlobalVariables.write (writer);
+ mCells.write (writer);
+ mPlayer->write (writer);
+ }
+
+ void World::readRecord (ESM::ESMReader& reader, int32_t type,
+ const std::map<int, int>& contentFileMap)
+ {
+ if (!mStore.readRecord (reader, type) &&
+ !mGlobalVariables.readRecord (reader, type) &&
+ !mPlayer->readRecord (reader, type) &&
+ !mCells.readRecord (reader, type, contentFileMap))
+ {
+ throw std::runtime_error ("unknown record in saved game");
+ }
+ }
void World::ensureNeededRecords()
{
@@ -355,7 +375,6 @@ namespace MWWorld
{
delete mWeatherManager;
delete mWorldScene;
- delete mGlobalVariables;
delete mRendering;
delete mPhysics;
@@ -389,16 +408,35 @@ namespace MWWorld
return &mFallback;
}
- Ptr::CellStore *World::getExterior (int x, int y)
+ CellStore *World::getExterior (int x, int y)
{
return mCells.getExterior (x, y);
}
- Ptr::CellStore *World::getInterior (const std::string& name)
+ CellStore *World::getInterior (const std::string& name)
{
return mCells.getInterior (name);
}
+ CellStore *World::getCell (const ESM::CellId& id)
+ {
+ if (id.mPaged)
+ return getExterior (id.mIndex.mX, id.mIndex.mY);
+ else
+ return getInterior (id.mWorldspace);
+ }
+
+ void World::useDeathCamera()
+ {
+ if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() )
+ {
+ mRendering->getCamera()->togglePreviewMode(false);
+ mRendering->getCamera()->toggleVanityMode(false);
+ }
+ if(mRendering->getCamera()->isFirstPerson())
+ togglePOV();
+ }
+
MWWorld::Player& World::getPlayer()
{
return *mPlayer;
@@ -424,60 +462,57 @@ namespace MWWorld
return mWorldScene->hasCellChanged();
}
- Globals::Data& World::getGlobalVariable (const std::string& name)
+ void World::setGlobalInt (const std::string& name, int value)
{
- return (*mGlobalVariables)[name];
+ if (name=="gamehour")
+ setHour (value);
+ else if (name=="day")
+ setDay (value);
+ else if (name=="month")
+ setMonth (value);
+ else
+ mGlobalVariables[name].setInteger (value);
}
- Globals::Data World::getGlobalVariable (const std::string& name) const
+ void World::setGlobalFloat (const std::string& name, float value)
{
- return (*mGlobalVariables)[name];
+ if (name=="gamehour")
+ setHour (value);
+ else if (name=="day")
+ setDay (value);
+ else if (name=="month")
+ setMonth (value);
+ else
+ mGlobalVariables[name].setFloat (value);
}
- char World::getGlobalVariableType (const std::string& name) const
+ int World::getGlobalInt (const std::string& name) const
{
- return mGlobalVariables->getType (name);
+ return mGlobalVariables[name].getInteger();
}
- std::vector<std::string> World::getGlobals () const
+ float World::getGlobalFloat (const std::string& name) const
{
- return mGlobalVariables->getGlobals();
+ return mGlobalVariables[name].getFloat();
}
- std::string World::getCurrentCellName () const
+ char World::getGlobalVariableType (const std::string& name) const
{
- std::string name;
+ return mGlobalVariables.getType (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");
+ std::string World::getCellName (const MWWorld::CellStore *cell) const
+ {
+ if (!cell)
+ cell = mWorldScene->getCurrentCell();
- if (setting && setting->mValue.getType()==ESM::VT_String)
- name = setting->mValue.getString();
- }
+ if (!cell->mCell->isExterior() || !cell->mCell->mName.empty())
+ return cell->mCell->mName;
- }
- }
- else
- {
- name = cell->mCell->mName;
- }
+ if (const ESM::Region* region = getStore().get<ESM::Region>().search (cell->mCell->mRegion))
+ return region->mName;
- return name;
+ return getStore().get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
}
void World::removeRefScript (MWWorld::RefData *ref)
@@ -485,41 +520,46 @@ namespace MWWorld
mLocalScripts.remove (ref);
}
- Ptr World::getPtr (const std::string& name, bool activeOnly)
+ Ptr World::searchPtr (const std::string& name, bool activeOnly)
{
+ Ptr ret;
// 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;
-
std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
// active cells
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
iter!=mWorldScene->getActiveCells().end(); ++iter)
{
- Ptr::CellStore* cellstore = *iter;
+ CellStore* cellstore = *iter;
Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true);
if (!ptr.isEmpty())
return ptr;
}
+ Ptr ptr = Class::get (mPlayer->getPlayer()).
+ getContainerStore (mPlayer->getPlayer()).search (lowerCaseName);
+
+ if (!ptr.isEmpty())
+ return ptr;
+
if (!activeOnly)
{
- Ptr ptr = mCells.getPtr (lowerCaseName);
-
- if (!ptr.isEmpty())
- return ptr;
+ ret = mCells.getPtr (lowerCaseName);
}
+ return ret;
+ }
+ Ptr World::getPtr (const std::string& name, bool activeOnly)
+ {
+ Ptr ret = searchPtr(name, activeOnly);
+ if (!ret.isEmpty())
+ return ret;
throw std::runtime_error ("unknown ID: " + name);
}
@@ -538,7 +578,7 @@ namespace MWWorld
for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin());
iter!=mWorldScene->getActiveCells().end(); ++iter)
{
- Ptr::CellStore* cellstore = *iter;
+ CellStore* cellstore = *iter;
Ptr ptr = getPtrViaHandle (handle, *cellstore);
if (!ptr.isEmpty())
@@ -548,7 +588,7 @@ namespace MWWorld
return MWWorld::Ptr();
}
- void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell)
+ void World::addContainerScripts(const Ptr& reference, CellStore * cell)
{
if( reference.getTypeName()==typeid (ESM::Container).name() ||
reference.getTypeName()==typeid (ESM::NPC).name() ||
@@ -570,6 +610,10 @@ namespace MWWorld
void World::enable (const Ptr& reference)
{
+ // enable is a no-op for items in containers
+ if (!reference.isInCell())
+ return;
+
if (!reference.getRefData().isEnabled())
{
reference.getRefData().enable();
@@ -600,6 +644,10 @@ namespace MWWorld
void World::disable (const Ptr& reference)
{
+ // disable is a no-op for items in containers
+ if (!reference.isInCell())
+ return;
+
if (reference.getRefData().isEnabled())
{
reference.getRefData().disable();
@@ -615,14 +663,15 @@ namespace MWWorld
mWeatherManager->advanceTime (hours);
- hours += mGlobalVariables->getFloat ("gamehour");
+ hours += mGlobalVariables["gamehour"].getFloat();
setHour (hours);
int days = hours / 24;
if (days>0)
- mGlobalVariables->setInt ("dayspassed", days + mGlobalVariables->getInt ("dayspassed"));
+ mGlobalVariables["dayspassed"].setInteger (
+ days + mGlobalVariables["dayspassed"].getInteger());
}
void World::setHour (double hour)
@@ -634,14 +683,14 @@ namespace MWWorld
hour = std::fmod (hour, 24);
- mGlobalVariables->setFloat ("gamehour", hour);
+ mGlobalVariables["gamehour"].setFloat (hour);
mRendering->skySetHour (hour);
mWeatherManager->setHour (hour);
if (days>0)
- setDay (days + mGlobalVariables->getInt ("day"));
+ setDay (days + mGlobalVariables["day"].getInteger());
}
void World::setDay (int day)
@@ -649,7 +698,7 @@ namespace MWWorld
if (day<1)
day = 1;
- int month = mGlobalVariables->getInt ("month");
+ int month = mGlobalVariables["month"].getInteger();
while (true)
{
@@ -664,14 +713,14 @@ namespace MWWorld
else
{
month = 0;
- mGlobalVariables->setInt ("year", mGlobalVariables->getInt ("year")+1);
+ mGlobalVariables["year"].setInteger (mGlobalVariables["year"].getInteger()+1);
}
day -= days;
}
- mGlobalVariables->setInt ("day", day);
- mGlobalVariables->setInt ("month", month);
+ mGlobalVariables["day"].setInteger (day);
+ mGlobalVariables["month"].setInteger (month);
mRendering->skySetDate (day, month);
@@ -688,31 +737,56 @@ namespace MWWorld
int days = getDaysPerMonth (month);
- if (mGlobalVariables->getInt ("day")>days)
- mGlobalVariables->setInt ("day", days);
+ if (mGlobalVariables["day"].getInteger()>days)
+ mGlobalVariables["day"].setInteger (days);
- mGlobalVariables->setInt ("month", month);
+ mGlobalVariables["month"].setInteger (month);
if (years>0)
- mGlobalVariables->setInt ("year", years+mGlobalVariables->getInt ("year"));
+ mGlobalVariables["year"].setInteger (years+mGlobalVariables["year"].getInteger());
- mRendering->skySetDate (mGlobalVariables->getInt ("day"), month);
+ mRendering->skySetDate (mGlobalVariables["day"].getInteger(), month);
}
- int World::getDay()
+ int World::getDay() const
{
- return mGlobalVariables->getInt("day");
+ return mGlobalVariables["day"].getInteger();
}
- int World::getMonth()
+ int World::getMonth() const
{
- return mGlobalVariables->getInt("month");
+ return mGlobalVariables["month"].getInteger();
+ }
+
+ int World::getYear() const
+ {
+ return mGlobalVariables["year"].getInteger();
+ }
+
+ std::string World::getMonthName (int month) const
+ {
+ if (month==-1)
+ month = getMonth();
+
+ const int months = 12;
+
+ if (month<0 || month>=months)
+ return "";
+
+ static const char *monthNames[months] =
+ {
+ "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand",
+ "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed",
+ "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar"
+ };
+
+ return getStore().get<ESM::GameSetting>().find (monthNames[month])->mValue.getString();
}
TimeStamp World::getTimeStamp() const
{
- return TimeStamp (mGlobalVariables->getFloat ("gamehour"),
- mGlobalVariables->getInt ("dayspassed"));
+ return TimeStamp (mGlobalVariables["gamehour"].getFloat(),
+ mGlobalVariables["dayspassed"].getInteger());
}
bool World::toggleSky()
@@ -748,21 +822,29 @@ namespace MWWorld
float World::getTimeScaleFactor() const
{
- return mGlobalVariables->getFloat ("timescale");
+ return mGlobalVariables["timescale"].getFloat();
}
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
{
- removeContainerScripts(getPlayer().getPlayer());
+ removeContainerScripts(getPlayerPtr());
mWorldScene->changeToInteriorCell(cellName, position);
- addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell());
+ addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
}
void World::changeToExteriorCell (const ESM::Position& position)
{
- removeContainerScripts(getPlayer().getPlayer());
+ removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position);
- addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell());
+ addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
+ }
+
+ void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position)
+ {
+ if (cellId.mPaged)
+ changeToExteriorCell (position);
+ else
+ changeToInteriorCell (cellId.mWorldspace, position);
}
void World::markCellAsUnchanged()
@@ -812,6 +894,8 @@ namespace MWWorld
if(anim != NULL)
{
Ogre::Node *node = anim->getNode("Head");
+ if (node == NULL)
+ node = anim->getNode("Bip01 Head");
if(node != NULL)
pos += node->_getDerivedPosition();
}
@@ -869,7 +953,7 @@ namespace MWWorld
int cellY = newCell.mCell->getGridY();
mWorldScene->changeCell(cellX, cellY, pos, false);
}
- addContainerScripts (getPlayer().getPlayer(), &newCell);
+ addContainerScripts (getPlayerPtr(), &newCell);
}
else
{
@@ -894,6 +978,7 @@ namespace MWWorld
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
mRendering->updateObjectCell(ptr, copy);
+ MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy);
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
mechMgr->updateCell(ptr, copy);
@@ -1141,7 +1226,7 @@ namespace MWWorld
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
while (it != mDoorStates.end())
{
- if (!mWorldScene->isCellActive(*it->first.getCell()))
+ if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
mDoorStates.erase(it++);
else
{
@@ -1216,7 +1301,7 @@ namespace MWWorld
if (Misc::StringUtils::ciEqual (ids[i], record.mRace))
break;
- mGlobalVariables->setInt ("pcrace", (i == ids.size()) ? 0 : i+1);
+ mGlobalVariables["pcrace"].setInteger (i == ids.size() ? 0 : i+1);
const ESM::NPC *player =
mPlayer->getPlayer().get<ESM::NPC>()->mBase;
@@ -1267,6 +1352,9 @@ namespace MWWorld
mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true);
}
+ if (mGoToJail && !paused)
+ goToJail();
+
updateWeather(duration);
mWorldScene->update (duration, paused);
@@ -1278,7 +1366,7 @@ namespace MWWorld
updateWindowManager ();
- if (mPlayer->getPlayer().getCell()->isExterior())
+ if (!paused && mPlayer->getPlayer().getCell()->isExterior())
{
ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition();
mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos));
@@ -1374,7 +1462,7 @@ namespace MWWorld
bool World::isCellExterior() const
{
- Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+ CellStore *currentCell = mWorldScene->getCurrentCell();
if (currentCell)
{
return currentCell->mCell->isExterior();
@@ -1384,7 +1472,7 @@ namespace MWWorld
bool World::isCellQuasiExterior() const
{
- Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+ CellStore *currentCell = mWorldScene->getCurrentCell();
if (currentCell)
{
if (!(currentCell->mCell->mData.mFlags & ESM::Cell::QuasiEx))
@@ -1493,9 +1581,9 @@ namespace MWWorld
cell = mCells.getExterior(cellX, cellY);
}
else
- cell = getPlayer().getPlayer().getCell();
+ cell = getPlayerPtr().getCell();
- ESM::Position pos = getPlayer().getPlayer().getRefData().getPosition();
+ ESM::Position pos = getPlayerPtr().getRefData().getPosition();
pos.pos[0] = result.second[0];
pos.pos[1] = result.second[1];
pos.pos[2] = result.second[2];
@@ -1527,23 +1615,28 @@ namespace MWWorld
}
- Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos, bool adjustPos)
+ Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, ESM::Position pos, bool adjustPos)
{
- /// \todo add searching correct cell for position specified
- MWWorld::Ptr dropped =
- MWWorld::Class::get(object).copyToCell(object, cell, pos);
-
if (object.getClass().isActor() || adjustPos)
{
Ogre::Vector3 min, max;
if (mPhysics->getObjectAABB(object, min, max)) {
- float *pos = dropped.getRefData().getPosition().pos;
- pos[0] -= (min.x + max.x) / 2;
- pos[1] -= (min.y + max.y) / 2;
- pos[2] -= min.z;
+ pos.pos[0] -= (min.x + max.x) / 2;
+ pos.pos[1] -= (min.y + max.y) / 2;
+ pos.pos[2] -= min.z;
}
}
+ if (cell.isExterior())
+ {
+ int cellX, cellY;
+ positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY);
+ cell = *mCells.getExterior(cellX, cellY);
+ }
+
+ MWWorld::Ptr dropped =
+ MWWorld::Class::get(object).copyToCell(object, cell, pos);
+
if (mWorldScene->isCellActive(cell)) {
if (dropped.getRefData().isEnabled()) {
mWorldScene->addObjectToScene(dropped);
@@ -1560,7 +1653,7 @@ namespace MWWorld
void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount)
{
- MWWorld::Ptr::CellStore* cell = actor.getCell();
+ MWWorld::CellStore* cell = actor.getCell();
ESM::Position pos =
actor.getRefData().getPosition();
@@ -1605,13 +1698,17 @@ namespace MWWorld
if(!ptr.getClass().isActor())
return false;
+ if (ptr.getClass().getCreatureStats(ptr).isDead())
+ return false;
+
+ if (ptr.getClass().isFlying(ptr))
+ return true;
+
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
- if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0
+ if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0
&& isLevitationEnabled())
return true;
- // TODO: Check if flying creature
-
const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle());
if(!actor || !actor->getCollisionMode())
return true;
@@ -1626,7 +1723,7 @@ namespace MWWorld
return false;
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
- if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::SlowFall)).mMagnitude > 0)
+ if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).mMagnitude > 0)
return true;
return false;
@@ -1658,7 +1755,7 @@ namespace MWWorld
}
bool
- World::isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const
+ World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const
{
if (!(cell->mCell->mData.mFlags & ESM::Cell::HasWater)) {
return false;
@@ -1678,9 +1775,9 @@ namespace MWWorld
return mRendering->vanityRotateCamera(rot);
}
- void World::setCameraDistance(float dist, bool adjust, bool override)
+ void World::setCameraDistance(float dist, bool adjust, bool override_)
{
- return mRendering->setCameraDistance(dist, adjust, override);;
+ return mRendering->setCameraDistance(dist, adjust, override_);
}
void World::setupPlayer()
@@ -1698,17 +1795,19 @@ namespace MWWorld
void World::renderPlayer()
{
mRendering->renderPlayer(mPlayer->getPlayer());
- mPhysics->addActor(mPlayer->getPlayer());
- }
- void World::setupExternalRendering (MWRender::ExternalRendering& rendering)
- {
- mRendering->setupExternalRendering (rendering);
+ // At this point the Animation object is live, and the CharacterController associated with it must be created.
+ // It has to be done at this point: resetCamera below does animation->setViewMode -> CharacterController::forceStateUpdate
+ // so we should make sure not to use a "stale" controller for that.
+ MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer());
+
+ mPhysics->addActor(mPlayer->getPlayer());
+ mRendering->resetCamera();
}
int World::canRest ()
{
- Ptr::CellStore *currentCell = mWorldScene->getCurrentCell();
+ CellStore *currentCell = mWorldScene->getCurrentCell();
Ptr player = mPlayer->getPlayer();
RefData &refdata = player.getRefData();
@@ -1744,6 +1843,11 @@ namespace MWWorld
mRendering->frameStarted(dt, paused);
}
+ void World::screenshot(Ogre::Image &image, int w, int h)
+ {
+ mRendering->screenshot(image, w, h);
+ }
+
void World::activateDoor(const MWWorld::Ptr& door)
{
if (mDoorStates.find(door) != mDoorStates.end())
@@ -1836,13 +1940,6 @@ namespace MWWorld
bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
{
- // This is a placeholder! Needs to go into an NPC awareness check function (see
- // https://wiki.openmw.org/index.php?title=Research:NPC_AI_Behaviour#NPC_Awareness_Check )
- if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude)
- return false;
- if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude > 100)
- return false;
-
Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents();
float* pos1 = npc.getRefData().getPosition().pos;
Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents();
@@ -1960,6 +2057,10 @@ namespace MWWorld
npcStats.setWerewolf(werewolf);
+ // This is a bit dangerous. Equipped items other than WerewolfRobe may reference
+ // bones that do not even exist with the werewolf object root.
+ // Therefore, make sure to unequip everything at once, and only fire the change event
+ // (which will rebuild the animation parts) afterwards. unequipAll will do this for us.
MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
invStore.unequipAll(actor);
@@ -1967,13 +2068,17 @@ namespace MWWorld
{
InventoryStore &inv = actor.getClass().getInventoryStore(actor);
- inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("WerewolfRobe", 1, actor), actor);
+ inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor);
}
else
{
- actor.getClass().getContainerStore(actor).remove("WerewolfRobe", 1, actor);
+ actor.getClass().getContainerStore(actor).remove("werewolfrobe", 1, actor);
}
+ // NpcAnimation::updateParts will already rebuild the animation when it detects change of Npc type.
+ // the following is just for reattaching the camera properly.
+ mRendering->rebuildPtr(actor);
+
if(actor.getRefData().getHandle() == "player")
{
// Update the GUI only when called on the player
@@ -1991,8 +2096,6 @@ namespace MWWorld
windowManager->unsetForceHide(MWGui::GW_Magic);
}
}
-
- mRendering->rebuildPtr(actor);
}
void World::applyWerewolfAcrobatics(const Ptr &actor)
@@ -2000,7 +2103,7 @@ namespace MWWorld
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);
+ stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getFloat());
}
bool World::getGodModeState()
@@ -2037,7 +2140,7 @@ namespace MWWorld
std::string message;
bool fail = false;
- bool isPlayer = (actor == getPlayer().getPlayer());
+ bool isPlayer = (actor == getPlayerPtr());
std::string selectedSpell = stats.getSpells().getSelectedSpell();
@@ -2066,8 +2169,11 @@ namespace MWWorld
}
// Reduce mana
- magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
- stats.setMagicka(magicka);
+ if (!fail)
+ {
+ magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
+ stats.setMagicka(magicka);
+ }
}
if (isPlayer && fail)
@@ -2079,13 +2185,14 @@ namespace MWWorld
void World::castSpell(const Ptr &actor)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
- InventoryStore& inv = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr target = getFacedObject();
std::string selectedSpell = stats.getSpells().getSelectedSpell();
MWMechanics::CastSpell cast(actor, target);
+ if (!target.isEmpty())
+ cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
if (!selectedSpell.empty())
{
@@ -2093,9 +2200,11 @@ namespace MWWorld
cast.cast(spell);
}
- else if (inv.getSelectedEnchantItem() != inv.end())
+ else if (actor.getClass().hasInventoryStore(actor))
{
- cast.cast(*inv.getSelectedEnchantItem());
+ MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
+ if (inv.getSelectedEnchantItem() != inv.end())
+ cast.cast(*inv.getSelectedEnchantItem());
}
}
@@ -2152,11 +2261,18 @@ namespace MWWorld
state.mId = id;
state.mActorHandle = actor.getRefData().getHandle();
state.mSpeed = speed;
- state.mEffects = effects;
state.mStack = stack;
+ // Only interested in "on target" effects
+ for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
+ iter!=effects.mList.end(); ++iter)
+ {
+ if (iter->mRange == ESM::RT_Target)
+ state.mEffects.mList.push_back(*iter);
+ }
+
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
- sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f);
+ sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mProjectiles[ptr] = state;
}
@@ -2168,6 +2284,7 @@ namespace MWWorld
{
if (!mWorldScene->isCellActive(*it->first.getCell()))
{
+ deleteObject(it->first);
mProjectiles.erase(it++);
continue;
}
@@ -2176,14 +2293,13 @@ namespace MWWorld
Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
- // TODO: Why -rot.z, but not -rot.x?
+ // TODO: Why -rot.z, but not -rot.x? (note: same issue in MovementSolver::move)
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z);
orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X);
- // This is just a guess, probably wrong
- static float fProjectileMinSpeed = getStore().get<ESM::GameSetting>().find("fProjectileMinSpeed")->getFloat();
- static float fProjectileMaxSpeed = getStore().get<ESM::GameSetting>().find("fProjectileMaxSpeed")->getFloat();
- float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * it->second.mSpeed;
+
+ static float fTargetSpellMaxSpeed = getStore().get<ESM::GameSetting>().find("fTargetSpellMaxSpeed")->getFloat();
+ float speed = fTargetSpellMaxSpeed * it->second.mSpeed;
Ogre::Vector3 direction = orient.yAxis();
direction.normalise();
@@ -2201,11 +2317,10 @@ namespace MWWorld
if (obstacle == ptr)
continue;
- explode = true;
-
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
if (caster.isEmpty())
caster = obstacle;
+
if (obstacle.isEmpty())
{
// Terrain
@@ -2213,19 +2328,23 @@ namespace MWWorld
else
{
MWMechanics::CastSpell cast(caster, obstacle);
- cast.mStack = it->second.mStack;
+ cast.mHitPosition = pos;
cast.mId = it->second.mId;
cast.mSourceName = it->second.mSourceName;
- cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false);
+ cast.mStack = it->second.mStack;
+ cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true);
}
- deleteObject(ptr);
- mProjectiles.erase(it++);
+ explode = true;
}
if (explode)
{
- // TODO: Explode
+ MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
+ explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
+
+ deleteObject(ptr);
+ mProjectiles.erase(it++);
continue;
}
@@ -2262,10 +2381,16 @@ namespace MWWorld
}
}
+ const std::vector<std::string>& World::getContentFiles() const
+ {
+ return mContentFiles;
+ }
+
void World::breakInvisibility(const Ptr &actor)
{
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
- actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
+ if (actor.getClass().hasInventoryStore(actor))
+ actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
}
bool World::isDark() const
@@ -2275,6 +2400,8 @@ namespace MWWorld
bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result)
{
+ if (cell->isExterior())
+ return false;
MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors;
CellRefList<ESM::Door>::List& refList = doors.mList;
@@ -2295,8 +2422,12 @@ namespace MWWorld
}
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
- const std::string& id, Ogre::Vector3 worldPos)
+ const std::string& id)
{
+ Ogre::Vector3 worldPos;
+ if (!findInteriorPositionInWorldSpace(ptr.getCell(), worldPos))
+ worldPos = mPlayer->getLastKnownExteriorPosition();
+
MWWorld::Ptr closestMarker;
float closestDistance = FLT_MAX;
@@ -2390,11 +2521,11 @@ namespace MWWorld
const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects();
float dist=0;
if (type == World::Detect_Creature)
- dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectAnimal)).mMagnitude;
+ dist = effects.get(ESM::MagicEffect::DetectAnimal).mMagnitude;
else if (type == World::Detect_Key)
- dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectKey)).mMagnitude;
+ dist = effects.get(ESM::MagicEffect::DetectKey).mMagnitude;
else if (type == World::Detect_Enchantment)
- dist = effects.get(MWMechanics::EffectKey(ESM::MagicEffect::DetectEnchantment)).mMagnitude;
+ dist = effects.get(ESM::MagicEffect::DetectEnchantment).mMagnitude;
if (!dist)
return;
@@ -2417,4 +2548,264 @@ namespace MWWorld
// with the Telekinesis effect.
return feet * 22;
}
+
+ MWWorld::Ptr World::getPlayerPtr()
+ {
+ return mPlayer->getPlayer();
+ }
+
+ void World::updateDialogueGlobals()
+ {
+ MWWorld::Ptr player = getPlayerPtr();
+ int bounty = player.getClass().getNpcStats(player).getBounty();
+ int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId);
+
+ float fCrimeGoldDiscountMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->getFloat();
+ float fCrimeGoldTurnInMult = getStore().get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->getFloat();
+
+ int discount = bounty*fCrimeGoldDiscountMult;
+ int turnIn = bounty * fCrimeGoldTurnInMult;
+
+ mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0);
+
+ mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0);
+ mGlobalVariables["crimegolddiscount"].setInteger(discount);
+
+ mGlobalVariables["crimegoldturnin"].setInteger(turnIn);
+ mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0);
+ }
+
+ void World::confiscateStolenItems(const Ptr &ptr)
+ {
+ Ogre::Vector3 playerPos;
+ if (!findInteriorPositionInWorldSpace(ptr.getCell(), playerPos))
+ playerPos = mPlayer->getLastKnownExteriorPosition();
+
+ MWWorld::Ptr closestChest;
+ float closestDistance = FLT_MAX;
+
+ std::vector<MWWorld::Ptr> chests;
+ mCells.getInteriorPtrs("stolen_goods", chests);
+
+ Ogre::Vector3 chestPos;
+ for (std::vector<MWWorld::Ptr>::iterator it = chests.begin(); it != chests.end(); ++it)
+ {
+ if (!findInteriorPositionInWorldSpace(it->getCell(), chestPos))
+ continue;
+
+ float distance = playerPos.squaredDistance(chestPos);
+ if (distance < closestDistance)
+ {
+ closestDistance = distance;
+ closestChest = *it;
+ }
+ }
+
+ if (!closestChest.isEmpty())
+ {
+ ContainerStore& store = ptr.getClass().getContainerStore(ptr);
+ for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
+ {
+ if (!it->getCellRef().mOwner.empty() && it->getCellRef().mOwner != "player")
+ {
+ closestChest.getClass().getContainerStore(closestChest).add(*it, it->getRefData().getCount(), closestChest);
+ store.remove(*it, it->getRefData().getCount(), ptr);
+ }
+ }
+ }
+ }
+
+ void World::goToJail()
+ {
+ if (!mGoToJail)
+ {
+ // Save for next update, since the player should be able to read the dialog text first
+ mGoToJail = true;
+ return;
+ }
+ else
+ {
+ mGoToJail = false;
+
+ MWWorld::Ptr player = getPlayerPtr();
+ teleportToClosestMarker(player, "prisonmarker");
+ int bounty = player.getClass().getNpcStats(player).getBounty();
+ player.getClass().getNpcStats(player).setBounty(0);
+ confiscateStolenItems(player);
+
+ int iDaysinPrisonMod = getStore().get<ESM::GameSetting>().find("iDaysinPrisonMod")->getInt();
+ int days = std::max(1, bounty / iDaysinPrisonMod);
+
+ advanceTime(days * 24);
+
+ std::set<int> skills;
+ for (int day=0; day<days; ++day)
+ {
+ int skill = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * ESM::Skill::Length;
+ skills.insert(skill);
+
+ MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill);
+ if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak)
+ value.setBase(std::min(100, value.getBase()+1));
+ else
+ value.setBase(value.getBase()-1);
+ }
+
+ const Store<ESM::GameSetting>& gmst = getStore().get<ESM::GameSetting>();
+
+ std::string message;
+ if (days == 1)
+ message = gmst.find("sNotifyMessage42")->getString();
+ else
+ message = gmst.find("sNotifyMessage43")->getString();
+
+ std::stringstream dayStr;
+ dayStr << days;
+ if (message.find("%d") != std::string::npos)
+ message.replace(message.find("%d"), 2, dayStr.str());
+
+ for (std::set<int>::iterator it = skills.begin(); it != skills.end(); ++it)
+ {
+ std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[*it])->getString();
+ std::stringstream skillValue;
+ skillValue << player.getClass().getNpcStats(player).getSkill(*it).getBase();
+ std::string skillMsg = gmst.find("sNotifyMessage44")->getString();
+ if (*it == ESM::Skill::Sneak || *it == ESM::Skill::Security)
+ skillMsg = gmst.find("sNotifyMessage39")->getString();
+
+ if (skillMsg.find("%s") != std::string::npos)
+ skillMsg.replace(skillMsg.find("%s"), 2, skillName);
+ if (skillMsg.find("%d") != std::string::npos)
+ skillMsg.replace(skillMsg.find("%d"), 2, skillValue.str());
+ message += "\n" + skillMsg;
+ }
+
+ std::vector<std::string> buttons;
+ buttons.push_back("#{sOk}");
+ MWBase::Environment::get().getWindowManager()->messageBox(message, buttons);
+ }
+ }
+
+ void World::spawnRandomCreature(const std::string &creatureList)
+ {
+ const ESM::CreatureLevList* list = getStore().get<ESM::CreatureLevList>().find(creatureList);
+
+ int iNumberCreatures = getStore().get<ESM::GameSetting>().find("iNumberCreatures")->getInt();
+ int numCreatures = 1 + std::rand()/ (static_cast<double> (RAND_MAX) + 1) * iNumberCreatures; // [1, iNumberCreatures]
+
+ for (int i=0; i<numCreatures; ++i)
+ {
+ std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
+ if (selectedCreature.empty())
+ return;
+
+ ESM::Position ipos = mPlayer->getPlayer().getRefData().getPosition();
+ Ogre::Vector3 pos(ipos.pos);
+ Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
+ const float distance = 50;
+ pos = pos + distance*rot.yAxis();
+ ipos.pos[0] = pos.x;
+ ipos.pos[1] = pos.y;
+ ipos.pos[2] = pos.z;
+ ipos.rot[0] = 0;
+ ipos.rot[1] = 0;
+ ipos.rot[2] = 0;
+
+ MWWorld::CellStore* cell = mPlayer->getPlayer().getCell();
+ MWWorld::ManualRef ref(getStore(), selectedCreature, 1);
+ ref.getPtr().getCellRef().mPos = ipos;
+
+ safePlaceObject(ref.getPtr(),*cell,ipos);
+ }
+ }
+
+ void World::spawnBloodEffect(const Ptr &ptr, const Vector3 &worldPosition)
+ {
+ int type = ptr.getClass().getBloodTexture(ptr);
+ std::string texture;
+ switch (type)
+ {
+ case 2:
+ texture = getFallback()->getFallbackString("Blood_Texture_2");
+ break;
+ case 1:
+ texture = getFallback()->getFallbackString("Blood_Texture_1");
+ break;
+ case 0:
+ default:
+ texture = getFallback()->getFallbackString("Blood_Texture_0");
+ break;
+ }
+
+ std::stringstream modelName;
+ modelName << "Blood_Model_";
+ int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 3; // [0, 2]
+ modelName << roll;
+ std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str());
+
+ mRendering->spawnEffect(model, texture, worldPosition);
+ }
+
+ void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster,
+ const std::string& id, const std::string& sourceName)
+ {
+ std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
+ for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
+ effectIt != effects.mList.end(); ++effectIt)
+ {
+ const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
+
+ if (effectIt->mArea <= 0)
+ continue; // Not an area effect
+
+ // Spawn the explosion orb effect
+ const ESM::Static* areaStatic;
+ if (!effect->mCasting.empty())
+ areaStatic = getStore().get<ESM::Static>().find (effect->mArea);
+ else
+ areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea");
+
+ mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, effectIt->mArea);
+
+ // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
+ static const std::string schools[] = {
+ "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+ };
+ MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+ if(!effect->mAreaSound.empty())
+ sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
+ else
+ sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
+
+ // Get the actors in range of the effect
+ std::vector<MWWorld::Ptr> objects;
+ MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
+ origin, feetToGameUnits(effectIt->mArea), objects);
+
+ for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected)
+ toApply[*affected].push_back(*effectIt);
+ }
+
+ // Now apply the appropriate effects to each actor in range
+ for (std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply)
+ {
+ MWWorld::Ptr source = caster;
+ // Vanilla-compatible behaviour of never applying the spell to the caster
+ // (could be changed by mods later)
+ if (apply->first == caster)
+ continue;
+
+ if (source.isEmpty())
+ source = apply->first;
+
+ MWMechanics::CastSpell cast(source, apply->first);
+ cast.mHitPosition = origin;
+ cast.mId = id;
+ cast.mSourceName = sourceName;
+ cast.mStack = false;
+ ESM::EffectList effects;
+ effects.mList = apply->second;
+ cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true);
+ }
+ }
}
diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp
index 1aecb6fb64..036cafe2db 100644
--- a/apps/openmw/mwworld/worldimp.hpp
+++ b/apps/openmw/mwworld/worldimp.hpp
@@ -11,6 +11,7 @@
#include "localscripts.hpp"
#include "timestamp.hpp"
#include "fallback.hpp"
+#include "globals.hpp"
#include "../mwbase/world.hpp"
@@ -41,6 +42,7 @@ namespace MWRender
class SkyManager;
class CellRender;
class Animation;
+ class Camera;
}
struct ContentLoader;
@@ -64,7 +66,7 @@ namespace MWWorld
std::vector<ESM::ESMReader> mEsm;
MWWorld::ESMStore mStore;
LocalScripts mLocalScripts;
- MWWorld::Globals *mGlobalVariables;
+ MWWorld::Globals mGlobalVariables;
MWWorld::PhysicsSystem *mPhysics;
bool mSky;
@@ -73,12 +75,13 @@ namespace MWWorld
OEngine::Physic::PhysicEngine* mPhysEngine;
bool mGodMode;
+ std::vector<std::string> mContentFiles;
// not implemented
World (const World&);
World& operator= (const World&);
- Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore);
+ Ptr getPtrViaHandle (const std::string& handle, CellStore& cellStore);
int mActivationDistanceOverride;
std::string mFacedHandle;
@@ -114,7 +117,7 @@ namespace MWWorld
bool moveObjectImp (const Ptr& ptr, float x, float y, float z);
///< @return true if the active cell (cell player is in) changed
- Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos, bool adjustPos=true);
+ Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, ESM::Position pos, bool adjustPos=true);
void updateWindowManager ();
void performUpdateSceneQueries ();
@@ -125,7 +128,7 @@ namespace MWWorld
float getObjectActivationDistance ();
void removeContainerScripts(const Ptr& reference);
- void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell);
+ void addContainerScripts(const Ptr& reference, CellStore* cell);
void PCDropped (const Ptr& item);
void processDoors(float duration);
@@ -151,6 +154,7 @@ namespace MWWorld
bool mTeleportEnabled;
bool mLevitationEnabled;
+ bool mGoToJail;
/// Called when \a object is moved to an inactive cell
void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr);
@@ -169,13 +173,27 @@ namespace MWWorld
virtual void startNewGame();
+ virtual void clear();
+
+ virtual int countSavedGameRecords() const;
+
+ virtual void write (ESM::ESMWriter& writer) const;
+
+ virtual void readRecord (ESM::ESMReader& reader, int32_t type,
+ const std::map<int, int>& contentFileMap);
+
virtual OEngine::Render::Fader* getFader();
- ///< \ŧodo remove this function. Rendering details should not be exposed.
+ ///< \todo remove this function. Rendering details should not be exposed.
virtual CellStore *getExterior (int x, int y);
virtual CellStore *getInterior (const std::string& name);
+ virtual CellStore *getCell (const ESM::CellId& id);
+
+ //switch to POV before showing player's death animation
+ virtual void useDeathCamera();
+
virtual void setWaterHeight(const float height);
virtual void toggleWater();
@@ -187,6 +205,7 @@ namespace MWWorld
virtual const Fallback *getFallback() const;
virtual Player& getPlayer();
+ virtual MWWorld::Ptr getPlayerPtr();
virtual const MWWorld::ESMStore& getStore() const;
@@ -213,16 +232,26 @@ namespace MWWorld
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 void setGlobalInt (const std::string& name, int value);
+ ///< Set value independently from real type.
- virtual Globals::Data getGlobalVariable (const std::string& name) const;
+ virtual void setGlobalFloat (const std::string& name, float value);
+ ///< Set value independently from real type.
+
+ virtual int getGlobalInt (const std::string& name) const;
+ ///< Get value independently from real type.
+
+ virtual float getGlobalFloat (const std::string& name) const;
+ ///< Get value independently from real type.
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 std::string getCellName (const MWWorld::CellStore *cell = 0) const;
+ ///< Return name of the cell.
+ ///
+ /// \note If cell==0, the cell the player is currently in will be used instead to
+ /// generate a name.
virtual void removeRefScript (MWWorld::RefData *ref);
//< Remove the script attached to ref from mLocalScripts
@@ -231,6 +260,10 @@ namespace MWWorld
///< Return a pointer to a liveCellRef with the given name.
/// \param activeOnly do non search inactive cells.
+ virtual Ptr searchPtr (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.
@@ -256,8 +289,12 @@ namespace MWWorld
virtual void setDay (int day);
///< Set in-game time day.
- virtual int getDay();
- virtual int getMonth();
+ virtual int getDay() const;
+ virtual int getMonth() const;
+ virtual int getYear() const;
+
+ virtual std::string getMonthName (int month = -1) const;
+ ///< Return name of month (-1: current month)
virtual TimeStamp getTimeStamp() const;
///< Return current in-game time stamp.
@@ -286,6 +323,8 @@ namespace MWWorld
virtual void changeToExteriorCell (const ESM::Position& position);
///< Move to exterior cell.
+ virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position);
+
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.
@@ -404,7 +443,7 @@ namespace MWWorld
///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 isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const;
virtual bool isOnGround(const MWWorld::Ptr &ptr) const;
virtual void togglePOV() {
@@ -456,8 +495,6 @@ namespace MWWorld
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
@@ -472,6 +509,7 @@ namespace MWWorld
virtual void playVideo(const std::string& name, bool allowSkipping);
virtual void stopVideo();
virtual void frameStarted (float dt, bool paused);
+ virtual void screenshot (Ogre::Image& image, int w, int h);
/// Find center of exterior cell above land surface
/// \return false if exterior with given name not exists, true otherwise
@@ -517,23 +555,43 @@ namespace MWWorld
virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName);
+
+ virtual const std::vector<std::string>& getContentFiles() const;
+
virtual void breakInvisibility (const MWWorld::Ptr& actor);
// Are we in an exterior or pseudo-exterior cell and it's night?
virtual bool isDark() const;
virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result);
- /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
- /// closest to \a worldPos.
+ /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker)
/// @note id must be lower case
virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr,
- const std::string& id, Ogre::Vector3 worldPos);
+ const std::string& id);
/// List all references (filtered by \a type) detected by \a ptr. The range
/// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type.
/// @note This also works for references in containers.
virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector<MWWorld::Ptr>& out,
DetectionType type);
+
+ /// Update the value of some globals according to the world state, which may be used by dialogue entries.
+ /// This should be called when initiating a dialogue.
+ virtual void updateDialogueGlobals();
+
+ /// Moves all stolen items from \a ptr to the closest evidence chest.
+ virtual void confiscateStolenItems(const MWWorld::Ptr& ptr);
+
+ virtual void goToJail ();
+
+ /// Spawn a random creature from a levelled list next to the player
+ virtual void spawnRandomCreature(const std::string& creatureList);
+
+ /// Spawn a blood effect for \a ptr at \a worldPosition
+ virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition);
+
+ virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
+ const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName);
};
}
diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake
deleted file mode 100644
index 0dc02f5b60..0000000000
--- a/cmake/FindSDL.cmake
+++ /dev/null
@@ -1,177 +0,0 @@
-# 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/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000000..56ff1d5459
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake
@@ -0,0 +1,154 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
+#
+# Returns the refspec and sha hash of the current head revision
+#
+# git_describe(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe on the source tree, and adjusting
+# the output so that it tests false if an error occurs.
+#
+# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe --exact-match on the source tree,
+# and adjusting the output so that it tests false if there was no exact
+# matching tag.
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if(__get_git_revision_description)
+ return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+function(get_git_head_revision _refspecvar _hashvar)
+ set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+ while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
+ set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
+ get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
+ if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
+ # We have reached the root directory, we are not in git
+ set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ return()
+ endif()
+
+ set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+ endwhile()
+
+ # check if this is a submodule
+ if(NOT IS_DIRECTORY ${GIT_DIR})
+ file(READ ${GIT_DIR} submodule)
+ string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
+ get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
+ get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
+ endif()
+
+ set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+
+ if(NOT EXISTS "${GIT_DATA}")
+ file(MAKE_DIRECTORY "${GIT_DATA}")
+ endif()
+
+ if(NOT EXISTS "${GIT_DIR}/HEAD")
+ return()
+ endif()
+
+ set(HEAD_FILE "${GIT_DATA}/HEAD")
+ configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
+
+ configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
+ "${GIT_DATA}/grabRef.cmake" @ONLY)
+ include("${GIT_DATA}/grabRef.cmake")
+
+ set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+ set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
+
+function(git_describe _var)
+ #get_git_head_revision(refspec hash)
+
+ if(NOT GIT_FOUND)
+ set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
+ return()
+ endif()
+
+ #if(NOT hash)
+ # set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
+ # return()
+ #endif()
+
+ # TODO sanitize
+ #if((${ARGN}" MATCHES "&&") OR
+ # (ARGN MATCHES "||") OR
+ # (ARGN MATCHES "\\;"))
+ # message("Please report the following error to the project!")
+ # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
+ #endif()
+
+ #message(STATUS "Arguments to execute_process: ${ARGN}")
+
+ execute_process(COMMAND
+ "${GIT_EXECUTABLE}"
+ describe
+ #${hash}
+ ${ARGN}
+ WORKING_DIRECTORY
+ "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE
+ res
+ OUTPUT_VARIABLE
+ out
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+ if(NOT res EQUAL 0)
+ set(out "${out}-${res}-NOTFOUND")
+ endif()
+
+ set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+function(get_git_tag_revision _var)
+ if(NOT GIT_FOUND)
+ set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
+ return()
+ endif()
+
+ execute_process(COMMAND
+ "${GIT_EXECUTABLE}"
+ rev-list
+ ${ARGN}
+ WORKING_DIRECTORY
+ "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE
+ res
+ OUTPUT_VARIABLE
+ out
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT res EQUAL 0)
+ set(out "${out}-${res}-NOTFOUND")
+ endif()
+
+ set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+
diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000000..888ce13aab
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,38 @@
+#
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(HEAD_HASH)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if(HEAD_CONTENTS MATCHES "ref")
+ # named branch
+ string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+ if(EXISTS "@GIT_DIR@/${HEAD_REF}")
+ configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+ elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
+ configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+ set(HEAD_HASH "${HEAD_REF}")
+ endif()
+else()
+ # detached HEAD
+ configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+if(NOT HEAD_HASH)
+ file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+ string(STRIP "${HEAD_HASH}" HEAD_HASH)
+endif()
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index a037fd5fa4..831b140575 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -1,5 +1,9 @@
project (Components)
set (CMAKE_BUILD_TYPE DEBUG)
+
+# Version file
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp")
+
# source files
add_component_dir (settings
@@ -40,6 +44,8 @@ add_component_dir (esm
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
+ savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate
+ npcstats creaturestats
)
add_component_dir (misc
@@ -54,7 +60,8 @@ add_component_dir (files
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
+ stringparser tokenloc nullerrorhandler opcodes extensions0 declarationparser
+ quickfileparser
)
add_component_dir (interpreter
@@ -75,8 +82,12 @@ add_component_dir (loadinglistener
)
add_component_dir (ogreinit
- ogreinit ogreplugin
- )
+ ogreinit ogreplugin
+ )
+
+add_component_dir (version
+ version
+ )
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)
diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp
index 8f07b9e503..eb741fb105 100644
--- a/components/bsa/bsa_archive.cpp
+++ b/components/bsa/bsa_archive.cpp
@@ -23,6 +23,8 @@
#include "bsa_archive.hpp"
+#include <boost/filesystem.hpp>
+
#include <OgreFileSystem.h>
#include <OgreArchive.h>
#include <OgreArchiveFactory.h>
diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp
index 18f7377ff2..7f9ebaae10 100644
--- a/components/bsa/bsa_archive.hpp
+++ b/components/bsa/bsa_archive.hpp
@@ -22,8 +22,6 @@
*/
#include <string>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string.hpp>
#include <algorithm>
#ifndef BSA_BSA_ARCHIVE_H
diff --git a/components/compiler/context.hpp b/components/compiler/context.hpp
index 1b02613c59..84bb89bdc4 100644
--- a/components/compiler/context.hpp
+++ b/components/compiler/context.hpp
@@ -33,11 +33,18 @@ namespace Compiler
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 std::pair<char, bool> getMemberType (const std::string& name,
+ const std::string& id) const = 0;
+ ///< Return type of member variable \a name in script \a id or in script of reference of
+ /// \a id
+ /// \return first: 'l: long, 's': short, 'f': float, ' ': does not exist.
+ /// second: true: script of reference
virtual bool isId (const std::string& name) const = 0;
///< Does \a name match an ID, that can be referenced?
+
+ virtual bool isJournalId (const std::string& name) const = 0;
+ ///< Does \a name match a journal ID?
};
}
diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp
index 5d74ee9d42..aefe6d16da 100644
--- a/components/compiler/controlparser.cpp
+++ b/components/compiler/controlparser.cpp
@@ -7,6 +7,8 @@
#include "scanner.hpp"
#include "generator.hpp"
+#include "errorhandler.hpp"
+#include "skipparser.hpp"
namespace Compiler
{
@@ -70,7 +72,7 @@ namespace Compiler
}
else if (keyword==Scanner::K_else)
{
- mState = IfElseEndState;
+ mState = IfElseJunkState; /// \todo should be IfElseEndState; add an option for that
}
return true;
@@ -106,7 +108,7 @@ namespace Compiler
Codes expr;
mExprParser.append (expr);
- Generator::jump (loop, -static_cast<int> (mCodeBlock.size()-expr.size()));
+ Generator::jump (loop, -static_cast<int> (mCodeBlock.size()+expr.size()));
std::copy (expr.begin(), expr.end(), std::back_inserter (mCode));
@@ -120,7 +122,7 @@ namespace Compiler
Codes loop2;
- Generator::jump (loop2, -static_cast<int> (mCodeBlock.size()-expr.size()-skip.size()));
+ Generator::jump (loop2, -static_cast<int> (mCodeBlock.size()+expr.size()+skip.size()));
if (loop.size()!=loop2.size())
throw std::logic_error (
@@ -153,7 +155,7 @@ namespace Compiler
}
}
- ControlParser::ControlParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ ControlParser::ControlParser (ErrorHandler& errorHandler, const Context& context, Locals& locals,
Literals& literals)
: Parser (errorHandler, context), mLocals (locals), mLiterals (literals),
mLineParser (errorHandler, context, locals, literals, mCodeBlock),
@@ -186,8 +188,11 @@ namespace Compiler
{
if (mState==StartState)
{
- if (keyword==Scanner::K_if)
+ if (keyword==Scanner::K_if || keyword==Scanner::K_elseif)
{
+ if (keyword==Scanner::K_elseif)
+ getErrorHandler().warning ("elseif without matching if", loc);
+
mExprParser.reset();
scanner.scan (mExprParser);
@@ -203,7 +208,8 @@ namespace Compiler
return true;
}
}
- else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState)
+ else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState ||
+ mState==IfElseJunkState)
{
if (parseIfBody (keyword, loc, scanner))
return true;
@@ -226,6 +232,7 @@ namespace Compiler
case IfEndState: mState = IfBodyState; return true;
case IfElseifEndState: mState = IfElseifBodyState; return true;
case IfElseEndState: mState = IfElseBodyState; return true;
+ case IfElseJunkState: mState = IfElseBodyState; return true;
case WhileEndState: mState = WhileBodyState; return true;
@@ -243,7 +250,13 @@ namespace Compiler
default: ;
}
-
+ }
+ else if (code==Scanner::S_open && mState==IfElseJunkState)
+ {
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ mState = IfElseBodyState;
+ return true;
}
return Parser::parseSpecial (code, loc, scanner);
diff --git a/components/compiler/controlparser.hpp b/components/compiler/controlparser.hpp
index 50fd2d1f9e..1175a0ed58 100644
--- a/components/compiler/controlparser.hpp
+++ b/components/compiler/controlparser.hpp
@@ -26,7 +26,8 @@ namespace Compiler
IfElseEndState, IfElseBodyState,
IfEndifState,
WhileEndState, WhileBodyState,
- WhileEndwhileState
+ WhileEndwhileState,
+ IfElseJunkState
};
typedef std::vector<Interpreter::Type_Code> Codes;
@@ -47,7 +48,7 @@ namespace Compiler
public:
- ControlParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ ControlParser (ErrorHandler& errorHandler, const Context& context, Locals& locals,
Literals& literals);
void appendCode (std::vector<Interpreter::Type_Code>& code) const;
diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp
new file mode 100644
index 0000000000..d17f49caf0
--- /dev/null
+++ b/components/compiler/declarationparser.cpp
@@ -0,0 +1,83 @@
+
+#include "declarationparser.hpp"
+
+#include <components/misc/stringops.hpp>
+
+#include "scanner.hpp"
+#include "errorhandler.hpp"
+#include "skipparser.hpp"
+#include "locals.hpp"
+
+Compiler::DeclarationParser::DeclarationParser (ErrorHandler& errorHandler, const Context& context,
+ Locals& locals)
+: Parser (errorHandler, context), mLocals (locals), mState (State_Begin), mType (0)
+{}
+
+bool Compiler::DeclarationParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+{
+ if (mState==State_Name)
+ {
+ 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 (ignoring declaration)",
+ loc);
+
+ mState = State_End;
+ return true;
+ }
+
+ mLocals.declare (mType, name2);
+
+ mState = State_End;
+ return true;
+ }
+
+ return Parser::parseName (name, loc, scanner);
+}
+
+bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+{
+ if (mState==State_Begin)
+ {
+ switch (keyword)
+ {
+ case Scanner::K_short: mType = 's'; break;
+ case Scanner::K_long: mType = 'l'; break;
+ case Scanner::K_float: mType = 'f'; break;
+ default: mType = 0;
+ }
+
+ if (mType)
+ {
+ mState = State_Name;
+ return true;
+ }
+ }
+ else if (mState==State_Name)
+ {
+ // allow keywords to be used as local variable names. MW script compiler, you suck!
+ /// \todo option to disable this atrocity.
+ return parseName (loc.mLiteral, loc, scanner);
+ }
+
+ return Parser::parseKeyword (keyword, loc, scanner);
+}
+
+bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+{
+ if (code==Scanner::S_newline && mState==State_End)
+ return false;
+
+ return Parser::parseSpecial (code, loc, scanner);
+}
+
+void Compiler::DeclarationParser::reset()
+{
+ mState = State_Begin;
+} \ No newline at end of file
diff --git a/components/compiler/declarationparser.hpp b/components/compiler/declarationparser.hpp
new file mode 100644
index 0000000000..43dd835705
--- /dev/null
+++ b/components/compiler/declarationparser.hpp
@@ -0,0 +1,43 @@
+#ifndef COMPILER_DECLARATIONPARSER_H_INCLUDED
+#define COMPILER_DECLARATIONPARSER_H_INCLUDED
+
+#include "parser.hpp"
+
+namespace Compiler
+{
+ class Locals;
+
+ class DeclarationParser : public Parser
+ {
+ enum State
+ {
+ State_Begin, State_Name, State_End
+ };
+
+ Locals& mLocals;
+ State mState;
+ char mType;
+
+ public:
+
+ DeclarationParser (ErrorHandler& errorHandler, const Context& context, Locals& locals);
+
+ 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();
+
+ };
+}
+
+#endif
diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp
index ee13c837d1..fe58836cca 100644
--- a/components/compiler/errorhandler.cpp
+++ b/components/compiler/errorhandler.cpp
@@ -5,7 +5,7 @@ namespace Compiler
{
// constructor
- ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0) {}
+ ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0), mWarningsMode (1) {}
// destructor
@@ -36,8 +36,13 @@ namespace Compiler
void ErrorHandler::warning (const std::string& message, const TokenLoc& loc)
{
- ++mWarnings;
- report (message, loc, WarningMessage);
+ if (mWarningsMode==1)
+ {
+ ++mWarnings;
+ report (message, loc, WarningMessage);
+ }
+ else if (mWarningsMode==2)
+ error (message, loc);
}
// Generate an error message.
@@ -62,4 +67,9 @@ namespace Compiler
{
mErrors = mWarnings = 0;
}
+
+ void ErrorHandler::setWarningsMode (int mode)
+ {
+ mWarningsMode = mode;
+ }
}
diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp
index 256065854a..e5922a6be5 100644
--- a/components/compiler/errorhandler.hpp
+++ b/components/compiler/errorhandler.hpp
@@ -16,6 +16,7 @@ namespace Compiler
{
int mWarnings;
int mErrors;
+ int mWarningsMode;
protected:
@@ -60,8 +61,11 @@ namespace Compiler
void endOfFile();
///< Generate an error message for an unexpected EOF.
- virtual void reset();
+ virtual void reset();
///< Remove all previous error/warning events
+
+ void setWarningsMode (int mode);
+ ///< // 0 ignore, 1 rate as warning, 2 rate as error
};
}
diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp
index 94240c5eb9..0c013b18f1 100644
--- a/components/compiler/exprparser.cpp
+++ b/components/compiler/exprparser.cpp
@@ -7,6 +7,8 @@
#include <stack>
#include <iterator>
+#include <components/misc/stringops.hpp>
+
#include "generator.hpp"
#include "scanner.hpp"
#include "errorhandler.hpp"
@@ -14,7 +16,6 @@
#include "stringparser.hpp"
#include "extensions.hpp"
#include "context.hpp"
-#include <components/misc/stringops.hpp>
namespace Compiler
{
@@ -203,21 +204,22 @@ namespace Compiler
std::string name2 = Misc::StringUtils::lowerCase (name);
std::string id = Misc::StringUtils::lowerCase (mExplicit);
- char type = getContext().getMemberType (name2, id);
+ std::pair<char, bool> type = getContext().getMemberType (name2, id);
- if (type!=' ')
+ if (type.first!=' ')
{
- Generator::fetchMember (mCode, mLiterals, type, name2, id);
+ Generator::fetchMember (mCode, mLiterals, type.first, name2, id, !type.second);
+
mNextOperand = false;
mExplicit.clear();
- mOperands.push_back (type=='f' ? 'f' : 'l');
+ mOperands.push_back (type.first=='f' ? 'f' : 'l');
return true;
}
return false;
}
- ExprParser::ExprParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ ExprParser::ExprParser (ErrorHandler& errorHandler, const 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)
@@ -308,6 +310,22 @@ namespace Compiler
return true;
}
+ // die in a fire, Morrowind script compiler!
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ if (getContext().isJournalId (name2))
+ {
+ // JournalID used as an argument. Use the index of that JournalID
+ Generator::pushString (mCode, mLiterals, name2);
+ int keyword = extensions->searchKeyword ("getjournalindex");
+ extensions->generateFunctionCode (keyword, mCode, mLiterals, mExplicit, 0);
+ mNextOperand = false;
+ mOperands.push_back ('l');
+
+ return 2;
+ }
+ }
+
if (mExplicit.empty() && getContext().isId (name2))
{
mExplicit = name2;
@@ -326,6 +344,31 @@ namespace Compiler
bool ExprParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
{
+ if (const Extensions *extensions = getContext().getExtensions())
+ {
+ std::string argumentType; // ignored
+ bool hasExplicit = false; // ignored
+ if (extensions->isInstruction (keyword, argumentType, hasExplicit))
+ {
+ // pretend this is not a keyword
+ return parseName (loc.mLiteral, loc, scanner);
+ }
+ }
+
+ if (keyword==Scanner::K_end || keyword==Scanner::K_begin ||
+ keyword==Scanner::K_short || keyword==Scanner::K_long ||
+ keyword==Scanner::K_float || keyword==Scanner::K_if ||
+ keyword==Scanner::K_endif || keyword==Scanner::K_else ||
+ keyword==Scanner::K_elseif || keyword==Scanner::K_while ||
+ keyword==Scanner::K_endwhile || keyword==Scanner::K_return ||
+ keyword==Scanner::K_messagebox || keyword==Scanner::K_set ||
+ keyword==Scanner::K_to || keyword==Scanner::K_startscript ||
+ keyword==Scanner::K_stopscript || keyword==Scanner::K_enable ||
+ keyword==Scanner::K_disable)
+ {
+ return parseName (loc.mLiteral, loc, scanner);
+ }
+
mFirst = false;
if (!mExplicit.empty())
@@ -368,8 +411,15 @@ namespace Compiler
char returnType;
std::string argumentType;
- if (extensions->isFunction (keyword, returnType, argumentType, true))
+ bool hasExplicit = true;
+ if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit))
{
+ if (!hasExplicit)
+ {
+ getErrorHandler().warning ("stray explicit reference (ignoring it)", loc);
+ mExplicit.clear();
+ }
+
start();
mTokenLoc = loc;
@@ -490,7 +540,9 @@ namespace Compiler
char returnType;
std::string argumentType;
- if (extensions->isFunction (keyword, returnType, argumentType, false))
+ bool hasExplicit = false;
+
+ if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit))
{
mTokenLoc = loc;
int optionals = parseArguments (argumentType, scanner);
@@ -518,6 +570,14 @@ namespace Compiler
{
if (!mExplicit.empty())
{
+ if (mRefOp && code==Scanner::S_open)
+ {
+ /// \todo add option to disable this workaround
+ mOperators.push_back ('(');
+ mTokenLoc = loc;
+ return true;
+ }
+
if (!mRefOp && code==Scanner::S_ref)
{
mRefOp = true;
@@ -687,11 +747,11 @@ namespace Compiler
{
optional = true;
}
- else if (*iter=='S' || *iter=='c')
+ else if (*iter=='S' || *iter=='c' || *iter=='x')
{
stringParser.reset();
- if (optional)
+ if (optional || *iter=='x')
stringParser.setOptional (true);
if (*iter=='c') stringParser.smashCase();
@@ -700,18 +760,21 @@ namespace Compiler
if (optional && stringParser.isEmpty())
break;
- if (invert)
+ if (*iter!='x')
{
- std::vector<Interpreter::Type_Code> tmp;
- stringParser.append (tmp);
+ if (invert)
+ {
+ std::vector<Interpreter::Type_Code> tmp;
+ stringParser.append (tmp);
- stack.push (tmp);
- }
- else
- stringParser.append (code);
+ stack.push (tmp);
+ }
+ else
+ stringParser.append (code);
- if (optional)
- ++optionalCount;
+ if (optional)
+ ++optionalCount;
+ }
}
else
{
diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp
index 8ce5409d23..6a4e1be2ff 100644
--- a/components/compiler/exprparser.hpp
+++ b/components/compiler/exprparser.hpp
@@ -58,7 +58,7 @@ namespace Compiler
public:
- ExprParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ ExprParser (ErrorHandler& errorHandler, const Context& context, Locals& locals,
Literals& literals, bool argument = false);
///< constructor
/// \param argument Parser is used to parse function- or instruction-
@@ -101,6 +101,7 @@ namespace Compiler
/// \param arguments Each character represents one arguments ('l': integer,
/// 'f': float, 'S': string, 'c': string (case smashed), '/': following arguments are
/// optional)
+ /// 'x': optional string that will be ignored (die in a fire, MW script compiler!)
/// \param invert Store arguments in reverted order.
/// \return number of optional arguments
};
diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp
index c6a74b234b..c09abcbafd 100644
--- a/components/compiler/extensions.cpp
+++ b/components/compiler/extensions.cpp
@@ -22,7 +22,7 @@ namespace Compiler
}
bool Extensions::isFunction (int keyword, char& returnType, std::string& argumentType,
- bool explicitReference) const
+ bool& explicitReference) const
{
std::map<int, Function>::const_iterator iter = mFunctions.find (keyword);
@@ -30,7 +30,7 @@ namespace Compiler
return false;
if (explicitReference && iter->second.mCodeExplicit==-1)
- return false;
+ explicitReference = false;
returnType = iter->second.mReturn;
argumentType = iter->second.mArguments;
@@ -38,7 +38,7 @@ namespace Compiler
}
bool Extensions::isInstruction (int keyword, std::string& argumentType,
- bool explicitReference) const
+ bool& explicitReference) const
{
std::map<int, Instruction>::const_iterator iter = mInstructions.find (keyword);
@@ -46,7 +46,7 @@ namespace Compiler
return false;
if (explicitReference && iter->second.mCodeExplicit==-1)
- return false;
+ explicitReference = false;
argumentType = iter->second.mArguments;
return true;
diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp
index 53ebfa31b5..18bb24ed09 100644
--- a/components/compiler/extensions.hpp
+++ b/components/compiler/extensions.hpp
@@ -1,5 +1,5 @@
#ifndef COMPILER_EXTENSIONS_H_INCLUDED
-#define COMPILER_EXTENSINOS_H_INCLUDED
+#define COMPILER_EXTENSIONS_H_INCLUDED
#include <string>
#include <map>
@@ -47,13 +47,17 @@ namespace Compiler
/// - keyword must be all lower case.
bool isFunction (int keyword, char& returnType, std::string& argumentType,
- bool explicitReference) const;
+ bool& explicitReference) const;
///< Is this keyword registered with a function? If yes, return return and argument
/// types.
+ /// \param explicitReference In: has explicit reference; Out: set to false, if
+ /// explicit reference is not available for this instruction.
bool isInstruction (int keyword, std::string& argumentType,
- bool explicitReference) const;
+ bool& explicitReference) const;
///< Is this keyword registered with a function? If yes, return argument types.
+ /// \param explicitReference In: has explicit reference; Out: set to false, if
+ /// explicit reference is not available for this instruction.
void registerFunction (const std::string& keyword, char returnType,
const std::string& argumentType, int code, int codeExplicit = -1);
diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp
index e95f6f6985..78b6409f22 100644
--- a/components/compiler/extensions0.cpp
+++ b/components/compiler/extensions0.cpp
@@ -41,7 +41,7 @@ namespace Compiler
opcodeAiEscortCellExplicit);
extensions.registerInstruction ("aiwander", "fff/llllllllll", opcodeAiWander,
opcodeAiWanderExplicit);
- extensions.registerInstruction ("aifollow", "cffff/l", opcodeAiFollow,
+ extensions.registerInstruction ("aifollow", "cffff/llllllll", opcodeAiFollow,
opcodeAiFollowExplicit);
extensions.registerInstruction ("aifollowcell", "ccffff/l", opcodeAiFollowCell,
opcodeAiFollowCellExplicit);
@@ -61,12 +61,15 @@ namespace Compiler
extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit);
extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI);
extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI);
+ extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit);
+ extensions.registerInstruction("stopcombat", "x", opcodeStopCombat, opcodeStopCombatExplicit);
extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit);
extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit);
extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit);
extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit);
extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit);
extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit);
+ extensions.registerFunction("gettarget", 'l', "c", opcodeGetTarget, opcodeGetTargetExplicit);
}
}
@@ -187,7 +190,7 @@ namespace Compiler
extensions.registerInstruction ("enableclassmenu", "", opcodeEnableClassMenu);
extensions.registerInstruction ("enablenamemenu", "", opcodeEnableNameMenu);
extensions.registerInstruction ("enableracemenu", "", opcodeEnableRaceMenu);
- extensions.registerInstruction ("enablestatreviewmenu", "",
+ extensions.registerInstruction ("enablestatreviewmenu", "",
opcodeEnableStatsReviewMenu);
extensions.registerInstruction ("enableinventorymenu", "", opcodeEnableInventoryMenu);
@@ -198,7 +201,7 @@ namespace Compiler
extensions.registerInstruction ("enablerest", "", opcodeEnableRest);
extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest);
- extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu);
+ extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu, opcodeShowRestMenuExplicit);
extensions.registerFunction ("getbuttonpressed", 'l', "", opcodeGetButtonPressed);
@@ -222,6 +225,8 @@ namespace Compiler
extensions.registerInstruction ("activate", "", opcodeActivate);
extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit);
extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit);
+ extensions.registerInstruction ("cast", "SS", opcodeCast, opcodeCastExplicit);
+ extensions.registerInstruction ("explodespell", "S", opcodeExplodeSpell, opcodeExplodeSpellExplicit);
extensions.registerInstruction ("togglecollisionboxes", "", opcodeToggleCollisionBoxes);
extensions.registerInstruction ("togglecollisiongrid", "", opcodeToggleCollisionDebug);
extensions.registerInstruction ("tcb", "", opcodeToggleCollisionBoxes);
@@ -239,16 +244,21 @@ namespace Compiler
extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode);
extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode);
extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep);
+ extensions.registerFunction ("getpcjumping", 'l', "", opcodeGetPcJumping);
extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc);
extensions.registerInstruction ("playbink", "Sl", opcodePlayBink);
+ extensions.registerInstruction ("payfine", "", opcodePayFine);
+ extensions.registerInstruction ("payfinethief", "", opcodePayFineThief);
+ extensions.registerInstruction ("gotojail", "", opcodeGoToJail);
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 ("removesoulgem", "c/l", 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 ("getspellreadied", 'l', "", opcodeGetSpellReadied, opcodeGetSpellReadiedExplicit);
extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit);
extensions.registerFunction ("getcurrenttime", 'f', "", opcodeGetCurrentTime);
extensions.registerInstruction ("setdelete", "l", opcodeSetDelete, opcodeSetDeleteExplicit);
@@ -266,6 +276,8 @@ namespace Compiler
extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode);
extensions.registerInstruction ("disablelevitation", "", opcodeDisableLevitation);
extensions.registerInstruction ("enablelevitation", "", opcodeEnableLevitation);
+ extensions.registerFunction ("getpcinjail", 'l', "", opcodeGetPcInJail);
+ extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling);
}
}
@@ -386,9 +398,15 @@ namespace Compiler
extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel);
extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel);
- extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit);
+ extensions.registerInstruction ("addspell", "cx", opcodeAddSpell, opcodeAddSpellExplicit);
extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell,
opcodeRemoveSpellExplicit);
+ extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects,
+ opcodeRemoveSpellEffectsExplicit);
+ extensions.registerInstruction ("removeeffects", "l", opcodeRemoveEffects,
+ opcodeRemoveEffectsExplicit);
+ extensions.registerInstruction ("resurrect", "", opcodeResurrect,
+ opcodeResurrectExplicit);
extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit);
extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank);
diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp
index 9b02e4273f..c67e51e572 100644
--- a/components/compiler/generator.cpp
+++ b/components/compiler/generator.cpp
@@ -260,34 +260,34 @@ namespace
code.push_back (Compiler::Generator::segment5 (44));
}
- void opStoreMemberShort (Compiler::Generator::CodeContainer& code)
+ void opStoreMemberShort (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (59));
+ code.push_back (Compiler::Generator::segment5 (global ? 65 : 59));
}
- void opStoreMemberLong (Compiler::Generator::CodeContainer& code)
+ void opStoreMemberLong (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (60));
+ code.push_back (Compiler::Generator::segment5 (global ? 66 : 60));
}
- void opStoreMemberFloat (Compiler::Generator::CodeContainer& code)
+ void opStoreMemberFloat (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (61));
+ code.push_back (Compiler::Generator::segment5 (global ? 67 : 61));
}
- void opFetchMemberShort (Compiler::Generator::CodeContainer& code)
+ void opFetchMemberShort (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (62));
+ code.push_back (Compiler::Generator::segment5 (global ? 68 : 62));
}
- void opFetchMemberLong (Compiler::Generator::CodeContainer& code)
+ void opFetchMemberLong (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (63));
+ code.push_back (Compiler::Generator::segment5 (global ? 69 : 63));
}
- void opFetchMemberFloat (Compiler::Generator::CodeContainer& code)
+ void opFetchMemberFloat (Compiler::Generator::CodeContainer& code, bool global)
{
- code.push_back (Compiler::Generator::segment5 (64));
+ code.push_back (Compiler::Generator::segment5 (global ? 70 : 64));
}
void opRandom (Compiler::Generator::CodeContainer& code)
@@ -593,7 +593,7 @@ namespace Compiler
else if (offset<0)
opJumpBackward (code, -offset);
else
- throw std::logic_error ("inifite loop");
+ throw std::logic_error ("infinite loop");
}
void jumpOnZero (CodeContainer& code, int offset)
@@ -738,7 +738,8 @@ namespace Compiler
}
void assignToMember (CodeContainer& code, Literals& literals, char localType,
- const std::string& name, const std::string& id, const CodeContainer& value, char valueType)
+ const std::string& name, const std::string& id, const CodeContainer& value,
+ char valueType, bool global)
{
int index = literals.addString (name);
@@ -766,17 +767,17 @@ namespace Compiler
{
case 'f':
- opStoreMemberFloat (code);
+ opStoreMemberFloat (code, global);
break;
case 's':
- opStoreMemberShort (code);
+ opStoreMemberShort (code, global);
break;
case 'l':
- opStoreMemberLong (code);
+ opStoreMemberLong (code, global);
break;
default:
@@ -786,7 +787,7 @@ namespace Compiler
}
void fetchMember (CodeContainer& code, Literals& literals, char localType,
- const std::string& name, const std::string& id)
+ const std::string& name, const std::string& id, bool global)
{
int index = literals.addString (name);
@@ -800,17 +801,17 @@ namespace Compiler
{
case 'f':
- opFetchMemberFloat (code);
+ opFetchMemberFloat (code, global);
break;
case 's':
- opFetchMemberShort (code);
+ opFetchMemberShort (code, global);
break;
case 'l':
- opFetchMemberLong (code);
+ opFetchMemberLong (code, global);
break;
default:
diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp
index feab26c93d..b511161225 100644
--- a/components/compiler/generator.hpp
+++ b/components/compiler/generator.hpp
@@ -102,10 +102,12 @@ namespace Compiler
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);
+ const std::string& name, const std::string& id, const CodeContainer& value, char valueType, bool global);
+ ///< \param global Member of a global script instead of a script of a reference.
void fetchMember (CodeContainer& code, Literals& literals, char memberType,
- const std::string& name, const std::string& id);
+ const std::string& name, const std::string& id, bool global);
+ ///< \param global Member of a global script instead of a script of a reference.
void random (CodeContainer& code);
diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp
index 3d9ac0a93a..5457d76255 100644
--- a/components/compiler/lineparser.cpp
+++ b/components/compiler/lineparser.cpp
@@ -1,6 +1,8 @@
#include "lineparser.hpp"
+#include <components/misc/stringops.hpp>
+
#include "scanner.hpp"
#include "context.hpp"
#include "errorhandler.hpp"
@@ -8,7 +10,7 @@
#include "locals.hpp"
#include "generator.hpp"
#include "extensions.hpp"
-#include <components/misc/stringops.hpp>
+#include "declarationparser.hpp"
namespace Compiler
{
@@ -48,7 +50,7 @@ namespace Compiler
}
}
- LineParser::LineParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ LineParser::LineParser (ErrorHandler& errorHandler, const 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),
@@ -82,33 +84,9 @@ namespace Compiler
bool LineParser::parseName (const std::string& name, const TokenLoc& loc,
Scanner& scanner)
{
- if (mState==ShortState || mState==LongState || mState==FloatState)
+ if (mState==PotentialEndState)
{
- 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);
-
+ getErrorHandler().warning ("stay string argument (ignoring it)", loc);
mState = EndState;
return true;
}
@@ -142,12 +120,13 @@ namespace Compiler
if (mState==SetMemberVarState)
{
mMemberName = name;
- char type = getContext().getMemberType (mMemberName, mName);
+ std::pair<char, bool> type = getContext().getMemberType (mMemberName, mName);
- if (type!=' ')
+ if (type.first!=' ')
{
mState = SetMemberVarState2;
- mType = type;
+ mType = type.first;
+ mReferenceMember = type.second;
return true;
}
@@ -240,6 +219,34 @@ namespace Compiler
bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
{
+ if (mState==SetMemberVarState)
+ {
+ mMemberName = loc.mLiteral;
+ std::pair<char, bool> type = getContext().getMemberType (mMemberName, mName);
+
+ if (type.first!=' ')
+ {
+ mState = SetMemberVarState2;
+ mType = type.first;
+ mReferenceMember = type.second;
+ return true;
+ }
+ }
+
+ if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to)
+ {
+ getErrorHandler().warning ("unknown variable (ignoring set instruction)", loc);
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return false;
+ }
+
+ if (mState==SetState)
+ {
+ // allow keywords to be used as variable names when assigning a value to a variable.
+ return parseName (loc.mLiteral, loc, scanner);
+ }
+
if (mState==BeginState || mState==ExplicitState)
{
switch (keyword)
@@ -247,13 +254,13 @@ namespace Compiler
case Scanner::K_enable:
Generator::enable (mCode, mLiterals, mExplicit);
- mState = EndState;
+ mState = PotentialEndState;
return true;
case Scanner::K_disable:
Generator::disable (mCode, mLiterals, mExplicit);
- mState = EndState;
+ mState = PotentialEndState;
return true;
}
@@ -262,8 +269,15 @@ namespace Compiler
{
std::string argumentType;
- if (extensions->isInstruction (keyword, argumentType, mState==ExplicitState))
+ bool hasExplicit = mState==ExplicitState;
+ if (extensions->isInstruction (keyword, argumentType, hasExplicit))
{
+ if (!hasExplicit && mState==ExplicitState)
+ {
+ getErrorHandler().warning ("stray explicit reference (ignoring it)", loc);
+ mExplicit.clear();
+ }
+
int optionals = mExprParser.parseArguments (argumentType, scanner, mCode, true);
extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals);
@@ -287,9 +301,16 @@ namespace Compiler
char returnType;
std::string argumentType;
- if (extensions->isFunction (keyword, returnType, argumentType,
- !mExplicit.empty()))
+ bool hasExplicit = !mExplicit.empty();
+
+ if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit))
{
+ if (!hasExplicit && !mExplicit.empty())
+ {
+ getErrorHandler().warning ("stray explicit reference (ignoring it)", loc);
+ mExplicit.clear();
+ }
+
scanner.putbackKeyword (keyword, loc);
parseExpression (scanner, loc);
mState = EndState;
@@ -299,13 +320,38 @@ namespace Compiler
}
}
+ if (mState==ExplicitState)
+ {
+ // drop stray explicit reference
+ getErrorHandler().warning ("stray explicit reference (ignoring it)", loc);
+ mState = BeginState;
+ mExplicit.clear();
+ }
+
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_short:
+ case Scanner::K_long:
+ case Scanner::K_float:
+ {
+ if (!getContext().canDeclareLocals())
+ {
+ getErrorHandler().error (
+ "local variables can't be declared in this context", loc);
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return true;
+ }
+
+ DeclarationParser declaration (getErrorHandler(), getContext(), mLocals);
+ if (declaration.parseKeyword (keyword, loc, scanner))
+ scanner.scan (declaration);
+
+ return true;
+ }
+
case Scanner::K_set: mState = SetState; return true;
case Scanner::K_messagebox: mState = MessageState; return true;
@@ -328,6 +374,24 @@ namespace Compiler
Generator::stopScript (mCode);
mState = EndState;
return true;
+
+ case Scanner::K_else:
+
+ getErrorHandler().warning ("stay else (ignoring it)", loc);
+ mState = EndState;
+ return true;
+
+ case Scanner::K_endif:
+
+ getErrorHandler().warning ("stay endif (ignoring it)", loc);
+ mState = EndState;
+ return true;
+
+ case Scanner::K_begin:
+
+ getErrorHandler().warning ("stay begin (ignoring it)", loc);
+ mState = EndState;
+ return true;
}
}
else if (mState==SetLocalVarState && keyword==Scanner::K_to)
@@ -365,7 +429,8 @@ namespace Compiler
std::vector<Interpreter::Type_Code> code;
char type = mExprParser.append (code);
- Generator::assignToMember (mCode, mLiterals, mType, mMemberName, mName, code, type);
+ Generator::assignToMember (mCode, mLiterals, mType, mMemberName, mName, code, type,
+ !mReferenceMember);
mState = EndState;
return true;
@@ -389,7 +454,8 @@ namespace Compiler
bool LineParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{
- if (code==Scanner::S_newline && (mState==EndState || mState==BeginState))
+ if (code==Scanner::S_newline &&
+ (mState==EndState || mState==BeginState || mState==PotentialEndState))
return false;
if (code==Scanner::S_comma && mState==MessageState)
diff --git a/components/compiler/lineparser.hpp b/components/compiler/lineparser.hpp
index aa74cd232f..4f2d33bde5 100644
--- a/components/compiler/lineparser.hpp
+++ b/components/compiler/lineparser.hpp
@@ -20,11 +20,10 @@ namespace Compiler
enum State
{
BeginState,
- ShortState, LongState, FloatState,
SetState, SetLocalVarState, SetGlobalVarState, SetPotentialMemberVarState,
SetMemberVarState, SetMemberVarState2,
MessageState, MessageCommaState, MessageButtonState, MessageButtonCommaState,
- EndState,
+ EndState, PotentialEndState /* may have a stray string argument */,
PotentialExplicitState, ExplicitState, MemberState
};
@@ -34,6 +33,7 @@ namespace Compiler
State mState;
std::string mName;
std::string mMemberName;
+ bool mReferenceMember;
int mButtons;
std::string mExplicit;
char mType;
@@ -44,7 +44,7 @@ namespace Compiler
public:
- LineParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ LineParser (ErrorHandler& errorHandler, const Context& context, Locals& locals,
Literals& literals, std::vector<Interpreter::Type_Code>& code,
bool allowExpression = false);
///< \param allowExpression Allow lines consisting of a naked expression
diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp
index d93e738494..60a5704bf3 100644
--- a/components/compiler/locals.cpp
+++ b/components/compiler/locals.cpp
@@ -7,6 +7,8 @@
#include <ostream>
#include <iterator>
+#include <components/misc/stringops.hpp>
+
namespace Compiler
{
const std::vector<std::string>& Locals::get (char type) const
@@ -15,27 +17,27 @@ namespace Compiler
{
case 's': return mShorts;
case 'l': return mLongs;
- case 'f': return mFloats;
+ 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;
}
@@ -45,10 +47,10 @@ namespace Compiler
{
case 's': return mShorts;
case 'l': return mLongs;
- case 'f': return mFloats;
+ case 'f': return mFloats;
}
-
- throw std::logic_error ("unknown variable type");
+
+ throw std::logic_error ("unknown variable type");
}
char Locals::getType (const std::string& name) const
@@ -58,35 +60,35 @@ namespace Compiler
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);
+ 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(),
@@ -94,12 +96,12 @@ namespace Compiler
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);
+ get (type).push_back (Misc::StringUtils::lowerCase (name));
}
-
+
void Locals::clear()
{
get ('s').clear();
diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp
index e54b7798c5..d5bf052538 100644
--- a/components/compiler/locals.hpp
+++ b/components/compiler/locals.hpp
@@ -8,35 +8,35 @@
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);
-
+
+ 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.
};
diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp
index b7d44a851e..1dbdbf7e72 100644
--- a/components/compiler/opcodes.hpp
+++ b/components/compiler/opcodes.hpp
@@ -53,6 +53,12 @@ namespace Compiler
const int opcodeGetLineOfSightExplicit = 0x2000223;
const int opcodeToggleAI = 0x2000224;
const int opcodeToggleAIExplicit = 0x2000225;
+ const int opcodeGetTarget = 0x2000238;
+ const int opcodeGetTargetExplicit = 0x2000239;
+ const int opcodeStartCombat = 0x200023a;
+ const int opcodeStartCombatExplicit = 0x200023b;
+ const int opcodeStopCombat = 0x200023c;
+ const int opcodeStopCombatExplicit = 0x200023d;
}
namespace Animation
@@ -161,6 +167,7 @@ namespace Compiler
const int opcodeEnableStatsMenu = 0x2000016;
const int opcodeEnableRest = 0x2000017;
const int opcodeShowRestMenu = 0x2000018;
+ const int opcodeShowRestMenuExplicit = 0x2000234;
const int opcodeGetButtonPressed = 0x2000137;
const int opcodeToggleFogOfWar = 0x2000145;
const int opcodeToggleFullHelp = 0x2000151;
@@ -188,6 +195,7 @@ namespace Compiler
const int opcodeDontSaveObject = 0x2000153;
const int opcodeToggleVanityMode = 0x2000174;
const int opcodeGetPcSleep = 0x200019f;
+ const int opcodeGetPcJumping = 0x2000233;
const int opcodeWakeUpPc = 0x20001a2;
const int opcodeGetLocked = 0x20001c7;
const int opcodeGetLockedExplicit = 0x20001c8;
@@ -195,8 +203,8 @@ namespace Compiler
const int opcodeGetEffectExplicit = 0x20001d0;
const int opcodeAddSoulGem = 0x20001f3;
const int opcodeAddSoulGemExplicit = 0x20001f4;
- const int opcodeRemoveSoulGem = 0x20001f5;
- const int opcodeRemoveSoulGemExplicit = 0x20001f6;
+ const int opcodeRemoveSoulGem = 0x20027;
+ const int opcodeRemoveSoulGemExplicit = 0x20028;
const int opcodeDrop = 0x20001f8;
const int opcodeDropExplicit = 0x20001f9;
const int opcodeDropSoulGem = 0x20001fa;
@@ -205,6 +213,8 @@ namespace Compiler
const int opcodeGetAttackedExplicit = 0x20001d4;
const int opcodeGetWeaponDrawn = 0x20001d7;
const int opcodeGetWeaponDrawnExplicit = 0x20001d8;
+ const int opcodeGetSpellReadied = 0x2000231;
+ const int opcodeGetSpellReadiedExplicit = 0x2000232;
const int opcodeGetSpellEffects = 0x20001db;
const int opcodeGetSpellEffectsExplicit = 0x20001dc;
const int opcodeGetCurrentTime = 0x20001dd;
@@ -219,6 +229,9 @@ namespace Compiler
const int opcodeGetStandingActorExplicit = 0x200020f;
const int opcodeGetWindSpeed = 0x2000212;
const int opcodePlayBink = 0x20001f7;
+ const int opcodeGoToJail = 0x2000235;
+ const int opcodePayFine = 0x2000236;
+ const int opcodePayFineThief = 0x2000237;
const int opcodeHitOnMe = 0x2000213;
const int opcodeHitOnMeExplicit = 0x2000214;
const int opcodeDisableTeleporting = 0x2000215;
@@ -228,6 +241,12 @@ namespace Compiler
const int opcodeToggleGodMode = 0x200021f;
const int opcodeDisableLevitation = 0x2000220;
const int opcodeEnableLevitation = 0x2000221;
+ const int opcodeCast = 0x2000227;
+ const int opcodeCastExplicit = 0x2000228;
+ const int opcodeExplodeSpell = 0x2000229;
+ const int opcodeExplodeSpellExplicit = 0x200022a;
+ const int opcodeGetPcInJail = 0x200023e;
+ const int opcodeGetPcTraveling = 0x200023f;
}
namespace Sky
@@ -365,6 +384,13 @@ namespace Compiler
const int opcodeIsWerewolfExplicit = 0x20001fe;
const int opcodeGetWerewolfKills = 0x20001e2;
+
+ const int opcodeRemoveSpellEffects = 0x200022b;
+ const int opcodeRemoveSpellEffectsExplicit = 0x200022c;
+ const int opcodeRemoveEffects = 0x200022d;
+ const int opcodeRemoveEffectsExplicit = 0x200022e;
+ const int opcodeResurrect = 0x200022f;
+ const int opcodeResurrectExplicit = 0x2000230;
}
namespace Transformation
@@ -392,7 +418,7 @@ namespace Compiler
const int opcodePlaceItemCell = 0x200019a;
const int opcodePlaceItem = 0x200019b;
- const int opcodePlaceAtPc = 0x200019c;
+ const int opcodePlaceAtPc = 0x200019c;
const int opcodePlaceAtMe = 0x200019d;
const int opcodePlaceAtMeExplicit = 0x200019e;
const int opcodeModScale = 0x20001e3;
diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp
index 8d11c4086d..781fbad8cb 100644
--- a/components/compiler/parser.cpp
+++ b/components/compiler/parser.cpp
@@ -52,7 +52,7 @@ namespace Compiler
// Return context
- Context& Parser::getContext()
+ const Context& Parser::getContext() const
{
return mContext;
}
@@ -64,7 +64,7 @@ namespace Compiler
return lowerCase;
}
- Parser::Parser (ErrorHandler& errorHandler, Context& context)
+ Parser::Parser (ErrorHandler& errorHandler, const Context& context)
: mErrorHandler (errorHandler), mContext (context), mOptional (false), mEmpty (true)
{}
diff --git a/components/compiler/parser.hpp b/components/compiler/parser.hpp
index 4fec570e9f..54e913b202 100644
--- a/components/compiler/parser.hpp
+++ b/components/compiler/parser.hpp
@@ -17,7 +17,7 @@ namespace Compiler
class Parser
{
ErrorHandler& mErrorHandler;
- Context& mContext;
+ const Context& mContext;
bool mOptional;
bool mEmpty;
@@ -38,14 +38,14 @@ namespace Compiler
ErrorHandler& getErrorHandler();
///< Return error handler
- Context& getContext();
+ const Context& getContext() const;
///< Return context
static std::string toLower (const std::string& name);
public:
- Parser (ErrorHandler& errorHandler, Context& context);
+ Parser (ErrorHandler& errorHandler, const Context& context);
///< constructor
virtual ~Parser();
diff --git a/components/compiler/quickfileparser.cpp b/components/compiler/quickfileparser.cpp
new file mode 100644
index 0000000000..f3d8063b2f
--- /dev/null
+++ b/components/compiler/quickfileparser.cpp
@@ -0,0 +1,52 @@
+
+#include "quickfileparser.hpp"
+
+#include "skipparser.hpp"
+#include "scanner.hpp"
+
+Compiler::QuickFileParser::QuickFileParser (ErrorHandler& errorHandler, const Context& context,
+ Locals& locals)
+: Parser (errorHandler, context), mDeclarationParser (errorHandler, context, locals)
+{}
+
+bool Compiler::QuickFileParser::parseName (const std::string& name, const TokenLoc& loc,
+ Scanner& scanner)
+{
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return true;
+}
+
+bool Compiler::QuickFileParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
+{
+ if (keyword==Scanner::K_end)
+ return false;
+
+ if (keyword==Scanner::K_short || keyword==Scanner::K_long || keyword==Scanner::K_float)
+ {
+ mDeclarationParser.reset();
+ scanner.putbackKeyword (keyword, loc);
+ scanner.scan (mDeclarationParser);
+ return true;
+ }
+
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ return true;
+}
+
+bool Compiler::QuickFileParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
+{
+ if (code!=Scanner::S_newline)
+ {
+ SkipParser skip (getErrorHandler(), getContext());
+ scanner.scan (skip);
+ }
+
+ return true;
+}
+
+void Compiler::QuickFileParser::parseEOF (Scanner& scanner)
+{
+
+} \ No newline at end of file
diff --git a/components/compiler/quickfileparser.hpp b/components/compiler/quickfileparser.hpp
new file mode 100644
index 0000000000..440d910387
--- /dev/null
+++ b/components/compiler/quickfileparser.hpp
@@ -0,0 +1,39 @@
+#ifndef COMPILER_QUICKFILEPARSER_H_INCLUDED
+#define COMPILER_QUICKFILEPARSER_H_INCLUDED
+
+#include "parser.hpp"
+#include "declarationparser.hpp"
+
+namespace Compiler
+{
+ class Locals;
+
+ /// \brief File parser variant that ignores everything but variable declarations
+ class QuickFileParser : public Parser
+ {
+ DeclarationParser mDeclarationParser;
+
+ public:
+
+ QuickFileParser (ErrorHandler& errorHandler, const Context& context, Locals& locals);
+
+ 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.
+ };
+}
+
+#endif
+
diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp
index 816443c447..46e50a2e9b 100644
--- a/components/compiler/scanner.cpp
+++ b/components/compiler/scanner.cpp
@@ -370,9 +370,9 @@ namespace Compiler
if (c=='\n')
special = S_newline;
- else if (c=='(')
+ else if (c=='(' || c=='[') /// \todo option to disable the use of [ as alias for (
special = S_open;
- else if (c==')')
+ else if (c==')' || c==']') /// \todo option to disable the use of ] as alias for )
special = S_close;
else if (c=='.')
{
diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp
index 5b3d244b0d..ea11be5f03 100644
--- a/components/compiler/scriptparser.cpp
+++ b/components/compiler/scriptparser.cpp
@@ -7,7 +7,7 @@
namespace Compiler
{
- ScriptParser::ScriptParser (ErrorHandler& errorHandler, Context& context,
+ ScriptParser::ScriptParser (ErrorHandler& errorHandler, const Context& context,
Locals& locals, bool end)
: Parser (errorHandler, context), mOutput (locals),
mLineParser (errorHandler, context, locals, mOutput.getLiterals(), mOutput.getCode()),
@@ -32,7 +32,7 @@ namespace Compiler
bool ScriptParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
{
- if (keyword==Scanner::K_while || keyword==Scanner::K_if)
+ if (keyword==Scanner::K_while || keyword==Scanner::K_if || keyword==Scanner::K_elseif)
{
mControlParser.reset();
if (mControlParser.parseKeyword (keyword, loc, scanner))
@@ -71,6 +71,12 @@ namespace Compiler
if (code==Scanner::S_newline) // empty line
return true;
+ if (code==Scanner::S_open) /// \todo Option to switch this off
+ {
+ scanner.putbackSpecial (code, loc);
+ return parseKeyword (Scanner::K_if, loc, scanner);
+ }
+
mLineParser.reset();
if (mLineParser.parseSpecial (code, loc, scanner))
scanner.scan (mLineParser);
diff --git a/components/compiler/scriptparser.hpp b/components/compiler/scriptparser.hpp
index bb4809dabd..000244c793 100644
--- a/components/compiler/scriptparser.hpp
+++ b/components/compiler/scriptparser.hpp
@@ -12,7 +12,7 @@ namespace Compiler
class Locals;
// Script parser, to be used in dialogue scripts and as part of FileParser
-
+
class ScriptParser : public Parser
{
Output mOutput;
@@ -21,14 +21,14 @@ namespace Compiler
bool mEnd;
public:
-
+
/// \param end of script is marked by end keyword.
- ScriptParser (ErrorHandler& errorHandler, Context& context, Locals& locals,
+ ScriptParser (ErrorHandler& errorHandler, const 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.
@@ -43,8 +43,8 @@ namespace Compiler
/// \return fetch another token?
virtual void parseEOF (Scanner& scanner);
- ///< Handle EOF token.
-
+ ///< Handle EOF token.
+
void reset();
///< Reset parser to clean state.
};
diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp
index 2d09b63ba0..c7cb31f58e 100644
--- a/components/compiler/skipparser.cpp
+++ b/components/compiler/skipparser.cpp
@@ -5,7 +5,7 @@
namespace Compiler
{
- SkipParser::SkipParser (ErrorHandler& errorHandler, Context& context)
+ SkipParser::SkipParser (ErrorHandler& errorHandler, const Context& context)
: Parser (errorHandler, context)
{}
@@ -34,7 +34,7 @@ namespace Compiler
{
if (code==Scanner::S_newline)
return false;
-
+
return true;
}
}
diff --git a/components/compiler/skipparser.hpp b/components/compiler/skipparser.hpp
index 17f1c8d988..239c8bb02c 100644
--- a/components/compiler/skipparser.hpp
+++ b/components/compiler/skipparser.hpp
@@ -8,13 +8,13 @@ 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);
-
+
+ SkipParser (ErrorHandler& errorHandler, const Context& context);
+
virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner);
///< Handle an int token.
/// \return fetch another token?
diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp
index 09c902131a..a86c15794f 100644
--- a/components/compiler/stringparser.cpp
+++ b/components/compiler/stringparser.cpp
@@ -10,7 +10,7 @@
namespace Compiler
{
- StringParser::StringParser (ErrorHandler& errorHandler, Context& context, Literals& literals)
+ StringParser::StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals)
: Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false)
{
diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp
index f692c650b8..3859a24965 100644
--- a/components/compiler/stringparser.hpp
+++ b/components/compiler/stringparser.hpp
@@ -10,22 +10,22 @@
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);
+
+ StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals);
virtual bool parseName (const std::string& name, const TokenLoc& loc,
Scanner& scanner);
@@ -35,15 +35,15 @@ namespace Compiler
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).
+ ///< Reset parser to clean state (this includes the smashCase function).
};
}
diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp
index b06cb529a7..8a31aadf55 100644
--- a/components/esm/aipackage.hpp
+++ b/components/esm/aipackage.hpp
@@ -30,7 +30,7 @@ namespace ESM
short mDuration;
unsigned char mTimeOfDay;
unsigned char mIdle[8];
- unsigned char mUnk;
+ unsigned char mShouldRepeat;
};
struct AITravel
diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp
new file mode 100644
index 0000000000..5bc8b7aef3
--- /dev/null
+++ b/components/esm/cellid.cpp
@@ -0,0 +1,26 @@
+
+#include "cellid.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::CellId::load (ESMReader &esm)
+{
+ mWorldspace = esm.getHNString ("SPAC");
+
+ if (esm.isNextSub ("CIDX"))
+ {
+ esm.getHT (mIndex, 8);
+ mPaged = true;
+ }
+ else
+ mPaged = false;
+}
+
+void ESM::CellId::save (ESMWriter &esm) const
+{
+ esm.writeHNString ("SPAC", mWorldspace);
+
+ if (mPaged)
+ esm.writeHNT ("CIDX", mIndex, 8);
+} \ No newline at end of file
diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp
new file mode 100644
index 0000000000..54dbdae780
--- /dev/null
+++ b/components/esm/cellid.hpp
@@ -0,0 +1,28 @@
+#ifndef OPENMW_ESM_CELLID_H
+#define OPENMW_ESM_CELLID_H
+
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ struct CellId
+ {
+ struct CellIndex
+ {
+ int mX;
+ int mY;
+ };
+
+ std::string mWorldspace;
+ CellIndex mIndex;
+ bool mPaged;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp
index e91059b26f..00b15f4a30 100644
--- a/components/esm/cellref.cpp
+++ b/components/esm/cellref.cpp
@@ -1,11 +1,87 @@
#include "cellref.hpp"
+#include "esmreader.hpp"
#include "esmwriter.hpp"
-void ESM::CellRef::save(ESMWriter &esm) const
+void ESM::CellRef::load (ESMReader& esm, bool wideRefNum)
{
- esm.writeHNT("FRMR", mRefnum);
+ // NAM0 sometimes appears here, sometimes further on
+ mNam0 = 0;
+ if (esm.isNextSub ("NAM0"))
+ esm.getHT (mNam0);
+
+ if (wideRefNum)
+ esm.getHNT (mRefNum, "FRMR", 8);
+ else
+ esm.getHNT (mRefNum.mIndex, "FRMR");
+
+ mRefID = esm.getHNString ("NAME");
+
+ // Again, UNAM sometimes appears after NAME and sometimes later.
+ // Or perhaps this UNAM means something different?
+ mReferenceBlocked = -1;
+ esm.getHNOT (mReferenceBlocked, "UNAM");
+
+ mScale = 1.0;
+ esm.getHNOT (mScale, "XSCL");
+
+ mOwner = esm.getHNOString ("ANAM");
+ mGlob = esm.getHNOString ("BNAM");
+ mSoul = esm.getHNOString ("XSOL");
+
+ mFaction = esm.getHNOString ("CNAM");
+ mFactIndex = -2;
+ esm.getHNOT (mFactIndex, "INDX");
+
+ mGoldValue = 1;
+ mCharge = -1;
+ mEnchantmentCharge = -1;
+
+ esm.getHNOT (mEnchantmentCharge, "XCHG");
+
+ esm.getHNOT (mCharge, "INTV");
+
+ esm.getHNOT (mGoldValue, "NAM9");
+
+ // Present for doors that teleport you to another cell.
+ if (esm.isNextSub ("DODT"))
+ {
+ mTeleport = true;
+ esm.getHT (mDoorDest);
+ mDestCell = esm.getHNOString ("DNAM");
+ }
+ else
+ mTeleport = false;
+
+ mLockLevel = -1;
+ esm.getHNOT (mLockLevel, "FLTV");
+ mKey = esm.getHNOString ("KNAM");
+ mTrap = esm.getHNOString ("TNAM");
+
+ mFltv = 0;
+ esm.getHNOT (mReferenceBlocked, "UNAM");
+ esm.getHNOT (mFltv, "FLTV");
+
+ esm.getHNOT(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 (mNam0);
+}
+
+void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const
+{
+ if (wideRefNum)
+ esm.writeHNT ("FRMR", mRefNum, 8);
+ else
+ esm.writeHNT ("FRMR", mRefNum.mIndex, 4);
+
esm.writeHNCString("NAME", mRefID);
if (mScale != 1.0) {
@@ -31,34 +107,38 @@ void ESM::CellRef::save(ESMWriter &esm) const
esm.writeHNT("NAM9", mGoldValue);
}
- if (mTeleport)
+ if (mTeleport && !inInventory)
{
esm.writeHNT("DODT", mDoorDest);
esm.writeHNOCString("DNAM", mDestCell);
}
- if (mLockLevel != -1) {
+ if (mLockLevel != -1 && !inInventory)
esm.writeHNT("FLTV", mLockLevel);
- }
- esm.writeHNOCString("KNAM", mKey);
- esm.writeHNOCString("TNAM", mTrap);
- if (mReferenceBlocked != -1) {
+ if (!inInventory)
+ esm.writeHNOCString ("KNAM", mKey);
+
+ if (!inInventory)
+ esm.writeHNOCString ("TNAM", mTrap);
+
+ if (mReferenceBlocked != -1)
esm.writeHNT("UNAM", mReferenceBlocked);
- }
- if (mFltv != 0) {
+
+ if (mFltv != 0 && !inInventory)
esm.writeHNT("FLTV", mFltv);
- }
- esm.writeHNT("DATA", mPos, 24);
- if (mNam0 != 0) {
+ if (!inInventory)
+ esm.writeHNT("DATA", mPos, 24);
+
+ if (mNam0 != 0 && !inInventory)
esm.writeHNT("NAM0", mNam0);
- }
}
void ESM::CellRef::blank()
{
- mRefnum = 0;
+ mRefNum.mIndex = 0;
+ mRefNum.mContentFile = -1;
mRefID.clear();
mScale = 1;
mOwner.clear();
@@ -84,4 +164,9 @@ void ESM::CellRef::blank()
mPos.pos[i] = 0;
mPos.rot[i] = 0;
}
-} \ No newline at end of file
+}
+
+bool ESM::operator== (const CellRef::RefNum& left, const CellRef::RefNum& right)
+{
+ return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile;
+}
diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp
index 5f1066cf8d..16f6603a28 100644
--- a/components/esm/cellref.hpp
+++ b/components/esm/cellref.hpp
@@ -8,6 +8,7 @@
namespace ESM
{
class ESMWriter;
+ class ESMReader;
/* Cell reference. This represents ONE object (of many) inside the
cell. The cell references are not loaded as part of the normal
@@ -19,8 +20,14 @@ namespace ESM
{
public:
- int mRefnum; // Reference number
- std::string mRefID; // ID of object being referenced (must be lowercase)
+ struct RefNum
+ {
+ int mIndex;
+ int mContentFile; // -1 no content file
+ };
+
+ RefNum mRefNum; // Reference number
+ std::string mRefID; // ID of object being referenced
float mScale; // Scale applied to mesh
@@ -34,8 +41,8 @@ namespace ESM
// 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.
+ // The faction that owns this object (and will get angry if
+ // you take it and are not a faction member)
std::string mFaction;
// INDX might be PC faction rank required to use the item? Sometimes
@@ -71,9 +78,6 @@ namespace ESM
// -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.
@@ -83,10 +87,14 @@ namespace ESM
// Position and rotation of this object within the cell
Position mPos;
- void save(ESMWriter &esm) const;
+ void load (ESMReader& esm, bool wideRefNum = false);
+
+ void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const;
void blank();
};
+
+ bool operator== (const CellRef::RefNum& left, const CellRef::RefNum& right);
}
#endif
diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp
new file mode 100644
index 0000000000..1f7e8197ec
--- /dev/null
+++ b/components/esm/cellstate.cpp
@@ -0,0 +1,17 @@
+
+#include "cellstate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::CellState::load (ESMReader &esm)
+{
+ mWaterLevel = 0;
+ esm.getHNOT (mWaterLevel, "WLVL");
+}
+
+void ESM::CellState::save (ESMWriter &esm) const
+{
+ if (!mId.mPaged)
+ esm.writeHNT ("WLVL", mWaterLevel);
+} \ No newline at end of file
diff --git a/components/esm/cellstate.hpp b/components/esm/cellstate.hpp
new file mode 100644
index 0000000000..cd0db30675
--- /dev/null
+++ b/components/esm/cellstate.hpp
@@ -0,0 +1,25 @@
+#ifndef OPENMW_ESM_CELLSTATE_H
+#define OPENMW_ESM_CELLSTATE_H
+
+#include "cellid.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ /// \note Does not include references
+ struct CellState
+ {
+ CellId mId;
+
+ float mWaterLevel;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp
new file mode 100644
index 0000000000..5dcf17733e
--- /dev/null
+++ b/components/esm/containerstate.cpp
@@ -0,0 +1,16 @@
+
+#include "containerstate.hpp"
+
+void ESM::ContainerState::load (ESMReader &esm)
+{
+ ObjectState::load (esm);
+
+ mInventory.load (esm);
+}
+
+void ESM::ContainerState::save (ESMWriter &esm, bool inInventory) const
+{
+ ObjectState::save (esm, inInventory);
+
+ mInventory.save (esm);
+} \ No newline at end of file
diff --git a/components/esm/containerstate.hpp b/components/esm/containerstate.hpp
new file mode 100644
index 0000000000..1ecf2b46ea
--- /dev/null
+++ b/components/esm/containerstate.hpp
@@ -0,0 +1,20 @@
+#ifndef OPENMW_ESM_CONTAINERSTATE_H
+#define OPENMW_ESM_CONTAINERSTATE_H
+
+#include "objectstate.hpp"
+#include "inventorystate.hpp"
+
+namespace ESM
+{
+ // format 0, saved games only
+
+ struct ContainerState : public ObjectState
+ {
+ InventoryState mInventory;
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm, bool inInventory = false) const;
+ };
+}
+
+#endif
diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp
new file mode 100644
index 0000000000..9e9b561026
--- /dev/null
+++ b/components/esm/creaturestate.cpp
@@ -0,0 +1,20 @@
+
+#include "creaturestate.hpp"
+
+void ESM::CreatureState::load (ESMReader &esm)
+{
+ ObjectState::load (esm);
+
+ mInventory.load (esm);
+
+ mCreatureStats.load (esm);
+}
+
+void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const
+{
+ ObjectState::save (esm, inInventory);
+
+ mInventory.save (esm);
+
+ mCreatureStats.save (esm);
+} \ No newline at end of file
diff --git a/components/esm/creaturestate.hpp b/components/esm/creaturestate.hpp
new file mode 100644
index 0000000000..604c2f3a70
--- /dev/null
+++ b/components/esm/creaturestate.hpp
@@ -0,0 +1,22 @@
+#ifndef OPENMW_ESM_CREATURESTATE_H
+#define OPENMW_ESM_CREATURESTATE_H
+
+#include "objectstate.hpp"
+#include "inventorystate.hpp"
+#include "creaturestats.hpp"
+
+namespace ESM
+{
+ // format 0, saved games only
+
+ struct CreatureState : public ObjectState
+ {
+ InventoryState mInventory;
+ CreatureStats mCreatureStats;
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm, bool inInventory = false) const;
+ };
+}
+
+#endif
diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp
new file mode 100644
index 0000000000..fe250089aa
--- /dev/null
+++ b/components/esm/creaturestats.cpp
@@ -0,0 +1,20 @@
+
+#include "creaturestats.hpp"
+
+void ESM::CreatureStats::load (ESMReader &esm)
+{
+ for (int i=0; i<8; ++i)
+ mAttributes[i].load (esm);
+
+ for (int i=0; i<3; ++i)
+ mDynamic[i].load (esm);
+}
+
+void ESM::CreatureStats::save (ESMWriter &esm) const
+{
+ for (int i=0; i<8; ++i)
+ mAttributes[i].save (esm);
+
+ for (int i=0; i<3; ++i)
+ mDynamic[i].save (esm);
+} \ No newline at end of file
diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp
new file mode 100644
index 0000000000..540044f389
--- /dev/null
+++ b/components/esm/creaturestats.hpp
@@ -0,0 +1,27 @@
+#ifndef OPENMW_ESM_CREATURESTATS_H
+#define OPENMW_ESM_CREATURESTATS_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "statstate.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct CreatureStats
+ {
+ StatState<int> mAttributes[8];
+ StatState<float> mDynamic[3];
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp
index dd7ebfe932..c1f1679924 100644
--- a/components/esm/defs.hpp
+++ b/components/esm/defs.hpp
@@ -83,6 +83,16 @@ enum RecNameInts
REC_STAT = 0x54415453,
REC_WEAP = 0x50414557,
+ // format 0 - saved games
+ REC_SAVE = 0x45564153,
+ REC_JOUR = 0x524f55a4,
+ REC_QUES = 0x53455551,
+ REC_GSCR = 0x52435347,
+ REC_PLAY = 0x59414c50,
+ REC_CSTA = 0x41545343,
+ REC_GMAP = 0x50414d47,
+ REC_DIAS = 0x53414944,
+
// format 1
REC_FILT = 0x544C4946
};
diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp
new file mode 100644
index 0000000000..b3544c85ce
--- /dev/null
+++ b/components/esm/dialoguestate.cpp
@@ -0,0 +1,21 @@
+
+#include "dialoguestate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::DialogueState::load (ESMReader &esm)
+{
+ while (esm.isNextSub ("TOPI"))
+ mKnownTopics.push_back (esm.getHString());
+}
+
+void ESM::DialogueState::save (ESMWriter &esm) const
+{
+ for (std::vector<std::string>::const_iterator iter (mKnownTopics.begin());
+ iter!=mKnownTopics.end(); ++iter)
+ {
+ esm.writeHNString ("TOPI", *iter);
+
+ }
+} \ No newline at end of file
diff --git a/components/esm/dialoguestate.hpp b/components/esm/dialoguestate.hpp
new file mode 100644
index 0000000000..9aa9eaefd3
--- /dev/null
+++ b/components/esm/dialoguestate.hpp
@@ -0,0 +1,23 @@
+#ifndef OPENMW_ESM_DIALOGUESTATE_H
+#define OPENMW_ESM_DIALOGUESTATE_H
+
+#include <string>
+#include <vector>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct DialogueState
+ {
+ std::vector<std::string> mKnownTopics;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp
index 51d86a2eeb..ebdb1e41f7 100644
--- a/components/esm/esmreader.cpp
+++ b/components/esm/esmreader.cpp
@@ -8,6 +8,11 @@ namespace ESM
using namespace Misc;
+ std::string ESMReader::getName() const
+ {
+ return mCtx.filename;
+ }
+
ESM_Context ESMReader::getContext()
{
// Update the file position before returning
@@ -302,8 +307,14 @@ std::string ESMReader::getString(int size)
char *ptr = &mBuffer[0];
getExact(ptr, size);
+ if (size>0 && ptr[size-1]==0)
+ --size;
+
// Convert to UTF8 and return
- return mEncoder->getUtf8(ptr, size);
+ if (mEncoder)
+ return mEncoder->getUtf8(ptr, size);
+
+ return std::string (ptr, size);
}
void ESMReader::fail(const std::string &msg)
diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp
index 3bf194c4e3..897c8fe731 100644
--- a/components/esm/esmreader.hpp
+++ b/components/esm/esmreader.hpp
@@ -38,6 +38,7 @@ public:
int getFormat() const;
const NAME &retSubName() const { return mCtx.subName; }
uint32_t getSubSize() const { return mCtx.leftSub; }
+ std::string getName() const;
/*************************************************************************
*
diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp
index f39aa2b898..91f123eb71 100644
--- a/components/esm/esmwriter.cpp
+++ b/components/esm/esmwriter.cpp
@@ -6,7 +6,7 @@
namespace ESM
{
- ESMWriter::ESMWriter() : mRecordCount (0), mCounting (true) {}
+ ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {}
unsigned int ESMWriter::getVersion() const
{
@@ -51,12 +51,6 @@ namespace ESM
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)
{
mRecordCount = 0;
@@ -86,14 +80,24 @@ namespace ESM
rec.name = name;
rec.position = mStream->tellp();
rec.size = 0;
- writeT<int>(0); // Size goes here
- writeT<int>(0); // Unused header?
+ writeT<uint32_t>(0); // Size goes here
+ writeT<uint32_t>(0); // Unused header?
writeT(flags);
mRecords.push_back(rec);
assert(mRecords.back().size == 0);
}
+ void ESMWriter::startRecord (uint32_t name, uint32_t flags)
+ {
+ std::string type;
+ for (int i=0; i<4; ++i)
+ /// \todo make endianess agnostic
+ type += reinterpret_cast<const char *> (&name)[i];
+
+ startRecord (type, flags);
+ }
+
void ESMWriter::startSubRecord(const std::string& name)
{
writeName(name);
@@ -101,7 +105,7 @@ namespace ESM
rec.name = name;
rec.position = mStream->tellp();
rec.size = 0;
- writeT<int>(0); // Size goes here
+ writeT<uint32_t>(0); // Size goes here
mRecords.push_back(rec);
assert(mRecords.back().size == 0);
@@ -116,13 +120,23 @@ namespace ESM
mStream->seekp(rec.position);
mCounting = false;
- write (reinterpret_cast<const char*> (&rec.size), sizeof(int));
+ write (reinterpret_cast<const char*> (&rec.size), sizeof(uint32_t));
mCounting = true;
mStream->seekp(0, std::ios::end);
}
+ void ESMWriter::endRecord (uint32_t name)
+ {
+ std::string type;
+ for (int i=0; i<4; ++i)
+ /// \todo make endianess agnostic
+ type += reinterpret_cast<const char *> (&name)[i];
+
+ endRecord (type);
+ }
+
void ESMWriter::writeHNString(const std::string& name, const std::string& data)
{
startSubRecord(name);
@@ -152,9 +166,9 @@ namespace ESM
else
{
// Convert to UTF8 and return
- std::string ascii = mEncoder->getLegacyEnc(data);
+ std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data;
- write(ascii.c_str(), ascii.size());
+ write(string.c_str(), string.size());
}
}
diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp
index 104f97f909..33650e6784 100644
--- a/components/esm/esmwriter.hpp
+++ b/components/esm/esmwriter.hpp
@@ -17,7 +17,7 @@ class ESMWriter
{
std::string name;
std::streampos position;
- size_t size;
+ uint32_t size;
};
public:
@@ -36,9 +36,6 @@ class ESMWriter
void addMaster(const std::string& name, uint64_t size);
- void save(const std::string& file);
- ///< Start saving a file by writing the TES3 header.
-
void save(std::ostream& file);
///< Start saving a file by writing the TES3 header.
@@ -93,8 +90,10 @@ class ESMWriter
}
void startRecord(const std::string& name, uint32_t flags = 0);
+ void startRecord(uint32_t name, uint32_t flags = 0);
void startSubRecord(const std::string& name);
void endRecord(const std::string& name);
+ void endRecord(uint32_t name);
void writeHString(const std::string& data);
void writeHCString(const std::string& data);
void writeName(const std::string& data);
diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp
new file mode 100644
index 0000000000..1fa5f907e0
--- /dev/null
+++ b/components/esm/globalmap.cpp
@@ -0,0 +1,26 @@
+#include "globalmap.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+#include "defs.hpp"
+
+unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP;
+
+void ESM::GlobalMap::load (ESMReader &esm)
+{
+ esm.getHNT(mBounds, "BNDS");
+
+ esm.getSubNameIs("DATA");
+ esm.getSubHeader();
+ mImageData.resize(esm.getSubSize());
+ esm.getExact(&mImageData[0], mImageData.size());
+}
+
+void ESM::GlobalMap::save (ESMWriter &esm) const
+{
+ esm.writeHNT("BNDS", mBounds);
+
+ esm.startSubRecord("DATA");
+ esm.write(&mImageData[0], mImageData.size());
+ esm.endRecord("DATA");
+}
diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp
new file mode 100644
index 0000000000..5d036c736f
--- /dev/null
+++ b/components/esm/globalmap.hpp
@@ -0,0 +1,34 @@
+#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H
+#define OPENMW_COMPONENTS_ESM_GLOBALMAP_H
+
+#include <vector>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ ///< \brief An image containing the explored areas on the global map.
+ struct GlobalMap
+ {
+ static unsigned int sRecordId;
+
+ // The minimum and maximum cell coordinates
+ struct Bounds
+ {
+ int mMinX, mMaxX, mMinY, mMaxY;
+ };
+
+ Bounds mBounds;
+
+ std::vector<char> mImageData;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+
+}
+
+#endif
diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp
new file mode 100644
index 0000000000..dcbd91140a
--- /dev/null
+++ b/components/esm/globalscript.cpp
@@ -0,0 +1,25 @@
+
+#include "globalscript.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::GlobalScript::load (ESMReader &esm)
+{
+ mId = esm.getHNString ("NAME");
+
+ mLocals.load (esm);
+
+ mRunning = 0;
+ esm.getHNOT (mRunning, "RUN_");
+}
+
+void ESM::GlobalScript::save (ESMWriter &esm) const
+{
+ esm.writeHNString ("NAME", mId);
+
+ mLocals.save (esm);
+
+ if (mRunning)
+ esm.writeHNT ("RUN_", mRunning);
+} \ No newline at end of file
diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp
new file mode 100644
index 0000000000..4fb8b7c480
--- /dev/null
+++ b/components/esm/globalscript.hpp
@@ -0,0 +1,24 @@
+#ifndef OPENMW_ESM_GLOBALSCRIPT_H
+#define OPENMW_ESM_GLOBALSCRIPT_H
+
+#include "locals.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ /// \brief Storage structure for global script state (only used in saved games)
+
+ struct GlobalScript
+ {
+ std::string mId;
+ Locals mLocals;
+ int mRunning;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif
diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp
new file mode 100644
index 0000000000..4d8cbc6228
--- /dev/null
+++ b/components/esm/inventorystate.cpp
@@ -0,0 +1,60 @@
+
+#include "inventorystate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace
+{
+ void read (ESM::ESMReader &esm, ESM::ObjectState& state, int& slot)
+ {
+ slot = -1;
+ esm.getHNOT (slot, "SLOT");
+
+ state.load (esm);
+ }
+
+ void write (ESM::ESMWriter &esm, const ESM::ObjectState& state, unsigned int type, int slot)
+ {
+ esm.writeHNT ("IOBJ", type);
+
+ if (slot!=-1)
+ esm.writeHNT ("SLOT", slot);
+
+ state.save (esm, true);
+ }
+}
+
+void ESM::InventoryState::load (ESMReader &esm)
+{
+ while (esm.isNextSub ("IOBJ"))
+ {
+ unsigned int id = 0;
+ esm.getHT (id);
+
+ if (id==ESM::REC_LIGH)
+ {
+ LightState state;
+ int slot;
+ read (esm, state, slot);
+ mLights.push_back (std::make_pair (state, slot));
+ }
+ else
+ {
+ ObjectState state;
+ int slot;
+ read (esm, state, slot);
+ mItems.push_back (std::make_pair (state, std::make_pair (id, slot)));
+ }
+ }
+}
+
+void ESM::InventoryState::save (ESMWriter &esm) const
+{
+ for (std::vector<std::pair<ObjectState, std::pair<unsigned int, int> > >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter)
+ write (esm, iter->first, iter->second.first, iter->second.second);
+
+ for (std::vector<std::pair<LightState, int> >::const_iterator iter (mLights.begin());
+ iter!=mLights.end(); ++iter)
+ write (esm, iter->first, ESM::REC_LIGH, iter->second);
+} \ No newline at end of file
diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp
new file mode 100644
index 0000000000..3cfffbccc9
--- /dev/null
+++ b/components/esm/inventorystate.hpp
@@ -0,0 +1,28 @@
+#ifndef OPENMW_ESM_INVENTORYSTATE_H
+#define OPENMW_ESM_INVENTORYSTATE_H
+
+#include "objectstate.hpp"
+#include "lightstate.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ /// \brief State for inventories and containers
+ struct InventoryState
+ {
+ // anything but lights (type, slot)
+ std::vector<std::pair<ObjectState, std::pair<unsigned int, int> > > mItems;
+
+ // lights (slot)
+ std::vector<std::pair<LightState, int> > mLights;
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm) const;
+ };
+}
+
+#endif
diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp
new file mode 100644
index 0000000000..445213de40
--- /dev/null
+++ b/components/esm/journalentry.cpp
@@ -0,0 +1,39 @@
+
+#include "journalentry.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::JournalEntry::load (ESMReader &esm)
+{
+ esm.getHNOT (mType, "JETY");
+ mTopic = esm.getHNString ("YETO");
+ mInfo = esm.getHNString ("YEIN");
+ mText = esm.getHNString ("TEXT");
+
+ if (mType==Type_Journal)
+ {
+ esm.getHNT (mDay, "JEDA");
+ esm.getHNT (mMonth, "JEMO");
+ esm.getHNT (mDayOfMonth, "JEDM");
+ }
+ else if (mType==Type_Topic)
+ mActorName = esm.getHNOString("ACT_");
+}
+
+void ESM::JournalEntry::save (ESMWriter &esm) const
+{
+ esm.writeHNT ("JETY", mType);
+ esm.writeHNString ("YETO", mTopic);
+ esm.writeHNString ("YEIN", mInfo);
+ esm.writeHNString ("TEXT", mText);
+
+ if (mType==Type_Journal)
+ {
+ esm.writeHNT ("JEDA", mDay);
+ esm.writeHNT ("JEMO", mMonth);
+ esm.writeHNT ("JEDM", mDayOfMonth);
+ }
+ else if (mType==Type_Topic)
+ esm.writeHNString ("ACT_", mActorName);
+}
diff --git a/components/esm/journalentry.hpp b/components/esm/journalentry.hpp
new file mode 100644
index 0000000000..76901a4b69
--- /dev/null
+++ b/components/esm/journalentry.hpp
@@ -0,0 +1,36 @@
+#ifndef OPENMW_ESM_JOURNALENTRY_H
+#define OPENMW_ESM_JOURNALENTRY_H
+
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct JournalEntry
+ {
+ enum Type
+ {
+ Type_Journal = 0,
+ Type_Topic = 1,
+ Type_Quest = 2
+ };
+
+ int mType;
+ std::string mTopic;
+ std::string mInfo;
+ std::string mText;
+ std::string mActorName; // Could also be Actor ID to allow switching of localisation, but since mText is plaintext anyway...
+ int mDay; // time stamp
+ int mMonth;
+ int mDayOfMonth;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif
diff --git a/components/esm/lightstate.cpp b/components/esm/lightstate.cpp
new file mode 100644
index 0000000000..1ef0408237
--- /dev/null
+++ b/components/esm/lightstate.cpp
@@ -0,0 +1,21 @@
+
+#include "lightstate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::LightState::load (ESMReader &esm)
+{
+ ObjectState::load (esm);
+
+ mTime = 0;
+ esm.getHNOT (mTime, "LTIM");
+}
+
+void ESM::LightState::save (ESMWriter &esm, bool inInventory) const
+{
+ ObjectState::save (esm, inInventory);
+
+ if (mTime)
+ esm.writeHNT ("LTIM", mTime);
+} \ No newline at end of file
diff --git a/components/esm/lightstate.hpp b/components/esm/lightstate.hpp
new file mode 100644
index 0000000000..a22735e079
--- /dev/null
+++ b/components/esm/lightstate.hpp
@@ -0,0 +1,19 @@
+#ifndef OPENMW_ESM_LIGHTSTATE_H
+#define OPENMW_ESM_LIGHTSTATE_H
+
+#include "objectstate.hpp"
+
+namespace ESM
+{
+ // format 0, saved games only
+
+ struct LightState : public ObjectState
+ {
+ float mTime;
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm, bool inInventory = false) const;
+ };
+}
+
+#endif
diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp
index c22c1b22b6..dd7bf3e427 100644
--- a/components/esm/loadcell.cpp
+++ b/components/esm/loadcell.cpp
@@ -3,26 +3,52 @@
#include <string>
#include <sstream>
#include <list>
+
#include <boost/concept_check.hpp>
+#include <components/misc/stringops.hpp>
+
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"
+#include "cellid.hpp"
-namespace ESM
+namespace
{
- unsigned int Cell::sRecordId = REC_CELL;
+ ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum
+ void adjustRefNum (ESM::CellRef::RefNum& refNum, ESM::ESMReader& reader)
+ {
+ int local = (refNum.mIndex & 0xff000000) >> 24;
-/// Some overloaded compare operators.
-bool operator==(const MovedCellRef& ref, int pRefnum)
-{
- return (ref.mRefnum == pRefnum);
+ 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.
+ refNum.mIndex &= 0x00ffffff; // delete old plugin ID
+ refNum.mContentFile = reader.getGameFiles()[local-1].index;
+ }
+ else
+ {
+ // This is an addition by the present plugin. Set the corresponding plugin index.
+ refNum.mContentFile = reader.getIndex();
+ }
+ }
}
-bool operator==(const CellRef& ref, int pRefnum)
+namespace ESM
{
- return (ref.mRefnum == pRefnum);
-}
+ unsigned int Cell::sRecordId = REC_CELL;
+
+ // Some overloaded compare operators.
+ bool operator== (const MovedCellRef& ref, const CellRef::RefNum& refNum)
+ {
+ return ref.mRefNum == refNum;
+ }
+
+ bool operator== (const CellRef& ref, const CellRef::RefNum& refNum)
+ {
+ return ref.mRefNum == refNum;
+ }
void Cell::load(ESMReader &esm, bool saveContext)
@@ -36,8 +62,6 @@ void Cell::load(ESMReader &esm, bool saveContext)
esm.getHNT(mData, "DATA", 12);
- // Water level
- mWater = -1;
mNAM0 = 0;
if (mData.mFlags & Interior)
@@ -49,9 +73,13 @@ void Cell::load(ESMReader &esm, bool saveContext)
esm.getHT(waterl);
mWater = (float) waterl;
mWaterInt = true;
+ mHasWaterLevelRecord = true;
}
else if (esm.isNextSub("WHGT"))
+ {
esm.getHT(mWater);
+ mHasWaterLevelRecord = true;
+ }
// Quasi-exterior cells have a region (which determines the
// weather), pure interior cells have ambient lighting
@@ -96,7 +124,7 @@ void Cell::save(ESMWriter &esm) const
esm.writeHNT("DATA", mData, 12);
if (mData.mFlags & Interior)
{
- if (mWater != -1) {
+ if (mHasWaterLevelRecord) {
if (mWaterInt) {
int water =
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
@@ -141,7 +169,7 @@ std::string Cell::getDescription() const
}
}
-bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
+bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted)
{
// TODO: Try and document reference numbering, I don't think this has been done anywhere else.
if (!esm.hasMoreSubs())
@@ -155,132 +183,28 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
// 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");
+ ref.load (esm);
// 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.getGameFiles();
- 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");
+ adjustRefNum (ref.mRefNum, esm);
- esm.getHNOT(ref.mGoldValue, "NAM9");
-
- // Present for doors that teleport you to another cell.
- if (esm.isNextSub("DODT"))
+ if (esm.isNextSub("DELE"))
{
- 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;
+ deleted = true;
+ }
+ else
+ deleted = false;
return true;
}
bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
{
- esm.getHT(mref.mRefnum);
+ esm.getHT(mref.mRefNum.mIndex);
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.getGameFiles();
- global = masters[local-1].index + 1;
- mref.mRefnum |= global << 24; // insert global plugin ID
+ adjustRefNum (mref.mRefNum, esm);
return true;
}
@@ -303,4 +227,37 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
mAmbi.mFog = 0;
mAmbi.mFogDensity = 0;
}
+
+ void Cell::merge(Cell *original, Cell *modified)
+ {
+ float waterLevel = original->mWater;
+ if (modified->mHasWaterLevelRecord)
+ {
+ waterLevel = modified->mWater;
+ }
+ // else: keep original water level, instead of resetting to 0
+
+ *original = *modified;
+ original->mWater = waterLevel;
+ }
+
+ CellId Cell::getCellId() const
+ {
+ CellId id;
+
+ id.mPaged = !(mData.mFlags & Interior);
+
+ if (id.mPaged)
+ {
+ id.mWorldspace = "default";
+ id.mIndex.mX = mData.mX;
+ id.mIndex.mY = mData.mY;
+ }
+ else
+ {
+ id.mWorldspace = Misc::StringUtils::lowerCase (mName);
+ }
+
+ return id;
+ }
}
diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp
index 61d586b9d8..f01c88c65d 100644
--- a/components/esm/loadcell.hpp
+++ b/components/esm/loadcell.hpp
@@ -18,6 +18,7 @@ namespace ESM
{
class ESMReader;
class ESMWriter;
+ class CellId;
/* 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
@@ -27,7 +28,7 @@ class ESMWriter;
class MovedCellRef
{
public:
- int mRefnum;
+ CellRef::RefNum mRefNum;
// Target cell (if exterior)
int mTarget[2];
@@ -37,9 +38,9 @@ public:
// 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);
+/// Overloaded compare operator used to search inside a list of cell refs.
+bool operator==(const MovedCellRef& ref, const CellRef::RefNum& refNum);
+bool operator==(const CellRef& ref, const CellRef::RefNum& refNum);
typedef std::list<MovedCellRef> MovedCellRefTracker;
typedef std::list<CellRef> CellRefTracker;
@@ -77,6 +78,11 @@ struct Cell
float mFogDensity;
};
+ Cell() : mWater(0), mHasWaterLevelRecord(false) {}
+
+ /// Merge \a modified into \a original
+ static void merge (Cell* original, Cell* modified);
+
// Interior cells are indexed by this (it's the 'id'), for exterior
// cells it is optional.
std::string mName;
@@ -88,6 +94,7 @@ struct Cell
DATAstruct mData;
AMBIstruct mAmbi;
float mWater; // Water level
+ bool mHasWaterLevelRecord;
bool mWaterInt;
int mMapColor;
int mNAM0;
@@ -141,7 +148,7 @@ struct 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);
+ static bool getNextRef(ESMReader &esm, CellRef &ref, bool& deleted);
/* This fetches an MVRF record, which is used to track moved references.
* Since they are comparably rare, we use a separate method for this.
@@ -150,6 +157,8 @@ struct Cell
void blank();
///< Set record to default state (does not touch the ID/index).
+
+ CellId getCellId() const;
};
}
#endif
diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp
index 99c4f52257..817c0e43c5 100644
--- a/components/esm/loadcrea.hpp
+++ b/components/esm/loadcrea.hpp
@@ -25,22 +25,26 @@ struct Creature
// Default is 0x48?
enum Flags
{
- Biped = 0x001,
- Respawn = 0x002,
- Weapon = 0x004, // Has weapon and shield
- None = 0x008, // ??
+ // Movement types
+ Bipedal = 0x001,
Swims = 0x010,
Flies = 0x020, // Don't know what happens if several
Walks = 0x040, // of these are set
+
+ Respawn = 0x002,
+ Weapon = 0x004, // Has weapon and shield
+ None = 0x008, // ??
Essential = 0x080,
- Skeleton = 0x400, // Does not have normal blood
- Metal = 0x800 // Has 'golden' blood
+
+ // Blood types
+ Skeleton = 0x400,
+ Metal = 0x800
};
enum Type
{
Creatures = 0,
- Deadra = 1,
+ Daedra = 1,
Undead = 2,
Humanoid = 3
};
@@ -63,10 +67,12 @@ struct Creature
int mHealth, mMana, mFatigue; // Stats
int mSoul; // The creatures soul value (used with soul gems.)
- int mCombat, mMagic, mStealth; // Don't know yet.
+ // Creatures have generalized combat, magic and stealth stats which substitute for
+ // the specific skills (in the same way as specializations).
+ int mCombat, mMagic, mStealth;
int mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3
int mGold;
- }; // 96 bytes
+ }; // 96 byte
NPDTstruct mData;
diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp
index 737494f6c2..0c0d662a8b 100644
--- a/components/esm/loadinfo.hpp
+++ b/components/esm/loadinfo.hpp
@@ -70,7 +70,7 @@ struct DialInfo
// Sound and text associated with this item
std::string mSound, mResponse;
- // Result script (uncomiled) to run whenever this dialog item is
+ // Result script (uncompiled) to run whenever this dialog item is
// selected
std::string mResultScript;
diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp
index 9c97eaa4de..1b701229e7 100644
--- a/components/esm/loadland.cpp
+++ b/components/esm/loadland.cpp
@@ -14,7 +14,7 @@ void Land::LandData::save(ESMWriter &esm)
esm.writeHNT("VNML", mNormals, sizeof(VNML));
}
if (mDataTypes & Land::DATA_VHGT) {
- static VHGT offsets;
+ VHGT offsets;
offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE;
offsets.mUnk1 = mUnk1;
offsets.mUnk2 = mUnk2;
diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp
index 9dcc6177a1..a4e1b85c2d 100644
--- a/components/esm/loadlevlist.hpp
+++ b/components/esm/loadlevlist.hpp
@@ -20,20 +20,6 @@ class ESMWriter;
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;
@@ -61,6 +47,14 @@ struct CreatureLevList: LeveledListBase
{
static unsigned int sRecordId;
+ enum Flags
+ {
+
+ AllLevels = 0x01 // Calculate from all levels <= player
+ // level, not just the closest below
+ // player.
+ };
+
CreatureLevList()
{
mRecName = "CNAM";
@@ -71,6 +65,20 @@ struct ItemLevList: LeveledListBase
{
static unsigned int sRecordId;
+ 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.
+ };
+
ItemLevList()
{
mRecName = "INAM";
diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp
index 77056b9ec6..8281f4969d 100644
--- a/components/esm/loadmgef.hpp
+++ b/components/esm/loadmgef.hpp
@@ -28,11 +28,7 @@ struct MagicEffect
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?)
+ CasterLinked = 0x20000 // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells.
};
enum MagnitudeDisplayType
{
diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp
index 1eac8d64fe..08f678b45f 100644
--- a/components/esm/loadnpc.hpp
+++ b/components/esm/loadnpc.hpp
@@ -105,7 +105,7 @@ struct NPC
char mNpdtType;
NPDTstruct52 mNpdt52;
- NPDTstruct12 mNpdt12; // Use this if npdt52.gold == -10
+ NPDTstruct12 mNpdt12; //for autocalculated characters
int mFlags;
diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp
index 30460c17a6..de679e8152 100644
--- a/components/esm/loadscpt.cpp
+++ b/components/esm/loadscpt.cpp
@@ -26,24 +26,24 @@ void Script::load(ESMReader &esm)
if (esm.isNextSub("SCVR"))
{
int s = mData.mStringTableSize;
- char* tmp = new char[s];
- esm.getHExact(tmp, s);
+
+ std::vector<char> tmp (s);
+ esm.getHExact (&tmp[0], 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;
+ char* str = &tmp[0];
for (size_t i = 0; i < mVarNames.size(); i++)
{
mVarNames[i] = std::string(str);
str += mVarNames[i].size() + 1;
- if (str - tmp > s)
+ if (str - &tmp[0] > s)
esm.fail("String table overflow");
}
- delete[] tmp;
}
// Script mData
diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp
index 262d4f6faa..d3d5250493 100644
--- a/components/esm/loadtes3.cpp
+++ b/components/esm/loadtes3.cpp
@@ -19,6 +19,15 @@ void ESM::Header::blank()
void ESM::Header::load (ESMReader &esm)
{
+ if (esm.isNextSub ("FORM"))
+ {
+ esm.getHT (mFormat);
+ if (mFormat<0)
+ esm.fail ("invalid format code");
+ }
+ else
+ mFormat = 0;
+
if (esm.isNextSub("HEDR"))
{
esm.getSubHeader();
@@ -29,15 +38,6 @@ void ESM::Header::load (ESMReader &esm)
esm.getT(mData.records);
}
- if (esm.isNextSub ("FORM"))
- {
- esm.getHT (mFormat);
- if (mFormat<0)
- esm.fail ("invalid format code");
- }
- else
- mFormat = 0;
-
while (esm.isNextSub ("MAST"))
{
MasterData m;
@@ -49,11 +49,11 @@ void ESM::Header::load (ESMReader &esm)
void ESM::Header::save (ESMWriter &esm)
{
- esm.writeHNT ("HEDR", mData, 300);
-
if (mFormat>0)
esm.writeHNT ("FORM", mFormat);
+ esm.writeHNT ("HEDR", mData, 300);
+
for (std::vector<Header::MasterData>::iterator iter = mMaster.begin();
iter != mMaster.end(); ++iter)
{
diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp
index fde716b916..14ddb47082 100644
--- a/components/esm/loadweap.hpp
+++ b/components/esm/loadweap.hpp
@@ -35,6 +35,13 @@ struct Weapon
Bolt = 13
};
+ enum AttackType
+ {
+ AT_Chop,
+ AT_Slash,
+ AT_Thrust
+ };
+
enum Flags
{
Magical = 0x01,
diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp
new file mode 100644
index 0000000000..9c470a0253
--- /dev/null
+++ b/components/esm/locals.cpp
@@ -0,0 +1,28 @@
+
+#include "locals.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::Locals::load (ESMReader &esm)
+{
+ while (esm.isNextSub ("LOCA"))
+ {
+ std::string id = esm.getHString();
+
+ Variant value;
+ value.read (esm, Variant::Format_Info);
+
+ mVariables.push_back (std::make_pair (id, value));
+ }
+}
+
+void ESM::Locals::save (ESMWriter &esm) const
+{
+ for (std::vector<std::pair<std::string, Variant> >::const_iterator iter (mVariables.begin());
+ iter!=mVariables.end(); ++iter)
+ {
+ esm.writeHNString ("LOCA", iter->first);
+ iter->second.write (esm, Variant::Format_Info);
+ }
+} \ No newline at end of file
diff --git a/components/esm/locals.hpp b/components/esm/locals.hpp
new file mode 100644
index 0000000000..af5afb23bd
--- /dev/null
+++ b/components/esm/locals.hpp
@@ -0,0 +1,27 @@
+#ifndef OPENMW_ESM_LOCALS_H
+#define OPENMW_ESM_LOCALS_H
+
+#include <vector>
+#include <string>
+
+#include "variant.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ /// \brief Storage structure for local variables (only used in saved games)
+ ///
+ /// \note This is not a top-level record.
+
+ struct Locals
+ {
+ std::vector<std::pair<std::string, Variant> > mVariables;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif
diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp
new file mode 100644
index 0000000000..e59ec3e268
--- /dev/null
+++ b/components/esm/npcstate.cpp
@@ -0,0 +1,24 @@
+
+#include "npcstate.hpp"
+
+void ESM::NpcState::load (ESMReader &esm)
+{
+ ObjectState::load (esm);
+
+ mInventory.load (esm);
+
+ mNpcStats.load (esm);
+
+ mCreatureStats.load (esm);
+}
+
+void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const
+{
+ ObjectState::save (esm, inInventory);
+
+ mInventory.save (esm);
+
+ mNpcStats.save (esm);
+
+ mCreatureStats.save (esm);
+} \ No newline at end of file
diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp
new file mode 100644
index 0000000000..39858d5533
--- /dev/null
+++ b/components/esm/npcstate.hpp
@@ -0,0 +1,24 @@
+#ifndef OPENMW_ESM_NPCSTATE_H
+#define OPENMW_ESM_NPCSTATE_H
+
+#include "objectstate.hpp"
+#include "inventorystate.hpp"
+#include "npcstats.hpp"
+#include "creaturestats.hpp"
+
+namespace ESM
+{
+ // format 0, saved games only
+
+ struct NpcState : public ObjectState
+ {
+ InventoryState mInventory;
+ NpcStats mNpcStats;
+ CreatureStats mCreatureStats;
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm, bool inInventory = false) const;
+ };
+}
+
+#endif
diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp
new file mode 100644
index 0000000000..531424ab23
--- /dev/null
+++ b/components/esm/npcstats.cpp
@@ -0,0 +1,133 @@
+
+#include "npcstats.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (0), mReputation (0) {}
+
+void ESM::NpcStats::load (ESMReader &esm)
+{
+ while (esm.isNextSub ("FACT"))
+ {
+ std::string id = esm.getHString();
+
+ Faction faction;
+
+ int expelled = 0;
+ esm.getHNOT (expelled, "FAEX");
+
+ if (expelled)
+ faction.mExpelled = true;
+
+ esm.getHNOT (faction.mRank, "FARA");
+
+ esm.getHNOT (faction.mReputation, "FARE");
+
+ mFactions.insert (std::make_pair (id, faction));
+ }
+
+ mDisposition = 0;
+ esm.getHNOT (mDisposition, "DISP");
+
+ for (int i=0; i<27; ++i)
+ {
+ mSkills[i].mRegular.load (esm);
+ mSkills[i].mWerewolf.load (esm);
+ }
+
+ mBounty = 0;
+ esm.getHNOT (mBounty, "BOUN");
+
+ mReputation = 0;
+ esm.getHNOT (mReputation, "REPU");
+
+ mWerewolfKills = 0;
+ esm.getHNOT (mWerewolfKills, "WKIL");
+
+ mProfit = 0;
+ esm.getHNOT (mProfit, "PROF");
+
+ mAttackStrength = 0;
+ esm.getHNOT (mAttackStrength, "ASTR");
+
+ mLevelProgress = 0;
+ esm.getHNOT (mLevelProgress, "LPRO");
+
+ esm.getHNT (mSkillIncrease, "INCR");
+
+ while (esm.isNextSub ("USED"))
+ mUsedIds.push_back (esm.getHString());
+
+ mTimeToStartDrowning = 0;
+ esm.getHNOT (mTimeToStartDrowning, "DRTI");
+
+ mLastDrowningHit = 0;
+ esm.getHNOT (mLastDrowningHit, "DRLH");
+
+ mLevelHealthBonus = 0;
+ esm.getHNOT (mLevelHealthBonus, "LVLH");
+}
+
+void ESM::NpcStats::save (ESMWriter &esm) const
+{
+ for (std::map<std::string, Faction>::const_iterator iter (mFactions.begin());
+ iter!=mFactions.end(); ++iter)
+ {
+ esm.writeHNString ("FACT", iter->first);
+
+ if (iter->second.mExpelled)
+ {
+ int expelled = 1;
+ esm.writeHNT ("FAEX", expelled);
+ }
+
+ if (iter->second.mRank)
+ esm.writeHNT ("FARA", iter->second.mRank);
+
+ if (iter->second.mReputation)
+ esm.writeHNT ("FARE", iter->second.mReputation);
+ }
+
+ if (mDisposition)
+ esm.writeHNT ("DISP", mDisposition);
+
+ for (int i=0; i<27; ++i)
+ {
+ mSkills[i].mRegular.save (esm);
+ mSkills[i].mWerewolf.save (esm);
+ }
+
+ if (mBounty)
+ esm.writeHNT ("BOUN", mBounty);
+
+ if (mReputation)
+ esm.writeHNT ("REPU", mReputation);
+
+ if (mWerewolfKills)
+ esm.writeHNT ("WKIL", mWerewolfKills);
+
+ if (mProfit)
+ esm.writeHNT ("PROF", mProfit);
+
+ if (mAttackStrength)
+ esm.writeHNT ("ASTR", mAttackStrength);
+
+ if (mLevelProgress)
+ esm.writeHNT ("LPRO", mLevelProgress);
+
+ esm.writeHNT ("INCR", mSkillIncrease);
+
+ for (std::vector<std::string>::const_iterator iter (mUsedIds.begin()); iter!=mUsedIds.end();
+ ++iter)
+ esm.writeHNT ("USED", *iter);
+
+ if (mTimeToStartDrowning)
+ esm.writeHNT ("DRTI", mTimeToStartDrowning);
+
+ if (mLastDrowningHit)
+ esm.writeHNT ("DRLH", mLastDrowningHit);
+
+ if (mLevelHealthBonus)
+ esm.writeHNT ("LVLH", mLevelHealthBonus);
+} \ No newline at end of file
diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp
new file mode 100644
index 0000000000..b3f70db252
--- /dev/null
+++ b/components/esm/npcstats.hpp
@@ -0,0 +1,54 @@
+#ifndef OPENMW_ESM_NPCSTATS_H
+#define OPENMW_ESM_NPCSTATS_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "statstate.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct NpcStats
+ {
+ struct Skill
+ {
+ StatState<int> mRegular;
+ StatState<int> mWerewolf;
+ };
+
+ struct Faction
+ {
+ bool mExpelled;
+ int mRank;
+ int mReputation;
+
+ Faction();
+ };
+
+ std::map<std::string, Faction> mFactions;
+ int mDisposition;
+ Skill mSkills[27];
+ int mBounty;
+ int mReputation;
+ int mWerewolfKills;
+ int mProfit;
+ float mAttackStrength;
+ int mLevelProgress;
+ int mSkillIncrease[8];
+ std::vector<std::string> mUsedIds;
+ float mTimeToStartDrowning;
+ float mLastDrowningHit;
+ float mLevelHealthBonus;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp
new file mode 100644
index 0000000000..be00f3ef6e
--- /dev/null
+++ b/components/esm/objectstate.cpp
@@ -0,0 +1,51 @@
+
+#include "objectstate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::ObjectState::load (ESMReader &esm)
+{
+ mRef.load (esm, true);
+
+ mHasLocals = 0;
+ esm.getHNOT (mHasLocals, "HLOC");
+
+ if (mHasLocals)
+ mLocals.load (esm);
+
+ mEnabled = 1;
+ esm.getHNOT (mEnabled, "ENAB");
+
+ mCount = 1;
+ esm.getHNOT (mCount, "COUN");
+
+ esm.getHNOT (mPosition, "POS_", 24);
+
+ esm.getHNOT (mLocalRotation, "LROT", 12);
+}
+
+void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const
+{
+ mRef.save (esm, true, inInventory);
+
+ if (mHasLocals)
+ {
+ esm.writeHNT ("HLOC", mHasLocals);
+ mLocals.save (esm);
+ }
+
+ if (!mEnabled && !inInventory)
+ esm.writeHNT ("ENAB", mEnabled);
+
+ if (mCount!=1)
+ esm.writeHNT ("COUN", mCount);
+
+ if (!inInventory)
+ {
+ esm.writeHNT ("POS_", mPosition, 24);
+ esm.writeHNT ("LROT", mLocalRotation, 12);
+ }
+}
+
+ESM::ObjectState::~ObjectState() {} \ No newline at end of file
diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp
new file mode 100644
index 0000000000..9c9ca5f2e8
--- /dev/null
+++ b/components/esm/objectstate.hpp
@@ -0,0 +1,36 @@
+#ifndef OPENMW_ESM_OBJECTSTATE_H
+#define OPENMW_ESM_OBJECTSTATE_H
+
+#include <string>
+#include <vector>
+
+#include "cellref.hpp"
+#include "locals.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ ///< \brief Save state for objects, that do not use custom data
+ struct ObjectState
+ {
+ CellRef mRef;
+
+ unsigned char mHasLocals;
+ Locals mLocals;
+ unsigned char mEnabled;
+ int mCount;
+ ESM::Position mPosition;
+ float mLocalRotation[3];
+
+ virtual void load (ESMReader &esm);
+ virtual void save (ESMWriter &esm, bool inInventory = false) const;
+
+ virtual ~ObjectState();
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/player.cpp b/components/esm/player.cpp
new file mode 100644
index 0000000000..d5ddc74d0a
--- /dev/null
+++ b/components/esm/player.cpp
@@ -0,0 +1,48 @@
+
+#include "player.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::Player::load (ESMReader &esm)
+{
+ mObject.load (esm);
+
+ mCellId.load (esm);
+
+ esm.getHNT (mLastKnownExteriorPosition, "LKEP", 12);
+
+ if (esm.isNextSub ("MARK"))
+ {
+ mHasMark = true;
+ esm.getHT (mMarkedPosition, 24);
+ mMarkedCell.load (esm);
+ }
+ else
+ mHasMark = false;
+
+ mAutoMove = 0;
+ esm.getHNOT (mAutoMove, "AMOV");
+
+ mBirthsign = esm.getHNString ("SIGN");
+}
+
+void ESM::Player::save (ESMWriter &esm) const
+{
+ mObject.save (esm);
+
+ mCellId.save (esm);
+
+ esm.writeHNT ("LKEP", mLastKnownExteriorPosition, 12);
+
+ if (mHasMark)
+ {
+ esm.writeHNT ("MARK", mMarkedPosition, 24);
+ mMarkedCell.save (esm);
+ }
+
+ if (mAutoMove)
+ esm.writeHNT ("AMOV", mAutoMove);
+
+ esm.writeHNString ("SIGN", mBirthsign);
+} \ No newline at end of file
diff --git a/components/esm/player.hpp b/components/esm/player.hpp
new file mode 100644
index 0000000000..0d70ee0904
--- /dev/null
+++ b/components/esm/player.hpp
@@ -0,0 +1,33 @@
+#ifndef OPENMW_ESM_PLAYER_H
+#define OPENMW_ESM_PLAYER_H
+
+#include <string>
+
+#include "npcstate.hpp"
+#include "cellid.hpp"
+#include "defs.hpp"
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct Player
+ {
+ NpcState mObject;
+ CellId mCellId;
+ float mLastKnownExteriorPosition[3];
+ unsigned char mHasMark;
+ ESM::Position mMarkedPosition;
+ CellId mMarkedCell;
+ unsigned char mAutoMove;
+ std::string mBirthsign;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp
new file mode 100644
index 0000000000..e938267255
--- /dev/null
+++ b/components/esm/queststate.cpp
@@ -0,0 +1,19 @@
+
+#include "queststate.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+void ESM::QuestState::load (ESMReader &esm)
+{
+ mTopic = esm.getHNString ("YETO");
+ esm.getHNOT (mState, "QSTA");
+ esm.getHNOT (mFinished, "QFIN");
+}
+
+void ESM::QuestState::save (ESMWriter &esm) const
+{
+ esm.writeHNString ("YETO", mTopic);
+ esm.writeHNT ("QSTA", mState);
+ esm.writeHNT ("QFIN", mFinished);
+} \ No newline at end of file
diff --git a/components/esm/queststate.hpp b/components/esm/queststate.hpp
new file mode 100644
index 0000000000..1769336f2f
--- /dev/null
+++ b/components/esm/queststate.hpp
@@ -0,0 +1,24 @@
+#ifndef OPENMW_ESM_QUESTSTATE_H
+#define OPENMW_ESM_QUESTSTATE_H
+
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct QuestState
+ {
+ std::string mTopic;
+ int mState;
+ unsigned char mFinished;
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp
new file mode 100644
index 0000000000..d6887f1704
--- /dev/null
+++ b/components/esm/savedgame.cpp
@@ -0,0 +1,46 @@
+
+#include "savedgame.hpp"
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+#include "defs.hpp"
+
+unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
+
+void ESM::SavedGame::load (ESMReader &esm)
+{
+ mPlayerName = esm.getHNString("PLNA");
+ esm.getHNOT (mPlayerLevel, "PLLE");
+ mPlayerClass = esm.getHNString("PLCL");
+ mPlayerCell = esm.getHNString("PLCE");
+ esm.getHNT (mInGameTime, "TSTM", 16);
+ esm.getHNT (mTimePlayed, "TIME");
+ mDescription = esm.getHNString ("DESC");
+
+ while (esm.isNextSub ("DEPE"))
+ mContentFiles.push_back (esm.getHString());
+
+ esm.getSubNameIs("SCRN");
+ esm.getSubHeader();
+ mScreenshot.resize(esm.getSubSize());
+ esm.getExact(&mScreenshot[0], mScreenshot.size());
+}
+
+void ESM::SavedGame::save (ESMWriter &esm) const
+{
+ esm.writeHNString ("PLNA", mPlayerName);
+ esm.writeHNT ("PLLE", mPlayerLevel);
+ esm.writeHNString ("PLCL", mPlayerClass);
+ esm.writeHNString ("PLCE", mPlayerCell);
+ esm.writeHNT ("TSTM", mInGameTime, 16);
+ esm.writeHNT ("TIME", mTimePlayed);
+ esm.writeHNString ("DESC", mDescription);
+
+ for (std::vector<std::string>::const_iterator iter (mContentFiles.begin());
+ iter!=mContentFiles.end(); ++iter)
+ esm.writeHNString ("DEPE", *iter);
+
+ esm.startSubRecord("SCRN");
+ esm.write(&mScreenshot[0], mScreenshot.size());
+ esm.endRecord("SCRN");
+}
diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp
new file mode 100644
index 0000000000..9c7bf551d7
--- /dev/null
+++ b/components/esm/savedgame.hpp
@@ -0,0 +1,41 @@
+#ifndef OPENMW_ESM_SAVEDGAME_H
+#define OPENMW_ESM_SAVEDGAME_H
+
+#include <vector>
+#include <string>
+
+namespace ESM
+{
+ class ESMReader;
+ class ESMWriter;
+
+ // format 0, saved games only
+
+ struct SavedGame
+ {
+ static unsigned int sRecordId;
+
+ struct TimeStamp
+ {
+ float mGameHour;
+ int mDay;
+ int mMonth;
+ int mYear;
+ };
+
+ std::vector<std::string> mContentFiles;
+ std::string mPlayerName;
+ int mPlayerLevel;
+ std::string mPlayerClass; // this is the ID and not the name of the class
+ std::string mPlayerCell;
+ TimeStamp mInGameTime;
+ double mTimePlayed;
+ std::string mDescription;
+ std::vector<char> mScreenshot; // raw jpg-encoded data
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+}
+
+#endif
diff --git a/components/esm/statstate.hpp b/components/esm/statstate.hpp
new file mode 100644
index 0000000000..4b4023bc22
--- /dev/null
+++ b/components/esm/statstate.hpp
@@ -0,0 +1,59 @@
+#ifndef OPENMW_ESM_STATSTATE_H
+#define OPENMW_ESM_STATSTATE_H
+
+#include "esmreader.hpp"
+#include "esmwriter.hpp"
+
+namespace ESM
+{
+ // format 0, saved games only
+
+ template<typename T>
+ struct StatState
+ {
+ T mBase;
+ T mMod;
+ T mCurrent;
+ T mDamage;
+ float mProgress;
+
+ StatState();
+
+ void load (ESMReader &esm);
+ void save (ESMWriter &esm) const;
+ };
+
+ template<typename T>
+ StatState<T>::StatState() : mBase (0), mMod (0), mCurrent (0), mDamage (0), mProgress (0) {}
+
+ template<typename T>
+ void StatState<T>::load (ESMReader &esm)
+ {
+ esm.getHNT (mBase, "STBA");
+ esm.getHNT (mMod, "STMO");
+ mCurrent = 0;
+ esm.getHNOT (mCurrent, "STCU");
+ mDamage = 0;
+ esm.getHNOT (mDamage, "STDA");
+ mProgress = 0;
+ esm.getHNOT (mProgress, "STPR");
+ }
+
+ template<typename T>
+ void StatState<T>::save (ESMWriter &esm) const
+ {
+ esm.writeHNT ("STBA", mBase);
+ esm.writeHNT ("STMO", mMod);
+
+ if (mCurrent)
+ esm.writeHNT ("STCU", mCurrent);
+
+ if (mDamage)
+ esm.writeHNT ("STDA", mDamage);
+
+ if (mProgress)
+ esm.writeHNT ("STPR", mProgress);
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp
index 2bba60a152..8ba9bb34f1 100644
--- a/components/esm/variant.hpp
+++ b/components/esm/variant.hpp
@@ -33,7 +33,7 @@ namespace ESM
{
Format_Global,
Format_Gmst,
- Format_Info
+ Format_Info // also used for local variables in saved game files
};
Variant();
diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp
index 71fd1b523c..06ee9fb4e8 100644
--- a/components/files/lowlevelfile.cpp
+++ b/components/files/lowlevelfile.cpp
@@ -219,7 +219,7 @@ LowLevelFile::LowLevelFile ()
LowLevelFile::~LowLevelFile ()
{
- if (mHandle == INVALID_HANDLE_VALUE)
+ if (mHandle != INVALID_HANDLE_VALUE)
CloseHandle (mHandle);
}
diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp
index bdba7b6af9..97e4fad4fc 100644
--- a/components/interpreter/context.hpp
+++ b/components/interpreter/context.hpp
@@ -50,33 +50,33 @@ namespace Interpreter
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;
@@ -96,17 +96,17 @@ namespace Interpreter
virtual void disable (const std::string& id = "") = 0;
- virtual int getMemberShort (const std::string& id, const std::string& name) const = 0;
+ virtual int getMemberShort (const std::string& id, const std::string& name, bool global) const = 0;
- virtual int getMemberLong (const std::string& id, const std::string& name) const = 0;
+ virtual int getMemberLong (const std::string& id, const std::string& name, bool global) const = 0;
- virtual float getMemberFloat (const std::string& id, const std::string& name) const = 0;
+ virtual float getMemberFloat (const std::string& id, const std::string& name, bool global) const = 0;
- virtual void setMemberShort (const std::string& id, const std::string& name, int value) = 0;
+ virtual void setMemberShort (const std::string& id, const std::string& name, int value, bool global) = 0;
- virtual void setMemberLong (const std::string& id, const std::string& name, int value) = 0;
+ virtual void setMemberLong (const std::string& id, const std::string& name, int value, bool global) = 0;
- virtual void setMemberFloat (const std::string& id, const std::string& name, float value)
+ virtual void setMemberFloat (const std::string& id, const std::string& name, float value, bool global)
= 0;
};
}
diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp
index 5774c96aec..6f3b5bb5aa 100644
--- a/components/interpreter/defines.cpp
+++ b/components/interpreter/defines.cpp
@@ -64,7 +64,7 @@ namespace Interpreter{
retval << context.getActionBinding("#{sRestKey}");
}
else if((found = Check(temp, "actionmenumode", &i, &start))){
- retval << context.getActionBinding("#{sJournal}");
+ retval << context.getActionBinding("#{sInventory}");
}
else if((found = Check(temp, "actionactivate", &i, &start))){
retval << context.getActionBinding("#{sActivate}");
@@ -88,10 +88,10 @@ namespace Interpreter{
retval << context.getActionBinding("#{sBack}");
}
else if((found = Check(temp, "actionuse", &i, &start))){
- retval << "PLACEHOLDER_ACTION_USE";
+ retval << context.getActionBinding("#{sUse}");
}
else if((found = Check(temp, "actionrun", &i, &start))){
- retval << "PLACEHOLDER_ACTION_RUN";
+ retval << context.getActionBinding("#{sRun}");
}
else if((found = Check(temp, "pcclass", &i, &start))){
retval << context.getPCClass();
diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt
index 91e0c060e7..990762268f 100644
--- a/components/interpreter/docs/vmformat.txt
+++ b/components/interpreter/docs/vmformat.txt
@@ -127,5 +127,11 @@ 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
+op 65: store stack[0] in member short stack[2] of global script with ID stack[1]
+op 66: store stack[0] in member long stack[2] of global script with ID stack[1]
+op 67: store stack[0] in member float stack[2] of global script with ID stack[1]
+op 68: replace stack[0] with member short stack[1] of global script with ID stack[0]
+op 69: replace stack[0] with member short stack[1] of global script with ID stack[0]
+op 70: replace stack[0] with member short stack[1] of global script with ID stack[0]
+opcodes 71-33554431 unused
opcodes 33554432-67108863 reserved for extensions
diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp
index 05f71f1cca..721cde3d8d 100644
--- a/components/interpreter/installopcodes.cpp
+++ b/components/interpreter/installopcodes.cpp
@@ -40,12 +40,18 @@ namespace Interpreter
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);
+ interpreter.installSegment5 (59, new OpStoreMemberShort (false));
+ interpreter.installSegment5 (60, new OpStoreMemberLong (false));
+ interpreter.installSegment5 (61, new OpStoreMemberFloat (false));
+ interpreter.installSegment5 (62, new OpFetchMemberShort (false));
+ interpreter.installSegment5 (63, new OpFetchMemberLong (false));
+ interpreter.installSegment5 (64, new OpFetchMemberFloat (false));
+ interpreter.installSegment5 (65, new OpStoreMemberShort (true));
+ interpreter.installSegment5 (66, new OpStoreMemberLong (true));
+ interpreter.installSegment5 (67, new OpStoreMemberFloat (true));
+ interpreter.installSegment5 (68, new OpFetchMemberShort (true));
+ interpreter.installSegment5 (69, new OpFetchMemberLong (true));
+ interpreter.installSegment5 (70, new OpFetchMemberFloat (true));
// math
interpreter.installSegment5 (9, new OpAddInt<Type_Integer>);
diff --git a/components/interpreter/localopcodes.hpp b/components/interpreter/localopcodes.hpp
index 731c16276d..7844a9ea73 100644
--- a/components/interpreter/localopcodes.hpp
+++ b/components/interpreter/localopcodes.hpp
@@ -208,8 +208,12 @@ namespace Interpreter
class OpStoreMemberShort : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpStoreMemberShort (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Integer data = runtime[0].mInteger;
@@ -218,7 +222,7 @@ namespace Interpreter
index = runtime[2].mInteger;
std::string variable = runtime.getStringLiteral (index);
- runtime.getContext().setMemberShort (id, variable, data);
+ runtime.getContext().setMemberShort (id, variable, data, mGlobal);
runtime.pop();
runtime.pop();
@@ -228,8 +232,12 @@ namespace Interpreter
class OpStoreMemberLong : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpStoreMemberLong (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Integer data = runtime[0].mInteger;
@@ -238,7 +246,7 @@ namespace Interpreter
index = runtime[2].mInteger;
std::string variable = runtime.getStringLiteral (index);
- runtime.getContext().setMemberLong (id, variable, data);
+ runtime.getContext().setMemberLong (id, variable, data, mGlobal);
runtime.pop();
runtime.pop();
@@ -248,8 +256,12 @@ namespace Interpreter
class OpStoreMemberFloat : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpStoreMemberFloat (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Float data = runtime[0].mFloat;
@@ -258,7 +270,7 @@ namespace Interpreter
index = runtime[2].mInteger;
std::string variable = runtime.getStringLiteral (index);
- runtime.getContext().setMemberFloat (id, variable, data);
+ runtime.getContext().setMemberFloat (id, variable, data, mGlobal);
runtime.pop();
runtime.pop();
@@ -268,8 +280,12 @@ namespace Interpreter
class OpFetchMemberShort : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpFetchMemberShort (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Integer index = runtime[0].mInteger;
@@ -278,15 +294,19 @@ namespace Interpreter
std::string variable = runtime.getStringLiteral (index);
runtime.pop();
- int value = runtime.getContext().getMemberShort (id, variable);
+ int value = runtime.getContext().getMemberShort (id, variable, mGlobal);
runtime[0].mInteger = value;
}
};
class OpFetchMemberLong : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpFetchMemberLong (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Integer index = runtime[0].mInteger;
@@ -295,15 +315,19 @@ namespace Interpreter
std::string variable = runtime.getStringLiteral (index);
runtime.pop();
- int value = runtime.getContext().getMemberLong (id, variable);
+ int value = runtime.getContext().getMemberLong (id, variable, mGlobal);
runtime[0].mInteger = value;
}
};
class OpFetchMemberFloat : public Opcode0
{
+ bool mGlobal;
+
public:
+ OpFetchMemberFloat (bool global) : mGlobal (global) {}
+
virtual void execute (Runtime& runtime)
{
Type_Integer index = runtime[0].mInteger;
@@ -312,7 +336,7 @@ namespace Interpreter
std::string variable = runtime.getStringLiteral (index);
runtime.pop();
- float value = runtime.getContext().getMemberFloat (id, variable);
+ float value = runtime.getContext().getMemberFloat (id, variable, mGlobal);
runtime[0].mFloat = value;
}
};
diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp
index 1b4c823a0e..1da8cf6956 100644
--- a/components/interpreter/miscopcodes.hpp
+++ b/components/interpreter/miscopcodes.hpp
@@ -48,9 +48,10 @@ namespace Interpreter
}
else if (c=='f' || c=='F' || c=='.')
{
- while (c!='f' && i<message.size())
+ while (c!='f' && i+1<message.size())
{
++i;
+ c = message[i];
}
float value = runtime[0].mFloat;
diff --git a/components/nif/node.hpp b/components/nif/node.hpp
index 6816a79a2e..eebcd8be8c 100644
--- a/components/nif/node.hpp
+++ b/components/nif/node.hpp
@@ -140,6 +140,9 @@ struct NiNode : Node
ParticleFlag_AutoPlay = 0x0020,
ParticleFlag_LocalSpace = 0x0080
};
+ enum ControllerFlags {
+ ControllerFlag_Active = 0x8
+ };
void read(NIFStream *nif)
{
diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp
index 9c4fee7a09..cdddb94d0a 100644
--- a/components/nifbullet/bulletnifloader.cpp
+++ b/components/nifbullet/bulletnifloader.cpp
@@ -229,9 +229,13 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *
{
if(node->hasBounds)
{
- mShape->mBoxTranslation = node->boundPos;
- mShape->mBoxRotation = node->boundRot;
- mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ));
+ // Checking for BBoxCollision flag causes issues with some actors :/
+ if (!(node->flags & Nif::NiNode::Flag_Hidden))
+ {
+ mShape->mBoxTranslation = node->boundPos;
+ mShape->mBoxRotation = node->boundRot;
+ mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ));
+ }
}
else if(node->recType == Nif::RC_NiTriShape)
{
diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp
index 8ae86b64a8..4dae1a93de 100644
--- a/components/nifogre/material.cpp
+++ b/components/nifogre/material.cpp
@@ -10,8 +10,6 @@
#include <OgreMaterialManager.h>
#include <OgreMaterial.h>
-#include <boost/lexical_cast.hpp>
-#include <boost/algorithm/string.hpp>
#include <boost/functional/hash.hpp>
@@ -157,7 +155,6 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
ctrls = ctrls->next;
}
}
- needTangents = !texName[Nif::NiTexturingProperty::BumpTexture].empty();
// Alpha modifiers
if(alphaprop)
@@ -278,6 +275,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
if (itr != sMaterialMap.end())
{
// a suitable material exists already - use it
+ sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(itr->second);
+ needTangents = !sh::retrieveValue<sh::StringValue>(instance->getProperty("normalMap"), instance).get().empty();
return itr->second;
}
// not found, create a new one
@@ -407,6 +406,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata,
instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off")));
// depth_func???
+ if (!texName[0].empty())
+ NifOverrides::Overrides::getMaterialOverrides(texName[0], instance);
+
+ // Don't use texName, as it may be overridden
+ needTangents = !sh::retrieveValue<sh::StringValue>(instance->getProperty("normalMap"), instance).get().empty();
+
return name;
}
diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp
index 80e377a49f..43622cb9ae 100644
--- a/components/nifogre/mesh.cpp
+++ b/components/nifogre/mesh.cpp
@@ -341,7 +341,7 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape
Nif::ControllerPtr ctrl = shape->controller;
do {
// Load GeomMorpherController into an Ogre::Pose and Animation
- if(ctrl->recType == Nif::RC_NiGeomMorpherController)
+ if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active)
{
const Nif::NiGeomMorpherController *geom =
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp
index 63e9057664..43e8934c82 100644
--- a/components/nifogre/ogrenifloader.cpp
+++ b/components/nifogre/ogrenifloader.cpp
@@ -26,7 +26,6 @@
#include <algorithm>
#include <OgreTechnique.h>
-#include <OgreRoot.h>
#include <OgreEntity.h>
#include <OgreSubEntity.h>
#include <OgreTagPoint.h>
@@ -36,6 +35,9 @@
#include <OgreMeshManager.h>
#include <OgreSkeletonManager.h>
#include <OgreControllerManager.h>
+#include <OgreMaterialManager.h>
+#include <OgreCamera.h>
+#include <OgreSceneManager.h>
#include <extern/shiny/Main/Factory.hpp>
@@ -309,25 +311,26 @@ public:
static void setVisible(Ogre::Node *node, int vis)
{
+ // Skinned meshes are attached to the scene node, not the bone.
+ // We use the Node's user data to connect it with the mesh.
+ Ogre::Any customData = node->getUserObjectBindings().getUserAny();
+
+ if (!customData.isEmpty())
+ Ogre::any_cast<Ogre::MovableObject*>(customData)->setVisible(vis);
+
+ Ogre::TagPoint *tag = dynamic_cast<Ogre::TagPoint*>(node);
+ if(tag != NULL)
+ {
+ Ogre::MovableObject *obj = tag->getChildObject();
+ if(obj != NULL)
+ obj->setVisible(vis);
+ }
+
Ogre::Node::ChildNodeIterator iter = node->getChildIterator();
while(iter.hasMoreElements())
{
node = iter.getNext();
setVisible(node, vis);
-
- // Skinned meshes and particle systems are attached to the scene node, not the bone.
- // We use the Node's user data to connect it with the mesh / particle system.
- Ogre::Any customData = node->getUserObjectBindings().getUserAny();
- if (!customData.isEmpty())
- Ogre::any_cast<Ogre::MovableObject*>(customData)->setVisible(vis);
-
- Ogre::TagPoint *tag = dynamic_cast<Ogre::TagPoint*>(node);
- if(tag != NULL)
- {
- Ogre::MovableObject *obj = tag->getChildObject();
- if(obj != NULL)
- obj->setVisible(vis);
- }
}
}
@@ -620,50 +623,52 @@ class NIFObjectLoader
scene->mEntities.push_back(entity);
if(scene->mSkelBase)
{
+ int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex);
+ Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast<Ogre::MovableObject*>(entity)));
+
if(entity->hasSkeleton())
entity->shareSkeletonInstanceWith(scene->mSkelBase);
else
- {
- int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex);
- Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
- trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast<Ogre::MovableObject*>(entity)));
scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity);
- }
}
Nif::ControllerPtr ctrl = node->controller;
while(!ctrl.empty())
{
- if(ctrl->recType == Nif::RC_NiUVController)
+ if (ctrl->flags & Nif::NiNode::ControllerFlag_Active)
{
- const Nif::NiUVController *uv = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
+ if(ctrl->recType == Nif::RC_NiUVController)
+ {
+ const Nif::NiUVController *uv = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
- Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
- Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
- Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr));
+ Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
+ Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
+ Ogre::ControllerValueRealPtr());
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr));
- UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
- scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
- Ogre::ControllerFunctionRealPtr func(function);
+ UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
- scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
- }
- else if(ctrl->recType == Nif::RC_NiGeomMorpherController)
- {
- const Nif::NiGeomMorpherController *geom = static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ else if(ctrl->recType == Nif::RC_NiGeomMorpherController)
+ {
+ const Nif::NiGeomMorpherController *geom = static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
- Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
- Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
- Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(
- entity, geom->data.getPtr(), geom->recIndex));
+ Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
+ Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
+ Ogre::ControllerValueRealPtr());
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(
+ entity, geom->data.getPtr(), geom->recIndex));
- GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
- scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
- Ogre::ControllerFunctionRealPtr func(function);
+ GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
- scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
}
ctrl = ctrl->next;
}
@@ -744,8 +749,8 @@ class NIFObjectLoader
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->setTimeToLive(partctrl->lifetime,
+ partctrl->lifetime + partctrl->lifetimeRandom);
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));
@@ -850,7 +855,7 @@ class NIFObjectLoader
Nif::ControllerPtr ctrl = partnode->controller;
while(!ctrl.empty())
{
- if(ctrl->recType == Nif::RC_NiParticleSystemController)
+ if(ctrl->recType == Nif::RC_NiParticleSystemController && ctrl->flags & Nif::NiNode::ControllerFlag_Active)
{
const Nif::NiParticleSystemController *partctrl = static_cast<const Nif::NiParticleSystemController*>(ctrl.getPtr());
@@ -877,6 +882,9 @@ class NIFObjectLoader
Ogre::ControllerFunctionRealPtr func(function);
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+
+ if (partflags&Nif::NiNode::ParticleFlag_AutoPlay)
+ partsys->fastForward(1, 0.1);
}
ctrl = ctrl->next;
}
@@ -891,40 +899,45 @@ class NIFObjectLoader
static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags)
{
do {
- if(ctrl->recType == Nif::RC_NiVisController)
+ if (ctrl->flags & Nif::NiNode::ControllerFlag_Active)
{
- const Nif::NiVisController *vis = static_cast<const Nif::NiVisController*>(ctrl.getPtr());
-
- int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
- Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
- Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
- Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
- Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr()));
-
- VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
- scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
- Ogre::ControllerFunctionRealPtr func(function);
-
- scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
- }
- else if(ctrl->recType == Nif::RC_NiKeyframeController)
- {
- const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
- if(!key->data.empty())
+ 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 = scene->mSkelBase->getSkeleton()->getBone(trgtid);
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());
- Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr()));
- KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr()));
+
+ VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
Ogre::ControllerFunctionRealPtr func(function);
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
}
+ else if(ctrl->recType == Nif::RC_NiKeyframeController)
+ {
+ 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 = scene->mSkelBase->getSkeleton()->getBone(trgtid);
+ // The keyframe controller will control this bone manually
+ trgtbone->setManuallyControlled(true);
+ 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()));
+ KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
+ scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
+ Ogre::ControllerFunctionRealPtr func(function);
+
+ scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
+ }
+ }
}
ctrl = ctrl->next;
} while(!ctrl.empty());
@@ -1030,7 +1043,7 @@ class NIFObjectLoader
e = e->extra;
}
- if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode))
+ if(!node->controller.empty())
createNodeControllers(name, node->controller, scene, animflags);
if(node->recType == Nif::RC_NiCamera)
@@ -1147,6 +1160,9 @@ public:
continue;
}
+ if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
+ continue;
+
const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
@@ -1209,20 +1225,27 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo
if(isskinned)
{
+ // Apparently both are allowed. Sigh.
+ // This could also mean that filters are supposed to work on the actual node
+ // hierarchy, rather than just trishapes, and the 'tri ' should be omitted?
std::string filter = "@shape=tri "+bonename;
+ std::string filter2 = "@shape="+bonename;
Misc::StringUtils::toLower(filter);
+ Misc::StringUtils::toLower(filter2);
for(size_t i = 0;i < scene->mEntities.size();i++)
{
Ogre::Entity *entity = scene->mEntities[i];
if(entity->hasSkeleton())
{
if(entity == scene->mSkelBase ||
- entity->getMesh()->getName().find(filter) != std::string::npos)
+ entity->getMesh()->getName().find(filter) != std::string::npos
+ || entity->getMesh()->getName().find(filter2) != std::string::npos)
parentNode->attachObject(entity);
}
else
{
- if(entity->getMesh()->getName().find(filter) == std::string::npos)
+ if(entity->getMesh()->getName().find(filter) == std::string::npos
+ || entity->getMesh()->getName().find(filter2) == std::string::npos)
entity->detachFromParent();
}
}
diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp
index e01ae22efd..c0482cf5e3 100644
--- a/components/nifogre/skeleton.cpp
+++ b/components/nifogre/skeleton.cpp
@@ -97,7 +97,7 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node)
{
Nif::ControllerPtr ctrl = node->controller;
do {
- if(ctrl->recType == Nif::RC_NiKeyframeController)
+ if(ctrl->recType == Nif::RC_NiKeyframeController && ctrl->flags & Nif::NiNode::ControllerFlag_Active)
return true;
} while(!(ctrl=ctrl->next).empty());
}
diff --git a/components/nifogre/tests/.gitignore b/components/nifogre/tests/.gitignore
deleted file mode 100644
index 1a55699834..0000000000
--- a/components/nifogre/tests/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-*.png
-meshlist.txt
-ogre.cfg
-*_test
-chris*
diff --git a/components/nifogre/tests/Makefile b/components/nifogre/tests/Makefile
deleted file mode 100644
index a7c50d1003..0000000000
--- a/components/nifogre/tests/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-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
deleted file mode 100644
index 657913f30f..0000000000
--- a/components/nifogre/tests/ogre_common.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#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
deleted file mode 100644
index 75e169d540..0000000000
--- a/components/nifogre/tests/ogre_manualresource_test.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- 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
deleted file mode 100644
index 72e51e3317..0000000000
--- a/components/nifogre/tests/ogre_mesh_common.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#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
deleted file mode 100644
index decd43df57..0000000000
--- a/components/nifogre/tests/ogre_nif_test.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#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
deleted file mode 100644
index df9139b95b..0000000000
--- a/components/nifogre/tests/ogre_skeleton_test.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#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
deleted file mode 100644
index 2eab2d50dd..0000000000
--- a/components/nifogre/tests/output/ogre_manualresource_test.out
+++ /dev/null
@@ -1,3 +0,0 @@
-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
deleted file mode 100644
index e69de29bb2..0000000000
--- a/components/nifogre/tests/output/ogre_nif_test.out
+++ /dev/null
diff --git a/components/nifogre/tests/output/ogre_skeleton_test.out b/components/nifogre/tests/output/ogre_skeleton_test.out
deleted file mode 100644
index ce01362503..0000000000
--- a/components/nifogre/tests/output/ogre_skeleton_test.out
+++ /dev/null
@@ -1 +0,0 @@
-hello
diff --git a/components/nifogre/tests/plugins.cfg b/components/nifogre/tests/plugins.cfg
deleted file mode 100644
index 9133aec32d..0000000000
--- a/components/nifogre/tests/plugins.cfg
+++ /dev/null
@@ -1,12 +0,0 @@
-# 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
deleted file mode 100755
index 2d07708adc..0000000000
--- a/components/nifogre/tests/test.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/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
index 191b4ac2fe..972cf1b843 100644
--- a/components/nifoverrides/nifoverrides.cpp
+++ b/components/nifoverrides/nifoverrides.cpp
@@ -4,14 +4,51 @@
#include <../components/misc/stringops.hpp>
+#include "../extern/shiny/Main/MaterialInstance.hpp"
+
+#include <stdexcept>
+
using namespace NifOverrides;
-Ogre::ConfigFile Overrides::mTransparencyOverrides = Ogre::ConfigFile();
+Overrides::TransparencyOverrideMap Overrides::mTransparencyOverrides = Overrides::TransparencyOverrideMap();
+Overrides::MaterialOverrideMap Overrides::mMaterialOverrides = Overrides::MaterialOverrideMap();
void Overrides::loadTransparencyOverrides (const std::string& file)
{
- mTransparencyOverrides.load(file);
+ Ogre::ConfigFile cf;
+ cf.load(file);
+
+ Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
+ while (seci.hasMoreElements())
+ {
+ Ogre::String sectionName = seci.peekNextKey();
+ mTransparencyOverrides[sectionName] =
+ Ogre::StringConverter::parseInt(cf.getSetting("alphaRejectValue", sectionName));
+ seci.getNext();
+ }
+}
+
+void Overrides::loadMaterialOverrides(const std::string &file)
+{
+ Ogre::ConfigFile cf;
+ cf.load(file);
+
+ Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
+ while (seci.hasMoreElements())
+ {
+ Ogre::String sectionName = seci.peekNextKey();
+
+ Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
+ Ogre::ConfigFile::SettingsMultiMap::iterator i;
+ std::map<std::string, std::string> overrides;
+ for (i = settings->begin(); i != settings->end(); ++i)
+ {
+ overrides[i->first] = i->second;
+ }
+ mMaterialOverrides[sectionName] = overrides;
+ }
+
}
TransparencyResult Overrides::getTransparencyOverride(const std::string& texture)
@@ -19,20 +56,25 @@ TransparencyResult Overrides::getTransparencyOverride(const std::string& texture
TransparencyResult result;
result.first = false;
- std::string tex = texture;
- Misc::StringUtils::toLower(tex);
+ TransparencyOverrideMap::iterator it = mTransparencyOverrides.find(Misc::StringUtils::lowerCase(texture));
+ if (it != mTransparencyOverrides.end())
+ {
+ result.first = true;
+ result.second = it->second;
+ }
- Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator();
- while (seci.hasMoreElements())
+ return result;
+}
+
+void Overrides::getMaterialOverrides(const std::string &texture, sh::MaterialInstance* material)
+{
+ MaterialOverrideMap::iterator it = mMaterialOverrides.find(Misc::StringUtils::lowerCase(texture));
+ if (it != mMaterialOverrides.end())
{
- Ogre::String sectionName = seci.peekNextKey();
- if (sectionName == tex)
+ const std::map<std::string, std::string>& overrides = it->second;
+ for (std::map<std::string, std::string>::const_iterator it = overrides.begin(); it != overrides.end(); ++it)
{
- result.first = true;
- result.second = Ogre::StringConverter::parseInt(mTransparencyOverrides.getSetting("alphaRejectValue", sectionName));
- break;
+ material->setProperty(it->first, sh::makeProperty(it->second));
}
- seci.getNext();
}
- return result;
}
diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp
index ba2e4cc3c3..edff876d44 100644
--- a/components/nifoverrides/nifoverrides.hpp
+++ b/components/nifoverrides/nifoverrides.hpp
@@ -3,19 +3,34 @@
#include <OgreConfigFile.h>
+namespace sh
+{
+ class MaterialInstance;
+}
+
namespace NifOverrides
{
typedef std::pair<bool, int> TransparencyResult;
- /// \brief provide overrides for some model / texture properties that bethesda has chosen poorly
+ /// Allows to provide overrides for some material properties in NIF files.
+ /// NIFs are a bit limited in that they don't allow specifying a material externally, which is
+ /// painful for texture modding.
+ /// We also use this to patch up transparency settings in certain NIFs that bethesda has chosen poorly.
class Overrides
{
public:
- static Ogre::ConfigFile mTransparencyOverrides;
+ typedef std::map<std::string, int> TransparencyOverrideMap;
+ static TransparencyOverrideMap mTransparencyOverrides;
+
+ typedef std::map<std::string, std::map<std::string, std::string> > MaterialOverrideMap;
+ static MaterialOverrideMap mMaterialOverrides;
+
void loadTransparencyOverrides (const std::string& file);
+ void loadMaterialOverrides (const std::string& file);
static TransparencyResult getTransparencyOverride(const std::string& texture);
+ static void getMaterialOverrides (const std::string& texture, sh::MaterialInstance* instance);
};
}
diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp
index 46424a29af..840cf4bb05 100644
--- a/components/ogreinit/ogreinit.cpp
+++ b/components/ogreinit/ogreinit.cpp
@@ -133,7 +133,7 @@ namespace OgreInit
pluginDir = OGRE_PLUGIN_DIR;
// if path is not specified try to find plugins inside the app bundle
if (pluginDir.empty())
- pluginDir = Ogre::macPluginPath();
+ pluginDir = Ogre::macFrameworksPath();
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
pluginDir = OGRE_PLUGIN_DIR_REL;
diff --git a/components/ogreinit/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp
index c319f77589..6070c43a87 100644
--- a/components/ogreinit/ogreplugin.cpp
+++ b/components/ogreinit/ogreplugin.cpp
@@ -6,18 +6,12 @@
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";
+ pluginExt = ".framework";
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
pluginExt = ".so";
diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp
index e9858eb945..b7c7d59a92 100644
--- a/components/settings/settings.hpp
+++ b/components/settings/settings.hpp
@@ -1,5 +1,5 @@
-#ifndef _COMPONENTS_SETTINGS_H
-#define _COMPONENTS_SETTINGS_H
+#ifndef COMPONENTS_SETTINGS_H
+#define COMPONENTS_SETTINGS_H
#include <OgreConfigFile.h>
diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp
index ce2118cdbd..a5c6290884 100644
--- a/components/terrain/chunk.cpp
+++ b/components/terrain/chunk.cpp
@@ -18,11 +18,13 @@ namespace Terrain
mVertexData = OGRE_NEW Ogre::VertexData;
mVertexData->vertexStart = 0;
+ unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
+
// Set the total number of vertices
- size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1);
+ size_t numVertsOneSide = mNode->getSize() * (verts-1);
numVertsOneSide /= 1 << lodLevel;
numVertsOneSide += 1;
- assert((int)numVertsOneSide == ESM::Land::LAND_SIZE);
+ assert(numVertsOneSide == verts);
mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp
index 5dcdb7fed8..8e78d22166 100644
--- a/components/terrain/material.cpp
+++ b/components/terrain/material.cpp
@@ -281,6 +281,7 @@ namespace Terrain
// normal map (optional)
bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap;
bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax;
+ bool useSpecular = layer.mSpecular;
if (useNormalMap)
{
anyNormalMaps = true;
@@ -292,8 +293,11 @@ namespace Terrain
sh::makeProperty (new sh::BooleanValue(useNormalMap)));
p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::BooleanValue(useParallax)));
+ p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i),
+ sh::makeProperty (new sh::BooleanValue(useSpecular)));
boost::hash_combine(normalMaps, useNormalMap);
boost::hash_combine(normalMaps, useNormalMap && layer.mParallax);
+ boost::hash_combine(normalMaps, useSpecular);
if (i+layerOffset > 0)
{
diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp
index 02225cb02d..82ccc7c89a 100644
--- a/components/terrain/quadtreenode.cpp
+++ b/components/terrain/quadtreenode.cpp
@@ -168,7 +168,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
if (mParent)
pos = mParent->getCenter();
pos = mCenter - pos;
- mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0));
+ float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
+ mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0));
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
}
@@ -203,6 +204,7 @@ void QuadTreeNode::initNeighbours()
void QuadTreeNode::initAabb()
{
+ float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
if (hasChildren())
{
for (int i=0; i<4; ++i)
@@ -210,11 +212,11 @@ void QuadTreeNode::initAabb()
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));
+ mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z),
+ Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, 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));
+ mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0),
+ mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0));
}
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
@@ -430,10 +432,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
// TODO - store this default material somewhere instead of creating one for each empty cell
MaterialGenerator matGen(mTerrain->getShadersEnabled());
std::vector<LayerInfo> layer;
- LayerInfo info;
- info.mDiffuseMap = "textures\\_land_default.dds";
- info.mParallax = false;
- layer.push_back(info);
+ layer.push_back(mTerrain->getStorage()->getDefaultLayer());
matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
return;
diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp
index 9f6fb5d242..ea299c096d 100644
--- a/components/terrain/quadtreenode.hpp
+++ b/components/terrain/quadtreenode.hpp
@@ -108,7 +108,7 @@ namespace Terrain
void destroyChunks(bool children);
/// Get the effective LOD level if this node was rendered in one chunk
- /// with ESM::Land::LAND_SIZE^2 vertices
+ /// with Storage::getCellVertices^2 vertices
size_t getNativeLodLevel() { return mLodLevel; }
/// Get the effective current LOD level used by the chunk rendering this node
diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp
index 9d6b44de8d..e69de29bb2 100644
--- a/components/terrain/storage.cpp
+++ b/components/terrain/storage.cpp
@@ -1,500 +0,0 @@
-#include "storage.hpp"
-
-#include <OgreVector2.h>
-#include <OgreTextureManager.h>
-#include <OgreStringConverter.h>
-#include <OgreRenderSystem.h>
-#include <OgreResourceGroupManager.h>
-#include <OgreRoot.h>
-
-#include <boost/algorithm/string.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<LayerInfo> &layerList)
- {
- // TODO - blending isn't completely right yet; the blending radius appears to be
- // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
- // 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(getLayerInfo(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];
- }
-
- LayerInfo Storage::getLayerInfo(const std::string& texture)
- {
- // Already have this cached?
- if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
- return mLayerInfoMap[texture];
-
- LayerInfo info;
- info.mParallax = false;
- info.mDiffuseMap = "textures\\" + texture;
- std::string texture_ = texture;
- boost::replace_last(texture_, ".", "_nh.");
- if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
- {
- info.mNormalMap = "textures\\" + texture_;
- info.mParallax = true;
- }
- else
- {
- texture_ = texture;
- boost::replace_last(texture_, ".", "_n.");
- if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
- info.mNormalMap = "textures\\" + texture_;
- }
-
- mLayerInfoMap[texture] = info;
-
- return info;
- }
-
-
-}
diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp
index 68fae01afa..d8cdab9ec1 100644
--- a/components/terrain/storage.hpp
+++ b/components/terrain/storage.hpp
@@ -1,9 +1,6 @@
#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>
@@ -16,6 +13,7 @@ namespace Terrain
std::string mDiffuseMap;
std::string mNormalMap;
bool mParallax; // Height info in normal map alpha channel?
+ bool mSpecular; // Specular info in diffuse map alpha channel?
};
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
@@ -23,13 +21,10 @@ namespace Terrain
{
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;
+ virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 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.
@@ -39,7 +34,7 @@ namespace Terrain
/// @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);
+ virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
/// Fill vertex buffers for a terrain chunk.
/// @param lodLevel LOD level, 0 = most detailed
@@ -48,10 +43,10 @@ namespace Terrain
/// @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,
+ virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
- Ogre::HardwareVertexBufferSharedPtr colourBuffer);
+ Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0;
/// 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
@@ -63,31 +58,19 @@ namespace Terrain
/// 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,
+ virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps,
- std::vector<LayerInfo>& 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);
+ std::vector<LayerInfo>& layerList) = 0;
- // 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;
+ virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
- UniqueTextureId getVtexIndexAt(int cellX, int cellY,
- int x, int y);
- std::string getTextureName (UniqueTextureId id);
+ virtual LayerInfo getDefaultLayer() = 0;
- std::map<std::string, LayerInfo> mLayerInfoMap;
+ /// Get the transformation factor for mapping cell units to world units.
+ virtual float getCellWorldSize() = 0;
- LayerInfo getLayerInfo(const std::string& texture);
+ /// Get the number of vertices on one side for each cell. Should be (power of two)+1
+ virtual int getCellVertices() = 0;
};
}
diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp
index 711ebbc8fa..4273f227d6 100644
--- a/components/terrain/world.cpp
+++ b/components/terrain/world.cpp
@@ -6,7 +6,6 @@
#include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h>
-#include <components/esm/loadland.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include "storage.hpp"
@@ -63,6 +62,10 @@ namespace Terrain
, mShaders(shaders)
, mVisible(true)
, mLoadingListener(loadingListener)
+ , mMaxX(0)
+ , mMinX(0)
+ , mMaxY(0)
+ , mMinY(0)
{
loadingListener->setLabel("Creating terrain");
loadingListener->indicateProgress();
@@ -77,20 +80,21 @@ namespace Terrain
mCompositeMapRenderTarget->setAutoUpdated(false);
mCompositeMapRenderTarget->addViewport(compositeMapCam);
- mBounds = storage->getBounds();
+ storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
- int origSizeX = mBounds.getSize().x;
- int origSizeY = mBounds.getSize().y;
+ int origSizeX = mMaxX-mMinX;
+ int origSizeY = mMaxY-mMinY;
// 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);
+ float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
+ float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
- mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
+ mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL);
buildQuadTree(mRootNode);
loadingListener->indicateProgress();
mRootNode->initAabb();
@@ -114,18 +118,19 @@ namespace Terrain
// We arrived at a leaf
float minZ,maxZ;
Ogre::Vector2 center = node->getCenter();
+ float cellWorldSize = getStorage()->getCellWorldSize();
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)));
+ node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
+ Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, 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 )
+ if (node->getCenter().x - halfSize > mMaxX
+ || node->getCenter().x + halfSize < mMinX
+ || node->getCenter().y - halfSize > mMaxY
+ || node->getCenter().y + halfSize < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{
@@ -162,15 +167,16 @@ namespace Terrain
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)
+ if (center.x > mMaxX
+ || center.x < mMinX
+ || center.y > mMaxY
+ || center.y < mMinY)
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);
+ float cellWorldSize = getStorage()->getCellWorldSize();
+ box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize,
+ box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize);
return box;
}
@@ -208,6 +214,8 @@ namespace Terrain
Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
{
+ unsigned int verts = mStorage->getCellVertices();
+
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
{
numIndices = mIndexBufferMap[flags]->getNumIndexes();
@@ -224,11 +232,11 @@ namespace Terrain
bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
size_t increment = 1 << lodLevel;
- assert((int)increment < ESM::Land::LAND_SIZE);
+ assert(increment < verts);
std::vector<short> indices;
- indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
+ indices.reserve((verts-1)*(verts-1)*2*3 / increment);
- size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
+ size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-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)
@@ -242,13 +250,13 @@ namespace Terrain
{
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(verts*col+row);
+ indices.push_back(verts*(col+increment)+row+increment);
+ indices.push_back(verts*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);
+ indices.push_back(verts*col+row);
+ indices.push_back(verts*(col+increment)+row);
+ indices.push_back(verts*(col+increment)+row+increment);
}
}
@@ -261,96 +269,96 @@ namespace Terrain
// 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)
+ for (size_t col = 0; col < verts-1; col += outerStep)
{
- indices.push_back(ESM::Land::LAND_SIZE*col+row);
- indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
+ indices.push_back(verts*col+row);
+ indices.push_back(verts*(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);
+ if (col+outerStep == verts-1)
+ indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep);
else
- indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
+ indices.push_back(verts*(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)
+ if (col+i == 0 || col+i == verts-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);
+ indices.push_back(verts*(col)+row);
+ indices.push_back(verts*(col+i+innerStep)+row+innerStep);
+ indices.push_back(verts*(col+i)+row+innerStep);
}
}
// North
- row = ESM::Land::LAND_SIZE-1;
+ row = verts-1;
outerStep = 1 << (lodDeltas[North] + lodLevel);
- for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
+ for (size_t col = 0; col < verts-1; col += outerStep)
{
- indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
- indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(verts*(col+outerStep)+row);
+ indices.push_back(verts*col+row);
// Make sure not to touch the left edge
if (col == 0)
- indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
+ indices.push_back(verts*(col+innerStep)+row-innerStep);
else
- indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
+ indices.push_back(verts*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)
+ if (col+i == 0 || col+i == verts-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);
+ indices.push_back(verts*(col+i)+row-innerStep);
+ indices.push_back(verts*(col+i+innerStep)+row-innerStep);
+ indices.push_back(verts*(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)
+ for (size_t row = 0; row < verts-1; row += outerStep)
{
- indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
- indices.push_back(ESM::Land::LAND_SIZE*col+row);
+ indices.push_back(verts*col+row+outerStep);
+ indices.push_back(verts*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);
+ if (row+outerStep == verts-1)
+ indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep);
else
- indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
+ indices.push_back(verts*(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)
+ if (row+i == 0 || row+i == verts-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);
+ indices.push_back(verts*col+row);
+ indices.push_back(verts*(col+innerStep)+row+i);
+ indices.push_back(verts*(col+innerStep)+row+i+innerStep);
}
}
// East
- col = ESM::Land::LAND_SIZE-1;
+ col = verts-1;
outerStep = 1 << (lodDeltas[East] + lodLevel);
- for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
+ for (size_t row = 0; row < verts-1; row += outerStep)
{
- indices.push_back(ESM::Land::LAND_SIZE*col+row);
- indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
+ indices.push_back(verts*col+row);
+ indices.push_back(verts*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);
+ indices.push_back(verts*(col-innerStep)+row+innerStep);
else
- indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
+ indices.push_back(verts*(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)
+ if (row+i == 0 || row+i == verts-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);
+ indices.push_back(verts*col+row+outerStep);
+ indices.push_back(verts*(col-innerStep)+row+i+innerStep);
+ indices.push_back(verts*(col-innerStep)+row+i);
}
}
}
diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp
index b8c1b0a7d6..bf733b889a 100644
--- a/components/terrain/world.hpp
+++ b/components/terrain/world.hpp
@@ -105,7 +105,7 @@ namespace Terrain
Ogre::SceneManager* mCompositeMapSceneMgr;
/// Bounds in cell units
- Ogre::AxisAlignedBox mBounds;
+ float mMinX, mMaxX, mMinY, mMaxY;
/// Minimum size of a terrain batch along one side (in cell units)
float mMinBatchSize;
diff --git a/apps/openmw/config.hpp.cmake b/components/version/version.hpp.cmake
index 848fbe0eb1..4cdfa32f08 100644
--- a/apps/openmw/config.hpp.cmake
+++ b/components/version/version.hpp.cmake
@@ -1,9 +1,13 @@
-#ifndef CONFIG_H
-#define CONFIG_H
+#ifndef VERSION_HPP
+#define VERSION_HPP
#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
+#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@"
+#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@"
+
+#endif // VERSION_HPP
+
diff --git a/credits.txt b/credits.txt
index bd0c6ca743..6012557638 100644
--- a/credits.txt
+++ b/credits.txt
@@ -20,10 +20,12 @@ Artem Kotsynyak (greye)
athile
Britt Mathis (galdor557)
BrotherBrick
+cc9cii
Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
darkf
+Dmitry Shkurskiy (endorph)
Douglas Diniz (Dgdiniz)
Douglas Mencken (dougmencken)
Edmondo Tommasina (edmondo)
diff --git a/extern/oics/ICSChannel.h b/extern/oics/ICSChannel.h
index f98f0d94d3..5ec6cd575b 100644
--- a/extern/oics/ICSChannel.h
+++ b/extern/oics/ICSChannel.h
@@ -24,8 +24,8 @@ 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_
+#ifndef OICS_Channel_H_
+#define OICS_Channel_H_
#include "ICSPrerequisites.h"
@@ -119,4 +119,4 @@ namespace ICS
}
-#endif \ No newline at end of file
+#endif
diff --git a/extern/oics/ICSControl.h b/extern/oics/ICSControl.h
index 7939c86b95..ebf75a3fef 100644
--- a/extern/oics/ICSControl.h
+++ b/extern/oics/ICSControl.h
@@ -24,8 +24,8 @@ 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_
+#ifndef OICS_Control_H_
+#define OICS_Control_H_
#include "ICSPrerequisites.h"
diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h
index f42f9c0b5f..a83ae539ef 100644
--- a/extern/oics/ICSInputControlSystem.h
+++ b/extern/oics/ICSInputControlSystem.h
@@ -24,8 +24,8 @@ 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_
+#ifndef OICS_InputControlSystem_H_
+#define OICS_InputControlSystem_H_
#include "ICSPrerequisites.h"
@@ -102,19 +102,19 @@ namespace ICS
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);
+ void mouseMoved(const SFO::MouseMotionEvent &evt);
+ void mousePressed(const SDL_MouseButtonEvent &evt, Uint8);
+ void mouseReleased(const SDL_MouseButtonEvent &evt, Uint8);
// KeyListener
- bool keyPressed(const SDL_KeyboardEvent &evt);
- bool keyReleased(const SDL_KeyboardEvent &evt);
+ void keyPressed(const SDL_KeyboardEvent &evt);
+ void 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);
+ void buttonPressed(const SDL_JoyButtonEvent &evt, int button);
+ void buttonReleased(const SDL_JoyButtonEvent &evt, int button);
+ void axisMoved(const SDL_JoyAxisEvent &evt, int axis);
+ void povMoved(const SDL_JoyHatEvent &evt, int index);
//TODO: does this have an SDL equivalent?
//bool sliderMoved(const OIS::JoyStickEvent &evt, int index);
diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp
index 8e501d5018..21adc9f74a 100644
--- a/extern/oics/ICSInputControlSystem_joystick.cpp
+++ b/extern/oics/ICSInputControlSystem_joystick.cpp
@@ -318,7 +318,7 @@ namespace ICS
}
// joyStick listeners
- bool InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button)
+ void InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button)
{
if(mActive)
{
@@ -354,11 +354,9 @@ namespace ICS
mDetectingBindingControl, evt.which, button, mDetectingBindingDirection);
}
}
-
- return true;
}
- bool InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button)
+ void InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button)
{
if(mActive)
{
@@ -371,10 +369,9 @@ namespace ICS
}
}
}
- return true;
}
- bool InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis)
+ void InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis)
{
if(mActive)
{
@@ -417,12 +414,10 @@ namespace ICS
}
}
}
-
- return true;
}
//Here be dragons, apparently
- bool InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index)
+ void InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index)
{
if(mActive)
{
@@ -542,13 +537,11 @@ namespace ICS
}
}
}
-
- return true;
}
//TODO: does this have an SDL equivalent?
/*
- bool InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index)
+ void InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index)
{
if(mActive)
{
@@ -590,8 +583,6 @@ namespace ICS
}
}
}
-
- return true;
}
*/
diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp
index 01d68f7843..0a9a34d63c 100644
--- a/extern/oics/ICSInputControlSystem_keyboard.cpp
+++ b/extern/oics/ICSInputControlSystem_keyboard.cpp
@@ -85,7 +85,7 @@ namespace ICS
return SDLK_UNKNOWN;
}
- bool InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt)
+ void InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt)
{
if(mActive)
{
@@ -118,11 +118,9 @@ namespace ICS
mDetectingBindingControl, evt.keysym.sym, mDetectingBindingDirection);
}
}
-
- return true;
- }
+ }
- bool InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt)
+ void InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt)
{
if(mActive)
{
@@ -132,8 +130,6 @@ namespace ICS
it->second.control->setChangingDirection(Control::STOP);
}
}
-
- return true;
}
void DetectingBindingListener::keyBindingDetected(InputControlSystem* ICS, Control* control
diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp
index 52eb894ed5..be18ebbc0e 100644
--- a/extern/oics/ICSInputControlSystem_mouse.cpp
+++ b/extern/oics/ICSInputControlSystem_mouse.cpp
@@ -219,7 +219,7 @@ namespace ICS
}
// mouse Listeners
- bool InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt)
+ void InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt)
{
if(mActive)
{
@@ -304,11 +304,9 @@ namespace ICS
}
}
}
-
- return true;
}
- bool InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn)
+ void InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn)
{
if(mActive)
{
@@ -341,11 +339,9 @@ namespace ICS
mDetectingBindingControl, btn, mDetectingBindingDirection);
}
}
-
- return true;
}
- bool InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn)
+ void InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn)
{
if(mActive)
{
@@ -355,8 +351,6 @@ namespace ICS
it->second.control->setChangingDirection(Control::STOP);
}
}
-
- return true;
}
// mouse auto bindings
diff --git a/extern/sdl4ogre/OISCompat.h b/extern/sdl4ogre/OISCompat.h
index 3cffa143db..a0acc5837a 100644
--- a/extern/sdl4ogre/OISCompat.h
+++ b/extern/sdl4ogre/OISCompat.h
@@ -1,5 +1,5 @@
-#ifndef _OIS_SDL_COMPAT_H
-#define _OIS_SDL_COMPAT_H
+#ifndef OIS_SDL_COMPAT_H
+#define OIS_SDL_COMPAT_H
#include <SDL_events.h>
#include <SDL_types.h>
diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp
index 35ec92a706..3036b236be 100644
--- a/extern/sdl4ogre/cursormanager.hpp
+++ b/extern/sdl4ogre/cursormanager.hpp
@@ -1,5 +1,5 @@
-#ifndef _SDL4OGRE_CURSOR_MANAGER_H
-#define _SDL4OGRE_CURSOR_MANAGER_H
+#ifndef SDL4OGRE_CURSOR_MANAGER_H
+#define SDL4OGRE_CURSOR_MANAGER_H
#include <SDL_types.h>
#include <string>
diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h
index 48adb45456..0fb4d6f060 100644
--- a/extern/sdl4ogre/events.h
+++ b/extern/sdl4ogre/events.h
@@ -26,9 +26,9 @@ 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;
+ virtual void mouseMoved( const MouseMotionEvent &arg ) = 0;
+ virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0;
+ virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0;
};
class KeyListener
@@ -36,8 +36,8 @@ 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;
+ virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0;
+ virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0;
};
class JoyListener
@@ -45,18 +45,18 @@ class JoyListener
public:
virtual ~JoyListener() {}
/** @remarks Joystick button down event */
- virtual bool buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0;
+ virtual void buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0;
/** @remarks Joystick button up event */
- virtual bool buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0;
+ virtual void buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0;
/** @remarks Joystick axis moved event */
- virtual bool axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0;
+ virtual void 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;}
+ virtual void povMoved( const SDL_JoyHatEvent &arg, int index) {}
};
class WindowListener
diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp
index 7ba69f013e..7e3e59b4a5 100644
--- a/extern/sdl4ogre/sdlcursormanager.hpp
+++ b/extern/sdl4ogre/sdlcursormanager.hpp
@@ -1,5 +1,5 @@
-#ifndef _SDL4OGRE_CURSORMANAGER_H
-#define _SDL4OGRE_CURSORMANAGER_H
+#ifndef SDL4OGRE_CURSORMANAGER_H
+#define SDL4OGRE_CURSORMANAGER_H
#include <SDL.h>
diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp
index a2b698f860..f08e3eff6b 100644
--- a/extern/sdl4ogre/sdlinputwrapper.hpp
+++ b/extern/sdl4ogre/sdlinputwrapper.hpp
@@ -1,5 +1,5 @@
-#ifndef _SDL4OGRE_SDLINPUTWRAPPER_H
-#define _SDL4OGRE_SDLINPUTWRAPPER_H
+#ifndef SDL4OGRE_SDLINPUTWRAPPER_H
+#define SDL4OGRE_SDLINPUTWRAPPER_H
#include <SDL_events.h>
diff --git a/files/materials/objects.mat b/files/materials/objects.mat
index 751b512431..2281226b0c 100644
--- a/files/materials/objects.mat
+++ b/files/materials/objects.mat
@@ -8,6 +8,7 @@ material openmw_objects_base
diffuseMap black.png
normalMap
emissiveMap
+ specMap
darkMap
use_emissive_map false
use_detail_map false
@@ -44,6 +45,7 @@ material openmw_objects_base
emissiveMap $emissiveMap
detailMap $detailMap
diffuseMap $diffuseMap
+ specMap $specMap
darkMap $darkMap
env_map $env_map
env_map_color $env_map_color
@@ -107,6 +109,11 @@ material openmw_objects_base
anim_texture2 textures\magicitem\caust.dds 32 2
colour_op add
}
+
+ texture_unit specMap
+ {
+ direct_texture $specMap
+ }
texture_unit shadowMap0
{
diff --git a/files/materials/objects.shader b/files/materials/objects.shader
index 93368f1f68..ed75babdd4 100644
--- a/files/materials/objects.shader
+++ b/files/materials/objects.shader
@@ -14,11 +14,14 @@
#define NEED_DEPTH
#endif
+#define SPECULAR 1
+
#define NORMAL_MAP @shPropertyHasValue(normalMap)
#define EMISSIVE_MAP @shPropertyHasValue(emissiveMap)
#define DETAIL_MAP @shPropertyHasValue(detailMap)
#define DIFFUSE_MAP @shPropertyHasValue(diffuseMap)
#define DARK_MAP @shPropertyHasValue(darkMap)
+#define SPEC_MAP @shPropertyHasValue(specMap) && SPECULAR
#define PARALLAX @shPropertyBool(use_parallax)
#define PARALLAX_SCALE 0.04
@@ -38,8 +41,6 @@
#define ENV_MAP @shPropertyBool(env_map)
-#define SPECULAR 1
-
#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR
#ifdef SH_VERTEX_SHADER
@@ -273,6 +274,10 @@
shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color)
#endif
+#if SPEC_MAP
+ shSampler2D(specMap)
+#endif
+
#if ENV_MAP || SPECULAR || PARALLAX
shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space)
#endif
@@ -511,8 +516,20 @@
float NdotL = max(dot(normal, light0Dir), 0);
float3 halfVec = normalize (light0Dir + eyeDir);
- float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec;
- shOutputColour(0).xyz += specular * shadow * diffuse.a;
+ float shininess = matShininess;
+#if SPEC_MAP
+ float4 specTex = shSample(specMap, UV.xy);
+ shininess *= (specTex.a);
+#endif
+
+ float3 specular = pow(max(dot(normal, halfVec), 0), shininess) * lightSpec0 * matSpec;
+#if SPEC_MAP
+ specular *= specTex.xyz;
+#else
+ specular *= diffuse.a;
+#endif
+
+ shOutputColour(0).xyz += specular * shadow;
#endif
#if FOG
diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader
index 86eef36ffa..1436de0c35 100644
--- a/files/materials/terrain.shader
+++ b/files/materials/terrain.shader
@@ -337,6 +337,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents
float2 thisLayerUV;
float4 normalTex;
+ float4 diffuseTex;
float3 eyeDir = normalize(cameraPos.xyz - worldPos);
#if PARALLAX
@@ -358,19 +359,18 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS );
#endif
-#if IS_FIRST_PASS
- #if @shIterator == 0
- // first layer of first pass is the base layer and doesn't need a blend map
- albedo = shSample(diffuseMap0, layerUV);
- #else
- albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator));
- #endif
+ diffuseTex = shSample(diffuseMap@shIterator, layerUV);
+#if !@shPropertyBool(use_specular_@shIterator)
+ diffuseTex.a = 0;
+#endif
+
+#if @shIterator == 0
+albedo = diffuseTex;
#else
- #if @shIterator == 0
- albedo = shSample(diffuseMap@shIterator, layerUV);
- #else
- albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator));
- #endif
+albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_component_@shIterator));
+#endif
+
+#if !IS_FIRST_PASS
previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator);
#endif
@@ -448,7 +448,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
float3 halfVec = normalize (light0Dir + eyeDir);
float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0;
- shOutputColour(0).xyz += specular * (1.f-albedo.a) * shadow;
+ shOutputColour(0).xyz += specular * (albedo.a) * shadow;
#endif
#if FOG
diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt
index 70fdf42489..ef223a6177 100644
--- a/files/mygui/CMakeLists.txt
+++ b/files/mygui/CMakeLists.txt
@@ -8,7 +8,6 @@ set(MYGUI_FILES
black.png
core.skin
core.xml
- EBGaramond-Regular.ttf
openmw_alchemy_window.layout
openmw_book.layout
openmw_box.skin.xml
diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf
deleted file mode 100644
index 3f6f6c191d..0000000000
--- a/files/mygui/EBGaramond-Regular.ttf
+++ /dev/null
Binary files differ
diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout
index 7d24a283e1..0c9a97d043 100644
--- a/files/mygui/openmw_console.layout
+++ b/files/mygui/openmw_console.layout
@@ -7,9 +7,12 @@
<Property key="Visible" value="false"/>
<!-- Log window -->
- <Widget type="EditBox" skin="MW_ConsoleLog" position="5 5 380 328" align="Stretch" name="list_History">
+ <Widget type="EditBox" skin="MW_TextBoxEdit" position="5 5 380 328" align="Stretch" name="list_History">
<Property key="MultiLine" value="1"/>
<Property key="ReadOnly" value="true"/>
+ <Property key="FontName" value="MonoFont"/>
+ <Property key="TextAlign" value="Left Top"/>
+ <Property key="TextColour" value="1 1 1"/>
</Widget>
<!-- Command line -->
diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml
index 219cce39ae..470451a0ed 100644
--- a/files/mygui/openmw_console.skin.xml
+++ b/files/mygui/openmw_console.skin.xml
@@ -2,19 +2,6 @@
<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 -->
diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout
index 06cc04ebeb..87651b0f26 100644
--- a/files/mygui/openmw_container_window.layout
+++ b/files/mygui/openmw_container_window.layout
@@ -3,6 +3,7 @@
<MyGUI type="Layout">
<Widget type="Window" skin="MW_Window" layer="Windows" position="0 0 600 300" name="_Main">
<Property key="MinSize" value="245 145"/>
+ <Property key="Visible" value="false"/>
<!-- Items -->
<Widget type="ItemView" skin="MW_ItemView" position="5 5 575 225" name="ItemView" align="Left Top Stretch">
diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml
index 726bfb281c..e4037561d7 100644
--- a/files/mygui/openmw_font.xml
+++ b/files/mygui/openmw_font.xml
@@ -1,31 +1,8 @@
<?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="Size" value="17"/>
<Property key="Resolution" value="50"/>
<Property key="Antialias" value="false"/>
<Property key="TabWidth" value="8"/>
diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout
index ecccd995bc..09e5ed9c7c 100644
--- a/files/mygui/openmw_inventory_window.layout
+++ b/files/mygui/openmw_inventory_window.layout
@@ -2,7 +2,7 @@
<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"/>
+ <Property key="MinSize" value="40 40"/>
<Widget type="Widget" skin="" position="0 0 224 223" align="Left Top" name="LeftPane">
@@ -12,11 +12,10 @@
<!-- 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 type="ImageBox" skin="ImageBox" position="0 0 212 161" align="Stretch" name="AvatarImage">
+ <UserString key="ToolTipType" value="AvatarItemSelection"/>
</Widget>
- <Widget type="TextBox" skin="ProgressText" position="0 150 212 24" align="HCenter Bottom" name="ArmorRating">
+ <Widget type="TextBox" skin="ProgressText" position="0 161 212 24" align="HStretch Bottom" name="ArmorRating">
<Property key="NeedMouse" value="false"/>
</Widget>
</Widget>
diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout
index 232f31b754..6e0efce7e1 100644
--- a/files/mygui/openmw_map_window.layout
+++ b/files/mygui/openmw_map_window.layout
@@ -2,7 +2,7 @@
<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"/>
+ <Property key="MinSize" value="40 40"/>
<!-- Local map -->
<Widget type="ScrollView" skin="MW_MapView" position="0 0 284 264" align="Stretch" name="LocalMap">
diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout
index 18de6a2399..ceb1a84288 100644
--- a/files/mygui/openmw_savegame_dialog.layout
+++ b/files/mygui/openmw_savegame_dialog.layout
@@ -17,8 +17,6 @@
<Widget type="ComboBox" skin="MW_ComboBox" position="0 0 200 24" name="SelectCharacter">
<Property key="Caption" value="Select Character"/>
- <Property key="AddItem" value="Gandalf (Level 654)"/>
- <Property key="AddItem" value="Frodo (Level 3)"/>
<UserString key="HStretch" value="true"/>
</Widget>
@@ -26,11 +24,6 @@
<Widget type="ListBox" skin="MW_List" position="0 0 200 200" name="SaveList">
<UserString key="HStretch" value="true"/>
<UserString key="VStretch" value="true"/>
- <Property key="AddItem" value="Quicksave"/>
- <Property key="AddItem" value="Autosave"/>
- <Property key="AddItem" value="Save 3"/>
- <Property key="AddItem" value="Save 2"/>
- <Property key="AddItem" value="Save 1"/>
</Widget>
</Widget>
@@ -51,8 +44,6 @@
<Widget type="AutoSizedEditBox" skin="SandText" position="0 0 263 0" name="InfoText">
<Property key="Static" value="true"/>
<Property key="MultiLine" value="true"/>
-
- <Property key="Caption" value="4:21 AM\nTuesday, November 5, 2013\n\nLevel 23\nBalmora, Guild of Mages\n16 Last Seed (Day 12) 9 a.m. "/>
</Widget>
diff --git a/files/mygui/openmw_spell_buying_window.layout b/files/mygui/openmw_spell_buying_window.layout
index 1510372ddb..b24a476c4c 100644
--- a/files/mygui/openmw_spell_buying_window.layout
+++ b/files/mygui/openmw_spell_buying_window.layout
@@ -11,7 +11,7 @@
</Widget>
<Widget type="TextBox" skin="NormalText" position="0 0 450 18" align="Right Top">
<Property key="TextAlign" value="Center"/>
- <Property key="Caption" value="#{sSpells}"/>
+ <Property key="Caption" value="#{sServiceSpellsTitle}"/>
</Widget>
diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout
index ab924da6d5..ec655ace81 100644
--- a/files/mygui/openmw_spell_window.layout
+++ b/files/mygui/openmw_spell_window.layout
@@ -2,7 +2,7 @@
<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"/>
+ <Property key="MinSize" value="40 40"/>
<!-- Effect box-->
<Widget type="Widget" skin="MW_Box" position="8 8 268 23" align="Left Top HStretch">
diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout
index 36c28c450e..6cdd4c02ae 100644
--- a/files/mygui/openmw_stats_window.layout
+++ b/files/mygui/openmw_stats_window.layout
@@ -2,7 +2,7 @@
<MyGUI type="Layout">
<Widget type="ExposedWindow" skin="MW_Window_Pinnable" layer="Windows" position="0 0 500 342" name="_Main">
- <Property key="MinSize" value="300 200"/>
+ <Property key="MinSize" value="40 40"/>
<Widget type="Widget" skin="" name="LeftPane" position="0 0 220 342">
@@ -100,7 +100,9 @@
</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">
+ <!-- TODO: this should be a scroll view -->
+ <Widget type="Widget" skin="" position="4 4 204 144" align="Left Top Stretch">
+ <Widget type="Button" skin="" position="0 0 204 18" name="Attrib1Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
@@ -114,7 +116,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 22 204 18" name="Attrib2Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 18 204 18" name="Attrib2Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
@@ -128,7 +130,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 40 204 18" name="Attrib3Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 36 204 18" name="Attrib3Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
@@ -142,7 +144,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 58 204 18" name="Attrib4Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 54 204 18" name="Attrib4Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
@@ -156,7 +158,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 76 204 18" name="Attrib5Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 72 204 18" name="Attrib5Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
@@ -170,7 +172,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 94 204 18" name="Attrib6Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 90 204 18" name="Attrib6Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
@@ -184,7 +186,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 112 204 18" name="Attrib7Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 108 204 18" name="Attrib7Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
@@ -198,7 +200,7 @@
</Widget>
</Widget>
- <Widget type="Button" skin="" position="4 130 204 18" name="Attrib8Box" align="Left Top HStretch">
+ <Widget type="Button" skin="" position="0 126 204 18" name="Attrib8Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
@@ -212,6 +214,7 @@
</Widget>
</Widget>
</Widget>
+ </Widget>
</Widget>
diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml
index 6a1dea60bf..15287bc74e 100644
--- a/files/mygui/openmw_text.skin.xml
+++ b/files/mygui/openmw_text.skin.xml
@@ -17,14 +17,6 @@
<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"/>
diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout
index 624c133f26..3f4fec59f3 100644
--- a/files/mygui/openmw_tooltips.layout
+++ b/files/mygui/openmw_tooltips.layout
@@ -184,11 +184,8 @@
</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>
diff --git a/files/mygui/openmw_travel_window.layout b/files/mygui/openmw_travel_window.layout
index db3fa24a08..683d47fe71 100644
--- a/files/mygui/openmw_travel_window.layout
+++ b/files/mygui/openmw_travel_window.layout
@@ -11,7 +11,7 @@
</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}"/>
+ <Property key="Caption" value="#D8C09A#{sServiceTravelTitle}"/>
</Widget>
@@ -32,4 +32,4 @@
</Widget>
-</MyGUI> \ No newline at end of file
+</MyGUI>
diff --git a/files/opencs.desktop b/files/opencs.desktop
index 80afa26bce..638de6ebf4 100644
--- a/files/opencs.desktop
+++ b/files/opencs.desktop
@@ -3,7 +3,7 @@ Type=Application
Name=OpenMW Content Editor
GenericName=Content Editor
Comment=A replacement for the Morrowind Construction Set.
-Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit
+Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit;
TryExec=opencs
Exec=opencs
Icon=opencs
diff --git a/files/opencs/multitype.png b/files/opencs/multitype.png
new file mode 100644
index 0000000000..05676e2de0
--- /dev/null
+++ b/files/opencs/multitype.png
Binary files differ
diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc
index eadcf96977..2b1e65ff03 100644
--- a/files/opencs/resources.qrc
+++ b/files/opencs/resources.qrc
@@ -56,6 +56,7 @@
<file>spell.png</file>
<file>static.png</file>
<file>weapon.png</file>
+ <file>multitype.png</file>
<file alias="startup/create-addon">raster/startup/big/create-addon.png</file>
<file alias="startup/create-game">raster/startup/big/new-game.png</file>
<file alias="startup/edit-content">raster/startup/big/edit-content.png</file>
diff --git a/files/openmw.desktop b/files/openmw.desktop
index 3e26018d0c..4a3a76f52d 100644
--- a/files/openmw.desktop
+++ b/files/openmw.desktop
@@ -3,7 +3,7 @@ Type=Application
Name=OpenMW Launcher
GenericName=Role Playing Game
Comment=An engine replacement for The Elder Scrolls III: Morrowind
-Keywords=Morrowind;Reimplementation Mods;esm;bsa
+Keywords=Morrowind;Reimplementation Mods;esm;bsa;
TryExec=omwlauncher
Exec=omwlauncher
Icon=openmw
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
index 22391fe936..4fb7097f84 100644
--- a/files/settings-default.cfg
+++ b/files/settings-default.cfg
@@ -1,4 +1,4 @@
-# WARNING: Editing this file might have no effect, as these
+# WARNING: Editing this file might have no effect, as these
# settings are overwritten by your user settings file.
[Video]
@@ -13,7 +13,7 @@ screen = 0
# Valid values:
# OpenGL Rendering Subsystem
# Direct3D9 Rendering Subsystem
-render system =
+render system =
# Valid values:
# none
@@ -171,6 +171,9 @@ ui y multiplier = 1.0
# Always use the most powerful attack when striking with a weapon (chop, slash or thrust)
best attack = false
+[Saves]
+character =
+
[Windows]
inventory x = 0
inventory y = 0.4275
diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui
index a1dfb172b2..5f2be05a2c 100644
--- a/files/ui/mainwindow.ui
+++ b/files/ui/mainwindow.ui
@@ -2,6 +2,14 @@
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>575</width>
+ <height>535</height>
+ </rect>
+ </property>
<property name="minimumSize">
<size>
<width>575</width>
@@ -56,11 +64,35 @@
</widget>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Close</set>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="versionLabel">
+ <property name="text">
+ <string>OpenMW version</string>
+ </property>
+ </widget>
+ </item>
+ <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="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h
index b20a3ff984..9572b8a7b7 100644
--- a/libs/openengine/bullet/BtOgreExtras.h
+++ b/libs/openengine/bullet/BtOgreExtras.h
@@ -13,8 +13,8 @@
* =====================================================================================
*/
-#ifndef _BtOgreShapes_H_
-#define _BtOgreShapes_H_
+#ifndef BtOgreShapes_H_
+#define BtOgreShapes_H_
#include "btBulletDynamicsCommon.h"
#include "OgreSimpleRenderable.h"
diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h
index 4ce2f181ee..dde606a4f2 100644
--- a/libs/openengine/bullet/BtOgreGP.h
+++ b/libs/openengine/bullet/BtOgreGP.h
@@ -14,8 +14,8 @@
* =====================================================================================
*/
-#ifndef _BtOgrePG_H_
-#define _BtOgrePG_H_
+#ifndef BtOgrePG_H_
+#define BtOgrePG_H_
#include "btBulletDynamicsCommon.h"
#include "BtOgreExtras.h"
diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h
index 9ff069a8f9..2e42fe1f91 100644
--- a/libs/openengine/bullet/BtOgrePG.h
+++ b/libs/openengine/bullet/BtOgrePG.h
@@ -14,8 +14,8 @@
* =====================================================================================
*/
-#ifndef _BtOgreGP_H_
-#define _BtOgreGP_H_
+#ifndef BtOgreGP_H_
+#define BtOgreGP_H_
#include "btBulletDynamicsCommon.h"
#include "OgreSceneNode.h"
diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h
index 98cda859db..0e5c652260 100644
--- a/libs/openengine/bullet/BulletShapeLoader.h
+++ b/libs/openengine/bullet/BulletShapeLoader.h
@@ -1,5 +1,5 @@
-#ifndef _BULLET_SHAPE_LOADER_H_
-#define _BULLET_SHAPE_LOADER_H_
+#ifndef OPENMW_BULLET_SHAPE_LOADER_H_
+#define OPENMW_BULLET_SHAPE_LOADER_H_
#include <OgreResource.h>
#include <OgreResourceManager.h>
diff --git a/libs/openengine/bullet/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp
deleted file mode 100644
index fc4f3278f4..0000000000
--- a/libs/openengine/bullet/btKinematicCharacterController.cpp
+++ /dev/null
@@ -1,643 +0,0 @@
-/*
-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
deleted file mode 100644
index d24cd97222..0000000000
--- a/libs/openengine/bullet/btKinematicCharacterController.h
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
-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
index 4e80088bf7..f124abb990 100644
--- a/libs/openengine/bullet/physic.cpp
+++ b/libs/openengine/bullet/physic.cpp
@@ -4,7 +4,6 @@
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <components/nifbullet/bulletnifloader.hpp>
#include "OgreRoot.h"
-#include "btKinematicCharacterController.h"
#include "BtOgrePG.h"
#include "BtOgreGP.h"
#include "BtOgreExtras.h"
@@ -24,7 +23,7 @@ namespace Physic
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);
+ mBoxRotationInverse = Ogre::Quaternion(inverse.w, inverse.x, inverse.y,inverse.z);
mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map
}
@@ -86,8 +85,8 @@ namespace Physic
Ogre::Quaternion PhysicActor::getRotation()
{
assert(mBody);
- btQuaternion quat = mBody->getWorldTransform().getRotation() * mBoxRotationInverse;
- return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ());
+ btQuaternion quat = mBody->getWorldTransform().getRotation();
+ return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()) * mBoxRotationInverse;
}
void PhysicActor::setScale(float scale){
diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp
index 6cd7244b85..4ef611dc87 100644
--- a/libs/openengine/bullet/physic.hpp
+++ b/libs/openengine/bullet/physic.hpp
@@ -162,7 +162,7 @@ namespace Physic
Ogre::Vector3 mBoxScaledTranslation;
Ogre::Quaternion mBoxRotation;
- btQuaternion mBoxRotationInverse;
+ Ogre::Quaternion mBoxRotationInverse;
Ogre::Vector3 mForce;
bool mOnGround;
diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp
index 9e5ec5414d..c86697497f 100644
--- a/libs/openengine/ogre/renderer.cpp
+++ b/libs/openengine/ogre/renderer.cpp
@@ -8,6 +8,7 @@
#include <OgreTextureManager.h>
#include <OgreTexture.h>
#include <OgreHardwarePixelBuffer.h>
+#include <OgreCamera.h>
#include <extern/sdl4ogre/sdlwindowhelper.hpp>
diff --git a/readme.txt b/readme.txt
index 242dcb5c0c..bb17a68e70 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind
OpenMW is an attempt at recreating the engine for the popular role-playing game
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
-Version: 0.28.0
+Version: 0.29.0
License: GPL (see GPL3.txt for more information)
Website: http://www.openmw.org
@@ -23,8 +23,8 @@ 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
+There's an OpenMW package available in the [community] Repository:
+https://www.archlinux.org/packages/?sort=&q=openmw
OS X:
Open DMG file, copy OpenMW folder anywhere, for example in /Applications
@@ -67,7 +67,13 @@ Allowed options:
--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
+ --script-warn [=arg(=1)] (=1) handling of warnings when compiling
+ scripts
+ 0 - ignore warning
+ 1 - show warning but consider script as
+ correctly compiled anyway
+ 2 - treat warnings as errors
+ --skip-menu [=arg(=1)] (=0) skip main menu on game startup
--fs-strict [=arg(=1)] (=0) strict file system handling (no case
folding)
--encoding arg (=win1252) Character encoding used in OpenMW game
@@ -91,6 +97,74 @@ Allowed options:
CHANGELOG
+0.29.0
+
+Bug #556: Video soundtrack not played when music volume is set to zero
+Bug #829: OpenMW uses up all available vram, when playing for extended time
+Bug #848: Wrong amount of footsteps playing in 1st person
+Bug #888: Ascended Sleepers have movement issues
+Bug #892: Explicit references are allowed on all script functions
+Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly
+Bug #1009: Lake Fjalding AI related slowdown.
+Bug #1041: Music playback issues on OS X >= 10.9
+Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window
+Bug #1060: Some message boxes are cut off at the bottom
+Bug #1062: Bittercup script does not work ('end' variable)
+Bug #1074: Inventory paperdoll obscures armour rating
+Bug #1077: Message after killing an essential NPC disappears too fast
+Bug #1078: "Clutterbane" shows empty charge bar
+Bug #1083: UndoWerewolf fails
+Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered
+Bug #1090: Start scripts fail when going to a non-predefined cell
+Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed.
+Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior
+Bug #1105: Magicka is depleted when using uncastable spells
+Bug #1106: Creatures should be able to run
+Bug #1107: TR cliffs have way too huge collision boxes in OpenMW
+Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded.
+Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop)
+Bug #1115: Memory leak when spying on Fargoth
+Bug #1137: Script execution fails (drenSlaveOwners script)
+Bug #1143: Mehra Milo quest (vivec informants) is broken
+Bug #1145: Issues with moving gold between inventory and containers
+Bug #1146: Issues with picking up stacks of gold
+Bug #1147: Dwemer Crossbows are held incorrectly
+Bug #1158: Armor rating should always stay below inventory mannequin
+Bug #1159: Quick keys can be set during character generation
+Bug #1160: Crash on equip lockpick when
+Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file
+Bug #1184: Game Save: overwriting an existing save does not actually overwrites the file
+Feature #30: Loading/Saving (still missing a few parts)
+Feature #101: AI Package: Activate
+Feature #103: AI Package: Follow, FollowCell
+Feature #138: Editor: Drag & Drop
+Feature #428: Player death
+Feature #505: Editor: Record Cloning
+Feature #701: Levelled creatures
+Feature #708: Improved Local Variable handling
+Feature #709: Editor: Script verifier
+Feature #764: Missing journal backend features
+Feature #777: Creature weapons/shields
+Feature #789: Editor: Referenceable record verifier
+Feature #924: Load/Save GUI (still missing loading screen and progress bars)
+Feature #946: Knockdown
+Feature #947: Decrease fatigue when running, swimming and attacking
+Feature #956: Melee Combat: Blocking
+Feature #957: Area magic
+Feature #960: Combat/AI combat for creatures
+Feature #962: Combat-Related AI instructions
+Feature #1075: Damage/Restore skill/attribute magic effects
+Feature #1076: Soultrap magic effect
+Feature #1081: Disease contraction
+Feature #1086: Blood particles
+Feature #1092: Interrupt resting
+Feature #1101: Inventory equip scripts
+Feature #1116: Version/Build number in Launcher window
+Feature #1119: Resistance/weakness to normal weapons magic effect
+Feature #1123: Slow Fall magic effect
+Feature #1130: Auto-calculate spells
+Feature #1164: Editor: Case-insensitive sorting in tables
+
0.28.0
Bug #399: Inventory changes are not visible immediately